2198de078f5ba1c89618ac9db116a9bf1dc16215
[public/netxms.git] / src / libnetxms / log.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Utility Library
4 ** Copyright (C) 2003-2014 Victor Kirhenshtein
5 **
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.
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 **
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.
19 **
20 ** File: log.cpp
21 **
22 **/
23
24 #include "libnetxms.h"
25
26 #if HAVE_SYSLOG_H
27 #include <syslog.h>
28 #endif
29
30 #define MAX_LOG_HISTORY_SIZE 128
31
32 /**
33 * Static data
34 */
35 #ifdef _WIN32
36 static HANDLE m_eventLogHandle = NULL;
37 static HMODULE m_msgModuleHandle = NULL;
38 #else
39 static unsigned int m_numMessages;
40 static const TCHAR **m_messages;
41 #endif
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
53 /**
54 * Format current time for output
55 */
56 static TCHAR *FormatLogTimestamp(TCHAR *buffer)
57 {
58 INT64 now = GetCurrentTimeMs();
59 time_t t = now / 1000;
60 #if HAVE_LOCALTIME_R
61 struct tm ltmBuffer;
62 struct tm *loc = localtime_r(&t, &ltmBuffer);
63 #else
64 struct tm *loc = localtime(&t);
65 #endif
66 _tcsftime(buffer, 32, _T("[%d-%b-%Y %H:%M:%S"), loc);
67 _sntprintf(&buffer[21], 8, _T(".%03d]"), (int)(now % 1000));
68 return buffer;
69 }
70
71 /**
72 * Set timestamp of start of the current day
73 */
74 static void SetDayStart()
75 {
76 time_t now = time(NULL);
77 struct tm dayStart;
78 #if HAVE_LOCALTIME_R
79 localtime_r(&now, &dayStart);
80 #else
81 struct tm *ltm = localtime(&now);
82 memcpy(&dayStart, ltm, sizeof(struct tm));
83 #endif
84 dayStart.tm_hour = 0;
85 dayStart.tm_min = 0;
86 dayStart.tm_sec = 0;
87 m_currentDayStart = mktime(&dayStart);
88 }
89
90 /**
91 * Set log rotation policy
92 * Setting log size to 0 or mode to NXLOG_ROTATION_DISABLED disables log rotation
93 */
94 BOOL LIBNETXMS_EXPORTABLE nxlog_set_rotation_policy(int rotationMode, int maxLogSize, int historySize, const TCHAR *dailySuffix)
95 {
96 BOOL isValid = TRUE;
97
98 if ((rotationMode >= 0) || (rotationMode <= 2))
99 {
100 m_rotationMode = rotationMode;
101 if (rotationMode == NXLOG_ROTATION_BY_SIZE)
102 {
103 if ((maxLogSize == 0) || (maxLogSize >= 1024))
104 {
105 m_maxLogSize = maxLogSize;
106 }
107 else
108 {
109 m_maxLogSize = 1024;
110 isValid = FALSE;
111 }
112
113 if ((historySize >= 0) && (historySize <= MAX_LOG_HISTORY_SIZE))
114 {
115 m_logHistorySize = historySize;
116 }
117 else
118 {
119 if (historySize > MAX_LOG_HISTORY_SIZE)
120 m_logHistorySize = MAX_LOG_HISTORY_SIZE;
121 isValid = FALSE;
122 }
123 }
124 else if (rotationMode == NXLOG_ROTATION_DAILY)
125 {
126 if ((dailySuffix != NULL) && (dailySuffix[0] != 0))
127 nx_strncpy(m_dailyLogSuffixTemplate, dailySuffix, sizeof(m_dailyLogSuffixTemplate) / sizeof(TCHAR));
128 SetDayStart();
129 }
130 }
131 else
132 {
133 isValid = FALSE;
134 }
135
136 return isValid;
137 }
138
139 /**
140 * Set console writer
141 */
142 void LIBNETXMS_EXPORTABLE nxlog_set_console_writer(NxLogConsoleWriter writer)
143 {
144 m_consoleWriter = writer;
145 }
146
147 /**
148 * Rotate log
149 */
150 static BOOL RotateLog(BOOL needLock)
151 {
152 int i;
153 TCHAR oldName[MAX_PATH], newName[MAX_PATH];
154
155 if (m_flags & NXLOG_USE_SYSLOG)
156 return FALSE; // Cannot rotate system logs
157
158 if (needLock)
159 MutexLock(m_mutexLogAccess);
160
161 if ((m_logFileHandle != NULL) && (m_flags & NXLOG_IS_OPEN))
162 {
163 fclose(m_logFileHandle);
164 m_flags &= ~NXLOG_IS_OPEN;
165 }
166
167 if (m_rotationMode == NXLOG_ROTATION_BY_SIZE)
168 {
169 // Delete old files
170 for(i = MAX_LOG_HISTORY_SIZE; i >= m_logHistorySize; i--)
171 {
172 _sntprintf(oldName, MAX_PATH, _T("%s.%d"), m_logFileName, i);
173 _tunlink(oldName);
174 }
175
176 // Shift file names
177 for(; i >= 0; i--)
178 {
179 _sntprintf(oldName, MAX_PATH, _T("%s.%d"), m_logFileName, i);
180 _sntprintf(newName, MAX_PATH, _T("%s.%d"), m_logFileName, i + 1);
181 _trename(oldName, newName);
182 }
183
184 // Rename current log to name.0
185 _sntprintf(newName, MAX_PATH, _T("%s.0"), m_logFileName);
186 _trename(m_logFileName, newName);
187 }
188 else if (m_rotationMode == NXLOG_ROTATION_DAILY)
189 {
190 #if HAVE_LOCALTIME_R
191 struct tm ltmBuffer;
192 struct tm *loc = localtime_r(&m_currentDayStart, &ltmBuffer);
193 #else
194 struct tm *loc = localtime(&m_currentDayStart);
195 #endif
196 TCHAR buffer[64];
197 _tcsftime(buffer, 64, m_dailyLogSuffixTemplate, loc);
198
199 // Rename current log to name.suffix
200 _sntprintf(newName, MAX_PATH, _T("%s.%s"), m_logFileName, buffer);
201 _trename(m_logFileName, newName);
202
203 SetDayStart();
204 }
205
206 // Reopen log
207 #if HAVE_FOPEN64
208 m_logFileHandle = _tfopen64(m_logFileName, _T("w"));
209 #else
210 m_logFileHandle = _tfopen(m_logFileName, _T("w"));
211 #endif
212 if (m_logFileHandle != NULL)
213 {
214 m_flags |= NXLOG_IS_OPEN;
215 TCHAR buffer[32];
216 _ftprintf(m_logFileHandle, _T("%s Log file truncated.\n"), FormatLogTimestamp(buffer));
217 }
218
219 if (needLock)
220 MutexUnlock(m_mutexLogAccess);
221
222 return (m_flags & NXLOG_IS_OPEN) ? TRUE : FALSE;
223 }
224
225 /**
226 * API for application to force log rotation
227 */
228 BOOL LIBNETXMS_EXPORTABLE nxlog_rotate()
229 {
230 return RotateLog(TRUE);
231 }
232
233 /**
234 * Initialize log
235 */
236 BOOL LIBNETXMS_EXPORTABLE nxlog_open(const TCHAR *logName, UINT32 flags,
237 const TCHAR *msgModule, unsigned int msgCount, const TCHAR **messages)
238 {
239 m_flags = flags & 0x7FFFFFFF;
240 #ifdef _WIN32
241 m_msgModuleHandle = GetModuleHandle(msgModule);
242 #else
243 m_numMessages = msgCount;
244 m_messages = messages;
245 #endif
246
247 if (m_flags & NXLOG_USE_SYSLOG)
248 {
249 #ifdef _WIN32
250 m_eventLogHandle = RegisterEventSource(NULL, logName);
251 if (m_eventLogHandle != NULL)
252 m_flags |= NXLOG_IS_OPEN;
253 #else
254 #ifdef UNICODE
255 char *mbBuffer;
256
257 mbBuffer = MBStringFromWideString(logName);
258 openlog(mbBuffer, LOG_PID, LOG_DAEMON);
259 free(mbBuffer);
260 #else
261 openlog(logName, LOG_PID, LOG_DAEMON);
262 #endif
263 m_flags |= NXLOG_IS_OPEN;
264 #endif
265 }
266 else
267 {
268 TCHAR buffer[32];
269
270 nx_strncpy(m_logFileName, logName, MAX_PATH);
271 #if HAVE_FOPEN64
272 m_logFileHandle = _tfopen64(logName, _T("a"));
273 #else
274 m_logFileHandle = _tfopen(logName, _T("a"));
275 #endif
276 if (m_logFileHandle != NULL)
277 {
278 m_flags |= NXLOG_IS_OPEN;
279 _ftprintf(m_logFileHandle, _T("\n%s Log file opened\n"), FormatLogTimestamp(buffer));
280 }
281
282 m_mutexLogAccess = MutexCreate();
283 SetDayStart();
284 }
285 return (m_flags & NXLOG_IS_OPEN) ? TRUE : FALSE;
286 }
287
288 /**
289 * Close log
290 */
291 void LIBNETXMS_EXPORTABLE nxlog_close()
292 {
293 if (m_flags & NXLOG_IS_OPEN)
294 {
295 if (m_flags & NXLOG_USE_SYSLOG)
296 {
297 #ifdef _WIN32
298 DeregisterEventSource(m_eventLogHandle);
299 #else
300 closelog();
301 #endif
302 }
303 else
304 {
305 if (m_logFileHandle != NULL)
306 fclose(m_logFileHandle);
307 if (m_mutexLogAccess != INVALID_MUTEX_HANDLE)
308 MutexDestroy(m_mutexLogAccess);
309 }
310 m_flags &= ~NXLOG_IS_OPEN;
311 }
312 }
313
314 /**
315 * Write record to log file
316 */
317 static void WriteLogToFile(TCHAR *message, const WORD wType)
318 {
319 TCHAR buffer[64];
320 TCHAR loglevel[64];
321
322 switch(wType)
323 {
324 case EVENTLOG_ERROR_TYPE:
325 _sntprintf(loglevel, 16, _T("[%s] "), _T("ERROR"));
326 break;
327 case EVENTLOG_WARNING_TYPE:
328 _sntprintf(loglevel, 16, _T("[%s] "), _T("WARN "));
329 break;
330 case EVENTLOG_INFORMATION_TYPE:
331 _sntprintf(loglevel, 16, _T("[%s] "), _T("INFO "));
332 break;
333 case EVENTLOG_DEBUG_TYPE:
334 _sntprintf(loglevel, 16, _T("[%s] "), _T("DEBUG"));
335 break;
336 default:
337 _tcsncpy(loglevel, _T(""), 1);
338 break;
339 }
340
341 // Prevent simultaneous write to log file
342 MutexLock(m_mutexLogAccess);
343
344 // Check for new day start
345 time_t t = time(NULL);
346 if ((m_rotationMode == NXLOG_ROTATION_DAILY) && (t >= m_currentDayStart + 86400))
347 {
348 RotateLog(FALSE);
349 }
350
351 FormatLogTimestamp(buffer);
352 if (m_logFileHandle != NULL)
353 {
354 _ftprintf(m_logFileHandle, _T("%s %s%s"), buffer, loglevel, message);
355 fflush(m_logFileHandle);
356 }
357 if (m_flags & NXLOG_PRINT_TO_STDOUT)
358 m_consoleWriter(_T("%s %s%s"), buffer, loglevel, message);
359
360 // Check log size
361 if ((m_logFileHandle != NULL) && (m_rotationMode == NXLOG_ROTATION_BY_SIZE) && (m_maxLogSize != 0))
362 {
363 struct stat st;
364
365 fstat(fileno(m_logFileHandle), &st);
366 if (st.st_size >= m_maxLogSize)
367 RotateLog(FALSE);
368 }
369
370 MutexUnlock(m_mutexLogAccess);
371 }
372
373 /**
374 * Format message (UNIX version)
375 */
376 #ifndef _WIN32
377
378 static TCHAR *FormatMessageUX(UINT32 dwMsgId, TCHAR **ppStrings)
379 {
380 const TCHAR *p;
381 TCHAR *pMsg;
382 int i, iSize, iLen;
383
384 if (dwMsgId >= m_numMessages)
385 {
386 // No message with this ID
387 pMsg = (TCHAR *)malloc(64 * sizeof(TCHAR));
388 _sntprintf(pMsg, 64, _T("MSG 0x%08X - Unable to find message text\n"), (unsigned int)dwMsgId);
389 }
390 else
391 {
392 iSize = (_tcslen(m_messages[dwMsgId]) + 2) * sizeof(TCHAR);
393 pMsg = (TCHAR *)malloc(iSize);
394
395 for(i = 0, p = m_messages[dwMsgId]; *p != 0; p++)
396 if (*p == _T('%'))
397 {
398 p++;
399 if ((*p >= _T('1')) && (*p <= _T('9')))
400 {
401 iLen = _tcslen(ppStrings[*p - _T('1')]);
402 iSize += iLen * sizeof(TCHAR);
403 pMsg = (TCHAR *)realloc(pMsg, iSize);
404 _tcscpy(&pMsg[i], ppStrings[*p - _T('1')]);
405 i += iLen;
406 }
407 else
408 {
409 if (*p == 0) // Handle single % character at the string end
410 break;
411 pMsg[i++] = *p;
412 }
413 }
414 else
415 {
416 pMsg[i++] = *p;
417 }
418 pMsg[i++] = _T('\n');
419 pMsg[i] = 0;
420 }
421
422 return pMsg;
423 }
424
425 #endif /* ! _WIN32 */
426
427 /**
428 * Write log record
429 * Parameters:
430 * msg - Message ID
431 * wType - Message type (see ReportEvent() for details)
432 * format - Parameter format string, each parameter represented by one character.
433 * The following format characters can be used:
434 * s - String (multibyte or UNICODE, depending on build)
435 * m - multibyte string
436 * u - UNICODE string
437 * d - Decimal integer
438 * x - Hex integer
439 * e - System error code (will appear in log as textual description)
440 * a - IP address in network byte order
441 * A - IPv6 address in network byte order
442 * H - IPv6 address in network byte order (will appear in [])
443 * I - pointer to InetAddress object
444 */
445 void LIBNETXMS_EXPORTABLE nxlog_write(DWORD msg, WORD wType, const char *format, ...)
446 {
447 va_list args;
448 TCHAR *strings[16], *pMsg, szBuffer[256];
449 int numStrings = 0;
450 UINT32 error;
451 #if defined(UNICODE) && !defined(_WIN32)
452 char *mbMsg;
453 #endif
454
455 if (!(m_flags & NXLOG_IS_OPEN))
456 return;
457
458 memset(strings, 0, sizeof(TCHAR *) * 16);
459
460 if (format != NULL)
461 {
462 va_start(args, format);
463
464 for(; (format[numStrings] != 0) && (numStrings < 16); numStrings++)
465 {
466 switch(format[numStrings])
467 {
468 case 's':
469 strings[numStrings] = _tcsdup(va_arg(args, TCHAR *));
470 break;
471 case 'm':
472 #ifdef UNICODE
473 strings[numStrings] = WideStringFromMBString(va_arg(args, char *));
474 #else
475 strings[numStrings] = strdup(va_arg(args, char *));
476 #endif
477 break;
478 case 'u':
479 #ifdef UNICODE
480 strings[numStrings] = wcsdup(va_arg(args, WCHAR *));
481 #else
482 strings[numStrings] = MBStringFromWideString(va_arg(args, WCHAR *));
483 #endif
484 break;
485 case 'd':
486 strings[numStrings] = (TCHAR *)malloc(16 * sizeof(TCHAR));
487 _sntprintf(strings[numStrings], 16, _T("%d"), (int)(va_arg(args, LONG)));
488 break;
489 case 'x':
490 strings[numStrings] = (TCHAR *)malloc(16 * sizeof(TCHAR));
491 _sntprintf(strings[numStrings], 16, _T("0x%08X"), (unsigned int)(va_arg(args, UINT32)));
492 break;
493 case 'a':
494 strings[numStrings] = (TCHAR *)malloc(20 * sizeof(TCHAR));
495 IpToStr(va_arg(args, UINT32), strings[numStrings]);
496 break;
497 case 'A':
498 strings[numStrings] = (TCHAR *)malloc(48 * sizeof(TCHAR));
499 Ip6ToStr(va_arg(args, BYTE *), strings[numStrings]);
500 break;
501 case 'H':
502 strings[numStrings] = (TCHAR *)malloc(48 * sizeof(TCHAR));
503 strings[numStrings][0] = _T('[');
504 Ip6ToStr(va_arg(args, BYTE *), strings[numStrings] + 1);
505 _tcscat(strings[numStrings], _T("]"));
506 break;
507 case 'I':
508 strings[numStrings] = (TCHAR *)malloc(48 * sizeof(TCHAR));
509 va_arg(args, InetAddress *)->toString(strings[numStrings]);
510 break;
511 case 'e':
512 error = va_arg(args, UINT32);
513 #ifdef _WIN32
514 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
515 FORMAT_MESSAGE_FROM_SYSTEM |
516 FORMAT_MESSAGE_IGNORE_INSERTS,
517 NULL, error,
518 MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), // Default language
519 (LPTSTR)&pMsg, 0, NULL) > 0)
520 {
521 pMsg[_tcscspn(pMsg, _T("\r\n"))] = 0;
522 strings[numStrings] = (TCHAR *)malloc((_tcslen(pMsg) + 1) * sizeof(TCHAR));
523 _tcscpy(strings[numStrings], pMsg);
524 LocalFree(pMsg);
525 }
526 else
527 {
528 strings[numStrings] = (TCHAR *)malloc(64 * sizeof(TCHAR));
529 _sntprintf(strings[numStrings], 64, _T("MSG 0x%08X - Unable to find message text"), error);
530 }
531 #else /* _WIN32 */
532 #if HAVE_STRERROR_R
533 #if HAVE_POSIX_STRERROR_R
534 strings[numStrings] = (TCHAR *)malloc(256 * sizeof(TCHAR));
535 _tcserror_r((int)error, strings[numStrings], 256);
536 #else
537 strings[numStrings] = _tcsdup(_tcserror_r((int)error, szBuffer, 256));
538 #endif
539 #else
540 strings[numStrings] = _tcsdup(_tcserror((int)error));
541 #endif
542 #endif
543 break;
544 default:
545 strings[numStrings] = (TCHAR *)malloc(32 * sizeof(TCHAR));
546 _sntprintf(strings[numStrings], 32, _T("BAD FORMAT (0x%08X)"), (unsigned int)(va_arg(args, UINT32)));
547 break;
548 }
549 }
550 va_end(args);
551 }
552
553 #ifdef _WIN32
554 if (!(m_flags & NXLOG_USE_SYSLOG) || (m_flags & NXLOG_PRINT_TO_STDOUT))
555 {
556 LPVOID lpMsgBuf;
557
558 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
559 FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
560 m_msgModuleHandle, msg, 0, (LPTSTR)&lpMsgBuf, 0, (va_list *)strings) > 0)
561 {
562 TCHAR *pCR;
563
564 // Replace trailing CR/LF pair with LF
565 pCR = _tcschr((TCHAR *)lpMsgBuf, _T('\r'));
566 if (pCR != NULL)
567 {
568 *pCR = _T('\n');
569 pCR++;
570 *pCR = 0;
571 }
572 if (m_flags & NXLOG_USE_SYSLOG)
573 {
574 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer), (TCHAR *)lpMsgBuf);
575 }
576 else
577 {
578 WriteLogToFile((TCHAR *)lpMsgBuf, wType);
579 }
580 LocalFree(lpMsgBuf);
581 }
582 else
583 {
584 TCHAR message[64];
585
586 _sntprintf(message, 64, _T("MSG 0x%08X - cannot format message"), msg);
587 if (m_flags & NXLOG_USE_SYSLOG)
588 {
589 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer), message);
590 }
591 else
592 {
593 WriteLogToFile(message, wType);
594 }
595 }
596 }
597
598 if (m_flags & NXLOG_USE_SYSLOG)
599 {
600 ReportEvent(m_eventLogHandle, (wType == EVENTLOG_DEBUG_TYPE) ? EVENTLOG_INFORMATION_TYPE : wType, 0, msg, NULL, numStrings, 0, (const TCHAR **)strings, NULL);
601 }
602 #else /* _WIN32 */
603 pMsg = FormatMessageUX(msg, strings);
604 if (m_flags & NXLOG_USE_SYSLOG)
605 {
606 int level;
607
608 switch(wType)
609 {
610 case EVENTLOG_ERROR_TYPE:
611 level = LOG_ERR;
612 break;
613 case EVENTLOG_WARNING_TYPE:
614 level = LOG_WARNING;
615 break;
616 case EVENTLOG_INFORMATION_TYPE:
617 level = LOG_NOTICE;
618 break;
619 case EVENTLOG_DEBUG_TYPE:
620 level = LOG_DEBUG;
621 break;
622 default:
623 level = LOG_INFO;
624 break;
625 }
626 #ifdef UNICODE
627 mbMsg = MBStringFromWideString(pMsg);
628 syslog(level, "%s", mbMsg);
629 free(mbMsg);
630 #else
631 syslog(level, "%s", pMsg);
632 #endif
633
634 if (m_flags & NXLOG_PRINT_TO_STDOUT)
635 {
636 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer), pMsg);
637 }
638 }
639 else
640 {
641 WriteLogToFile(pMsg, wType);
642 }
643 free(pMsg);
644 #endif /* _WIN32 */
645
646 while(--numStrings >= 0)
647 free(strings[numStrings]);
648 }