2 ** NetXMS - Network Management System
4 ** Copyright (C) 2003-2016 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 s_debugLevel
= 0;
47 static DWORD s_debugMsg
= 0;
48 static int m_rotationMode
= NXLOG_ROTATION_BY_SIZE
;
49 static int m_maxLogSize
= 4096 * 1024; // 4 MB
50 static int m_logHistorySize
= 4; // Keep up 4 previous log files
51 static TCHAR m_dailyLogSuffixTemplate
[64] = _T("%Y%m%d");
52 static time_t m_currentDayStart
= 0;
53 static NxLogConsoleWriter m_consoleWriter
= (NxLogConsoleWriter
)_tprintf
;
54 static String s_logBuffer
;
55 static THREAD s_writerThread
= INVALID_THREAD_HANDLE
;
56 static CONDITION s_writerStopCondition
= INVALID_CONDITION_HANDLE
;
61 void LIBNETXMS_EXPORTABLE
nxlog_set_debug_level(int level
)
67 * Get current debug level
69 int LIBNETXMS_EXPORTABLE
nxlog_get_debug_level()
75 * Format current time for output
77 static TCHAR
*FormatLogTimestamp(TCHAR
*buffer
)
79 INT64 now
= GetCurrentTimeMs();
80 time_t t
= now
/ 1000;
83 struct tm
*loc
= localtime_r(&t
, <mBuffer
);
85 struct tm
*loc
= localtime(&t
);
87 _tcsftime(buffer
, 32, _T("[%d-%b-%Y %H:%M:%S"), loc
);
88 _sntprintf(&buffer
[21], 8, _T(".%03d]"), (int)(now
% 1000));
93 * Set timestamp of start of the current day
95 static void SetDayStart()
97 time_t now
= time(NULL
);
100 localtime_r(&now
, &dayStart
);
102 struct tm
*ltm
= localtime(&now
);
103 memcpy(&dayStart
, ltm
, sizeof(struct tm
));
105 dayStart
.tm_hour
= 0;
108 m_currentDayStart
= mktime(&dayStart
);
112 * Set log rotation policy
113 * Setting log size to 0 or mode to NXLOG_ROTATION_DISABLED disables log rotation
115 bool LIBNETXMS_EXPORTABLE
nxlog_set_rotation_policy(int rotationMode
, int maxLogSize
, int historySize
, const TCHAR
*dailySuffix
)
119 if ((rotationMode
>= 0) || (rotationMode
<= 2))
121 m_rotationMode
= rotationMode
;
122 if (rotationMode
== NXLOG_ROTATION_BY_SIZE
)
124 if ((maxLogSize
== 0) || (maxLogSize
>= 1024))
126 m_maxLogSize
= maxLogSize
;
134 if ((historySize
>= 0) && (historySize
<= MAX_LOG_HISTORY_SIZE
))
136 m_logHistorySize
= historySize
;
140 if (historySize
> MAX_LOG_HISTORY_SIZE
)
141 m_logHistorySize
= MAX_LOG_HISTORY_SIZE
;
145 else if (rotationMode
== NXLOG_ROTATION_DAILY
)
147 if ((dailySuffix
!= NULL
) && (dailySuffix
[0] != 0))
148 nx_strncpy(m_dailyLogSuffixTemplate
, dailySuffix
, sizeof(m_dailyLogSuffixTemplate
) / sizeof(TCHAR
));
163 void LIBNETXMS_EXPORTABLE
nxlog_set_console_writer(NxLogConsoleWriter writer
)
165 m_consoleWriter
= writer
;
171 static bool RotateLog(bool needLock
)
174 TCHAR oldName
[MAX_PATH
], newName
[MAX_PATH
];
176 if (m_flags
& NXLOG_USE_SYSLOG
)
177 return FALSE
; // Cannot rotate system logs
180 MutexLock(m_mutexLogAccess
);
182 if ((m_logFileHandle
!= NULL
) && (m_flags
& NXLOG_IS_OPEN
))
184 fclose(m_logFileHandle
);
185 m_flags
&= ~NXLOG_IS_OPEN
;
188 if (m_rotationMode
== NXLOG_ROTATION_BY_SIZE
)
191 for(i
= MAX_LOG_HISTORY_SIZE
; i
>= m_logHistorySize
; i
--)
193 _sntprintf(oldName
, MAX_PATH
, _T("%s.%d"), m_logFileName
, i
);
200 _sntprintf(oldName
, MAX_PATH
, _T("%s.%d"), m_logFileName
, i
);
201 _sntprintf(newName
, MAX_PATH
, _T("%s.%d"), m_logFileName
, i
+ 1);
202 _trename(oldName
, newName
);
205 // Rename current log to name.0
206 _sntprintf(newName
, MAX_PATH
, _T("%s.0"), m_logFileName
);
207 _trename(m_logFileName
, newName
);
209 else if (m_rotationMode
== NXLOG_ROTATION_DAILY
)
213 struct tm
*loc
= localtime_r(&m_currentDayStart
, <mBuffer
);
215 struct tm
*loc
= localtime(&m_currentDayStart
);
218 _tcsftime(buffer
, 64, m_dailyLogSuffixTemplate
, loc
);
220 // Rename current log to name.suffix
221 _sntprintf(newName
, MAX_PATH
, _T("%s.%s"), m_logFileName
, buffer
);
222 _trename(m_logFileName
, newName
);
229 m_logFileHandle
= _tfopen64(m_logFileName
, _T("w"));
231 m_logFileHandle
= _tfopen(m_logFileName
, _T("w"));
233 if (m_logFileHandle
!= NULL
)
235 m_flags
|= NXLOG_IS_OPEN
;
237 _ftprintf(m_logFileHandle
, _T("%s Log file truncated.\n"), FormatLogTimestamp(buffer
));
238 fflush(m_logFileHandle
);
242 MutexUnlock(m_mutexLogAccess
);
244 return (m_flags
& NXLOG_IS_OPEN
) ? true : false;
248 * API for application to force log rotation
250 bool LIBNETXMS_EXPORTABLE
nxlog_rotate()
252 return RotateLog(true);
256 * Background writer thread
258 static THREAD_RESULT THREAD_CALL
BackgroundWriterThread(void *arg
)
263 stop
= ConditionWait(s_writerStopCondition
, 1000);
265 // Check for new day start
266 time_t t
= time(NULL
);
267 if ((m_rotationMode
== NXLOG_ROTATION_DAILY
) && (t
>= m_currentDayStart
+ 86400))
272 MutexLock(m_mutexLogAccess
);
273 if (!s_logBuffer
.isEmpty())
275 if (m_flags
& NXLOG_PRINT_TO_STDOUT
)
276 m_consoleWriter(_T("%s"), s_logBuffer
.getBuffer());
278 char *data
= s_logBuffer
.getUTF8String();
280 MutexUnlock(m_mutexLogAccess
);
283 fwrite(data
, 1, strlen(data
), m_logFileHandle
);
285 // write is used here because on linux fwrite is not working
286 // after calling fwprintf on a stream
287 size_t size
= strlen(data
);
291 int bw
= write(fileno(m_logFileHandle
), &data
[offset
], size
);
301 if ((m_logFileHandle
!= NULL
) && (m_rotationMode
== NXLOG_ROTATION_BY_SIZE
) && (m_maxLogSize
!= 0))
305 fstat(fileno(m_logFileHandle
), &st
);
306 if (st
.st_size
>= m_maxLogSize
)
312 MutexUnlock(m_mutexLogAccess
);
321 bool LIBNETXMS_EXPORTABLE
nxlog_open(const TCHAR
*logName
, UINT32 flags
,
322 const TCHAR
*msgModule
, unsigned int msgCount
, const TCHAR
**messages
, DWORD debugMsg
)
324 m_flags
= flags
& 0x7FFFFFFF;
326 m_msgModuleHandle
= GetModuleHandle(msgModule
);
328 m_numMessages
= msgCount
;
329 m_messages
= messages
;
331 s_debugMsg
= debugMsg
;
333 if (m_flags
& NXLOG_USE_SYSLOG
)
336 m_eventLogHandle
= RegisterEventSource(NULL
, logName
);
337 if (m_eventLogHandle
!= NULL
)
338 m_flags
|= NXLOG_IS_OPEN
;
343 mbBuffer
= MBStringFromWideString(logName
);
344 openlog(mbBuffer
, LOG_PID
, LOG_DAEMON
);
347 openlog(logName
, LOG_PID
, LOG_DAEMON
);
349 m_flags
|= NXLOG_IS_OPEN
;
356 nx_strncpy(m_logFileName
, logName
, MAX_PATH
);
358 m_logFileHandle
= _tfopen64(logName
, _T("a"));
360 m_logFileHandle
= _tfopen(logName
, _T("a"));
362 if (m_logFileHandle
!= NULL
)
364 m_flags
|= NXLOG_IS_OPEN
;
365 _ftprintf(m_logFileHandle
, _T("\n%s Log file opened\n"), FormatLogTimestamp(buffer
));
366 fflush(m_logFileHandle
);
367 if (m_flags
& NXLOG_BACKGROUND_WRITER
)
369 s_logBuffer
.setAllocationStep(8192);
370 s_writerStopCondition
= ConditionCreate(TRUE
);
371 s_writerThread
= ThreadCreateEx(BackgroundWriterThread
, 0, NULL
);
375 m_mutexLogAccess
= MutexCreate();
378 return (m_flags
& NXLOG_IS_OPEN
) ? true : false;
384 void LIBNETXMS_EXPORTABLE
nxlog_close()
386 if (m_flags
& NXLOG_IS_OPEN
)
388 if (m_flags
& NXLOG_USE_SYSLOG
)
391 DeregisterEventSource(m_eventLogHandle
);
398 if (m_flags
& NXLOG_BACKGROUND_WRITER
)
400 ConditionSet(s_writerStopCondition
);
401 ThreadJoin(s_writerThread
);
402 ConditionDestroy(s_writerStopCondition
);
404 if (m_logFileHandle
!= NULL
)
405 fclose(m_logFileHandle
);
406 if (m_mutexLogAccess
!= INVALID_MUTEX_HANDLE
)
407 MutexDestroy(m_mutexLogAccess
);
409 m_flags
&= ~NXLOG_IS_OPEN
;
414 * Write record to log file
416 static void WriteLogToFile(TCHAR
*message
, const WORD wType
)
423 case EVENTLOG_ERROR_TYPE
:
424 _sntprintf(loglevel
, 16, _T("[%s] "), _T("ERROR"));
426 case EVENTLOG_WARNING_TYPE
:
427 _sntprintf(loglevel
, 16, _T("[%s] "), _T("WARN "));
429 case EVENTLOG_INFORMATION_TYPE
:
430 _sntprintf(loglevel
, 16, _T("[%s] "), _T("INFO "));
432 case EVENTLOG_DEBUG_TYPE
:
433 _sntprintf(loglevel
, 16, _T("[%s] "), _T("DEBUG"));
436 _tcsncpy(loglevel
, _T(""), 1);
440 if (m_flags
& NXLOG_BACKGROUND_WRITER
)
442 MutexLock(m_mutexLogAccess
);
444 FormatLogTimestamp(buffer
);
445 s_logBuffer
.append(buffer
);
446 s_logBuffer
.append(_T(" "));
447 s_logBuffer
.append(loglevel
);
448 s_logBuffer
.append(message
);
450 MutexUnlock(m_mutexLogAccess
);
454 // Prevent simultaneous write to log file
455 MutexLock(m_mutexLogAccess
);
457 // Check for new day start
458 time_t t
= time(NULL
);
459 if ((m_rotationMode
== NXLOG_ROTATION_DAILY
) && (t
>= m_currentDayStart
+ 86400))
464 FormatLogTimestamp(buffer
);
465 if (m_logFileHandle
!= NULL
)
467 _ftprintf(m_logFileHandle
, _T("%s %s%s"), buffer
, loglevel
, message
);
468 fflush(m_logFileHandle
);
470 if (m_flags
& NXLOG_PRINT_TO_STDOUT
)
471 m_consoleWriter(_T("%s %s%s"), buffer
, loglevel
, message
);
474 if ((m_logFileHandle
!= NULL
) && (m_rotationMode
== NXLOG_ROTATION_BY_SIZE
) && (m_maxLogSize
!= 0))
478 fstat(fileno(m_logFileHandle
), &st
);
479 if (st
.st_size
>= m_maxLogSize
)
483 MutexUnlock(m_mutexLogAccess
);
488 * Format message (UNIX version)
492 static TCHAR
*FormatMessageUX(UINT32 dwMsgId
, TCHAR
**ppStrings
)
498 if (dwMsgId
>= m_numMessages
)
500 // No message with this ID
501 pMsg
= (TCHAR
*)malloc(64 * sizeof(TCHAR
));
502 _sntprintf(pMsg
, 64, _T("MSG 0x%08X - Unable to find message text\n"), (unsigned int)dwMsgId
);
506 iSize
= (_tcslen(m_messages
[dwMsgId
]) + 2) * sizeof(TCHAR
);
507 pMsg
= (TCHAR
*)malloc(iSize
);
509 for(i
= 0, p
= m_messages
[dwMsgId
]; *p
!= 0; p
++)
513 if ((*p
>= _T('1')) && (*p
<= _T('9')))
515 iLen
= _tcslen(ppStrings
[*p
- _T('1')]);
516 iSize
+= iLen
* sizeof(TCHAR
);
517 pMsg
= (TCHAR
*)realloc(pMsg
, iSize
);
518 _tcscpy(&pMsg
[i
], ppStrings
[*p
- _T('1')]);
523 if (*p
== 0) // Handle single % character at the string end
532 pMsg
[i
++] = _T('\n');
539 #endif /* ! _WIN32 */
545 * wType - Message type (see ReportEvent() for details)
546 * format - Parameter format string, each parameter represented by one character.
547 * The following format characters can be used:
548 * s - String (multibyte or UNICODE, depending on build)
549 * m - multibyte string
551 * d - Decimal integer
553 * e - System error code (will appear in log as textual description)
554 * a - IP address in network byte order
555 * A - IPv6 address in network byte order
556 * H - IPv6 address in network byte order (will appear in [])
557 * I - pointer to InetAddress object
559 void LIBNETXMS_EXPORTABLE
nxlog_write(DWORD msg
, WORD wType
, const char *format
, ...)
562 TCHAR
*strings
[16], *pMsg
, szBuffer
[256];
565 #if defined(UNICODE) && !defined(_WIN32)
569 if (!(m_flags
& NXLOG_IS_OPEN
) || (msg
== 0))
572 memset(strings
, 0, sizeof(TCHAR
*) * 16);
576 va_start(args
, format
);
578 for(; (format
[numStrings
] != 0) && (numStrings
< 16); numStrings
++)
580 switch(format
[numStrings
])
583 strings
[numStrings
] = _tcsdup(va_arg(args
, TCHAR
*));
587 strings
[numStrings
] = WideStringFromMBString(va_arg(args
, char *));
589 strings
[numStrings
] = strdup(va_arg(args
, char *));
594 strings
[numStrings
] = wcsdup(va_arg(args
, WCHAR
*));
596 strings
[numStrings
] = MBStringFromWideString(va_arg(args
, WCHAR
*));
600 strings
[numStrings
] = (TCHAR
*)malloc(16 * sizeof(TCHAR
));
601 _sntprintf(strings
[numStrings
], 16, _T("%d"), (int)(va_arg(args
, LONG
)));
604 strings
[numStrings
] = (TCHAR
*)malloc(16 * sizeof(TCHAR
));
605 _sntprintf(strings
[numStrings
], 16, _T("0x%08X"), (unsigned int)(va_arg(args
, UINT32
)));
608 strings
[numStrings
] = (TCHAR
*)malloc(20 * sizeof(TCHAR
));
609 IpToStr(va_arg(args
, UINT32
), strings
[numStrings
]);
612 strings
[numStrings
] = (TCHAR
*)malloc(48 * sizeof(TCHAR
));
613 Ip6ToStr(va_arg(args
, BYTE
*), strings
[numStrings
]);
616 strings
[numStrings
] = (TCHAR
*)malloc(48 * sizeof(TCHAR
));
617 strings
[numStrings
][0] = _T('[');
618 Ip6ToStr(va_arg(args
, BYTE
*), strings
[numStrings
] + 1);
619 _tcscat(strings
[numStrings
], _T("]"));
622 strings
[numStrings
] = (TCHAR
*)malloc(48 * sizeof(TCHAR
));
623 va_arg(args
, InetAddress
*)->toString(strings
[numStrings
]);
626 error
= va_arg(args
, UINT32
);
628 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
|
629 FORMAT_MESSAGE_FROM_SYSTEM
|
630 FORMAT_MESSAGE_IGNORE_INSERTS
,
632 MAKELANGID(LANG_NEUTRAL
,SUBLANG_DEFAULT
), // Default language
633 (LPTSTR
)&pMsg
, 0, NULL
) > 0)
635 pMsg
[_tcscspn(pMsg
, _T("\r\n"))] = 0;
636 strings
[numStrings
] = (TCHAR
*)malloc((_tcslen(pMsg
) + 1) * sizeof(TCHAR
));
637 _tcscpy(strings
[numStrings
], pMsg
);
642 strings
[numStrings
] = (TCHAR
*)malloc(64 * sizeof(TCHAR
));
643 _sntprintf(strings
[numStrings
], 64, _T("MSG 0x%08X - Unable to find message text"), error
);
647 #if HAVE_POSIX_STRERROR_R
648 strings
[numStrings
] = (TCHAR
*)malloc(256 * sizeof(TCHAR
));
649 _tcserror_r((int)error
, strings
[numStrings
], 256);
651 strings
[numStrings
] = _tcsdup(_tcserror_r((int)error
, szBuffer
, 256));
654 strings
[numStrings
] = _tcsdup(_tcserror((int)error
));
659 strings
[numStrings
] = (TCHAR
*)malloc(32 * sizeof(TCHAR
));
660 _sntprintf(strings
[numStrings
], 32, _T("BAD FORMAT (0x%08X)"), (unsigned int)(va_arg(args
, UINT32
)));
668 if (!(m_flags
& NXLOG_USE_SYSLOG
) || (m_flags
& NXLOG_PRINT_TO_STDOUT
))
672 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
|
673 FORMAT_MESSAGE_FROM_HMODULE
| FORMAT_MESSAGE_ARGUMENT_ARRAY
,
674 m_msgModuleHandle
, msg
, 0, (LPTSTR
)&lpMsgBuf
, 0, (va_list *)strings
) > 0)
678 // Replace trailing CR/LF pair with LF
679 pCR
= _tcschr((TCHAR
*)lpMsgBuf
, _T('\r'));
686 if (m_flags
& NXLOG_USE_SYSLOG
)
688 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer
), (TCHAR
*)lpMsgBuf
);
692 WriteLogToFile((TCHAR
*)lpMsgBuf
, wType
);
700 _sntprintf(message
, 64, _T("MSG 0x%08X - cannot format message"), msg
);
701 if (m_flags
& NXLOG_USE_SYSLOG
)
703 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer
), message
);
707 WriteLogToFile(message
, wType
);
712 if (m_flags
& NXLOG_USE_SYSLOG
)
714 ReportEvent(m_eventLogHandle
, (wType
== EVENTLOG_DEBUG_TYPE
) ? EVENTLOG_INFORMATION_TYPE
: wType
, 0, msg
, NULL
, numStrings
, 0, (const TCHAR
**)strings
, NULL
);
717 pMsg
= FormatMessageUX(msg
, strings
);
718 if (m_flags
& NXLOG_USE_SYSLOG
)
724 case EVENTLOG_ERROR_TYPE
:
727 case EVENTLOG_WARNING_TYPE
:
730 case EVENTLOG_INFORMATION_TYPE
:
733 case EVENTLOG_DEBUG_TYPE
:
741 mbMsg
= MBStringFromWideString(pMsg
);
742 syslog(level
, "%s", mbMsg
);
745 syslog(level
, "%s", pMsg
);
748 if (m_flags
& NXLOG_PRINT_TO_STDOUT
)
750 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer
), pMsg
);
755 WriteLogToFile(pMsg
, wType
);
760 while(--numStrings
>= 0)
761 free(strings
[numStrings
]);
765 * Write debug message
767 void LIBNETXMS_EXPORTABLE
nxlog_debug(int level
, const TCHAR
*format
, ...)
769 if (level
> s_debugLevel
)
773 va_start(args
, format
);
775 _vsntprintf(buffer
, 8192, format
, args
);
777 nxlog_write(s_debugMsg
, NXLOG_DEBUG
, "s", buffer
);
781 * Write debug message
783 void LIBNETXMS_EXPORTABLE
nxlog_debug2(int level
, const TCHAR
*format
, va_list args
)
785 if (level
> s_debugLevel
)
789 _vsntprintf(buffer
, 8192, format
, args
);
790 nxlog_write(s_debugMsg
, NXLOG_DEBUG
, "s", buffer
);