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