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