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