minor debug tags refactoring; implemented call nxlog_get_all_debug_tags; server conso...
[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
2df047f4
VK
79/**
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);
b7306e95 87 tagTreeSecondary->setRootDebugLevel(level); // Update the secondary tree
1e42f2e8 88 SwapAndWait();
b7306e95 89 tagTreeSecondary->setRootDebugLevel(level); // Update the previously active tree
93b48f0e
EJ
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{
b7306e95
VK
99 if ((tag == NULL) || !_tcscmp(tag, _T("*")))
100 {
101 nxlog_set_debug_level(level);
102 }
103 else
b41dff2a 104 {
93b48f0e 105 MutexLock(m_mutexDebugTagTreeWrite);
b7306e95 106 if ((level >= 0) && (level <= 9))
93b48f0e
EJ
107 {
108 tagTreeSecondary->add(tag, level);
1e42f2e8 109 SwapAndWait();
93b48f0e
EJ
110 tagTreeSecondary->add(tag, level);
111 }
b41dff2a 112 else if (level < 0)
93b48f0e
EJ
113 {
114 tagTreeSecondary->remove(tag);
1e42f2e8 115 SwapAndWait();
93b48f0e
EJ
116 tagTreeSecondary->remove(tag);
117 }
118 MutexUnlock(m_mutexDebugTagTreeWrite);
b41dff2a 119 }
2df047f4
VK
120}
121
122/**
123 * Get current debug level
124 */
125int LIBNETXMS_EXPORTABLE nxlog_get_debug_level()
126{
b7306e95 127 return tagTreeActive->getRootDebugLevel();
b41dff2a
EJ
128}
129
130/**
131 * Get current debug level for tag
132 */
133int LIBNETXMS_EXPORTABLE nxlog_get_debug_level_tag(const TCHAR *tag)
134{
b7306e95
VK
135 return tagTreeActive->getDebugLevel(tag);
136}
137
138/**
139 * Get all configured debug tags
140 */
141ObjectArray<DebugTagInfo> LIBNETXMS_EXPORTABLE *nxlog_get_all_debug_tags()
142{
143 return tagTreeActive->getAllTags();
2df047f4
VK
144}
145
588825b6
VK
146/**
147 * Set additional debug writer callback. It will be called for each line written with nxlog_debug.
148 */
7c587518 149extern "C" void LIBNETXMS_EXPORTABLE nxlog_set_debug_writer(NxLogDebugWriter writer)
588825b6
VK
150{
151 s_debugWriter = writer;
152}
153
e8387b24
VK
154/**
155 * Format current time for output
156 */
157static 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
d1218c93
VK
167 _tcsftime(buffer, 32, _T("%Y.%m.%d %H:%M:%S"), loc);
168 _sntprintf(&buffer[19], 8, _T(".%03d"), (int)(now % 1000));
e8387b24
VK
169 return buffer;
170}
4addc3a3 171
e8387b24
VK
172/**
173 * Set timestamp of start of the current day
174 */
5945d50a
VK
175static void SetDayStart()
176{
177 time_t now = time(NULL);
178 struct tm dayStart;
179#if HAVE_LOCALTIME_R
d0daa307 180 localtime_r(&now, &dayStart);
5945d50a
VK
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
e8387b24
VK
191/**
192 * Set log rotation policy
193 * Setting log size to 0 or mode to NXLOG_ROTATION_DISABLED disables log rotation
194 */
458b5d2b 195bool LIBNETXMS_EXPORTABLE nxlog_set_rotation_policy(int rotationMode, UINT64 maxLogSize, int historySize, const TCHAR *dailySuffix)
4addc3a3 196{
2df047f4 197 bool isValid = true;
bf0b35d0 198
5945d50a 199 if ((rotationMode >= 0) || (rotationMode <= 2))
bf0b35d0 200 {
458b5d2b 201 s_rotationMode = rotationMode;
5945d50a
VK
202 if (rotationMode == NXLOG_ROTATION_BY_SIZE)
203 {
204 if ((maxLogSize == 0) || (maxLogSize >= 1024))
205 {
458b5d2b 206 s_maxLogSize = maxLogSize;
5945d50a
VK
207 }
208 else
209 {
458b5d2b 210 s_maxLogSize = 1024;
2df047f4 211 isValid = false;
5945d50a
VK
212 }
213
214 if ((historySize >= 0) && (historySize <= MAX_LOG_HISTORY_SIZE))
215 {
458b5d2b 216 s_logHistorySize = historySize;
5945d50a
VK
217 }
218 else
219 {
220 if (historySize > MAX_LOG_HISTORY_SIZE)
458b5d2b 221 s_logHistorySize = MAX_LOG_HISTORY_SIZE;
2df047f4 222 isValid = false;
5945d50a
VK
223 }
224 }
225 else if (rotationMode == NXLOG_ROTATION_DAILY)
226 {
227 if ((dailySuffix != NULL) && (dailySuffix[0] != 0))
458b5d2b 228 nx_strncpy(s_dailyLogSuffixTemplate, dailySuffix, sizeof(s_dailyLogSuffixTemplate) / sizeof(TCHAR));
5945d50a
VK
229 SetDayStart();
230 }
bf0b35d0
VK
231 }
232 else
233 {
2df047f4 234 isValid = false;
bf0b35d0 235 }
4addc3a3 236
458b5d2b
VK
237 if (isValid)
238 nxlog_debug(0, _T("Log rotation policy set to %d (size=") UINT64_FMT _T(", count=%d)"), rotationMode, maxLogSize, historySize);
239
bf0b35d0 240 return isValid;
4addc3a3
VK
241}
242
e8387b24
VK
243/**
244 * Set console writer
245 */
9113e749 246void LIBNETXMS_EXPORTABLE nxlog_set_console_writer(NxLogConsoleWriter writer)
f669df41
VK
247{
248 m_consoleWriter = writer;
249}
250
e8387b24
VK
251/**
252 * Rotate log
253 */
2df047f4 254static bool RotateLog(bool needLock)
4addc3a3
VK
255{
256 int i;
257 TCHAR oldName[MAX_PATH], newName[MAX_PATH];
258
458b5d2b 259 if (s_flags & NXLOG_USE_SYSLOG)
4addc3a3
VK
260 return FALSE; // Cannot rotate system logs
261
262 if (needLock)
c17f6cbc 263 MutexLock(m_mutexLogAccess);
4addc3a3 264
458b5d2b 265 if ((m_logFileHandle != NULL) && (s_flags & NXLOG_IS_OPEN))
4addc3a3
VK
266 {
267 fclose(m_logFileHandle);
458b5d2b 268 s_flags &= ~NXLOG_IS_OPEN;
4addc3a3
VK
269 }
270
458b5d2b 271 if (s_rotationMode == NXLOG_ROTATION_BY_SIZE)
4addc3a3 272 {
5945d50a 273 // Delete old files
458b5d2b 274 for(i = MAX_LOG_HISTORY_SIZE; i >= s_logHistorySize; i--)
5945d50a
VK
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);
4addc3a3 291 }
458b5d2b 292 else if (s_rotationMode == NXLOG_ROTATION_DAILY)
4addc3a3 293 {
5945d50a
VK
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];
458b5d2b 301 _tcsftime(buffer, 64, s_dailyLogSuffixTemplate, loc);
4addc3a3 302
5945d50a
VK
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 }
4addc3a3 309
8c2489aa
VK
310 // Reopen log
311#if HAVE_FOPEN64
312 m_logFileHandle = _tfopen64(m_logFileName, _T("w"));
313#else
4addc3a3 314 m_logFileHandle = _tfopen(m_logFileName, _T("w"));
8c2489aa 315#endif
4addc3a3
VK
316 if (m_logFileHandle != NULL)
317 {
458b5d2b 318 s_flags |= NXLOG_IS_OPEN;
8c2489aa 319 TCHAR buffer[32];
e8387b24 320 _ftprintf(m_logFileHandle, _T("%s Log file truncated.\n"), FormatLogTimestamp(buffer));
95da9d21 321 fflush(m_logFileHandle);
45ac5dd0
EJ
322#ifndef _WIN32
323 int fd = fileno(m_logFileHandle);
324 fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
325#endif
8c2489aa 326 }
4addc3a3
VK
327
328 if (needLock)
329 MutexUnlock(m_mutexLogAccess);
330
458b5d2b 331 return (s_flags & NXLOG_IS_OPEN) ? true : false;
4addc3a3
VK
332}
333
e8387b24
VK
334/**
335 * API for application to force log rotation
336 */
2df047f4 337bool LIBNETXMS_EXPORTABLE nxlog_rotate()
4addc3a3 338{
2df047f4 339 return RotateLog(true);
4addc3a3 340}
5039dede 341
4e0e77e6
VK
342/**
343 * Background writer thread
344 */
345static 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);
458b5d2b 354 if ((s_rotationMode == NXLOG_ROTATION_DAILY) && (t >= m_currentDayStart + 86400))
4e0e77e6
VK
355 {
356 RotateLog(FALSE);
357 }
358
359 MutexLock(m_mutexLogAccess);
360 if (!s_logBuffer.isEmpty())
361 {
458b5d2b 362 if (s_flags & NXLOG_PRINT_TO_STDOUT)
4e0e77e6
VK
363 m_consoleWriter(_T("%s"), s_logBuffer.getBuffer());
364
70be5423 365 size_t buflen = s_logBuffer.length();
4e0e77e6
VK
366 char *data = s_logBuffer.getUTF8String();
367 s_logBuffer.clear();
368 MutexUnlock(m_mutexLogAccess);
369
458b5d2b 370 if (s_flags & NXLOG_DEBUG_MODE)
70be5423
VK
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
15fe7742
VK
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
d8692dd1
VK
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);
15fe7742 397#endif
3641e3ad 398 free(data);
4e0e77e6
VK
399
400 // Check log size
458b5d2b 401 if ((m_logFileHandle != NULL) && (s_rotationMode == NXLOG_ROTATION_BY_SIZE) && (s_maxLogSize != 0))
4e0e77e6 402 {
458b5d2b
VK
403 NX_STAT_STRUCT st;
404 NX_FSTAT(fileno(m_logFileHandle), &st);
f5ed8ede 405 if ((UINT64)st.st_size >= s_maxLogSize)
4e0e77e6
VK
406 RotateLog(FALSE);
407 }
408 }
409 else
410 {
411 MutexUnlock(m_mutexLogAccess);
412 }
413 }
414 return THREAD_OK;
415}
416
e8387b24
VK
417/**
418 * Initialize log
419 */
2df047f4 420bool LIBNETXMS_EXPORTABLE nxlog_open(const TCHAR *logName, UINT32 flags,
90a9e65e 421 const TCHAR *msgModule, unsigned int msgCount, const TCHAR **messages,
b41dff2a 422 DWORD debugMsg, DWORD debugMsgTag, DWORD genericMsg)
5039dede 423{
458b5d2b 424 s_flags = flags & 0x7FFFFFFF;
5039dede
AK
425#ifdef _WIN32
426 m_msgModuleHandle = GetModuleHandle(msgModule);
427#else
428 m_numMessages = msgCount;
429 m_messages = messages;
430#endif
2df047f4 431 s_debugMsg = debugMsg;
b41dff2a 432 s_debugMsgTag = debugMsgTag;
90a9e65e 433 s_genericMsg = genericMsg;
5039dede 434
458b5d2b 435 if (s_flags & NXLOG_USE_SYSLOG)
5039dede
AK
436 {
437#ifdef _WIN32
438 m_eventLogHandle = RegisterEventSource(NULL, logName);
439 if (m_eventLogHandle != NULL)
458b5d2b 440 s_flags |= NXLOG_IS_OPEN;
5039dede
AK
441#else
442#ifdef UNICODE
591b3549
VK
443 WideCharToMultiByte(CP_ACP, WC_DEFAULTCHAR | WC_COMPOSITECHECK, logName, -1, s_syslogName, 64, NULL, NULL);
444 s_syslogName[63] = 0;
5039dede 445#else
591b3549 446 nx_strncpy(s_syslogName, logName, 64);
5039dede 447#endif
591b3549 448 openlog(s_syslogName, LOG_PID, LOG_DAEMON);
458b5d2b 449 s_flags |= NXLOG_IS_OPEN;
5039dede
AK
450#endif
451 }
452 else
453 {
454 TCHAR buffer[32];
5039dede 455
4addc3a3 456 nx_strncpy(m_logFileName, logName, MAX_PATH);
8c2489aa
VK
457#if HAVE_FOPEN64
458 m_logFileHandle = _tfopen64(logName, _T("a"));
459#else
5039dede 460 m_logFileHandle = _tfopen(logName, _T("a"));
8c2489aa 461#endif
5039dede
AK
462 if (m_logFileHandle != NULL)
463 {
458b5d2b
VK
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);
95da9d21 467 fflush(m_logFileHandle);
45ac5dd0
EJ
468
469#ifndef _WIN32
470 int fd = fileno(m_logFileHandle);
471 fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
472#endif
473
458b5d2b 474 if (s_flags & NXLOG_BACKGROUND_WRITER)
4e0e77e6
VK
475 {
476 s_logBuffer.setAllocationStep(8192);
477 s_writerStopCondition = ConditionCreate(TRUE);
478 s_writerThread = ThreadCreateEx(BackgroundWriterThread, 0, NULL);
479 }
5039dede
AK
480 }
481
482 m_mutexLogAccess = MutexCreate();
93b48f0e 483 m_mutexDebugTagTreeWrite = MutexCreate();
5945d50a 484 SetDayStart();
5039dede 485 }
458b5d2b 486 return (s_flags & NXLOG_IS_OPEN) ? true : false;
5039dede
AK
487}
488
e8387b24
VK
489/**
490 * Close log
491 */
f669df41 492void LIBNETXMS_EXPORTABLE nxlog_close()
5039dede 493{
458b5d2b 494 if (s_flags & NXLOG_IS_OPEN)
5039dede 495 {
458b5d2b 496 if (s_flags & NXLOG_USE_SYSLOG)
5039dede
AK
497 {
498#ifdef _WIN32
499 DeregisterEventSource(m_eventLogHandle);
500#else
501 closelog();
502#endif
503 }
504 else
505 {
458b5d2b 506 if (s_flags & NXLOG_BACKGROUND_WRITER)
4e0e77e6
VK
507 {
508 ConditionSet(s_writerStopCondition);
509 ThreadJoin(s_writerThread);
510 ConditionDestroy(s_writerStopCondition);
511 }
93b48f0e 512
5039dede
AK
513 if (m_logFileHandle != NULL)
514 fclose(m_logFileHandle);
515 if (m_mutexLogAccess != INVALID_MUTEX_HANDLE)
516 MutexDestroy(m_mutexLogAccess);
93b48f0e
EJ
517 if (m_mutexDebugTagTreeWrite != INVALID_MUTEX_HANDLE)
518 MutexDestroy(m_mutexDebugTagTreeWrite);
5039dede 519 }
458b5d2b 520 s_flags &= ~NXLOG_IS_OPEN;
5039dede
AK
521 }
522}
523
e8387b24
VK
524/**
525 * Write record to log file
526 */
1e42f2e8 527static void WriteLogToFile(TCHAR *message, const WORD type)
5039dede 528{
1e42f2e8
VK
529 const TCHAR *loglevel;
530 switch(type)
9689f4eb 531 {
1e42f2e8
VK
532 case NXLOG_ERROR:
533 loglevel = _T("*E* ");
9689f4eb 534 break;
1e42f2e8
VK
535 case NXLOG_WARNING:
536 loglevel = _T("*W* ");
9689f4eb 537 break;
1e42f2e8
VK
538 case NXLOG_INFO:
539 loglevel = _T("*I* ");
9689f4eb 540 break;
1e42f2e8
VK
541 case NXLOG_DEBUG:
542 loglevel = _T("*D* ");
9689f4eb
VK
543 break;
544 default:
1e42f2e8 545 loglevel = _T("*?* ");
9689f4eb 546 break;
20ac6638 547 }
5039dede 548
1e42f2e8 549 TCHAR buffer[64];
458b5d2b 550 if (s_flags & NXLOG_BACKGROUND_WRITER)
4e0e77e6
VK
551 {
552 MutexLock(m_mutexLogAccess);
5945d50a 553
4e0e77e6
VK
554 FormatLogTimestamp(buffer);
555 s_logBuffer.append(buffer);
556 s_logBuffer.append(_T(" "));
557 s_logBuffer.append(loglevel);
558 s_logBuffer.append(message);
5039dede 559
4e0e77e6
VK
560 MutexUnlock(m_mutexLogAccess);
561 }
562 else
563 {
564 // Prevent simultaneous write to log file
565 MutexLock(m_mutexLogAccess);
4addc3a3 566
4e0e77e6
VK
567 // Check for new day start
568 time_t t = time(NULL);
458b5d2b 569 if ((s_rotationMode == NXLOG_ROTATION_DAILY) && (t >= m_currentDayStart + 86400))
4e0e77e6
VK
570 {
571 RotateLog(FALSE);
572 }
4addc3a3 573
4e0e77e6
VK
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 }
458b5d2b 580 if (s_flags & NXLOG_PRINT_TO_STDOUT)
4e0e77e6
VK
581 m_consoleWriter(_T("%s %s%s"), buffer, loglevel, message);
582
583 // Check log size
458b5d2b 584 if ((m_logFileHandle != NULL) && (s_rotationMode == NXLOG_ROTATION_BY_SIZE) && (s_maxLogSize != 0))
4e0e77e6 585 {
458b5d2b
VK
586 NX_STAT_STRUCT st;
587 NX_FSTAT(fileno(m_logFileHandle), &st);
f5ed8ede 588 if ((UINT64)st.st_size >= s_maxLogSize)
4e0e77e6
VK
589 RotateLog(FALSE);
590 }
591
592 MutexUnlock(m_mutexLogAccess);
593 }
5039dede
AK
594}
595
e8387b24
VK
596/**
597 * Format message (UNIX version)
598 */
5039dede
AK
599#ifndef _WIN32
600
967893bb 601static TCHAR *FormatMessageUX(UINT32 dwMsgId, TCHAR **ppStrings)
5039dede
AK
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));
8d131dda 611 _sntprintf(pMsg, 64, _T("MSG 0x%08X - Unable to find message text\n"), (unsigned int)dwMsgId);
5039dede
AK
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
e8387b24
VK
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
c7853753 665 * H - IPv6 address in network byte order (will appear in [])
9319c166 666 * I - pointer to InetAddress object
e8387b24 667 */
5039dede
AK
668void LIBNETXMS_EXPORTABLE nxlog_write(DWORD msg, WORD wType, const char *format, ...)
669{
670 va_list args;
b0823d06 671 TCHAR *strings[16], *pMsg, szBuffer[256];
5039dede 672 int numStrings = 0;
967893bb 673 UINT32 error;
5039dede
AK
674#if defined(UNICODE) && !defined(_WIN32)
675 char *mbMsg;
676#endif
677
458b5d2b 678 if (!(s_flags & NXLOG_IS_OPEN) || (msg == 0))
6d738067
VK
679 return;
680
26088824 681 memset(strings, 0, sizeof(TCHAR *) * 16);
5039dede
AK
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 {
26088824 691 case 's':
5039dede
AK
692 strings[numStrings] = _tcsdup(va_arg(args, TCHAR *));
693 break;
e86f3432
VK
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;
26088824 708 case 'd':
5039dede 709 strings[numStrings] = (TCHAR *)malloc(16 * sizeof(TCHAR));
8d131dda 710 _sntprintf(strings[numStrings], 16, _T("%d"), (int)(va_arg(args, LONG)));
5039dede 711 break;
26088824 712 case 'x':
5039dede 713 strings[numStrings] = (TCHAR *)malloc(16 * sizeof(TCHAR));
967893bb 714 _sntprintf(strings[numStrings], 16, _T("0x%08X"), (unsigned int)(va_arg(args, UINT32)));
5039dede 715 break;
26088824 716 case 'a':
5039dede 717 strings[numStrings] = (TCHAR *)malloc(20 * sizeof(TCHAR));
967893bb 718 IpToStr(va_arg(args, UINT32), strings[numStrings]);
5039dede 719 break;
36e44abe
VK
720 case 'A':
721 strings[numStrings] = (TCHAR *)malloc(48 * sizeof(TCHAR));
722 Ip6ToStr(va_arg(args, BYTE *), strings[numStrings]);
723 break;
c7853753
VK
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;
9319c166
VK
730 case 'I':
731 strings[numStrings] = (TCHAR *)malloc(48 * sizeof(TCHAR));
732 va_arg(args, InetAddress *)->toString(strings[numStrings]);
733 break;
26088824 734 case 'e':
967893bb 735 error = va_arg(args, UINT32);
5039dede
AK
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
1ddf3f0c 742 (LPTSTR)&pMsg, 0, NULL) > 0)
5039dede
AK
743 {
744 pMsg[_tcscspn(pMsg, _T("\r\n"))] = 0;
e0f99bf0 745 strings[numStrings] = (TCHAR *)malloc((_tcslen(pMsg) + 1) * sizeof(TCHAR));
5039dede
AK
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
b0823d06 756#if HAVE_POSIX_STRERROR_R
26088824
VK
757 strings[numStrings] = (TCHAR *)malloc(256 * sizeof(TCHAR));
758 _tcserror_r((int)error, strings[numStrings], 256);
b0823d06
VK
759#else
760 strings[numStrings] = _tcsdup(_tcserror_r((int)error, szBuffer, 256));
761#endif
5039dede 762#else
26088824 763 strings[numStrings] = _tcsdup(_tcserror((int)error));
5039dede
AK
764#endif
765#endif
766 break;
767 default:
768 strings[numStrings] = (TCHAR *)malloc(32 * sizeof(TCHAR));
967893bb 769 _sntprintf(strings[numStrings], 32, _T("BAD FORMAT (0x%08X)"), (unsigned int)(va_arg(args, UINT32)));
5039dede
AK
770 break;
771 }
772 }
773 va_end(args);
774 }
775
776#ifdef _WIN32
458b5d2b 777 if (!(s_flags & NXLOG_USE_SYSLOG) || (s_flags & NXLOG_PRINT_TO_STDOUT))
5039dede
AK
778 {
779 LPVOID lpMsgBuf;
780
781 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
782 FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
b9b2b87b 783 m_msgModuleHandle, msg, 0, (LPTSTR)&lpMsgBuf, 0, (va_list *)strings) > 0)
5039dede 784 {
d9ec360f 785 TCHAR *pCR;
5039dede
AK
786
787 // Replace trailing CR/LF pair with LF
d9ec360f 788 pCR = _tcschr((TCHAR *)lpMsgBuf, _T('\r'));
5039dede
AK
789 if (pCR != NULL)
790 {
d9ec360f 791 *pCR = _T('\n');
5039dede
AK
792 pCR++;
793 *pCR = 0;
794 }
458b5d2b 795 if (s_flags & NXLOG_USE_SYSLOG)
5039dede 796 {
e8387b24 797 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer), (TCHAR *)lpMsgBuf);
5039dede
AK
798 }
799 else
800 {
20ac6638 801 WriteLogToFile((TCHAR *)lpMsgBuf, wType);
5039dede
AK
802 }
803 LocalFree(lpMsgBuf);
804 }
805 else
806 {
807 TCHAR message[64];
808
b9b2b87b 809 _sntprintf(message, 64, _T("MSG 0x%08X - cannot format message"), msg);
458b5d2b 810 if (s_flags & NXLOG_USE_SYSLOG)
5039dede 811 {
e8387b24 812 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer), message);
5039dede
AK
813 }
814 else
815 {
20ac6638 816 WriteLogToFile(message, wType);
5039dede
AK
817 }
818 }
819 }
820
458b5d2b 821 if (s_flags & NXLOG_USE_SYSLOG)
5039dede
AK
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);
458b5d2b 827 if (s_flags & NXLOG_USE_SYSLOG)
5039dede
AK
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
458b5d2b 857 if (s_flags & NXLOG_PRINT_TO_STDOUT)
5039dede 858 {
e8387b24 859 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer), pMsg);
5039dede
AK
860 }
861 }
862 else
863 {
20ac6638 864 WriteLogToFile(pMsg, wType);
5039dede
AK
865 }
866 free(pMsg);
867#endif /* _WIN32 */
868
869 while(--numStrings >= 0)
870 free(strings[numStrings]);
871}
2df047f4 872
90a9e65e
VK
873/**
874 * Write generic message to log (useful for warning and error messages generated within libraries)
875 */
876void 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
2df047f4
VK
886/**
887 * Write debug message
888 */
889void LIBNETXMS_EXPORTABLE nxlog_debug(int level, const TCHAR *format, ...)
890{
b41dff2a 891 if (level > nxlog_get_debug_level_tag(_T("*")))
2df047f4
VK
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);
588825b6
VK
900
901 if (s_debugWriter != NULL)
b41dff2a 902 s_debugWriter(NULL, buffer);
2df047f4
VK
903}
904
905/**
906 * Write debug message
907 */
908void LIBNETXMS_EXPORTABLE nxlog_debug2(int level, const TCHAR *format, va_list args)
909{
b41dff2a 910 if (level > nxlog_get_debug_level_tag(_T("*")))
2df047f4
VK
911 return;
912
913 TCHAR buffer[8192];
914 _vsntprintf(buffer, 8192, format, args);
915 nxlog_write(s_debugMsg, NXLOG_DEBUG, "s", buffer);
588825b6
VK
916
917 if (s_debugWriter != NULL)
b41dff2a
EJ
918 s_debugWriter(NULL, buffer);
919}
920
921/**
922 * Write debug message with tag
923 */
e0461d7b 924static void nxlog_debug_tag_internal(const TCHAR *tag, int level, const TCHAR *format, va_list args)
b41dff2a 925{
42c338ac 926 TCHAR tagf[20];
1e42f2e8 927 int i;
42c338ac 928 for(i = 0; (i < 19) && tag[i] != 0; i++)
1e42f2e8 929 tagf[i] = tag[i];
42c338ac 930 for(; i < 19; i++)
1e42f2e8
VK
931 tagf[i] = ' ';
932 tagf[i] = 0;
933
b41dff2a
EJ
934 TCHAR buffer[8192];
935 _vsntprintf(buffer, 8192, format, args);
1e42f2e8 936 nxlog_write(s_debugMsgTag, NXLOG_DEBUG, "ss", tagf, buffer);
b41dff2a
EJ
937
938 if (s_debugWriter != NULL)
939 s_debugWriter(tag, buffer);
2df047f4 940}
e0461d7b
VK
941
942/**
943 * Write debug message with tag
944 */
945void 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 */
959void 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 */
970void 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}
c8d3cb8f
VK
982
983/**
984 * Write debug message with tag and object ID (added as last part of a tag)
985 */
986void 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}