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