Show milliseconds in SILC_LOG_DEBUG and family outputs
[runtime.git] / lib / silcutil / silclog.c
1 /*
2
3   silclog.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 1997 - 2008 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19
20 #include "silcruntime.h"
21
22 /* SilcLogSettings context */
23 typedef struct {
24   SilcUInt32 flushdelay;
25
26   char debug_string[128];
27   SilcLogDebugCb debug_cb;
28   void *debug_context;
29   SilcLogHexdumpCb hexdump_cb;
30   void *hexdump_context;
31
32   unsigned int timestamp       : 1;
33   unsigned int quick           : 1;
34   unsigned int debug           : 1;
35   unsigned int debug_hexdump   : 1;
36   unsigned int scheduled       : 1;
37   unsigned int no_init         : 1;
38   unsigned int starting        : 1;
39 } *SilcLogSettings, SilcLogSettingsStruct;
40
41 /* SilcLog context */
42 typedef struct {
43   char filename[256];
44   FILE *fp;
45   SilcUInt64 maxsize;
46   const char *typename;
47   SilcLogType type;
48   SilcLogCb cb;
49   void *context;
50 } *SilcLog, SilcLogStruct;
51
52 #ifndef SILC_SYMBIAN
53
54 /* Default settings */
55 static SilcLogSettingsStruct silclog =
56 {
57   300,
58   { 0 },
59   NULL, NULL,
60   NULL, NULL,
61   TRUE,
62   FALSE,
63   FALSE,
64   FALSE,
65   FALSE,
66   FALSE,
67   TRUE,
68 };
69
70 #endif /* !SILC_SYMBIAN */
71
72 /* Default log contexts */
73 #ifndef SILC_SYMBIAN
74 static SilcLogStruct silclogs[4] =
75 #else
76 const SilcLogStruct silclogs[4] =
77 #endif /* !SILC_SYMBIAN */
78 {
79   {"", NULL, 0, "Info", SILC_LOG_INFO, NULL, NULL},
80   {"", NULL, 0, "Warning", SILC_LOG_WARNING, NULL, NULL},
81   {"", NULL, 0, "Error", SILC_LOG_ERROR, NULL, NULL},
82   {"", NULL, 0, "Fatal", SILC_LOG_FATAL, NULL, NULL},
83 };
84
85 /* Return log context by type */
86
87 static SilcLog silc_log_get_context(SilcLogType type)
88 {
89   if (type < 1 || type > 4)
90     return NULL;
91   return (SilcLog)&silclogs[(int)type - 1];
92 }
93
94 /* Check log file site and cycle log file if it is over max size. */
95
96 static void silc_log_checksize(SilcLog log)
97 {
98   char newname[256];
99   SilcUInt64 size;
100
101   if (!log || !log->fp || !log->maxsize)
102     return;
103
104   size = silc_file_size(log->filename);
105   if (!size) {
106     fclose(log->fp);
107     log->fp = NULL;
108   }
109
110   if (size < log->maxsize)
111     return;
112
113   /* Cycle log file */
114   fprintf(log->fp,
115           "[%s] [%s] Cycling log file, over max log size (%lu kilobytes)\n",
116           silc_time_string(0), log->typename,
117           (unsigned long)log->maxsize / 1024);
118   fflush(log->fp);
119   fclose(log->fp);
120
121   memset(newname, 0, sizeof(newname));
122   silc_snprintf(newname, sizeof(newname) - 1, "%s.old", log->filename);
123   unlink(newname);
124   rename(log->filename, newname);
125
126   log->fp = fopen(log->filename, "w");
127   if (!log->fp)
128     SILC_LOG_WARNING(("Couldn't reopen log file '%s' for type '%s': %s",
129                       log->filename, log->typename, strerror(errno)));
130 #ifdef HAVE_CHMOD
131   chmod(log->filename, 0600);
132 #endif /* HAVE_CHMOD */
133 }
134
135 /* Internal timeout callback to flush log channels and check file sizes */
136
137 SILC_TASK_CALLBACK(silc_log_fflush_callback)
138 {
139 #ifndef SILC_SYMBIAN
140   SilcLog log;
141
142   if (!silclog.quick) {
143     silc_log_flush_all();
144     log = silc_log_get_context(SILC_LOG_INFO);
145     silc_log_checksize(log);
146     log = silc_log_get_context(SILC_LOG_WARNING);
147     silc_log_checksize(log);
148     log = silc_log_get_context(SILC_LOG_ERROR);
149     silc_log_checksize(log);
150     log = silc_log_get_context(SILC_LOG_FATAL);
151     silc_log_checksize(log);
152   }
153
154   silclog.starting = FALSE;
155
156   if (silclog.flushdelay < 2)
157     silclog.flushdelay = 2;
158   silc_schedule_task_add_timeout(context, silc_log_fflush_callback, context,
159                                  silclog.flushdelay, 0);
160 #endif /* !SILC_SYMBIAN */
161 }
162
163 /* Output log message to log file */
164
165 void silc_log_output(SilcLogType type, char *string)
166 {
167   const char *typename = NULL;
168   SilcLog log = silc_log_get_context(type);
169   FILE *fp;
170
171   if (!log)
172     goto end;
173
174   /* Forward to callback if set */
175   if (log->cb)
176     if ((*log->cb)(type, string, log->context))
177       goto end;
178
179   typename = log->typename;
180
181 #ifndef SILC_SYMBIAN
182   if (!silclog.scheduled) {
183     if (silclog.no_init == FALSE) {
184       fprintf(stderr,
185               "Warning, log files not initialized, "
186               "log output is going to stderr\n");
187       silclog.no_init = TRUE;
188     }
189
190     fp = stderr;
191     log = NULL;
192     goto found;
193   }
194 #endif /* !SILC_SYMBIAN */
195
196   /* Find open log file */
197   while (log) {
198     if (log->fp) {
199       fp = log->fp;
200       break;
201     }
202
203     log = silc_log_get_context(--type);
204   }
205   if (!log || !log->fp)
206     goto end;
207
208 #ifndef SILC_SYMBIAN
209  found:
210   if (silclog.timestamp)
211     fprintf(fp, "[%s] [%s] %s\n", silc_time_string(0), typename, string);
212   else
213     fprintf(fp, "[%s] %s\n", typename, string);
214
215   if (silclog.quick || silclog.starting) {
216     fflush(fp);
217     if (log)
218       silc_log_checksize(log);
219   }
220 #endif /* !SILC_SYMBIAN */
221
222  end:
223 #ifndef SILC_SYMBIAN
224   /* Output log to stderr if debugging */
225   if (typename && silclog.debug) {
226     fprintf(stderr, "[Logging] [%s] %s\n", typename, string);
227     fflush(stderr);
228   }
229 #else
230   fprintf(stderr, "[Logging] [%s] %s\n", typename, string);
231 #endif /* !SILC_SYMBIAN */
232
233   silc_free(string);
234 }
235
236 /* Set and initialize the specified log file. */
237
238 SilcBool silc_log_set_file(SilcLogType type, char *filename,
239                            SilcUInt32 maxsize, SilcSchedule scheduler)
240 {
241 #ifndef SILC_SYMBIAN
242   FILE *fp = NULL;
243   SilcLog log;
244
245   if (!scheduler)
246     scheduler = silc_schedule_get_global();
247
248   log = silc_log_get_context(type);
249   if (!log)
250     return FALSE;
251
252   SILC_LOG_DEBUG(("Setting '%s' file to %s (max size=%d)",
253                   log->typename, filename, maxsize));
254
255   /* Open log file */
256   if (filename) {
257     fp = fopen(filename, "a+");
258     if (!fp) {
259       fprintf(stderr, "warning: couldn't open log file '%s': %s\n",
260               filename, strerror(errno));
261       return FALSE;
262     }
263 #ifdef HAVE_CHMOD
264     chmod(filename, 0600);
265 #endif /* HAVE_CHMOD */
266   }
267
268   /* Close previous log file if it exists */
269   if (strlen(log->filename)) {
270     if (log->fp)
271       fclose(log->fp);
272     memset(log->filename, 0, sizeof(log->filename));
273     log->fp = NULL;
274   }
275
276   /* Set new log file */
277   if (fp) {
278     log->fp = fp;
279     log->maxsize = maxsize;
280
281     memset(log->filename, 0, sizeof(log->filename));
282     silc_strncat(log->filename, sizeof(log->filename), filename,
283                  strlen(filename));
284   }
285
286   /* Add flush timeout */
287   if (scheduler) {
288     silc_schedule_task_del_by_callback(scheduler, silc_log_fflush_callback);
289     silc_schedule_task_add_timeout(scheduler, silc_log_fflush_callback,
290                                    scheduler, 10, 0);
291     silclog.scheduled = TRUE;
292   }
293
294 #endif /* !SILC_SYMBIAN */
295   return TRUE;
296 }
297
298 /* Return log filename */
299
300 char *silc_log_get_file(SilcLogType type)
301 {
302   SilcLog log = silc_log_get_context(type);
303   return log && log->fp ? log->filename : NULL;
304 }
305
306 /* Sets a log callback, set callback to NULL to return to default behaviour */
307
308 void silc_log_set_callback(SilcLogType type, SilcLogCb cb, void *context)
309 {
310 #ifndef SILC_SYMBIAN
311   SilcLog log = silc_log_get_context(type);
312   if (log) {
313     log->cb = cb;
314     log->context = context;
315   }
316 #endif /* !SILC_SYMBIAN */
317 }
318
319 /* Reset log callbacks */
320
321 void silc_log_reset_callbacks(void)
322 {
323 #ifndef SILC_SYMBIAN
324   SilcLog log;
325   log = silc_log_get_context(SILC_LOG_INFO);
326   log->cb = log->context = NULL;
327   log = silc_log_get_context(SILC_LOG_WARNING);
328   log->cb = log->context = NULL;
329   log = silc_log_get_context(SILC_LOG_ERROR);
330   log->cb = log->context = NULL;
331   log = silc_log_get_context(SILC_LOG_FATAL);
332   log->cb = log->context = NULL;
333 #endif /* !SILC_SYMBIAN */
334 }
335
336 /* Flush all log files */
337
338 void silc_log_flush_all(void)
339 {
340   SilcLog log;
341   log = silc_log_get_context(SILC_LOG_INFO);
342   if (log->fp)
343     fflush(log->fp);
344   log = silc_log_get_context(SILC_LOG_WARNING);
345   if (log->fp)
346     fflush(log->fp);
347   log = silc_log_get_context(SILC_LOG_ERROR);
348   if (log->fp)
349     fflush(log->fp);
350   log = silc_log_get_context(SILC_LOG_FATAL);
351   if (log->fp)
352     fflush(log->fp);
353 }
354
355 /* Reset a log file */
356
357 static void silc_log_reset(SilcLog log)
358 {
359   if (log->fp) {
360     fflush(log->fp);
361     fclose(log->fp);
362   }
363
364   if (!strlen(log->filename))
365     return;
366
367   log->fp = fopen(log->filename, "a+");
368   if (!log->fp)
369     SILC_LOG_WARNING(("Couldn't reset log file '%s' for type '%s': %s",
370                       log->filename, log->typename, strerror(errno)));
371 }
372
373 /* Reset all log files */
374
375 void silc_log_reset_all(void)
376 {
377   SilcLog log;
378   log = silc_log_get_context(SILC_LOG_INFO);
379   if (log->fp)
380     silc_log_reset(log);
381   log = silc_log_get_context(SILC_LOG_WARNING);
382   if (log->fp)
383     silc_log_reset(log);
384   log = silc_log_get_context(SILC_LOG_ERROR);
385   if (log->fp)
386     silc_log_reset(log);
387   log = silc_log_get_context(SILC_LOG_FATAL);
388   if (log->fp)
389     silc_log_reset(log);
390   silc_log_flush_all();
391 }
392
393 /* Sets debug callbacks */
394
395 void silc_log_set_debug_callbacks(SilcLogDebugCb debug_cb,
396                                   void *debug_context,
397                                   SilcLogHexdumpCb hexdump_cb,
398                                   void *hexdump_context)
399 {
400 #ifndef SILC_SYMBIAN
401   silclog.debug_cb = debug_cb;
402   silclog.debug_context = debug_context;
403   silclog.hexdump_cb = hexdump_cb;
404   silclog.hexdump_context = hexdump_context;
405 #endif /* !SILC_SYMBIAN */
406 }
407
408 /* Resets debug callbacks */
409
410 void silc_log_reset_debug_callbacks()
411 {
412 #ifndef SILC_SYMBIAN
413   silclog.debug_cb = NULL;
414   silclog.debug_context = NULL;
415   silclog.hexdump_cb = NULL;
416   silclog.hexdump_context = NULL;
417 #endif /* !SILC_SYMBIAN */
418 }
419
420 /* Set current debug string */
421
422 void silc_log_set_debug_string(const char *debug_string)
423 {
424 #ifndef SILC_SYMBIAN
425   char *string;
426   int len;
427   if ((strchr(debug_string, '(') && strchr(debug_string, ')')) ||
428       strchr(debug_string, '$'))
429     string = silc_strdup(debug_string);
430   else
431     string = silc_string_regexify(debug_string);
432   len = strlen(string);
433   if (len >= sizeof(silclog.debug_string))
434     len = sizeof(silclog.debug_string) - 1;
435   memset(silclog.debug_string, 0, sizeof(silclog.debug_string));
436   strncpy(silclog.debug_string, string, len);
437   silc_free(string);
438 #endif /* !SILC_SYMBIAN */
439 }
440
441 /* Set timestamp */
442
443 void silc_log_timestamp(SilcBool enable)
444 {
445 #ifndef SILC_SYMBIAN
446   silclog.timestamp = enable;
447 #endif /* !SILC_SYMBIAN */
448 }
449
450 /* Set flushdelay */
451
452 void silc_log_flushdelay(SilcUInt32 flushdelay)
453 {
454 #ifndef SILC_SYMBIAN
455   silclog.flushdelay = flushdelay;
456 #endif /* !SILC_SYMBIAN */
457 }
458
459 /* Set quick logging */
460
461 void silc_log_quick(SilcBool enable)
462 {
463 #ifndef SILC_SYMBIAN
464   silclog.quick = enable;
465 #endif /* !SILC_SYMBIAN */
466 }
467
468 /* Set debugging */
469
470 void silc_log_debug(SilcBool enable)
471 {
472 #ifndef SILC_SYMBIAN
473   silclog.debug = enable;
474 #endif /* !SILC_SYMBIAN */
475 }
476
477 /* Set debug hexdump */
478
479 void silc_log_debug_hexdump(SilcBool enable)
480 {
481 #ifndef SILC_SYMBIAN
482   silclog.debug_hexdump = enable;
483 #endif /* !SILC_SYMBIAN */
484 }
485
486 /* Outputs the debug message to stderr. */
487
488 void silc_log_output_debug(char *file, const char *function,
489                            int line, char *string)
490 {
491   SilcTimeStruct curtime;
492
493 #ifndef SILC_SYMBIAN
494   if (!silclog.debug)
495     goto end;
496
497   if (!silc_string_regex_match(silclog.debug_string, file) &&
498       !silc_string_regex_match(silclog.debug_string, function))
499     goto end;
500
501   if (silclog.debug_cb) {
502     if ((*silclog.debug_cb)(file, (char *)function, line, string,
503                             silclog.debug_context))
504       goto end;
505   }
506 #endif /* !SILC_SYMBIAN */
507
508   silc_time_value(0, &curtime);
509
510 #ifdef SILC_WIN32
511   if (strrchr(function, '\\'))
512     fprintf(stderr, "%s:%d: %s\n", strrchr(function, '\\') + 1, line, string);
513   else
514 #endif /* SILC_WIN32 */
515 #ifdef SILC_SYMBIAN
516   silc_symbian_debug(function, line, string);
517 #else
518   fprintf(stderr, "%02d:%02d:%02d.%03d %s:%d: %s\n", curtime.hour,
519           curtime.minute, curtime.second, curtime.msecond, function, line,
520           string);
521   fflush(stderr);
522 #endif /* SILC_SYMBIAN */
523
524  end:
525   silc_free(string);
526 }
527
528 /* Hexdumps a message */
529
530 void silc_log_output_hexdump(char *file, const char *function,
531                              int line, void *data_in,
532                              SilcUInt32 len, char *string)
533 {
534 #ifndef SILC_SYMBIAN
535   if (!silclog.debug_hexdump)
536     goto end;
537
538   if (!silc_string_regex_match(silclog.debug_string, file) &&
539       !silc_string_regex_match(silclog.debug_string, function))
540     goto end;
541
542   if (silclog.hexdump_cb) {
543     if ((*silclog.hexdump_cb)(file, (char *)function, line,
544                               data_in, len, string, silclog.hexdump_context))
545       goto end;
546   }
547 #endif /* !SILC_SYMBIAN */
548
549   fprintf(stderr, "%s:%d: %s\n", function, line, string);
550
551   silc_hexdump(data_in, len, stderr);
552
553  end:
554   silc_free(string);
555 }