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