Revert "Build without redis on Debian squeeze, Ubuntu Lucid"
[yaz-moved-to-github.git] / src / log.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) Index Data
3  * See the file LICENSE for details.
4  */
5
6 /**
7  * \file log.c
8  * \brief Logging utility
9  */
10
11 #if HAVE_CONFIG_H
12 #include <config.h>
13 #endif
14
15 #ifdef WIN32
16 #include <windows.h>
17 #include <sys/stat.h>
18 #endif
19
20 #if HAVE_SYS_STAT_H
21 #include <sys/stat.h>
22 #endif
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <stdarg.h>
27 #include <errno.h>
28 #include <time.h>
29 #include <yaz/yaz-iconv.h>
30 #include <yaz/errno.h>
31 #include <yaz/thread_id.h>
32 #include <yaz/log.h>
33 #include <yaz/mutex.h>
34 #include <yaz/snprintf.h>
35 #include <yaz/xmalloc.h>
36 #if YAZ_POSIX_THREADS
37 #include <pthread.h>
38 #endif
39
40 static int l_level = YLOG_DEFAULT_LEVEL;
41
42 enum l_file_type { use_stderr, use_none, use_file };
43
44 struct {
45     enum l_file_type type;
46     FILE *log_file;
47     char l_prefix[512];
48     char l_prefix2[512];
49     char l_fname[512];
50 } yaz_log_info = {
51     use_stderr, 0, "", "", ""
52 };
53
54 static void (*start_hook_func)(int, const char *, void *) = NULL;
55 static void *start_hook_info;
56
57 static void (*end_hook_func)(int, const char *, void *) = NULL;
58 static void *end_hook_info;
59
60 static void (*hook_func)(int, const char *, void *) = NULL;
61 static void *hook_info;
62
63 static char l_old_default_format[] = "%H:%M:%S-%d/%m";
64 static char l_new_default_format[] = "%Y%m%d-%H%M%S";
65 #define TIMEFORMAT_LEN 50
66 #define TID_LEN        30
67 static char l_custom_format[TIMEFORMAT_LEN] = "";
68 static char *l_actual_format = l_old_default_format;
69
70 /** l_max_size tells when to rotate the log. The default value is
71     0 which means DISABLED. This is to be preffered if YAZ runs
72     as a server using logrotate etc.
73     A positive size specifies the file size in bytes when a log rotate
74     will occur. Note that in order for this to work YAZ must have
75     permissions to do so.
76  */
77 static int l_max_size = 0;
78
79 #define MAX_MASK_NAMES 35   /* 32 bits plus a few combo names */
80 static struct {
81     int mask;
82     char *name;
83 } mask_names[MAX_MASK_NAMES] =
84 {
85     { YLOG_FATAL,  "fatal"},
86     { YLOG_DEBUG,  "debug"},
87     { YLOG_WARN,   "warn" },
88     { YLOG_LOG,    "log"  },
89     { YLOG_ERRNO,  ""},
90     { YLOG_MALLOC, "malloc"},
91     { YLOG_TID,    "tid"  },
92     { YLOG_APP,    "app"   },
93     { YLOG_NOTIME, "notime" },
94     { YLOG_APP2,   "app2" },
95     { YLOG_APP3,   "app3" },
96     { YLOG_ALL,    "all"  },
97     { YLOG_FLUSH,  "flush" },
98     { YLOG_LOGLVL, "loglevel" },
99     { 0,           "none" },
100     { 0, NULL }
101     /* the rest will be filled in if the user defines dynamic modules*/
102 };
103
104 static unsigned int next_log_bit = YLOG_LAST_BIT<<1; /* first dynamic bit */
105
106 static int yaz_log_reopen_flag = 0;
107
108 static YAZ_MUTEX log_mutex = 0;
109
110 static void yaz_log_open(void);
111
112 void yaz_log_lock(void)
113 {
114     yaz_mutex_enter(log_mutex);
115 }
116
117 void yaz_log_unlock(void)
118 {
119     yaz_mutex_leave(log_mutex);
120 }
121
122 void yaz_log_init_globals(void)
123 {
124     char *env;
125
126     if (log_mutex == 0)
127         yaz_mutex_create(&log_mutex);
128 #if YAZ_POSIX_THREADS
129     pthread_atfork(yaz_log_lock, yaz_log_unlock, yaz_log_unlock);
130 #endif
131     env = getenv("YAZ_LOG");
132     if (env)
133         l_level = yaz_log_mask_str_x(env, l_level);
134 }
135
136 FILE *yaz_log_file(void)
137 {
138     FILE *f = 0;
139     switch (yaz_log_info.type)
140     {
141         case use_stderr: f = stderr; break;
142         case use_none: f = 0; break;
143         case use_file: f = yaz_log_info.log_file; break;
144     }
145     return f;
146 }
147
148 void yaz_log_close(void)
149 {
150     if (yaz_log_info.type == use_file && yaz_log_info.log_file)
151     {
152         fclose(yaz_log_info.log_file);
153         yaz_log_info.log_file = 0;
154     }
155 }
156
157 void yaz_log_init_file(const char *fname)
158 {
159     yaz_init_globals();
160
161     yaz_log_close();
162     if (fname)
163     {
164         if (*fname == '\0')
165             yaz_log_info.type = use_stderr; /* empty name; use stderr */
166         else
167             yaz_log_info.type = use_file;
168         strncpy(yaz_log_info.l_fname, fname, sizeof(yaz_log_info.l_fname)-1);
169         yaz_log_info.l_fname[sizeof(yaz_log_info.l_fname)-1] = '\0';
170     }
171     else
172     {
173         yaz_log_info.type = use_none;  /* NULL name; use no file at all */
174         yaz_log_info.l_fname[0] = '\0';
175     }
176     yaz_log_open();
177 }
178
179 static void rotate_log(const char *cur_fname)
180 {
181     int i;
182
183 #ifdef WIN32
184     /* windows can't rename a file if it is open */
185     yaz_log_close();
186 #endif
187     for (i = 0; i<9; i++)
188     {
189         char fname_str[FILENAME_MAX];
190         struct stat stat_buf;
191
192         yaz_snprintf(fname_str, sizeof(fname_str), "%s.%d", cur_fname, i);
193         if (stat(fname_str, &stat_buf) != 0)
194             break;
195     }
196     for (; i >= 0; --i)
197     {
198         char fname_str[2][FILENAME_MAX];
199
200         if (i > 0)
201             yaz_snprintf(fname_str[0], sizeof(fname_str[0]),
202                          "%s.%d", cur_fname, i-1);
203         else
204             yaz_snprintf(fname_str[0], sizeof(fname_str[0]),
205                          "%s", cur_fname);
206         yaz_snprintf(fname_str[1], sizeof(fname_str[1]),
207                      "%s.%d", cur_fname, i);
208 #ifdef WIN32
209         MoveFileEx(fname_str[0], fname_str[1], MOVEFILE_REPLACE_EXISTING);
210 #else
211         rename(fname_str[0], fname_str[1]);
212 #endif
213     }
214 }
215
216
217 void yaz_log_init_level(int level)
218 {
219     yaz_init_globals();
220     if ( (l_level & YLOG_FLUSH) != (level & YLOG_FLUSH) )
221     {
222         l_level = level;
223         yaz_log_open(); /* make sure we set buffering right */
224     }
225     else
226         l_level = level;
227
228     if (l_level  & YLOG_LOGLVL)
229     {  /* dump the log level bits */
230         const char *bittype = "Static ";
231         int i, sz;
232
233         yaz_log(YLOG_LOGLVL, "Setting log level to %d = 0x%08x",
234                 l_level, l_level);
235         /* determine size of mask_names (locked) */
236         for (sz = 0; mask_names[sz].name; sz++)
237             ;
238         /* second pass without lock */
239         for (i = 0; i < sz; i++)
240             if (mask_names[i].mask && *mask_names[i].name)
241                 if (strcmp(mask_names[i].name, "all") != 0)
242                 {
243                     yaz_log(YLOG_LOGLVL, "%s log bit %08x '%s' is %s",
244                             bittype, mask_names[i].mask, mask_names[i].name,
245                             (level & mask_names[i].mask)?  "ON": "off");
246                     if (mask_names[i].mask > YLOG_LAST_BIT)
247                         bittype = "Dynamic";
248                 }
249     }
250 }
251
252 void yaz_log_init_prefix(const char *prefix)
253 {
254     if (prefix && *prefix)
255         yaz_snprintf(yaz_log_info.l_prefix,
256                      sizeof(yaz_log_info.l_prefix), "%s ", prefix);
257     else
258         *yaz_log_info.l_prefix = 0;
259 }
260
261 void yaz_log_init_prefix2(const char *prefix)
262 {
263     if (prefix && *prefix)
264         yaz_snprintf(yaz_log_info.l_prefix2,
265                      sizeof(yaz_log_info.l_prefix2), "%s ", prefix);
266     else
267         *yaz_log_info.l_prefix2 = 0;
268 }
269
270 void yaz_log_init(int level, const char *prefix, const char *fname)
271 {
272     yaz_init_globals();
273     yaz_log_init_level(level);
274     yaz_log_init_prefix(prefix);
275     if (fname && *fname)
276         yaz_log_init_file(fname);
277 }
278
279 void yaz_log_init_max_size(int mx)
280 {
281     if (mx > 0)
282         l_max_size = mx;
283     else
284         l_max_size = 0;
285 }
286
287 void yaz_log_set_handler(void (*func)(int, const char *, void *), void *info)
288 {
289     hook_func = func;
290     hook_info = info;
291 }
292
293 void log_event_start(void (*func)(int, const char *, void *), void *info)
294 {
295     start_hook_func = func;
296     start_hook_info = info;
297 }
298
299 void log_event_end(void (*func)(int, const char *, void *), void *info)
300 {
301     end_hook_func = func;
302     end_hook_info = info;
303 }
304
305 static void yaz_log_open_check(struct tm *tm, int force, const char *filemode)
306 {
307     char new_filename[512];
308     static char cur_filename[512] = "";
309
310     if (yaz_log_info.type != use_file)
311         return;
312
313     if (yaz_log_reopen_flag)
314     {
315         force = 1;
316         yaz_log_reopen_flag = 0;
317     }
318     if (*yaz_log_info.l_fname)
319     {
320         strftime(new_filename, sizeof(new_filename)-1, yaz_log_info.l_fname,
321                  tm);
322         if (strcmp(new_filename, cur_filename))
323         {
324             strcpy(cur_filename, new_filename);
325             force = 1;
326         }
327     }
328
329     if (l_max_size > 0 && yaz_log_info.log_file)
330     {
331         long flen = ftell(yaz_log_info.log_file);
332         if (flen > l_max_size)
333         {
334             rotate_log(cur_filename);
335             force = 1;
336         }
337     }
338     if (force && *cur_filename)
339     {
340         FILE *new_file;
341 #ifdef WIN32
342         yaz_log_close();
343 #endif
344         if (!strncmp(cur_filename, "fd=", 3))
345             new_file = fdopen(atoi(cur_filename + 3), filemode);
346         else
347             new_file = fopen(cur_filename, filemode);
348         if (new_file)
349         {
350             yaz_log_close();
351             yaz_log_info.log_file = new_file;
352         }
353         else
354         {
355             /* disable log rotate */
356             l_max_size = 0;
357         }
358     }
359 }
360
361 static void yaz_log_do_reopen(const char *filemode)
362 {
363     time_t cur_time = time(0);
364 #if HAVE_LOCALTIME_R
365     struct tm tm0, *tm = &tm0;
366 #else
367     struct tm *tm;
368 #endif
369
370     yaz_log_lock();
371 #if HAVE_LOCALTIME_R
372     localtime_r(&cur_time, tm);
373 #else
374     tm = localtime(&cur_time);
375 #endif
376     yaz_log_open_check(tm, 1, filemode);
377     yaz_log_unlock();
378 }
379
380 void yaz_log_reopen()
381 {
382     yaz_log_reopen_flag = 1;
383 }
384
385 static void yaz_log_open()
386 {
387     yaz_log_do_reopen("a");
388 }
389
390 void yaz_log_trunc()
391 {
392     yaz_log_do_reopen("w");
393 }
394
395 static void yaz_strftime(char *dst, size_t sz,
396                          const char *fmt, const struct tm *tm)
397 {
398     strftime(dst, sz, fmt, tm);
399 }
400
401 static void yaz_log_to_file(int level, const char *fmt, va_list ap,
402                             const char *error_cp)
403 {
404     FILE *file;
405     time_t ti = time(0);
406 #if HAVE_LOCALTIME_R
407     struct tm tm0, *tm = &tm0;
408 #else
409     struct tm *tm;
410 #endif
411
412     yaz_log_lock();
413 #if HAVE_LOCALTIME_R
414     localtime_r(&ti, tm);
415 #else
416     tm = localtime(&ti);
417 #endif
418
419     yaz_log_open_check(tm, 0, "a");
420     file = yaz_log_file(); /* file may change in yaz_log_open_check */
421
422     if (file)
423     {
424         char tbuf[TIMEFORMAT_LEN];
425         char tid[TID_LEN];
426         char flags[1024];
427         int i;
428
429         *flags = '\0';
430         for (i = 0; level && mask_names[i].name; i++)
431             if ( mask_names[i].mask & level)
432             {
433                 if (*mask_names[i].name && mask_names[i].mask &&
434                     mask_names[i].mask != YLOG_ALL)
435                 {
436                     if (strlen(flags) + strlen(mask_names[i].name)
437                                              <   sizeof(flags) - 4)
438                     {
439                         strcat(flags, "[");
440                         strcat(flags, mask_names[i].name);
441                         strcat(flags, "]");
442                     }
443                     level &= ~mask_names[i].mask;
444                 }
445             }
446
447         tbuf[0] = '\0';
448         if (!(l_level & YLOG_NOTIME))
449         {
450             yaz_strftime(tbuf, TIMEFORMAT_LEN-2, l_actual_format, tm);
451             tbuf[TIMEFORMAT_LEN-2] = '\0';
452         }
453         if (tbuf[0])
454             strcat(tbuf, " ");
455         tid[0] = '\0';
456
457         if (l_level & YLOG_TID)
458         {
459             yaz_thread_id_cstr(tid, sizeof(tid)-1);
460             if (tid[0])
461                 strcat(tid, " ");
462         }
463
464         fprintf(file, "%s%s%s%s %s", tbuf, yaz_log_info.l_prefix,
465                 tid, flags, yaz_log_info.l_prefix2);
466         vfprintf(file, fmt, ap);
467         if (error_cp)
468             fprintf(file, " [%s]", error_cp);
469         fputs("\n", file);
470         if (l_level & YLOG_FLUSH)
471             fflush(file);
472     }
473     yaz_log_unlock();
474 }
475
476 void yaz_log(int level, const char *fmt, ...)
477 {
478     va_list ap;
479     FILE *file;
480     int o_level = level;
481     char *error_cp = 0, error_buf[128];
482
483     if (o_level & YLOG_ERRNO)
484     {
485         yaz_strerror(error_buf, sizeof(error_buf));
486         error_cp = error_buf;
487     }
488     yaz_init_globals();
489     if (!(level & l_level))
490         return;
491     va_start(ap, fmt);
492
493     file = yaz_log_file();
494     if (start_hook_func || hook_func || end_hook_func)
495     {
496         char buf[1024];
497         /* 30 is enough for our 'rest of output' message */
498         yaz_vsnprintf(buf, sizeof(buf)-30, fmt, ap);
499         if (strlen(buf) >= sizeof(buf)-31)
500             strcat(buf, " [rest of output omitted]");
501         if (start_hook_func)
502             (*start_hook_func)(o_level, buf, start_hook_info);
503         if (hook_func)
504             (*hook_func)(o_level, buf, hook_info);
505         if (file)
506             yaz_log_to_file(level, fmt, ap, error_cp);
507         if (end_hook_func)
508             (*end_hook_func)(o_level, buf, end_hook_info);
509     }
510     else
511     {
512         if (file)
513             yaz_log_to_file(level, fmt, ap, error_cp);
514     }
515     va_end(ap);
516 }
517
518 void yaz_log_time_format(const char *fmt)
519 {
520     if ( !fmt || !*fmt)
521     { /* no format, default to new */
522         l_actual_format = l_new_default_format;
523         return;
524     }
525     if (0==strcmp(fmt, "old"))
526     { /* force the old format */
527         l_actual_format = l_old_default_format;
528         return;
529     }
530     /* else use custom format */
531     strncpy(l_custom_format, fmt, TIMEFORMAT_LEN-1);
532     l_custom_format[TIMEFORMAT_LEN-1] = '\0';
533     l_actual_format = l_custom_format;
534 }
535
536 /** cleans a loglevel name from leading paths and suffixes */
537 static char *clean_name(const char *name, size_t len, char *namebuf, size_t buflen)
538 {
539     char *p = namebuf;
540     char *start = namebuf;
541     if (buflen <= len)
542         len = buflen-1;
543     strncpy(namebuf, name, len);
544     namebuf[len] = '\0';
545     while ((p = strchr(start, '/')))
546         start = p+1;
547     if ((p = strrchr(start, '.')))
548         *p = '\0';
549     return start;
550 }
551
552 static int define_module_bit(const char *name)
553 {
554     size_t i;
555
556     for (i = 0; mask_names[i].name; i++)
557         if (0 == strcmp(mask_names[i].name, name))
558         {
559             return mask_names[i].mask;
560         }
561     if ( (i>=MAX_MASK_NAMES) || (next_log_bit & (1U<<31) ))
562     {
563         yaz_log(YLOG_WARN, "No more log bits left, not logging '%s'", name);
564         return 0;
565     }
566     mask_names[i].mask = (int) next_log_bit; /* next_log_bit can hold int */
567     next_log_bit = next_log_bit<<1;
568     mask_names[i].name = (char *) malloc(strlen(name)+1);
569     strcpy(mask_names[i].name, name);
570     mask_names[i+1].name = NULL;
571     mask_names[i+1].mask = 0;
572     return mask_names[i].mask;
573 }
574
575 int yaz_log_module_level(const char *name)
576 {
577     int i;
578     char clean[255];
579     char *n = clean_name(name, strlen(name), clean, sizeof(clean));
580     yaz_init_globals();
581
582     for (i = 0; mask_names[i].name; i++)
583         if (0==strcmp(n, mask_names[i].name))
584         {
585             yaz_log(YLOG_LOGLVL, "returning log bit 0x%x for '%s' %s",
586                     mask_names[i].mask, n,
587                     strcmp(n,name) ? name : "");
588             return mask_names[i].mask;
589         }
590     yaz_log(YLOG_LOGLVL, "returning NO log bit for '%s' %s", n,
591             strcmp(n, name) ? name : "" );
592     return 0;
593 }
594
595 int yaz_log_mask_str(const char *str)
596 {
597     yaz_init_globals(); /* since l_level may be affected */
598     return yaz_log_mask_str_x(str, l_level);
599 }
600
601 /* this function is called by yaz_log_init_globals & yaz_init_globals
602    and, thus, may not call any of them indirectly */
603 int yaz_log_mask_str_x(const char *str, int level)
604 {
605     const char *p;
606
607     while (*str)
608     {
609         int negated = 0;
610         for (p = str; *p && *p != ','; p++)
611             ;
612         if (*str=='-')
613         {
614             negated = 1;
615             str++;
616         }
617         if (yaz_isdigit(*str))
618         {
619             level = atoi(str);
620         }
621         else
622         {
623             char clean[509];
624             char *n = clean_name(str, (size_t) (p - str), clean, sizeof(clean));
625             int mask = define_module_bit(n);
626             if (!mask)
627                 level = 0;  /* 'none' clears them all */
628             else if (negated)
629                 level &= ~mask;
630             else
631                 level |= mask;
632         }
633         if (*p == ',')
634             p++;
635         str = p;
636     }
637     return level;
638 }
639 /*
640  * Local variables:
641  * c-basic-offset: 4
642  * c-file-style: "Stroustrup"
643  * indent-tabs-mode: nil
644  * End:
645  * vim: shiftwidth=4 tabstop=8 expandtab
646  */
647