3c1508d5d6738d85ae2ca16c517aae810e51df9e
[public/netxms.git] / src / libnetxms / log.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Utility Library
4 ** Copyright (C) 2003-2010 Victor Kirhenshtein
5 **
6 ** This program is free software; you can redistribute it and/or modify
7 ** it under the terms of the GNU General Public License as published by
8 ** the Free Software Foundation; either version 2 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 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
31 //
32 // Static data
33 //
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 DWORD m_flags = 0;
46 static int m_maxLogSize = 4096 * 1024; // 4 MB
47 static int m_logHistorySize = 4; // Keep up 4 previous log files
48
49
50 //
51 // Set log rotation policy
52 // Setting log size to 0 disables log rotation
53 //
54
55 BOOL LIBNETXMS_EXPORTABLE nxlog_set_rotation_policy(int maxLogSize, int historySize)
56 {
57 // Validate parameters
58 if (((maxLogSize != 0) && (maxLogSize < 1024)) || (historySize < 0) || (historySize > 9))
59 return FALSE;
60
61 m_maxLogSize = maxLogSize;
62 m_logHistorySize = historySize;
63
64 return TRUE;
65 }
66
67
68 //
69 // Rotate log
70 //
71
72 static BOOL RotateLog(BOOL needLock)
73 {
74 int i;
75 TCHAR oldName[MAX_PATH], newName[MAX_PATH];
76
77 if (m_flags & NXLOG_USE_SYSLOG)
78 return FALSE; // Cannot rotate system logs
79
80 if (needLock)
81 MutexLock(m_mutexLogAccess, INFINITE);
82
83 if ((m_logFileHandle != NULL) && (m_flags & NXLOG_IS_OPEN))
84 {
85 fclose(m_logFileHandle);
86 m_flags &= ~NXLOG_IS_OPEN;
87 }
88
89 // Delete old files
90 for(i = 9; i >= m_logHistorySize; i--)
91 {
92 _sntprintf(oldName, MAX_PATH, _T("%s.%d"), m_logFileName, i);
93 _tunlink(oldName);
94 }
95
96 // Shift file names
97 for(; i >= 0; i--)
98 {
99 _sntprintf(oldName, MAX_PATH, _T("%s.%d"), m_logFileName, i);
100 _sntprintf(newName, MAX_PATH, _T("%s.%d"), m_logFileName, i + 1);
101 _trename(oldName, newName);
102 }
103
104 // Rename current log to name.0
105 _sntprintf(newName, MAX_PATH, _T("%s.0"), m_logFileName);
106 _trename(m_logFileName, newName);
107
108 // Reopen log
109 m_logFileHandle = _tfopen(m_logFileName, _T("w"));
110 if (m_logFileHandle != NULL)
111 {
112 time_t t;
113 struct tm *loc;
114 #if HAVE_LOCALTIME_R
115 struct tm ltmBuffer;
116 #endif
117 TCHAR buffer[32];
118
119 m_flags |= NXLOG_IS_OPEN;
120 t = time(NULL);
121 #if HAVE_LOCALTIME_R
122 loc = localtime_r(&t, &ltmBuffer);
123 #else
124 loc = localtime(&t);
125 #endif
126 _tcsftime(buffer, 32, _T("%d-%b-%Y %H:%M:%S"), loc);
127 _ftprintf(m_logFileHandle, _T("[%s] Log file truncated.\n"), buffer);
128 }
129
130 if (needLock)
131 MutexUnlock(m_mutexLogAccess);
132
133 return (m_flags & NXLOG_IS_OPEN) ? TRUE : FALSE;
134 }
135
136
137 //
138 // API for application to force log rotation
139 //
140
141 BOOL LIBNETXMS_EXPORTABLE nxlog_rotate()
142 {
143 return RotateLog(TRUE);
144 }
145
146
147 //
148 // Initialize log
149 //
150
151 BOOL LIBNETXMS_EXPORTABLE nxlog_open(const TCHAR *logName, DWORD flags,
152 const TCHAR *msgModule, unsigned int msgCount, const TCHAR **messages)
153 {
154 m_flags = flags & 0x7FFFFFFF;
155 #ifdef _WIN32
156 m_msgModuleHandle = GetModuleHandle(msgModule);
157 #else
158 m_numMessages = msgCount;
159 m_messages = messages;
160 #endif
161
162 if (m_flags & NXLOG_USE_SYSLOG)
163 {
164 #ifdef _WIN32
165 m_eventLogHandle = RegisterEventSource(NULL, logName);
166 if (m_eventLogHandle != NULL)
167 m_flags |= NXLOG_IS_OPEN;
168 #else
169 #ifdef UNICODE
170 char *mbBuffer;
171
172 mbBuffer = MBStringFromWideString(logName);
173 openlog(mbBuffer, LOG_PID, LOG_DAEMON);
174 free(mbBuffer);
175 #else
176 openlog(logName, LOG_PID, LOG_DAEMON);
177 #endif
178 m_flags |= NXLOG_IS_OPEN;
179 #endif
180 }
181 else
182 {
183 TCHAR buffer[32];
184 struct tm *loc;
185 #if HAVE_LOCALTIME_R
186 struct tm ltmBuffer;
187 #endif
188 time_t t;
189
190 nx_strncpy(m_logFileName, logName, MAX_PATH);
191 m_logFileHandle = _tfopen(logName, _T("a"));
192 if (m_logFileHandle != NULL)
193 {
194 m_flags |= NXLOG_IS_OPEN;
195 t = time(NULL);
196 #if HAVE_LOCALTIME_R
197 loc = localtime_r(&t, &ltmBuffer);
198 #else
199 loc = localtime(&t);
200 #endif
201 _tcsftime(buffer, 32, _T("%d-%b-%Y %H:%M:%S"), loc);
202 _ftprintf(m_logFileHandle, _T("\n[%s] Log file opened\n"), buffer);
203 }
204
205 m_mutexLogAccess = MutexCreate();
206 }
207 return (m_flags & NXLOG_IS_OPEN) ? TRUE : FALSE;
208 }
209
210
211 //
212 // Close log
213 //
214
215 void LIBNETXMS_EXPORTABLE nxlog_close(void)
216 {
217 if (m_flags & NXLOG_IS_OPEN)
218 {
219 if (m_flags & NXLOG_USE_SYSLOG)
220 {
221 #ifdef _WIN32
222 DeregisterEventSource(m_eventLogHandle);
223 #else
224 closelog();
225 #endif
226 }
227 else
228 {
229 if (m_logFileHandle != NULL)
230 fclose(m_logFileHandle);
231 if (m_mutexLogAccess != INVALID_MUTEX_HANDLE)
232 MutexDestroy(m_mutexLogAccess);
233 }
234 m_flags &= ~NXLOG_IS_OPEN;
235 }
236 }
237
238
239 //
240 // Write record to log file
241 //
242
243 static void WriteLogToFile(TCHAR *message)
244 {
245 TCHAR buffer[64];
246 time_t t;
247 struct tm *loc;
248 #if HAVE_LOCALTIME_R
249 struct tm ltmBuffer;
250 #endif
251
252 // Prevent simultaneous write to log file
253 MutexLock(m_mutexLogAccess, INFINITE);
254
255 t = time(NULL);
256 #if HAVE_LOCALTIME_R
257 loc = localtime_r(&t, &ltmBuffer);
258 #else
259 loc = localtime(&t);
260 #endif
261 _tcsftime(buffer, 32, _T("[%d-%b-%Y %H:%M:%S]"), loc);
262 if (m_logFileHandle != NULL)
263 {
264 _ftprintf(m_logFileHandle, _T("%s %s"), buffer, message);
265 fflush(m_logFileHandle);
266 }
267 if (m_flags & NXLOG_PRINT_TO_STDOUT)
268 _tprintf(_T("%s %s"), buffer, message);
269
270 // Check log size
271 if (m_maxLogSize != 0)
272 {
273 struct stat st;
274
275 fstat(fileno(m_logFileHandle), &st);
276 if (st.st_size >= m_maxLogSize)
277 RotateLog(FALSE);
278 }
279
280 MutexUnlock(m_mutexLogAccess);
281 }
282
283
284 //
285 // Format message (UNIX version)
286 //
287
288 #ifndef _WIN32
289
290 static TCHAR *FormatMessageUX(DWORD dwMsgId, TCHAR **ppStrings)
291 {
292 const TCHAR *p;
293 TCHAR *pMsg;
294 int i, iSize, iLen;
295
296 if (dwMsgId >= m_numMessages)
297 {
298 // No message with this ID
299 pMsg = (TCHAR *)malloc(64 * sizeof(TCHAR));
300 _sntprintf(pMsg, 64, _T("MSG 0x%08X - Unable to find message text\n"), dwMsgId);
301 }
302 else
303 {
304 iSize = (_tcslen(m_messages[dwMsgId]) + 2) * sizeof(TCHAR);
305 pMsg = (TCHAR *)malloc(iSize);
306
307 for(i = 0, p = m_messages[dwMsgId]; *p != 0; p++)
308 if (*p == _T('%'))
309 {
310 p++;
311 if ((*p >= _T('1')) && (*p <= _T('9')))
312 {
313 iLen = _tcslen(ppStrings[*p - _T('1')]);
314 iSize += iLen * sizeof(TCHAR);
315 pMsg = (TCHAR *)realloc(pMsg, iSize);
316 _tcscpy(&pMsg[i], ppStrings[*p - _T('1')]);
317 i += iLen;
318 }
319 else
320 {
321 if (*p == 0) // Handle single % character at the string end
322 break;
323 pMsg[i++] = *p;
324 }
325 }
326 else
327 {
328 pMsg[i++] = *p;
329 }
330 pMsg[i++] = _T('\n');
331 pMsg[i] = 0;
332 }
333
334 return pMsg;
335 }
336
337 #endif /* ! _WIN32 */
338
339
340 //
341 // Write log record
342 // Parameters:
343 // msg - Message ID
344 // wType - Message type (see ReportEvent() for details)
345 // format - Parameter format string, each parameter represented by one character.
346 // The following format characters can be used:
347 // s - String
348 // d - Decimal integer
349 // x - Hex integer
350 // e - System error code (will appear in log as textual description)
351 // a - IP address in network byte order
352 //
353
354 void LIBNETXMS_EXPORTABLE nxlog_write(DWORD msg, WORD wType, const char *format, ...)
355 {
356 va_list args;
357 TCHAR *strings[16], *pMsg, szBuffer[256];
358 int numStrings = 0;
359 DWORD error;
360 time_t t;
361 struct tm *loc;
362 #if HAVE_LOCALTIME_R
363 struct tm ltmBuffer;
364 #endif
365 #if defined(UNICODE) && !defined(_WIN32)
366 char *mbMsg;
367 #endif
368
369 if (!(m_flags & NXLOG_IS_OPEN))
370 return;
371
372 memset(strings, 0, sizeof(TCHAR *) * 16);
373
374 if (format != NULL)
375 {
376 va_start(args, format);
377
378 for(; (format[numStrings] != 0) && (numStrings < 16); numStrings++)
379 {
380 switch(format[numStrings])
381 {
382 case 's':
383 strings[numStrings] = _tcsdup(va_arg(args, TCHAR *));
384 break;
385 case 'd':
386 strings[numStrings] = (TCHAR *)malloc(16 * sizeof(TCHAR));
387 _sntprintf(strings[numStrings], 16, _T("%d"), va_arg(args, LONG));
388 break;
389 case 'x':
390 strings[numStrings] = (TCHAR *)malloc(16 * sizeof(TCHAR));
391 _sntprintf(strings[numStrings], 16, _T("0x%08X"), va_arg(args, DWORD));
392 break;
393 case 'a':
394 strings[numStrings] = (TCHAR *)malloc(20 * sizeof(TCHAR));
395 IpToStr(va_arg(args, DWORD), strings[numStrings]);
396 break;
397 case 'e':
398 error = va_arg(args, DWORD);
399 #ifdef _WIN32
400 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
401 FORMAT_MESSAGE_FROM_SYSTEM |
402 FORMAT_MESSAGE_IGNORE_INSERTS,
403 NULL, error,
404 MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), // Default language
405 (LPTSTR)&pMsg,0,NULL)>0)
406 {
407 pMsg[_tcscspn(pMsg, _T("\r\n"))] = 0;
408 strings[numStrings] = (TCHAR *)malloc(_tcslen(pMsg) + 1);
409 _tcscpy(strings[numStrings], pMsg);
410 LocalFree(pMsg);
411 }
412 else
413 {
414 strings[numStrings] = (TCHAR *)malloc(64 * sizeof(TCHAR));
415 _sntprintf(strings[numStrings], 64, _T("MSG 0x%08X - Unable to find message text"), error);
416 }
417 #else /* _WIN32 */
418 #if HAVE_STRERROR_R
419 #if HAVE_POSIX_STRERROR_R
420 strings[numStrings] = (TCHAR *)malloc(256 * sizeof(TCHAR));
421 _tcserror_r((int)error, strings[numStrings], 256);
422 #else
423 strings[numStrings] = _tcsdup(_tcserror_r((int)error, szBuffer, 256));
424 #endif
425 #else
426 strings[numStrings] = _tcsdup(_tcserror((int)error));
427 #endif
428 #endif
429 break;
430 default:
431 strings[numStrings] = (TCHAR *)malloc(32 * sizeof(TCHAR));
432 _sntprintf(strings[numStrings], 32, _T("BAD FORMAT (0x%08X)"), va_arg(args, DWORD));
433 break;
434 }
435 }
436 va_end(args);
437 }
438
439 #ifdef _WIN32
440 if (!(m_flags & NXLOG_USE_SYSLOG) || (m_flags & NXLOG_PRINT_TO_STDOUT))
441 {
442 LPVOID lpMsgBuf;
443
444 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
445 FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
446 m_msgModuleHandle, msg, 0, (LPTSTR)&lpMsgBuf, 0, (va_list *)strings)>0)
447 {
448 char *pCR;
449
450 // Replace trailing CR/LF pair with LF
451 pCR = strchr((char *)lpMsgBuf, '\r');
452 if (pCR != NULL)
453 {
454 *pCR = '\n';
455 pCR++;
456 *pCR = 0;
457 }
458 if (m_flags & NXLOG_USE_SYSLOG)
459 {
460 t = time(NULL);
461 loc = localtime(&t);
462 _tcsftime(szBuffer, 32, _T("[%d-%b-%Y %H:%M:%S]"), loc);
463 _tprintf(_T("%s %s"), szBuffer, (TCHAR *)lpMsgBuf);
464 }
465 else
466 {
467 WriteLogToFile((TCHAR *)lpMsgBuf);
468 }
469 LocalFree(lpMsgBuf);
470 }
471 else
472 {
473 TCHAR message[64];
474
475 _sntprintf(message, 64, _T("MSG 0x%08X - Unable to find message text"), msg);
476 if (m_flags & NXLOG_USE_SYSLOG)
477 {
478 t = time(NULL);
479 loc = localtime(&t);
480 _tcsftime(szBuffer, 32, _T("[%d-%b-%Y %H:%M:%S]"), loc);
481 _tprintf(_T("%s %s"), szBuffer, message);
482 }
483 else
484 {
485 WriteLogToFile(message);
486 }
487 }
488 }
489
490 if (m_flags & NXLOG_USE_SYSLOG)
491 {
492 ReportEvent(m_eventLogHandle, (wType == EVENTLOG_DEBUG_TYPE) ? EVENTLOG_INFORMATION_TYPE : wType, 0, msg, NULL, numStrings, 0, (const TCHAR **)strings, NULL);
493 }
494 #else /* _WIN32 */
495 pMsg = FormatMessageUX(msg, strings);
496 if (m_flags & NXLOG_USE_SYSLOG)
497 {
498 int level;
499
500 switch(wType)
501 {
502 case EVENTLOG_ERROR_TYPE:
503 level = LOG_ERR;
504 break;
505 case EVENTLOG_WARNING_TYPE:
506 level = LOG_WARNING;
507 break;
508 case EVENTLOG_INFORMATION_TYPE:
509 level = LOG_NOTICE;
510 break;
511 case EVENTLOG_DEBUG_TYPE:
512 level = LOG_DEBUG;
513 break;
514 default:
515 level = LOG_INFO;
516 break;
517 }
518 #ifdef UNICODE
519 mbMsg = MBStringFromWideString(pMsg);
520 syslog(level, "%s", mbMsg);
521 free(mbMsg);
522 #else
523 syslog(level, "%s", pMsg);
524 #endif
525
526 if (m_flags & NXLOG_PRINT_TO_STDOUT)
527 {
528 t = time(NULL);
529 #if HAVE_LOCALTIME_R
530 loc = localtime_r(&t, &ltmBuffer);
531 #else
532 loc = localtime(&t);
533 #endif
534 _tcsftime(szBuffer, 32, _T("[%d-%b-%Y %H:%M:%S]"), loc);
535 _tprintf(_T("%s %s"), szBuffer, pMsg);
536 }
537 }
538 else
539 {
540 WriteLogToFile(pMsg);
541 }
542 free(pMsg);
543 #endif /* _WIN32 */
544
545 while(--numStrings >= 0)
546 free(strings[numStrings]);
547 }