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