No more manifest files
[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_deinit_globals(void)
158 {
159     if (log_mutex)
160     {
161         yaz_mutex_destroy(&log_mutex);
162         yaz_log_close();
163     }
164 }
165
166 void yaz_log_init_file(const char *fname)
167 {
168     yaz_init_globals();
169
170     yaz_log_close();
171     if (fname)
172     {
173         if (*fname == '\0')
174             yaz_log_info.type = use_stderr; /* empty name; use stderr */
175         else
176             yaz_log_info.type = use_file;
177         strncpy(yaz_log_info.l_fname, fname, sizeof(yaz_log_info.l_fname)-1);
178         yaz_log_info.l_fname[sizeof(yaz_log_info.l_fname)-1] = '\0';
179     }
180     else
181     {
182         yaz_log_info.type = use_none;  /* NULL name; use no file at all */
183         yaz_log_info.l_fname[0] = '\0';
184     }
185     yaz_log_open();
186 }
187
188 static void rotate_log(const char *cur_fname)
189 {
190     int i;
191
192 #ifdef WIN32
193     /* windows can't rename a file if it is open */
194     yaz_log_close();
195 #endif
196     for (i = 0; i<9; i++)
197     {
198         char fname_str[FILENAME_MAX];
199         struct stat stat_buf;
200
201         yaz_snprintf(fname_str, sizeof(fname_str), "%s.%d", cur_fname, i);
202         if (stat(fname_str, &stat_buf) != 0)
203             break;
204     }
205     for (; i >= 0; --i)
206     {
207         char fname_str[2][FILENAME_MAX];
208
209         if (i > 0)
210             yaz_snprintf(fname_str[0], sizeof(fname_str[0]),
211                          "%s.%d", cur_fname, i-1);
212         else
213             yaz_snprintf(fname_str[0], sizeof(fname_str[0]),
214                          "%s", cur_fname);
215         yaz_snprintf(fname_str[1], sizeof(fname_str[1]),
216                      "%s.%d", cur_fname, i);
217 #ifdef WIN32
218         MoveFileEx(fname_str[0], fname_str[1], MOVEFILE_REPLACE_EXISTING);
219 #else
220         rename(fname_str[0], fname_str[1]);
221 #endif
222     }
223 }
224
225
226 void yaz_log_init_level(int level)
227 {
228     yaz_init_globals();
229     if ( (l_level & YLOG_FLUSH) != (level & YLOG_FLUSH) )
230     {
231         l_level = level;
232         yaz_log_open(); /* make sure we set buffering right */
233     }
234     else
235         l_level = level;
236
237     if (l_level  & YLOG_LOGLVL)
238     {  /* dump the log level bits */
239         const char *bittype = "Static ";
240         int i, sz;
241
242         yaz_log(YLOG_LOGLVL, "Setting log level to %d = 0x%08x",
243                 l_level, l_level);
244         /* determine size of mask_names (locked) */
245         for (sz = 0; mask_names[sz].name; sz++)
246             ;
247         /* second pass without lock */
248         for (i = 0; i < sz; i++)
249             if (mask_names[i].mask && *mask_names[i].name)
250                 if (strcmp(mask_names[i].name, "all") != 0)
251                 {
252                     yaz_log(YLOG_LOGLVL, "%s log bit %08x '%s' is %s",
253                             bittype, mask_names[i].mask, mask_names[i].name,
254                             (level & mask_names[i].mask)?  "ON": "off");
255                     if (mask_names[i].mask > YLOG_LAST_BIT)
256                         bittype = "Dynamic";
257                 }
258     }
259 }
260
261 void yaz_log_init_prefix(const char *prefix)
262 {
263     if (prefix && *prefix)
264         yaz_snprintf(yaz_log_info.l_prefix,
265                      sizeof(yaz_log_info.l_prefix), "%s ", prefix);
266     else
267         *yaz_log_info.l_prefix = 0;
268 }
269
270 void yaz_log_init_prefix2(const char *prefix)
271 {
272     if (prefix && *prefix)
273         yaz_snprintf(yaz_log_info.l_prefix2,
274                      sizeof(yaz_log_info.l_prefix2), "%s ", prefix);
275     else
276         *yaz_log_info.l_prefix2 = 0;
277 }
278
279 void yaz_log_init(int level, const char *prefix, const char *fname)
280 {
281     yaz_init_globals();
282     yaz_log_init_level(level);
283     yaz_log_init_prefix(prefix);
284     if (fname && *fname)
285         yaz_log_init_file(fname);
286 }
287
288 void yaz_log_init_max_size(int mx)
289 {
290     if (mx > 0)
291         l_max_size = mx;
292     else
293         l_max_size = 0;
294 }
295
296 void yaz_log_set_handler(void (*func)(int, const char *, void *), void *info)
297 {
298     hook_func = func;
299     hook_info = info;
300 }
301
302 void log_event_start(void (*func)(int, const char *, void *), void *info)
303 {
304     start_hook_func = func;
305     start_hook_info = info;
306 }
307
308 void log_event_end(void (*func)(int, const char *, void *), void *info)
309 {
310     end_hook_func = func;
311     end_hook_info = info;
312 }
313
314 static void yaz_log_open_check(struct tm *tm, int force, const char *filemode)
315 {
316     char new_filename[512];
317     static char cur_filename[512] = "";
318
319     if (yaz_log_info.type != use_file)
320         return;
321
322     if (yaz_log_reopen_flag)
323     {
324         force = 1;
325         yaz_log_reopen_flag = 0;
326     }
327     if (*yaz_log_info.l_fname)
328     {
329         strftime(new_filename, sizeof(new_filename)-1, yaz_log_info.l_fname,
330                  tm);
331         if (strcmp(new_filename, cur_filename))
332         {
333             strcpy(cur_filename, new_filename);
334             force = 1;
335         }
336     }
337
338     if (l_max_size > 0 && yaz_log_info.log_file)
339     {
340         long flen = ftell(yaz_log_info.log_file);
341         if (flen > l_max_size)
342         {
343             rotate_log(cur_filename);
344             force = 1;
345         }
346     }
347     if (force && *cur_filename)
348     {
349         FILE *new_file;
350 #ifdef WIN32
351         yaz_log_close();
352 #endif
353         if (!strncmp(cur_filename, "fd=", 3))
354             new_file = fdopen(atoi(cur_filename + 3), filemode);
355         else
356             new_file = fopen(cur_filename, filemode);
357         if (new_file)
358         {
359             yaz_log_close();
360             yaz_log_info.log_file = new_file;
361         }
362         else
363         {
364             /* disable log rotate */
365             l_max_size = 0;
366         }
367     }
368 }
369
370 static void yaz_log_do_reopen(const char *filemode)
371 {
372     time_t cur_time = time(0);
373 #if HAVE_LOCALTIME_R
374     struct tm tm0, *tm = &tm0;
375 #else
376     struct tm *tm;
377 #endif
378
379     yaz_log_lock();
380 #if HAVE_LOCALTIME_R
381     localtime_r(&cur_time, tm);
382 #else
383     tm = localtime(&cur_time);
384 #endif
385     yaz_log_open_check(tm, 1, filemode);
386     yaz_log_unlock();
387 }
388
389 void yaz_log_reopen()
390 {
391     yaz_log_reopen_flag = 1;
392 }
393
394 static void yaz_log_open()
395 {
396     yaz_log_do_reopen("a");
397 }
398
399 void yaz_log_trunc()
400 {
401     yaz_log_do_reopen("w");
402 }
403
404 static void yaz_strftime(char *dst, size_t sz,
405                          const char *fmt, const struct tm *tm)
406 {
407     strftime(dst, sz, fmt, tm);
408 }
409
410 static void yaz_log_to_file(int level, const char *fmt, va_list ap,
411                             const char *error_cp)
412 {
413     FILE *file;
414     time_t ti = time(0);
415 #if HAVE_LOCALTIME_R
416     struct tm tm0, *tm = &tm0;
417 #else
418     struct tm *tm;
419 #endif
420
421     yaz_log_lock();
422 #if HAVE_LOCALTIME_R
423     localtime_r(&ti, tm);
424 #else
425     tm = localtime(&ti);
426 #endif
427
428     yaz_log_open_check(tm, 0, "a");
429     file = yaz_log_file(); /* file may change in yaz_log_open_check */
430
431     if (file)
432     {
433         char tbuf[TIMEFORMAT_LEN];
434         char tid[TID_LEN];
435         char flags[1024];
436         int i;
437
438         *flags = '\0';
439         for (i = 0; level && mask_names[i].name; i++)
440             if ( mask_names[i].mask & level)
441             {
442                 if (*mask_names[i].name && mask_names[i].mask &&
443                     mask_names[i].mask != YLOG_ALL)
444                 {
445                     if (strlen(flags) + strlen(mask_names[i].name)
446                                              <   sizeof(flags) - 4)
447                     {
448                         strcat(flags, "[");
449                         strcat(flags, mask_names[i].name);
450                         strcat(flags, "]");
451                     }
452                     level &= ~mask_names[i].mask;
453                 }
454             }
455
456         tbuf[0] = '\0';
457         if (!(l_level & YLOG_NOTIME))
458         {
459             yaz_strftime(tbuf, TIMEFORMAT_LEN-2, l_actual_format, tm);
460             tbuf[TIMEFORMAT_LEN-2] = '\0';
461         }
462         if (tbuf[0])
463             strcat(tbuf, " ");
464         tid[0] = '\0';
465
466         if (l_level & YLOG_TID)
467         {
468             yaz_thread_id_cstr(tid, sizeof(tid)-1);
469             if (tid[0])
470                 strcat(tid, " ");
471         }
472
473         fprintf(file, "%s%s%s%s %s", tbuf, yaz_log_info.l_prefix,
474                 tid, flags, yaz_log_info.l_prefix2);
475         vfprintf(file, fmt, ap);
476         if (error_cp)
477             fprintf(file, " [%s]", error_cp);
478         fputs("\n", file);
479         if (l_level & YLOG_FLUSH)
480             fflush(file);
481     }
482     yaz_log_unlock();
483 }
484
485 void yaz_log(int level, const char *fmt, ...)
486 {
487     va_list ap;
488     FILE *file;
489     int o_level = level;
490     char *error_cp = 0, error_buf[128];
491
492     if (o_level & YLOG_ERRNO)
493     {
494         yaz_strerror(error_buf, sizeof(error_buf));
495         error_cp = error_buf;
496     }
497     yaz_init_globals();
498     if (!(level & l_level))
499         return;
500     va_start(ap, fmt);
501
502     file = yaz_log_file();
503     if (start_hook_func || hook_func || end_hook_func)
504     {
505         char buf[1024];
506         /* 30 is enough for our 'rest of output' message */
507         yaz_vsnprintf(buf, sizeof(buf)-30, fmt, ap);
508         if (strlen(buf) >= sizeof(buf)-31)
509             strcat(buf, " [rest of output omitted]");
510         if (start_hook_func)
511             (*start_hook_func)(o_level, buf, start_hook_info);
512         if (hook_func)
513             (*hook_func)(o_level, buf, hook_info);
514         if (file)
515             yaz_log_to_file(level, fmt, ap, error_cp);
516         if (end_hook_func)
517             (*end_hook_func)(o_level, buf, end_hook_info);
518     }
519     else
520     {
521         if (file)
522             yaz_log_to_file(level, fmt, ap, error_cp);
523     }
524     va_end(ap);
525 }
526
527 void yaz_log_time_format(const char *fmt)
528 {
529     if ( !fmt || !*fmt)
530     { /* no format, default to new */
531         l_actual_format = l_new_default_format;
532         return;
533     }
534     if (0==strcmp(fmt, "old"))
535     { /* force the old format */
536         l_actual_format = l_old_default_format;
537         return;
538     }
539     /* else use custom format */
540     strncpy(l_custom_format, fmt, TIMEFORMAT_LEN-1);
541     l_custom_format[TIMEFORMAT_LEN-1] = '\0';
542     l_actual_format = l_custom_format;
543 }
544
545 /** cleans a loglevel name from leading paths and suffixes */
546 static char *clean_name(const char *name, size_t len, char *namebuf, size_t buflen)
547 {
548     char *p = namebuf;
549     char *start = namebuf;
550     if (buflen <= len)
551         len = buflen-1;
552     strncpy(namebuf, name, len);
553     namebuf[len] = '\0';
554     while ((p = strchr(start, '/')))
555         start = p+1;
556     if ((p = strrchr(start, '.')))
557         *p = '\0';
558     return start;
559 }
560
561 static int define_module_bit(const char *name)
562 {
563     size_t i;
564
565     for (i = 0; mask_names[i].name; i++)
566         if (0 == strcmp(mask_names[i].name, name))
567         {
568             return mask_names[i].mask;
569         }
570     if ( (i>=MAX_MASK_NAMES) || (next_log_bit & (1U<<31) ))
571     {
572         yaz_log(YLOG_WARN, "No more log bits left, not logging '%s'", name);
573         return 0;
574     }
575     mask_names[i].mask = (int) next_log_bit; /* next_log_bit can hold int */
576     next_log_bit = next_log_bit<<1;
577     mask_names[i].name = (char *) malloc(strlen(name)+1);
578     strcpy(mask_names[i].name, name);
579     mask_names[i+1].name = NULL;
580     mask_names[i+1].mask = 0;
581     return mask_names[i].mask;
582 }
583
584 int yaz_log_module_level(const char *name)
585 {
586     int i;
587     char clean[255];
588     char *n = clean_name(name, strlen(name), clean, sizeof(clean));
589     yaz_init_globals();
590
591     for (i = 0; mask_names[i].name; i++)
592         if (0==strcmp(n, mask_names[i].name))
593         {
594             yaz_log(YLOG_LOGLVL, "returning log bit 0x%x for '%s' %s",
595                     mask_names[i].mask, n,
596                     strcmp(n,name) ? name : "");
597             return mask_names[i].mask;
598         }
599     yaz_log(YLOG_LOGLVL, "returning NO log bit for '%s' %s", n,
600             strcmp(n, name) ? name : "" );
601     return 0;
602 }
603
604 int yaz_log_mask_str(const char *str)
605 {
606     yaz_init_globals(); /* since l_level may be affected */
607     return yaz_log_mask_str_x(str, l_level);
608 }
609
610 /* this function is called by yaz_log_init_globals & yaz_init_globals
611    and, thus, may not call any of them indirectly */
612 int yaz_log_mask_str_x(const char *str, int level)
613 {
614     const char *p;
615
616     while (*str)
617     {
618         int negated = 0;
619         for (p = str; *p && *p != ','; p++)
620             ;
621         if (*str=='-')
622         {
623             negated = 1;
624             str++;
625         }
626         if (yaz_isdigit(*str))
627         {
628             level = atoi(str);
629         }
630         else
631         {
632             char clean[509];
633             char *n = clean_name(str, (size_t) (p - str), clean, sizeof(clean));
634             int mask = define_module_bit(n);
635             if (!mask)
636                 level = 0;  /* 'none' clears them all */
637             else if (negated)
638                 level &= ~mask;
639             else
640                 level |= mask;
641         }
642         if (*p == ',')
643             p++;
644         str = p;
645     }
646     return level;
647 }
648 /*
649  * Local variables:
650  * c-basic-offset: 4
651  * c-file-style: "Stroustrup"
652  * indent-tabs-mode: nil
653  * End:
654  * vim: shiftwidth=4 tabstop=8 expandtab
655  */
656