db9566c51a8df987cb1e4900b330c9d5e3a13bed
[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("[%d-%b-%Y %H:%M:%S"), loc);
156 _sntprintf(&buffer[21], 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 wType)
516 {
517 TCHAR buffer[64];
518 TCHAR loglevel[64];
519
520 switch(wType)
521 {
522 case EVENTLOG_ERROR_TYPE:
523 _sntprintf(loglevel, 16, _T("[%s] "), _T("ERROR"));
524 break;
525 case EVENTLOG_WARNING_TYPE:
526 _sntprintf(loglevel, 16, _T("[%s] "), _T("WARN "));
527 break;
528 case EVENTLOG_INFORMATION_TYPE:
529 _sntprintf(loglevel, 16, _T("[%s] "), _T("INFO "));
530 break;
531 case EVENTLOG_DEBUG_TYPE:
532 _sntprintf(loglevel, 16, _T("[%s] "), _T("DEBUG"));
533 break;
534 default:
535 _tcsncpy(loglevel, _T(""), 1);
536 break;
537 }
538
539 if (s_flags & NXLOG_BACKGROUND_WRITER)
540 {
541 MutexLock(m_mutexLogAccess);
542
543 FormatLogTimestamp(buffer);
544 s_logBuffer.append(buffer);
545 s_logBuffer.append(_T(" "));
546 s_logBuffer.append(loglevel);
547 s_logBuffer.append(message);
548
549 MutexUnlock(m_mutexLogAccess);
550 }
551 else
552 {
553 // Prevent simultaneous write to log file
554 MutexLock(m_mutexLogAccess);
555
556 // Check for new day start
557 time_t t = time(NULL);
558 if ((s_rotationMode == NXLOG_ROTATION_DAILY) && (t >= m_currentDayStart + 86400))
559 {
560 RotateLog(FALSE);
561 }
562
563 FormatLogTimestamp(buffer);
564 if (m_logFileHandle != NULL)
565 {
566 _ftprintf(m_logFileHandle, _T("%s %s%s"), buffer, loglevel, message);
567 fflush(m_logFileHandle);
568 }
569 if (s_flags & NXLOG_PRINT_TO_STDOUT)
570 m_consoleWriter(_T("%s %s%s"), buffer, loglevel, message);
571
572 // Check log size
573 if ((m_logFileHandle != NULL) && (s_rotationMode == NXLOG_ROTATION_BY_SIZE) && (s_maxLogSize != 0))
574 {
575 NX_STAT_STRUCT st;
576 NX_FSTAT(fileno(m_logFileHandle), &st);
577 if ((UINT64)st.st_size >= s_maxLogSize)
578 RotateLog(FALSE);
579 }
580
581 MutexUnlock(m_mutexLogAccess);
582 }
583 }
584
585 /**
586 * Format message (UNIX version)
587 */
588 #ifndef _WIN32
589
590 static TCHAR *FormatMessageUX(UINT32 dwMsgId, TCHAR **ppStrings)
591 {
592 const TCHAR *p;
593 TCHAR *pMsg;
594 int i, iSize, iLen;
595
596 if (dwMsgId >= m_numMessages)
597 {
598 // No message with this ID
599 pMsg = (TCHAR *)malloc(64 * sizeof(TCHAR));
600 _sntprintf(pMsg, 64, _T("MSG 0x%08X - Unable to find message text\n"), (unsigned int)dwMsgId);
601 }
602 else
603 {
604 iSize = (_tcslen(m_messages[dwMsgId]) + 2) * sizeof(TCHAR);
605 pMsg = (TCHAR *)malloc(iSize);
606
607 for(i = 0, p = m_messages[dwMsgId]; *p != 0; p++)
608 if (*p == _T('%'))
609 {
610 p++;
611 if ((*p >= _T('1')) && (*p <= _T('9')))
612 {
613 iLen = _tcslen(ppStrings[*p - _T('1')]);
614 iSize += iLen * sizeof(TCHAR);
615 pMsg = (TCHAR *)realloc(pMsg, iSize);
616 _tcscpy(&pMsg[i], ppStrings[*p - _T('1')]);
617 i += iLen;
618 }
619 else
620 {
621 if (*p == 0) // Handle single % character at the string end
622 break;
623 pMsg[i++] = *p;
624 }
625 }
626 else
627 {
628 pMsg[i++] = *p;
629 }
630 pMsg[i++] = _T('\n');
631 pMsg[i] = 0;
632 }
633
634 return pMsg;
635 }
636
637 #endif /* ! _WIN32 */
638
639 /**
640 * Write log record
641 * Parameters:
642 * msg - Message ID
643 * wType - Message type (see ReportEvent() for details)
644 * format - Parameter format string, each parameter represented by one character.
645 * The following format characters can be used:
646 * s - String (multibyte or UNICODE, depending on build)
647 * m - multibyte string
648 * u - UNICODE string
649 * d - Decimal integer
650 * x - Hex integer
651 * e - System error code (will appear in log as textual description)
652 * a - IP address in network byte order
653 * A - IPv6 address in network byte order
654 * H - IPv6 address in network byte order (will appear in [])
655 * I - pointer to InetAddress object
656 */
657 void LIBNETXMS_EXPORTABLE nxlog_write(DWORD msg, WORD wType, const char *format, ...)
658 {
659 va_list args;
660 TCHAR *strings[16], *pMsg, szBuffer[256];
661 int numStrings = 0;
662 UINT32 error;
663 #if defined(UNICODE) && !defined(_WIN32)
664 char *mbMsg;
665 #endif
666
667 if (!(s_flags & NXLOG_IS_OPEN) || (msg == 0))
668 return;
669
670 memset(strings, 0, sizeof(TCHAR *) * 16);
671
672 if (format != NULL)
673 {
674 va_start(args, format);
675
676 for(; (format[numStrings] != 0) && (numStrings < 16); numStrings++)
677 {
678 switch(format[numStrings])
679 {
680 case 's':
681 strings[numStrings] = _tcsdup(va_arg(args, TCHAR *));
682 break;
683 case 'm':
684 #ifdef UNICODE
685 strings[numStrings] = WideStringFromMBString(va_arg(args, char *));
686 #else
687 strings[numStrings] = strdup(va_arg(args, char *));
688 #endif
689 break;
690 case 'u':
691 #ifdef UNICODE
692 strings[numStrings] = wcsdup(va_arg(args, WCHAR *));
693 #else
694 strings[numStrings] = MBStringFromWideString(va_arg(args, WCHAR *));
695 #endif
696 break;
697 case 'd':
698 strings[numStrings] = (TCHAR *)malloc(16 * sizeof(TCHAR));
699 _sntprintf(strings[numStrings], 16, _T("%d"), (int)(va_arg(args, LONG)));
700 break;
701 case 'x':
702 strings[numStrings] = (TCHAR *)malloc(16 * sizeof(TCHAR));
703 _sntprintf(strings[numStrings], 16, _T("0x%08X"), (unsigned int)(va_arg(args, UINT32)));
704 break;
705 case 'a':
706 strings[numStrings] = (TCHAR *)malloc(20 * sizeof(TCHAR));
707 IpToStr(va_arg(args, UINT32), strings[numStrings]);
708 break;
709 case 'A':
710 strings[numStrings] = (TCHAR *)malloc(48 * sizeof(TCHAR));
711 Ip6ToStr(va_arg(args, BYTE *), strings[numStrings]);
712 break;
713 case 'H':
714 strings[numStrings] = (TCHAR *)malloc(48 * sizeof(TCHAR));
715 strings[numStrings][0] = _T('[');
716 Ip6ToStr(va_arg(args, BYTE *), strings[numStrings] + 1);
717 _tcscat(strings[numStrings], _T("]"));
718 break;
719 case 'I':
720 strings[numStrings] = (TCHAR *)malloc(48 * sizeof(TCHAR));
721 va_arg(args, InetAddress *)->toString(strings[numStrings]);
722 break;
723 case 'e':
724 error = va_arg(args, UINT32);
725 #ifdef _WIN32
726 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
727 FORMAT_MESSAGE_FROM_SYSTEM |
728 FORMAT_MESSAGE_IGNORE_INSERTS,
729 NULL, error,
730 MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), // Default language
731 (LPTSTR)&pMsg, 0, NULL) > 0)
732 {
733 pMsg[_tcscspn(pMsg, _T("\r\n"))] = 0;
734 strings[numStrings] = (TCHAR *)malloc((_tcslen(pMsg) + 1) * sizeof(TCHAR));
735 _tcscpy(strings[numStrings], pMsg);
736 LocalFree(pMsg);
737 }
738 else
739 {
740 strings[numStrings] = (TCHAR *)malloc(64 * sizeof(TCHAR));
741 _sntprintf(strings[numStrings], 64, _T("MSG 0x%08X - Unable to find message text"), error);
742 }
743 #else /* _WIN32 */
744 #if HAVE_STRERROR_R
745 #if HAVE_POSIX_STRERROR_R
746 strings[numStrings] = (TCHAR *)malloc(256 * sizeof(TCHAR));
747 _tcserror_r((int)error, strings[numStrings], 256);
748 #else
749 strings[numStrings] = _tcsdup(_tcserror_r((int)error, szBuffer, 256));
750 #endif
751 #else
752 strings[numStrings] = _tcsdup(_tcserror((int)error));
753 #endif
754 #endif
755 break;
756 default:
757 strings[numStrings] = (TCHAR *)malloc(32 * sizeof(TCHAR));
758 _sntprintf(strings[numStrings], 32, _T("BAD FORMAT (0x%08X)"), (unsigned int)(va_arg(args, UINT32)));
759 break;
760 }
761 }
762 va_end(args);
763 }
764
765 #ifdef _WIN32
766 if (!(s_flags & NXLOG_USE_SYSLOG) || (s_flags & NXLOG_PRINT_TO_STDOUT))
767 {
768 LPVOID lpMsgBuf;
769
770 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
771 FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
772 m_msgModuleHandle, msg, 0, (LPTSTR)&lpMsgBuf, 0, (va_list *)strings) > 0)
773 {
774 TCHAR *pCR;
775
776 // Replace trailing CR/LF pair with LF
777 pCR = _tcschr((TCHAR *)lpMsgBuf, _T('\r'));
778 if (pCR != NULL)
779 {
780 *pCR = _T('\n');
781 pCR++;
782 *pCR = 0;
783 }
784 if (s_flags & NXLOG_USE_SYSLOG)
785 {
786 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer), (TCHAR *)lpMsgBuf);
787 }
788 else
789 {
790 WriteLogToFile((TCHAR *)lpMsgBuf, wType);
791 }
792 LocalFree(lpMsgBuf);
793 }
794 else
795 {
796 TCHAR message[64];
797
798 _sntprintf(message, 64, _T("MSG 0x%08X - cannot format message"), msg);
799 if (s_flags & NXLOG_USE_SYSLOG)
800 {
801 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer), message);
802 }
803 else
804 {
805 WriteLogToFile(message, wType);
806 }
807 }
808 }
809
810 if (s_flags & NXLOG_USE_SYSLOG)
811 {
812 ReportEvent(m_eventLogHandle, (wType == EVENTLOG_DEBUG_TYPE) ? EVENTLOG_INFORMATION_TYPE : wType, 0, msg, NULL, numStrings, 0, (const TCHAR **)strings, NULL);
813 }
814 #else /* _WIN32 */
815 pMsg = FormatMessageUX(msg, strings);
816 if (s_flags & NXLOG_USE_SYSLOG)
817 {
818 int level;
819
820 switch(wType)
821 {
822 case EVENTLOG_ERROR_TYPE:
823 level = LOG_ERR;
824 break;
825 case EVENTLOG_WARNING_TYPE:
826 level = LOG_WARNING;
827 break;
828 case EVENTLOG_INFORMATION_TYPE:
829 level = LOG_NOTICE;
830 break;
831 case EVENTLOG_DEBUG_TYPE:
832 level = LOG_DEBUG;
833 break;
834 default:
835 level = LOG_INFO;
836 break;
837 }
838 #ifdef UNICODE
839 mbMsg = MBStringFromWideString(pMsg);
840 syslog(level, "%s", mbMsg);
841 free(mbMsg);
842 #else
843 syslog(level, "%s", pMsg);
844 #endif
845
846 if (s_flags & NXLOG_PRINT_TO_STDOUT)
847 {
848 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer), pMsg);
849 }
850 }
851 else
852 {
853 WriteLogToFile(pMsg, wType);
854 }
855 free(pMsg);
856 #endif /* _WIN32 */
857
858 while(--numStrings >= 0)
859 free(strings[numStrings]);
860 }
861
862 /**
863 * Write generic message to log (useful for warning and error messages generated within libraries)
864 */
865 void LIBNETXMS_EXPORTABLE nxlog_write_generic(WORD type, const TCHAR *format, ...)
866 {
867 va_list args;
868 va_start(args, format);
869 TCHAR msg[8192];
870 _vsntprintf(msg, 8192, format, args);
871 va_end(args);
872 nxlog_write(s_genericMsg, type, "s", msg);
873 }
874
875 /**
876 * Write debug message
877 */
878 void LIBNETXMS_EXPORTABLE nxlog_debug(int level, const TCHAR *format, ...)
879 {
880 if (level > nxlog_get_debug_level_tag(_T("*")))
881 return;
882
883 va_list args;
884 va_start(args, format);
885 TCHAR buffer[8192];
886 _vsntprintf(buffer, 8192, format, args);
887 va_end(args);
888 nxlog_write(s_debugMsg, NXLOG_DEBUG, "s", buffer);
889
890 if (s_debugWriter != NULL)
891 s_debugWriter(NULL, buffer);
892 }
893
894 /**
895 * Write debug message
896 */
897 void LIBNETXMS_EXPORTABLE nxlog_debug2(int level, const TCHAR *format, va_list args)
898 {
899 if (level > nxlog_get_debug_level_tag(_T("*")))
900 return;
901
902 TCHAR buffer[8192];
903 _vsntprintf(buffer, 8192, format, args);
904 nxlog_write(s_debugMsg, NXLOG_DEBUG, "s", buffer);
905
906 if (s_debugWriter != NULL)
907 s_debugWriter(NULL, buffer);
908 }
909
910 /**
911 * Write debug message with tag
912 */
913 void LIBNETXMS_EXPORTABLE nxlog_debug_tag(const TCHAR *tag, int level, const TCHAR *format, ...)
914 {
915 if (level > nxlog_get_debug_level_tag(tag))
916 return;
917
918 va_list args;
919 va_start(args, format);
920 TCHAR buffer[8192];
921 _vsntprintf(buffer, 8192, format, args);
922 va_end(args);
923 nxlog_write(s_debugMsgTag, NXLOG_DEBUG, "ss", tag, buffer);
924
925 if (s_debugWriter != NULL)
926 s_debugWriter(tag, buffer);
927 }