2 ** NetXMS - Network Management System
4 ** Copyright (C) 2003-2014 Victor Kirhenshtein
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.
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.
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.
24 #include "libnetxms.h"
30 #define MAX_LOG_HISTORY_SIZE 128
36 static HANDLE m_eventLogHandle
= NULL
;
37 static HMODULE m_msgModuleHandle
= NULL
;
39 static unsigned int m_numMessages
;
40 static const TCHAR
**m_messages
;
42 static TCHAR m_logFileName
[MAX_PATH
] = _T("");
43 static FILE *m_logFileHandle
= NULL
;
44 static MUTEX m_mutexLogAccess
= INVALID_MUTEX_HANDLE
;
45 static UINT32 m_flags
= 0;
46 static int m_rotationMode
= NXLOG_ROTATION_BY_SIZE
;
47 static int m_maxLogSize
= 4096 * 1024; // 4 MB
48 static int m_logHistorySize
= 4; // Keep up 4 previous log files
49 static TCHAR m_dailyLogSuffixTemplate
[64] = _T("%Y%m%d");
50 static time_t m_currentDayStart
= 0;
51 static NxLogConsoleWriter m_consoleWriter
= (NxLogConsoleWriter
)_tprintf
;
52 static String s_logBuffer
;
53 static THREAD s_writerThread
= INVALID_THREAD_HANDLE
;
54 static CONDITION s_writerStopCondition
= INVALID_CONDITION_HANDLE
;
57 * Format current time for output
59 static TCHAR
*FormatLogTimestamp(TCHAR
*buffer
)
61 INT64 now
= GetCurrentTimeMs();
62 time_t t
= now
/ 1000;
65 struct tm
*loc
= localtime_r(&t
, <mBuffer
);
67 struct tm
*loc
= localtime(&t
);
69 _tcsftime(buffer
, 32, _T("[%d-%b-%Y %H:%M:%S"), loc
);
70 _sntprintf(&buffer
[21], 8, _T(".%03d]"), (int)(now
% 1000));
75 * Set timestamp of start of the current day
77 static void SetDayStart()
79 time_t now
= time(NULL
);
82 localtime_r(&now
, &dayStart
);
84 struct tm
*ltm
= localtime(&now
);
85 memcpy(&dayStart
, ltm
, sizeof(struct tm
));
90 m_currentDayStart
= mktime(&dayStart
);
94 * Set log rotation policy
95 * Setting log size to 0 or mode to NXLOG_ROTATION_DISABLED disables log rotation
97 BOOL LIBNETXMS_EXPORTABLE
nxlog_set_rotation_policy(int rotationMode
, int maxLogSize
, int historySize
, const TCHAR
*dailySuffix
)
101 if ((rotationMode
>= 0) || (rotationMode
<= 2))
103 m_rotationMode
= rotationMode
;
104 if (rotationMode
== NXLOG_ROTATION_BY_SIZE
)
106 if ((maxLogSize
== 0) || (maxLogSize
>= 1024))
108 m_maxLogSize
= maxLogSize
;
116 if ((historySize
>= 0) && (historySize
<= MAX_LOG_HISTORY_SIZE
))
118 m_logHistorySize
= historySize
;
122 if (historySize
> MAX_LOG_HISTORY_SIZE
)
123 m_logHistorySize
= MAX_LOG_HISTORY_SIZE
;
127 else if (rotationMode
== NXLOG_ROTATION_DAILY
)
129 if ((dailySuffix
!= NULL
) && (dailySuffix
[0] != 0))
130 nx_strncpy(m_dailyLogSuffixTemplate
, dailySuffix
, sizeof(m_dailyLogSuffixTemplate
) / sizeof(TCHAR
));
145 void LIBNETXMS_EXPORTABLE
nxlog_set_console_writer(NxLogConsoleWriter writer
)
147 m_consoleWriter
= writer
;
153 static BOOL
RotateLog(BOOL needLock
)
156 TCHAR oldName
[MAX_PATH
], newName
[MAX_PATH
];
158 if (m_flags
& NXLOG_USE_SYSLOG
)
159 return FALSE
; // Cannot rotate system logs
162 MutexLock(m_mutexLogAccess
);
164 if ((m_logFileHandle
!= NULL
) && (m_flags
& NXLOG_IS_OPEN
))
166 fclose(m_logFileHandle
);
167 m_flags
&= ~NXLOG_IS_OPEN
;
170 if (m_rotationMode
== NXLOG_ROTATION_BY_SIZE
)
173 for(i
= MAX_LOG_HISTORY_SIZE
; i
>= m_logHistorySize
; i
--)
175 _sntprintf(oldName
, MAX_PATH
, _T("%s.%d"), m_logFileName
, i
);
182 _sntprintf(oldName
, MAX_PATH
, _T("%s.%d"), m_logFileName
, i
);
183 _sntprintf(newName
, MAX_PATH
, _T("%s.%d"), m_logFileName
, i
+ 1);
184 _trename(oldName
, newName
);
187 // Rename current log to name.0
188 _sntprintf(newName
, MAX_PATH
, _T("%s.0"), m_logFileName
);
189 _trename(m_logFileName
, newName
);
191 else if (m_rotationMode
== NXLOG_ROTATION_DAILY
)
195 struct tm
*loc
= localtime_r(&m_currentDayStart
, <mBuffer
);
197 struct tm
*loc
= localtime(&m_currentDayStart
);
200 _tcsftime(buffer
, 64, m_dailyLogSuffixTemplate
, loc
);
202 // Rename current log to name.suffix
203 _sntprintf(newName
, MAX_PATH
, _T("%s.%s"), m_logFileName
, buffer
);
204 _trename(m_logFileName
, newName
);
211 m_logFileHandle
= _tfopen64(m_logFileName
, _T("w"));
213 m_logFileHandle
= _tfopen(m_logFileName
, _T("w"));
215 if (m_logFileHandle
!= NULL
)
217 m_flags
|= NXLOG_IS_OPEN
;
219 _ftprintf(m_logFileHandle
, _T("%s Log file truncated.\n"), FormatLogTimestamp(buffer
));
220 fflush(m_logFileHandle
);
224 MutexUnlock(m_mutexLogAccess
);
226 return (m_flags
& NXLOG_IS_OPEN
) ? TRUE
: FALSE
;
230 * API for application to force log rotation
232 BOOL LIBNETXMS_EXPORTABLE
nxlog_rotate()
234 return RotateLog(TRUE
);
238 * Background writer thread
240 static THREAD_RESULT THREAD_CALL
BackgroundWriterThread(void *arg
)
245 stop
= ConditionWait(s_writerStopCondition
, 1000);
247 // Check for new day start
248 time_t t
= time(NULL
);
249 if ((m_rotationMode
== NXLOG_ROTATION_DAILY
) && (t
>= m_currentDayStart
+ 86400))
254 MutexLock(m_mutexLogAccess
);
255 if (!s_logBuffer
.isEmpty())
257 if (m_flags
& NXLOG_PRINT_TO_STDOUT
)
258 m_consoleWriter(_T("%s"), s_logBuffer
.getBuffer());
260 char *data
= s_logBuffer
.getUTF8String();
262 MutexUnlock(m_mutexLogAccess
);
265 fwrite(data
, 1, strlen(data
), m_logFileHandle
);
267 // write is used here because on linux fwrite is not working
268 // after calling fwprintf on a stream
269 size_t size
= strlen(data
);
273 int bw
= write(fileno(m_logFileHandle
), &data
[offset
], size
);
283 if ((m_logFileHandle
!= NULL
) && (m_rotationMode
== NXLOG_ROTATION_BY_SIZE
) && (m_maxLogSize
!= 0))
287 fstat(fileno(m_logFileHandle
), &st
);
288 if (st
.st_size
>= m_maxLogSize
)
294 MutexUnlock(m_mutexLogAccess
);
303 BOOL LIBNETXMS_EXPORTABLE
nxlog_open(const TCHAR
*logName
, UINT32 flags
,
304 const TCHAR
*msgModule
, unsigned int msgCount
, const TCHAR
**messages
)
306 m_flags
= flags
& 0x7FFFFFFF;
308 m_msgModuleHandle
= GetModuleHandle(msgModule
);
310 m_numMessages
= msgCount
;
311 m_messages
= messages
;
314 if (m_flags
& NXLOG_USE_SYSLOG
)
317 m_eventLogHandle
= RegisterEventSource(NULL
, logName
);
318 if (m_eventLogHandle
!= NULL
)
319 m_flags
|= NXLOG_IS_OPEN
;
324 mbBuffer
= MBStringFromWideString(logName
);
325 openlog(mbBuffer
, LOG_PID
, LOG_DAEMON
);
328 openlog(logName
, LOG_PID
, LOG_DAEMON
);
330 m_flags
|= NXLOG_IS_OPEN
;
337 nx_strncpy(m_logFileName
, logName
, MAX_PATH
);
339 m_logFileHandle
= _tfopen64(logName
, _T("a"));
341 m_logFileHandle
= _tfopen(logName
, _T("a"));
343 if (m_logFileHandle
!= NULL
)
345 m_flags
|= NXLOG_IS_OPEN
;
346 _ftprintf(m_logFileHandle
, _T("\n%s Log file opened\n"), FormatLogTimestamp(buffer
));
347 fflush(m_logFileHandle
);
348 if (m_flags
& NXLOG_BACKGROUND_WRITER
)
350 s_logBuffer
.setAllocationStep(8192);
351 s_writerStopCondition
= ConditionCreate(TRUE
);
352 s_writerThread
= ThreadCreateEx(BackgroundWriterThread
, 0, NULL
);
356 m_mutexLogAccess
= MutexCreate();
359 return (m_flags
& NXLOG_IS_OPEN
) ? TRUE
: FALSE
;
365 void LIBNETXMS_EXPORTABLE
nxlog_close()
367 if (m_flags
& NXLOG_IS_OPEN
)
369 if (m_flags
& NXLOG_USE_SYSLOG
)
372 DeregisterEventSource(m_eventLogHandle
);
379 if (m_flags
& NXLOG_BACKGROUND_WRITER
)
381 ConditionSet(s_writerStopCondition
);
382 ThreadJoin(s_writerThread
);
383 ConditionDestroy(s_writerStopCondition
);
385 if (m_logFileHandle
!= NULL
)
386 fclose(m_logFileHandle
);
387 if (m_mutexLogAccess
!= INVALID_MUTEX_HANDLE
)
388 MutexDestroy(m_mutexLogAccess
);
390 m_flags
&= ~NXLOG_IS_OPEN
;
395 * Write record to log file
397 static void WriteLogToFile(TCHAR
*message
, const WORD wType
)
404 case EVENTLOG_ERROR_TYPE
:
405 _sntprintf(loglevel
, 16, _T("[%s] "), _T("ERROR"));
407 case EVENTLOG_WARNING_TYPE
:
408 _sntprintf(loglevel
, 16, _T("[%s] "), _T("WARN "));
410 case EVENTLOG_INFORMATION_TYPE
:
411 _sntprintf(loglevel
, 16, _T("[%s] "), _T("INFO "));
413 case EVENTLOG_DEBUG_TYPE
:
414 _sntprintf(loglevel
, 16, _T("[%s] "), _T("DEBUG"));
417 _tcsncpy(loglevel
, _T(""), 1);
421 if (m_flags
& NXLOG_BACKGROUND_WRITER
)
423 MutexLock(m_mutexLogAccess
);
425 FormatLogTimestamp(buffer
);
426 s_logBuffer
.append(buffer
);
427 s_logBuffer
.append(_T(" "));
428 s_logBuffer
.append(loglevel
);
429 s_logBuffer
.append(message
);
431 MutexUnlock(m_mutexLogAccess
);
435 // Prevent simultaneous write to log file
436 MutexLock(m_mutexLogAccess
);
438 // Check for new day start
439 time_t t
= time(NULL
);
440 if ((m_rotationMode
== NXLOG_ROTATION_DAILY
) && (t
>= m_currentDayStart
+ 86400))
445 FormatLogTimestamp(buffer
);
446 if (m_logFileHandle
!= NULL
)
448 _ftprintf(m_logFileHandle
, _T("%s %s%s"), buffer
, loglevel
, message
);
449 fflush(m_logFileHandle
);
451 if (m_flags
& NXLOG_PRINT_TO_STDOUT
)
452 m_consoleWriter(_T("%s %s%s"), buffer
, loglevel
, message
);
455 if ((m_logFileHandle
!= NULL
) && (m_rotationMode
== NXLOG_ROTATION_BY_SIZE
) && (m_maxLogSize
!= 0))
459 fstat(fileno(m_logFileHandle
), &st
);
460 if (st
.st_size
>= m_maxLogSize
)
464 MutexUnlock(m_mutexLogAccess
);
469 * Format message (UNIX version)
473 static TCHAR
*FormatMessageUX(UINT32 dwMsgId
, TCHAR
**ppStrings
)
479 if (dwMsgId
>= m_numMessages
)
481 // No message with this ID
482 pMsg
= (TCHAR
*)malloc(64 * sizeof(TCHAR
));
483 _sntprintf(pMsg
, 64, _T("MSG 0x%08X - Unable to find message text\n"), (unsigned int)dwMsgId
);
487 iSize
= (_tcslen(m_messages
[dwMsgId
]) + 2) * sizeof(TCHAR
);
488 pMsg
= (TCHAR
*)malloc(iSize
);
490 for(i
= 0, p
= m_messages
[dwMsgId
]; *p
!= 0; p
++)
494 if ((*p
>= _T('1')) && (*p
<= _T('9')))
496 iLen
= _tcslen(ppStrings
[*p
- _T('1')]);
497 iSize
+= iLen
* sizeof(TCHAR
);
498 pMsg
= (TCHAR
*)realloc(pMsg
, iSize
);
499 _tcscpy(&pMsg
[i
], ppStrings
[*p
- _T('1')]);
504 if (*p
== 0) // Handle single % character at the string end
513 pMsg
[i
++] = _T('\n');
520 #endif /* ! _WIN32 */
526 * wType - Message type (see ReportEvent() for details)
527 * format - Parameter format string, each parameter represented by one character.
528 * The following format characters can be used:
529 * s - String (multibyte or UNICODE, depending on build)
530 * m - multibyte string
532 * d - Decimal integer
534 * e - System error code (will appear in log as textual description)
535 * a - IP address in network byte order
536 * A - IPv6 address in network byte order
537 * H - IPv6 address in network byte order (will appear in [])
538 * I - pointer to InetAddress object
540 void LIBNETXMS_EXPORTABLE
nxlog_write(DWORD msg
, WORD wType
, const char *format
, ...)
543 TCHAR
*strings
[16], *pMsg
, szBuffer
[256];
546 #if defined(UNICODE) && !defined(_WIN32)
550 if (!(m_flags
& NXLOG_IS_OPEN
) || (msg
== 0))
553 memset(strings
, 0, sizeof(TCHAR
*) * 16);
557 va_start(args
, format
);
559 for(; (format
[numStrings
] != 0) && (numStrings
< 16); numStrings
++)
561 switch(format
[numStrings
])
564 strings
[numStrings
] = _tcsdup(va_arg(args
, TCHAR
*));
568 strings
[numStrings
] = WideStringFromMBString(va_arg(args
, char *));
570 strings
[numStrings
] = strdup(va_arg(args
, char *));
575 strings
[numStrings
] = wcsdup(va_arg(args
, WCHAR
*));
577 strings
[numStrings
] = MBStringFromWideString(va_arg(args
, WCHAR
*));
581 strings
[numStrings
] = (TCHAR
*)malloc(16 * sizeof(TCHAR
));
582 _sntprintf(strings
[numStrings
], 16, _T("%d"), (int)(va_arg(args
, LONG
)));
585 strings
[numStrings
] = (TCHAR
*)malloc(16 * sizeof(TCHAR
));
586 _sntprintf(strings
[numStrings
], 16, _T("0x%08X"), (unsigned int)(va_arg(args
, UINT32
)));
589 strings
[numStrings
] = (TCHAR
*)malloc(20 * sizeof(TCHAR
));
590 IpToStr(va_arg(args
, UINT32
), strings
[numStrings
]);
593 strings
[numStrings
] = (TCHAR
*)malloc(48 * sizeof(TCHAR
));
594 Ip6ToStr(va_arg(args
, BYTE
*), strings
[numStrings
]);
597 strings
[numStrings
] = (TCHAR
*)malloc(48 * sizeof(TCHAR
));
598 strings
[numStrings
][0] = _T('[');
599 Ip6ToStr(va_arg(args
, BYTE
*), strings
[numStrings
] + 1);
600 _tcscat(strings
[numStrings
], _T("]"));
603 strings
[numStrings
] = (TCHAR
*)malloc(48 * sizeof(TCHAR
));
604 va_arg(args
, InetAddress
*)->toString(strings
[numStrings
]);
607 error
= va_arg(args
, UINT32
);
609 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
|
610 FORMAT_MESSAGE_FROM_SYSTEM
|
611 FORMAT_MESSAGE_IGNORE_INSERTS
,
613 MAKELANGID(LANG_NEUTRAL
,SUBLANG_DEFAULT
), // Default language
614 (LPTSTR
)&pMsg
, 0, NULL
) > 0)
616 pMsg
[_tcscspn(pMsg
, _T("\r\n"))] = 0;
617 strings
[numStrings
] = (TCHAR
*)malloc((_tcslen(pMsg
) + 1) * sizeof(TCHAR
));
618 _tcscpy(strings
[numStrings
], pMsg
);
623 strings
[numStrings
] = (TCHAR
*)malloc(64 * sizeof(TCHAR
));
624 _sntprintf(strings
[numStrings
], 64, _T("MSG 0x%08X - Unable to find message text"), error
);
628 #if HAVE_POSIX_STRERROR_R
629 strings
[numStrings
] = (TCHAR
*)malloc(256 * sizeof(TCHAR
));
630 _tcserror_r((int)error
, strings
[numStrings
], 256);
632 strings
[numStrings
] = _tcsdup(_tcserror_r((int)error
, szBuffer
, 256));
635 strings
[numStrings
] = _tcsdup(_tcserror((int)error
));
640 strings
[numStrings
] = (TCHAR
*)malloc(32 * sizeof(TCHAR
));
641 _sntprintf(strings
[numStrings
], 32, _T("BAD FORMAT (0x%08X)"), (unsigned int)(va_arg(args
, UINT32
)));
649 if (!(m_flags
& NXLOG_USE_SYSLOG
) || (m_flags
& NXLOG_PRINT_TO_STDOUT
))
653 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
|
654 FORMAT_MESSAGE_FROM_HMODULE
| FORMAT_MESSAGE_ARGUMENT_ARRAY
,
655 m_msgModuleHandle
, msg
, 0, (LPTSTR
)&lpMsgBuf
, 0, (va_list *)strings
) > 0)
659 // Replace trailing CR/LF pair with LF
660 pCR
= _tcschr((TCHAR
*)lpMsgBuf
, _T('\r'));
667 if (m_flags
& NXLOG_USE_SYSLOG
)
669 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer
), (TCHAR
*)lpMsgBuf
);
673 WriteLogToFile((TCHAR
*)lpMsgBuf
, wType
);
681 _sntprintf(message
, 64, _T("MSG 0x%08X - cannot format message"), msg
);
682 if (m_flags
& NXLOG_USE_SYSLOG
)
684 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer
), message
);
688 WriteLogToFile(message
, wType
);
693 if (m_flags
& NXLOG_USE_SYSLOG
)
695 ReportEvent(m_eventLogHandle
, (wType
== EVENTLOG_DEBUG_TYPE
) ? EVENTLOG_INFORMATION_TYPE
: wType
, 0, msg
, NULL
, numStrings
, 0, (const TCHAR
**)strings
, NULL
);
698 pMsg
= FormatMessageUX(msg
, strings
);
699 if (m_flags
& NXLOG_USE_SYSLOG
)
705 case EVENTLOG_ERROR_TYPE
:
708 case EVENTLOG_WARNING_TYPE
:
711 case EVENTLOG_INFORMATION_TYPE
:
714 case EVENTLOG_DEBUG_TYPE
:
722 mbMsg
= MBStringFromWideString(pMsg
);
723 syslog(level
, "%s", mbMsg
);
726 syslog(level
, "%s", pMsg
);
729 if (m_flags
& NXLOG_PRINT_TO_STDOUT
)
731 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer
), pMsg
);
736 WriteLogToFile(pMsg
, wType
);
741 while(--numStrings
>= 0)
742 free(strings
[numStrings
]);