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