added tags to tunnel debug; debug tag length increased
[public/netxms.git] / src / libnetxms / log.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Utility Library
4 ** Copyright (C) 2003-2017 Victor Kirhenshtein
5 **
6 ** This program is free software; you can redistribute it and/or modify
7 ** it under the terms of the GNU Lesser General Public License as published
8 ** by the Free Software Foundation; either version 3 of the License, or
9 ** (at your option) any later version.
10 **
11 ** This program is distributed in the hope that it will be useful,
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ** GNU General Public License for more details.
15 **
16 ** You should have received a copy of the GNU Lesser General Public License
17 ** along with this program; if not, write to the Free Software
18 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 **
20 ** File: log.cpp
21 **
22 **/
23
24 #include "libnetxms.h"
25 #include <nxstat.h>
26 #include "debug_tag_tree.h"
27
28 #if HAVE_SYSLOG_H
29 #include <syslog.h>
30 #endif
31
32 #define MAX_LOG_HISTORY_SIZE 128
33
34 /**
35 * Static data
36 */
37 #ifdef _WIN32
38 static HANDLE m_eventLogHandle = NULL;
39 static HMODULE m_msgModuleHandle = NULL;
40 #else
41 static unsigned int m_numMessages;
42 static const TCHAR **m_messages;
43 static char s_syslogName[64];
44 #endif
45 static TCHAR m_logFileName[MAX_PATH] = _T("");
46 static FILE *m_logFileHandle = NULL;
47 static MUTEX m_mutexLogAccess = INVALID_MUTEX_HANDLE;
48 static UINT32 s_flags = 0;
49 static DWORD s_debugMsg = 0;
50 static DWORD s_debugMsgTag = 0;
51 static DWORD s_genericMsg = 0;
52 static int s_rotationMode = NXLOG_ROTATION_BY_SIZE;
53 static UINT64 s_maxLogSize = 4096 * 1024; // 4 MB
54 static int s_logHistorySize = 4; // Keep up 4 previous log files
55 static TCHAR s_dailyLogSuffixTemplate[64] = _T("%Y%m%d");
56 static time_t m_currentDayStart = 0;
57 static NxLogConsoleWriter m_consoleWriter = (NxLogConsoleWriter)_tprintf;
58 static String s_logBuffer;
59 static THREAD s_writerThread = INVALID_THREAD_HANDLE;
60 static CONDITION s_writerStopCondition = INVALID_CONDITION_HANDLE;
61 static NxLogDebugWriter s_debugWriter = NULL;
62 static DebugTagTree* volatile tagTreeActive = new DebugTagTree();
63 static DebugTagTree* volatile tagTreeSecondary = new DebugTagTree();
64 static MUTEX m_mutexDebugTagTreeWrite = INVALID_MUTEX_HANDLE;
65
66 /**
67 * Swaps tag tree pointers and waits till reader count drops to 0
68 */
69 static inline void SwapAndWait()
70 {
71 tagTreeSecondary = InterlockedExchangeObjectPointer(&tagTreeActive, tagTreeSecondary);
72 ThreadSleepMs(10);
73
74 // Wait for tree reader count to drop to 0
75 while(tagTreeSecondary->getReaderCount() > 0)
76 ThreadSleepMs(10);
77 }
78
79 /**
80 * Set debug level
81 */
82 void LIBNETXMS_EXPORTABLE nxlog_set_debug_level(int level)
83 {
84 if ((level >= 0) && (level <= 9))
85 {
86 MutexLock(m_mutexDebugTagTreeWrite);
87 tagTreeSecondary->setRootDebugLvl(level); // Update the secondary tree
88 SwapAndWait();
89 tagTreeSecondary->setRootDebugLvl(level); // Update the previously active tree
90 MutexUnlock(m_mutexDebugTagTreeWrite);
91 }
92 }
93
94 /**
95 * Set debug level for tag
96 */
97 void LIBNETXMS_EXPORTABLE nxlog_set_debug_level_tag(const TCHAR *tag, int level)
98 {
99 if (tag != NULL)
100 {
101 MutexLock(m_mutexDebugTagTreeWrite);
102 if((level >= 0) && (level <= 9))
103 {
104 tagTreeSecondary->add(tag, level);
105 SwapAndWait();
106 tagTreeSecondary->add(tag, level);
107 }
108 else if (level < 0)
109 {
110 tagTreeSecondary->remove(tag);
111 SwapAndWait();
112 tagTreeSecondary->remove(tag);
113 }
114 MutexUnlock(m_mutexDebugTagTreeWrite);
115 }
116 }
117
118 /**
119 * Get current debug level
120 */
121 int LIBNETXMS_EXPORTABLE nxlog_get_debug_level()
122 {
123 return tagTreeActive->getRootDebugLvl();
124 }
125
126 /**
127 * Get current debug level for tag
128 */
129 int LIBNETXMS_EXPORTABLE nxlog_get_debug_level_tag(const TCHAR *tag)
130 {
131 return tagTreeActive->getDebugLvl(tag);
132 }
133
134 /**
135 * Set additional debug writer callback. It will be called for each line written with nxlog_debug.
136 */
137 extern "C" void LIBNETXMS_EXPORTABLE nxlog_set_debug_writer(NxLogDebugWriter writer)
138 {
139 s_debugWriter = writer;
140 }
141
142 /**
143 * Format current time for output
144 */
145 static TCHAR *FormatLogTimestamp(TCHAR *buffer)
146 {
147 INT64 now = GetCurrentTimeMs();
148 time_t t = now / 1000;
149 #if HAVE_LOCALTIME_R
150 struct tm ltmBuffer;
151 struct tm *loc = localtime_r(&t, &ltmBuffer);
152 #else
153 struct tm *loc = localtime(&t);
154 #endif
155 _tcsftime(buffer, 32, _T("%Y.%m.%d %H:%M:%S"), loc);
156 _sntprintf(&buffer[19], 8, _T(".%03d"), (int)(now % 1000));
157 return buffer;
158 }
159
160 /**
161 * Set timestamp of start of the current day
162 */
163 static void SetDayStart()
164 {
165 time_t now = time(NULL);
166 struct tm dayStart;
167 #if HAVE_LOCALTIME_R
168 localtime_r(&now, &dayStart);
169 #else
170 struct tm *ltm = localtime(&now);
171 memcpy(&dayStart, ltm, sizeof(struct tm));
172 #endif
173 dayStart.tm_hour = 0;
174 dayStart.tm_min = 0;
175 dayStart.tm_sec = 0;
176 m_currentDayStart = mktime(&dayStart);
177 }
178
179 /**
180 * Set log rotation policy
181 * Setting log size to 0 or mode to NXLOG_ROTATION_DISABLED disables log rotation
182 */
183 bool LIBNETXMS_EXPORTABLE nxlog_set_rotation_policy(int rotationMode, UINT64 maxLogSize, int historySize, const TCHAR *dailySuffix)
184 {
185 bool isValid = true;
186
187 if ((rotationMode >= 0) || (rotationMode <= 2))
188 {
189 s_rotationMode = rotationMode;
190 if (rotationMode == NXLOG_ROTATION_BY_SIZE)
191 {
192 if ((maxLogSize == 0) || (maxLogSize >= 1024))
193 {
194 s_maxLogSize = maxLogSize;
195 }
196 else
197 {
198 s_maxLogSize = 1024;
199 isValid = false;
200 }
201
202 if ((historySize >= 0) && (historySize <= MAX_LOG_HISTORY_SIZE))
203 {
204 s_logHistorySize = historySize;
205 }
206 else
207 {
208 if (historySize > MAX_LOG_HISTORY_SIZE)
209 s_logHistorySize = MAX_LOG_HISTORY_SIZE;
210 isValid = false;
211 }
212 }
213 else if (rotationMode == NXLOG_ROTATION_DAILY)
214 {
215 if ((dailySuffix != NULL) && (dailySuffix[0] != 0))
216 nx_strncpy(s_dailyLogSuffixTemplate, dailySuffix, sizeof(s_dailyLogSuffixTemplate) / sizeof(TCHAR));
217 SetDayStart();
218 }
219 }
220 else
221 {
222 isValid = false;
223 }
224
225 if (isValid)
226 nxlog_debug(0, _T("Log rotation policy set to %d (size=") UINT64_FMT _T(", count=%d)"), rotationMode, maxLogSize, historySize);
227
228 return isValid;
229 }
230
231 /**
232 * Set console writer
233 */
234 void LIBNETXMS_EXPORTABLE nxlog_set_console_writer(NxLogConsoleWriter writer)
235 {
236 m_consoleWriter = writer;
237 }
238
239 /**
240 * Rotate log
241 */
242 static bool RotateLog(bool needLock)
243 {
244 int i;
245 TCHAR oldName[MAX_PATH], newName[MAX_PATH];
246
247 if (s_flags & NXLOG_USE_SYSLOG)
248 return FALSE; // Cannot rotate system logs
249
250 if (needLock)
251 MutexLock(m_mutexLogAccess);
252
253 if ((m_logFileHandle != NULL) && (s_flags & NXLOG_IS_OPEN))
254 {
255 fclose(m_logFileHandle);
256 s_flags &= ~NXLOG_IS_OPEN;
257 }
258
259 if (s_rotationMode == NXLOG_ROTATION_BY_SIZE)
260 {
261 // Delete old files
262 for(i = MAX_LOG_HISTORY_SIZE; i >= s_logHistorySize; i--)
263 {
264 _sntprintf(oldName, MAX_PATH, _T("%s.%d"), m_logFileName, i);
265 _tunlink(oldName);
266 }
267
268 // Shift file names
269 for(; i >= 0; i--)
270 {
271 _sntprintf(oldName, MAX_PATH, _T("%s.%d"), m_logFileName, i);
272 _sntprintf(newName, MAX_PATH, _T("%s.%d"), m_logFileName, i + 1);
273 _trename(oldName, newName);
274 }
275
276 // Rename current log to name.0
277 _sntprintf(newName, MAX_PATH, _T("%s.0"), m_logFileName);
278 _trename(m_logFileName, newName);
279 }
280 else if (s_rotationMode == NXLOG_ROTATION_DAILY)
281 {
282 #if HAVE_LOCALTIME_R
283 struct tm ltmBuffer;
284 struct tm *loc = localtime_r(&m_currentDayStart, &ltmBuffer);
285 #else
286 struct tm *loc = localtime(&m_currentDayStart);
287 #endif
288 TCHAR buffer[64];
289 _tcsftime(buffer, 64, s_dailyLogSuffixTemplate, loc);
290
291 // Rename current log to name.suffix
292 _sntprintf(newName, MAX_PATH, _T("%s.%s"), m_logFileName, buffer);
293 _trename(m_logFileName, newName);
294
295 SetDayStart();
296 }
297
298 // Reopen log
299 #if HAVE_FOPEN64
300 m_logFileHandle = _tfopen64(m_logFileName, _T("w"));
301 #else
302 m_logFileHandle = _tfopen(m_logFileName, _T("w"));
303 #endif
304 if (m_logFileHandle != NULL)
305 {
306 s_flags |= NXLOG_IS_OPEN;
307 TCHAR buffer[32];
308 _ftprintf(m_logFileHandle, _T("%s Log file truncated.\n"), FormatLogTimestamp(buffer));
309 fflush(m_logFileHandle);
310 #ifndef _WIN32
311 int fd = fileno(m_logFileHandle);
312 fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
313 #endif
314 }
315
316 if (needLock)
317 MutexUnlock(m_mutexLogAccess);
318
319 return (s_flags & NXLOG_IS_OPEN) ? true : false;
320 }
321
322 /**
323 * API for application to force log rotation
324 */
325 bool LIBNETXMS_EXPORTABLE nxlog_rotate()
326 {
327 return RotateLog(true);
328 }
329
330 /**
331 * Background writer thread
332 */
333 static THREAD_RESULT THREAD_CALL BackgroundWriterThread(void *arg)
334 {
335 bool stop = false;
336 while(!stop)
337 {
338 stop = ConditionWait(s_writerStopCondition, 1000);
339
340 // Check for new day start
341 time_t t = time(NULL);
342 if ((s_rotationMode == NXLOG_ROTATION_DAILY) && (t >= m_currentDayStart + 86400))
343 {
344 RotateLog(FALSE);
345 }
346
347 MutexLock(m_mutexLogAccess);
348 if (!s_logBuffer.isEmpty())
349 {
350 if (s_flags & NXLOG_PRINT_TO_STDOUT)
351 m_consoleWriter(_T("%s"), s_logBuffer.getBuffer());
352
353 size_t buflen = s_logBuffer.length();
354 char *data = s_logBuffer.getUTF8String();
355 s_logBuffer.clear();
356 MutexUnlock(m_mutexLogAccess);
357
358 if (s_flags & NXLOG_DEBUG_MODE)
359 {
360 char marker[64];
361 sprintf(marker, "##(" INT64_FMTA ")" INT64_FMTA " @" INT64_FMTA "\n",
362 (INT64)buflen, (INT64)strlen(data), GetCurrentTimeMs());
363 #ifdef _WIN32
364 fwrite(marker, 1, strlen(marker), m_logFileHandle);
365 #else
366 write(fileno(m_logFileHandle), marker, strlen(marker));
367 #endif
368 }
369
370 #ifdef _WIN32
371 fwrite(data, 1, strlen(data), m_logFileHandle);
372 #else
373 // write is used here because on linux fwrite is not working
374 // after calling fwprintf on a stream
375 size_t size = strlen(data);
376 size_t offset = 0;
377 do
378 {
379 int bw = write(fileno(m_logFileHandle), &data[offset], size);
380 if (bw < 0)
381 break;
382 size -= bw;
383 offset += bw;
384 } while(size > 0);
385 #endif
386 free(data);
387
388 // Check log size
389 if ((m_logFileHandle != NULL) && (s_rotationMode == NXLOG_ROTATION_BY_SIZE) && (s_maxLogSize != 0))
390 {
391 NX_STAT_STRUCT st;
392 NX_FSTAT(fileno(m_logFileHandle), &st);
393 if ((UINT64)st.st_size >= s_maxLogSize)
394 RotateLog(FALSE);
395 }
396 }
397 else
398 {
399 MutexUnlock(m_mutexLogAccess);
400 }
401 }
402 return THREAD_OK;
403 }
404
405 /**
406 * Initialize log
407 */
408 bool LIBNETXMS_EXPORTABLE nxlog_open(const TCHAR *logName, UINT32 flags,
409 const TCHAR *msgModule, unsigned int msgCount, const TCHAR **messages,
410 DWORD debugMsg, DWORD debugMsgTag, DWORD genericMsg)
411 {
412 s_flags = flags & 0x7FFFFFFF;
413 #ifdef _WIN32
414 m_msgModuleHandle = GetModuleHandle(msgModule);
415 #else
416 m_numMessages = msgCount;
417 m_messages = messages;
418 #endif
419 s_debugMsg = debugMsg;
420 s_debugMsgTag = debugMsgTag;
421 s_genericMsg = genericMsg;
422
423 if (s_flags & NXLOG_USE_SYSLOG)
424 {
425 #ifdef _WIN32
426 m_eventLogHandle = RegisterEventSource(NULL, logName);
427 if (m_eventLogHandle != NULL)
428 s_flags |= NXLOG_IS_OPEN;
429 #else
430 #ifdef UNICODE
431 WideCharToMultiByte(CP_ACP, WC_DEFAULTCHAR | WC_COMPOSITECHECK, logName, -1, s_syslogName, 64, NULL, NULL);
432 s_syslogName[63] = 0;
433 #else
434 nx_strncpy(s_syslogName, logName, 64);
435 #endif
436 openlog(s_syslogName, LOG_PID, LOG_DAEMON);
437 s_flags |= NXLOG_IS_OPEN;
438 #endif
439 }
440 else
441 {
442 TCHAR buffer[32];
443
444 nx_strncpy(m_logFileName, logName, MAX_PATH);
445 #if HAVE_FOPEN64
446 m_logFileHandle = _tfopen64(logName, _T("a"));
447 #else
448 m_logFileHandle = _tfopen(logName, _T("a"));
449 #endif
450 if (m_logFileHandle != NULL)
451 {
452 s_flags |= NXLOG_IS_OPEN;
453 _ftprintf(m_logFileHandle, _T("\n%s Log file opened (rotation policy %d, max size ") UINT64_FMT _T(")\n"),
454 FormatLogTimestamp(buffer), s_rotationMode, s_maxLogSize);
455 fflush(m_logFileHandle);
456
457 #ifndef _WIN32
458 int fd = fileno(m_logFileHandle);
459 fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
460 #endif
461
462 if (s_flags & NXLOG_BACKGROUND_WRITER)
463 {
464 s_logBuffer.setAllocationStep(8192);
465 s_writerStopCondition = ConditionCreate(TRUE);
466 s_writerThread = ThreadCreateEx(BackgroundWriterThread, 0, NULL);
467 }
468 }
469
470 m_mutexLogAccess = MutexCreate();
471 m_mutexDebugTagTreeWrite = MutexCreate();
472 SetDayStart();
473 }
474 return (s_flags & NXLOG_IS_OPEN) ? true : false;
475 }
476
477 /**
478 * Close log
479 */
480 void LIBNETXMS_EXPORTABLE nxlog_close()
481 {
482 if (s_flags & NXLOG_IS_OPEN)
483 {
484 if (s_flags & NXLOG_USE_SYSLOG)
485 {
486 #ifdef _WIN32
487 DeregisterEventSource(m_eventLogHandle);
488 #else
489 closelog();
490 #endif
491 }
492 else
493 {
494 if (s_flags & NXLOG_BACKGROUND_WRITER)
495 {
496 ConditionSet(s_writerStopCondition);
497 ThreadJoin(s_writerThread);
498 ConditionDestroy(s_writerStopCondition);
499 }
500
501 if (m_logFileHandle != NULL)
502 fclose(m_logFileHandle);
503 if (m_mutexLogAccess != INVALID_MUTEX_HANDLE)
504 MutexDestroy(m_mutexLogAccess);
505 if (m_mutexDebugTagTreeWrite != INVALID_MUTEX_HANDLE)
506 MutexDestroy(m_mutexDebugTagTreeWrite);
507 }
508 s_flags &= ~NXLOG_IS_OPEN;
509 }
510 }
511
512 /**
513 * Write record to log file
514 */
515 static void WriteLogToFile(TCHAR *message, const WORD type)
516 {
517 const TCHAR *loglevel;
518 switch(type)
519 {
520 case NXLOG_ERROR:
521 loglevel = _T("*E* ");
522 break;
523 case NXLOG_WARNING:
524 loglevel = _T("*W* ");
525 break;
526 case NXLOG_INFO:
527 loglevel = _T("*I* ");
528 break;
529 case NXLOG_DEBUG:
530 loglevel = _T("*D* ");
531 break;
532 default:
533 loglevel = _T("*?* ");
534 break;
535 }
536
537 TCHAR buffer[64];
538 if (s_flags & NXLOG_BACKGROUND_WRITER)
539 {
540 MutexLock(m_mutexLogAccess);
541
542 FormatLogTimestamp(buffer);
543 s_logBuffer.append(buffer);
544 s_logBuffer.append(_T(" "));
545 s_logBuffer.append(loglevel);
546 s_logBuffer.append(message);
547
548 MutexUnlock(m_mutexLogAccess);
549 }
550 else
551 {
552 // Prevent simultaneous write to log file
553 MutexLock(m_mutexLogAccess);
554
555 // Check for new day start
556 time_t t = time(NULL);
557 if ((s_rotationMode == NXLOG_ROTATION_DAILY) && (t >= m_currentDayStart + 86400))
558 {
559 RotateLog(FALSE);
560 }
561
562 FormatLogTimestamp(buffer);
563 if (m_logFileHandle != NULL)
564 {
565 _ftprintf(m_logFileHandle, _T("%s %s%s"), buffer, loglevel, message);
566 fflush(m_logFileHandle);
567 }
568 if (s_flags & NXLOG_PRINT_TO_STDOUT)
569 m_consoleWriter(_T("%s %s%s"), buffer, loglevel, message);
570
571 // Check log size
572 if ((m_logFileHandle != NULL) && (s_rotationMode == NXLOG_ROTATION_BY_SIZE) && (s_maxLogSize != 0))
573 {
574 NX_STAT_STRUCT st;
575 NX_FSTAT(fileno(m_logFileHandle), &st);
576 if ((UINT64)st.st_size >= s_maxLogSize)
577 RotateLog(FALSE);
578 }
579
580 MutexUnlock(m_mutexLogAccess);
581 }
582 }
583
584 /**
585 * Format message (UNIX version)
586 */
587 #ifndef _WIN32
588
589 static TCHAR *FormatMessageUX(UINT32 dwMsgId, TCHAR **ppStrings)
590 {
591 const TCHAR *p;
592 TCHAR *pMsg;
593 int i, iSize, iLen;
594
595 if (dwMsgId >= m_numMessages)
596 {
597 // No message with this ID
598 pMsg = (TCHAR *)malloc(64 * sizeof(TCHAR));
599 _sntprintf(pMsg, 64, _T("MSG 0x%08X - Unable to find message text\n"), (unsigned int)dwMsgId);
600 }
601 else
602 {
603 iSize = (_tcslen(m_messages[dwMsgId]) + 2) * sizeof(TCHAR);
604 pMsg = (TCHAR *)malloc(iSize);
605
606 for(i = 0, p = m_messages[dwMsgId]; *p != 0; p++)
607 if (*p == _T('%'))
608 {
609 p++;
610 if ((*p >= _T('1')) && (*p <= _T('9')))
611 {
612 iLen = _tcslen(ppStrings[*p - _T('1')]);
613 iSize += iLen * sizeof(TCHAR);
614 pMsg = (TCHAR *)realloc(pMsg, iSize);
615 _tcscpy(&pMsg[i], ppStrings[*p - _T('1')]);
616 i += iLen;
617 }
618 else
619 {
620 if (*p == 0) // Handle single % character at the string end
621 break;
622 pMsg[i++] = *p;
623 }
624 }
625 else
626 {
627 pMsg[i++] = *p;
628 }
629 pMsg[i++] = _T('\n');
630 pMsg[i] = 0;
631 }
632
633 return pMsg;
634 }
635
636 #endif /* ! _WIN32 */
637
638 /**
639 * Write log record
640 * Parameters:
641 * msg - Message ID
642 * wType - Message type (see ReportEvent() for details)
643 * format - Parameter format string, each parameter represented by one character.
644 * The following format characters can be used:
645 * s - String (multibyte or UNICODE, depending on build)
646 * m - multibyte string
647 * u - UNICODE string
648 * d - Decimal integer
649 * x - Hex integer
650 * e - System error code (will appear in log as textual description)
651 * a - IP address in network byte order
652 * A - IPv6 address in network byte order
653 * H - IPv6 address in network byte order (will appear in [])
654 * I - pointer to InetAddress object
655 */
656 void LIBNETXMS_EXPORTABLE nxlog_write(DWORD msg, WORD wType, const char *format, ...)
657 {
658 va_list args;
659 TCHAR *strings[16], *pMsg, szBuffer[256];
660 int numStrings = 0;
661 UINT32 error;
662 #if defined(UNICODE) && !defined(_WIN32)
663 char *mbMsg;
664 #endif
665
666 if (!(s_flags & NXLOG_IS_OPEN) || (msg == 0))
667 return;
668
669 memset(strings, 0, sizeof(TCHAR *) * 16);
670
671 if (format != NULL)
672 {
673 va_start(args, format);
674
675 for(; (format[numStrings] != 0) && (numStrings < 16); numStrings++)
676 {
677 switch(format[numStrings])
678 {
679 case 's':
680 strings[numStrings] = _tcsdup(va_arg(args, TCHAR *));
681 break;
682 case 'm':
683 #ifdef UNICODE
684 strings[numStrings] = WideStringFromMBString(va_arg(args, char *));
685 #else
686 strings[numStrings] = strdup(va_arg(args, char *));
687 #endif
688 break;
689 case 'u':
690 #ifdef UNICODE
691 strings[numStrings] = wcsdup(va_arg(args, WCHAR *));
692 #else
693 strings[numStrings] = MBStringFromWideString(va_arg(args, WCHAR *));
694 #endif
695 break;
696 case 'd':
697 strings[numStrings] = (TCHAR *)malloc(16 * sizeof(TCHAR));
698 _sntprintf(strings[numStrings], 16, _T("%d"), (int)(va_arg(args, LONG)));
699 break;
700 case 'x':
701 strings[numStrings] = (TCHAR *)malloc(16 * sizeof(TCHAR));
702 _sntprintf(strings[numStrings], 16, _T("0x%08X"), (unsigned int)(va_arg(args, UINT32)));
703 break;
704 case 'a':
705 strings[numStrings] = (TCHAR *)malloc(20 * sizeof(TCHAR));
706 IpToStr(va_arg(args, UINT32), strings[numStrings]);
707 break;
708 case 'A':
709 strings[numStrings] = (TCHAR *)malloc(48 * sizeof(TCHAR));
710 Ip6ToStr(va_arg(args, BYTE *), strings[numStrings]);
711 break;
712 case 'H':
713 strings[numStrings] = (TCHAR *)malloc(48 * sizeof(TCHAR));
714 strings[numStrings][0] = _T('[');
715 Ip6ToStr(va_arg(args, BYTE *), strings[numStrings] + 1);
716 _tcscat(strings[numStrings], _T("]"));
717 break;
718 case 'I':
719 strings[numStrings] = (TCHAR *)malloc(48 * sizeof(TCHAR));
720 va_arg(args, InetAddress *)->toString(strings[numStrings]);
721 break;
722 case 'e':
723 error = va_arg(args, UINT32);
724 #ifdef _WIN32
725 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
726 FORMAT_MESSAGE_FROM_SYSTEM |
727 FORMAT_MESSAGE_IGNORE_INSERTS,
728 NULL, error,
729 MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), // Default language
730 (LPTSTR)&pMsg, 0, NULL) > 0)
731 {
732 pMsg[_tcscspn(pMsg, _T("\r\n"))] = 0;
733 strings[numStrings] = (TCHAR *)malloc((_tcslen(pMsg) + 1) * sizeof(TCHAR));
734 _tcscpy(strings[numStrings], pMsg);
735 LocalFree(pMsg);
736 }
737 else
738 {
739 strings[numStrings] = (TCHAR *)malloc(64 * sizeof(TCHAR));
740 _sntprintf(strings[numStrings], 64, _T("MSG 0x%08X - Unable to find message text"), error);
741 }
742 #else /* _WIN32 */
743 #if HAVE_STRERROR_R
744 #if HAVE_POSIX_STRERROR_R
745 strings[numStrings] = (TCHAR *)malloc(256 * sizeof(TCHAR));
746 _tcserror_r((int)error, strings[numStrings], 256);
747 #else
748 strings[numStrings] = _tcsdup(_tcserror_r((int)error, szBuffer, 256));
749 #endif
750 #else
751 strings[numStrings] = _tcsdup(_tcserror((int)error));
752 #endif
753 #endif
754 break;
755 default:
756 strings[numStrings] = (TCHAR *)malloc(32 * sizeof(TCHAR));
757 _sntprintf(strings[numStrings], 32, _T("BAD FORMAT (0x%08X)"), (unsigned int)(va_arg(args, UINT32)));
758 break;
759 }
760 }
761 va_end(args);
762 }
763
764 #ifdef _WIN32
765 if (!(s_flags & NXLOG_USE_SYSLOG) || (s_flags & NXLOG_PRINT_TO_STDOUT))
766 {
767 LPVOID lpMsgBuf;
768
769 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
770 FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
771 m_msgModuleHandle, msg, 0, (LPTSTR)&lpMsgBuf, 0, (va_list *)strings) > 0)
772 {
773 TCHAR *pCR;
774
775 // Replace trailing CR/LF pair with LF
776 pCR = _tcschr((TCHAR *)lpMsgBuf, _T('\r'));
777 if (pCR != NULL)
778 {
779 *pCR = _T('\n');
780 pCR++;
781 *pCR = 0;
782 }
783 if (s_flags & NXLOG_USE_SYSLOG)
784 {
785 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer), (TCHAR *)lpMsgBuf);
786 }
787 else
788 {
789 WriteLogToFile((TCHAR *)lpMsgBuf, wType);
790 }
791 LocalFree(lpMsgBuf);
792 }
793 else
794 {
795 TCHAR message[64];
796
797 _sntprintf(message, 64, _T("MSG 0x%08X - cannot format message"), msg);
798 if (s_flags & NXLOG_USE_SYSLOG)
799 {
800 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer), message);
801 }
802 else
803 {
804 WriteLogToFile(message, wType);
805 }
806 }
807 }
808
809 if (s_flags & NXLOG_USE_SYSLOG)
810 {
811 ReportEvent(m_eventLogHandle, (wType == EVENTLOG_DEBUG_TYPE) ? EVENTLOG_INFORMATION_TYPE : wType, 0, msg, NULL, numStrings, 0, (const TCHAR **)strings, NULL);
812 }
813 #else /* _WIN32 */
814 pMsg = FormatMessageUX(msg, strings);
815 if (s_flags & NXLOG_USE_SYSLOG)
816 {
817 int level;
818
819 switch(wType)
820 {
821 case EVENTLOG_ERROR_TYPE:
822 level = LOG_ERR;
823 break;
824 case EVENTLOG_WARNING_TYPE:
825 level = LOG_WARNING;
826 break;
827 case EVENTLOG_INFORMATION_TYPE:
828 level = LOG_NOTICE;
829 break;
830 case EVENTLOG_DEBUG_TYPE:
831 level = LOG_DEBUG;
832 break;
833 default:
834 level = LOG_INFO;
835 break;
836 }
837 #ifdef UNICODE
838 mbMsg = MBStringFromWideString(pMsg);
839 syslog(level, "%s", mbMsg);
840 free(mbMsg);
841 #else
842 syslog(level, "%s", pMsg);
843 #endif
844
845 if (s_flags & NXLOG_PRINT_TO_STDOUT)
846 {
847 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer), pMsg);
848 }
849 }
850 else
851 {
852 WriteLogToFile(pMsg, wType);
853 }
854 free(pMsg);
855 #endif /* _WIN32 */
856
857 while(--numStrings >= 0)
858 free(strings[numStrings]);
859 }
860
861 /**
862 * Write generic message to log (useful for warning and error messages generated within libraries)
863 */
864 void LIBNETXMS_EXPORTABLE nxlog_write_generic(WORD type, const TCHAR *format, ...)
865 {
866 va_list args;
867 va_start(args, format);
868 TCHAR msg[8192];
869 _vsntprintf(msg, 8192, format, args);
870 va_end(args);
871 nxlog_write(s_genericMsg, type, "s", msg);
872 }
873
874 /**
875 * Write debug message
876 */
877 void LIBNETXMS_EXPORTABLE nxlog_debug(int level, const TCHAR *format, ...)
878 {
879 if (level > nxlog_get_debug_level_tag(_T("*")))
880 return;
881
882 va_list args;
883 va_start(args, format);
884 TCHAR buffer[8192];
885 _vsntprintf(buffer, 8192, format, args);
886 va_end(args);
887 nxlog_write(s_debugMsg, NXLOG_DEBUG, "s", buffer);
888
889 if (s_debugWriter != NULL)
890 s_debugWriter(NULL, buffer);
891 }
892
893 /**
894 * Write debug message
895 */
896 void LIBNETXMS_EXPORTABLE nxlog_debug2(int level, const TCHAR *format, va_list args)
897 {
898 if (level > nxlog_get_debug_level_tag(_T("*")))
899 return;
900
901 TCHAR buffer[8192];
902 _vsntprintf(buffer, 8192, format, args);
903 nxlog_write(s_debugMsg, NXLOG_DEBUG, "s", buffer);
904
905 if (s_debugWriter != NULL)
906 s_debugWriter(NULL, buffer);
907 }
908
909 /**
910 * Write debug message with tag
911 */
912 static void nxlog_debug_tag_internal(const TCHAR *tag, int level, const TCHAR *format, va_list args)
913 {
914 TCHAR tagf[20];
915 int i;
916 for(i = 0; (i < 19) && tag[i] != 0; i++)
917 tagf[i] = tag[i];
918 for(; i < 19; i++)
919 tagf[i] = ' ';
920 tagf[i] = 0;
921
922 TCHAR buffer[8192];
923 _vsntprintf(buffer, 8192, format, args);
924 nxlog_write(s_debugMsgTag, NXLOG_DEBUG, "ss", tagf, buffer);
925
926 if (s_debugWriter != NULL)
927 s_debugWriter(tag, buffer);
928 }
929
930 /**
931 * Write debug message with tag
932 */
933 void LIBNETXMS_EXPORTABLE nxlog_debug_tag(const TCHAR *tag, int level, const TCHAR *format, ...)
934 {
935 if (level > nxlog_get_debug_level_tag(tag))
936 return;
937
938 va_list args;
939 va_start(args, format);
940 nxlog_debug_tag_internal(tag, level, format, args);
941 va_end(args);
942 }
943
944 /**
945 * Write debug message with tag
946 */
947 void LIBNETXMS_EXPORTABLE nxlog_debug_tag2(const TCHAR *tag, int level, const TCHAR *format, va_list args)
948 {
949 if (level > nxlog_get_debug_level_tag(tag))
950 return;
951
952 nxlog_debug_tag_internal(tag, level, format, args);
953 }
954
955 /**
956 * Write debug message with tag and object ID (added as last part of a tag)
957 */
958 void LIBNETXMS_EXPORTABLE nxlog_debug_tag_object(const TCHAR *tag, UINT32 objectId, int level, const TCHAR *format, ...)
959 {
960 TCHAR fullTag[256];
961 _sntprintf(fullTag, 256, _T("%s.%u"), tag, objectId);
962 if (level > nxlog_get_debug_level_tag(fullTag))
963 return;
964
965 va_list args;
966 va_start(args, format);
967 nxlog_debug_tag_internal(fullTag, level, format, args);
968 va_end(args);
969 }
970
971 /**
972 * Write debug message with tag and object ID (added as last part of a tag)
973 */
974 void LIBNETXMS_EXPORTABLE nxlog_debug_tag_object2(const TCHAR *tag, UINT32 objectId, int level, const TCHAR *format, va_list args)
975 {
976 TCHAR fullTag[256];
977 _sntprintf(fullTag, 256, _T("%s.%u"), tag, objectId);
978 if (level > nxlog_get_debug_level_tag(fullTag))
979 return;
980
981 nxlog_debug_tag_internal(fullTag, level, format, args);
982 }