app: Introduce some routines to help with application startup
[osmocom-bb.git] / src / logging.c
1 /* Debugging/Logging support code */
2
3 /* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
4  * (C) 2008 by Holger Hans Peter Freyther <zecke@selfish.org>
5  * All Rights Reserved
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  */
22
23 #include "../config.h"
24
25 #include <stdarg.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <ctype.h>
30
31 #ifdef HAVE_STRINGS_H
32 #include <strings.h>
33 #endif
34 #include <time.h>
35 #include <errno.h>
36
37 #include <osmocom/core/talloc.h>
38 #include <osmocom/core/utils.h>
39 #include <osmocom/core/logging.h>
40
41 #include <osmocom/vty/logging.h>        /* for LOGGING_STR. */
42
43 const struct log_info *osmo_log_info;
44
45 static struct log_context log_context;
46 static void *tall_log_ctx = NULL;
47 LLIST_HEAD(osmo_log_target_list);
48
49 #define LOGLEVEL_DEFS   6       /* Number of loglevels.*/
50
51 static const struct value_string loglevel_strs[LOGLEVEL_DEFS+1] = {
52         { 0,            "EVERYTHING" },
53         { LOGL_DEBUG,   "DEBUG" },
54         { LOGL_INFO,    "INFO" },
55         { LOGL_NOTICE,  "NOTICE" },
56         { LOGL_ERROR,   "ERROR" },
57         { LOGL_FATAL,   "FATAL" },
58         { 0, NULL },
59 };
60
61 /* You have to keep this in sync with the structure loglevel_strs. */
62 const char *loglevel_descriptions[LOGLEVEL_DEFS+1] = {
63         "Log simply everything",
64         "Log debug messages and higher levels",
65         "Log informational messages and higher levels",
66         "Log noticable messages and higher levels",
67         "Log error messages and higher levels",
68         "Log only fatal messages",
69         NULL,
70 };
71
72 int log_parse_level(const char *lvl)
73 {
74         return get_string_value(loglevel_strs, lvl);
75 }
76
77 const char *log_level_str(unsigned int lvl)
78 {
79         return get_value_string(loglevel_strs, lvl);
80 }
81
82 int log_parse_category(const char *category)
83 {
84         int i;
85
86         for (i = 0; i < osmo_log_info->num_cat; ++i) {
87                 if (!strcasecmp(osmo_log_info->cat[i].name+1, category))
88                         return i;
89         }
90
91         return -EINVAL;
92 }
93
94 /*
95  * Parse the category mask.
96  * The format can be this: category1:category2:category3
97  * or category1,2:category2,3:...
98  */
99 void log_parse_category_mask(struct log_target* target, const char *_mask)
100 {
101         int i = 0;
102         char *mask = strdup(_mask);
103         char *category_token = NULL;
104
105         /* Disable everything to enable it afterwards */
106         for (i = 0; i < ARRAY_SIZE(target->categories); ++i)
107                 target->categories[i].enabled = 0;
108
109         category_token = strtok(mask, ":");
110         do {
111                 for (i = 0; i < osmo_log_info->num_cat; ++i) {
112                         char* colon = strstr(category_token, ",");
113                         int length = strlen(category_token);
114
115                         if (colon)
116                             length = colon - category_token;
117
118                         if (strncasecmp(osmo_log_info->cat[i].name,
119                                         category_token, length) == 0) {
120                                 int level = 0;
121
122                                 if (colon)
123                                         level = atoi(colon+1);
124
125                                 target->categories[i].enabled = 1;
126                                 target->categories[i].loglevel = level;
127                         }
128                 }
129         } while ((category_token = strtok(NULL, ":")));
130
131         free(mask);
132 }
133
134 static const char* color(int subsys)
135 {
136         if (subsys < osmo_log_info->num_cat)
137                 return osmo_log_info->cat[subsys].color;
138
139         return NULL;
140 }
141
142 static void _output(struct log_target *target, unsigned int subsys,
143                     unsigned int level, char *file, int line, int cont,
144                     const char *format, va_list ap)
145 {
146         char buf[4096];
147         int ret, len = 0, offset = 0, rem = sizeof(buf);
148
149         /* are we using color */
150         if (target->use_color) {
151                 const char *c = color(subsys);
152                 if (c) {
153                         ret = snprintf(buf + offset, rem, "%s", color(subsys));
154                         if (ret < 0)
155                                 goto err;
156                         OSMO_SNPRINTF_RET(ret, rem, offset, len);
157                 }
158         }
159         if (!cont) {
160                 if (target->print_timestamp) {
161                         char *timestr;
162                         time_t tm;
163                         tm = time(NULL);
164                         timestr = ctime(&tm);
165                         timestr[strlen(timestr)-1] = '\0';
166                         ret = snprintf(buf + offset, rem, "%s ", timestr);
167                         if (ret < 0)
168                                 goto err;
169                         OSMO_SNPRINTF_RET(ret, rem, offset, len);
170                 }
171                 ret = snprintf(buf + offset, rem, "<%4.4x> %s:%d ",
172                                 subsys, file, line);
173                 if (ret < 0)
174                         goto err;
175                 OSMO_SNPRINTF_RET(ret, rem, offset, len);
176         }
177         ret = vsnprintf(buf + offset, rem, format, ap);
178         if (ret < 0)
179                 goto err;
180         OSMO_SNPRINTF_RET(ret, rem, offset, len);
181
182         ret = snprintf(buf + offset, rem, "%s",
183                         target->use_color ? "\033[0;m" : "");
184         if (ret < 0)
185                 goto err;
186         OSMO_SNPRINTF_RET(ret, rem, offset, len);
187 err:
188         buf[sizeof(buf)-1] = '\0';
189         target->output(target, level, buf);
190 }
191
192
193 static void _logp(unsigned int subsys, int level, char *file, int line,
194                   int cont, const char *format, va_list ap)
195 {
196         struct log_target *tar;
197
198         llist_for_each_entry(tar, &osmo_log_target_list, entry) {
199                 struct log_category *category;
200                 int output = 0;
201
202                 category = &tar->categories[subsys];
203                 /* subsystem is not supposed to be logged */
204                 if (!category->enabled)
205                         continue;
206
207                 /* Check the global log level */
208                 if (tar->loglevel != 0 && level < tar->loglevel)
209                         continue;
210
211                 /* Check the category log level */
212                 if (tar->loglevel == 0 && category->loglevel != 0 &&
213                     level < category->loglevel)
214                         continue;
215
216                 /* Apply filters here... if that becomes messy we will
217                  * need to put filters in a list and each filter will
218                  * say stop, continue, output */
219                 if ((tar->filter_map & LOG_FILTER_ALL) != 0)
220                         output = 1;
221                 else if (osmo_log_info->filter_fn)
222                         output = osmo_log_info->filter_fn(&log_context,
223                                                        tar);
224                 if (!output)
225                         continue;
226
227                 _output(tar, subsys, level, file, line, cont, format, ap);
228         }
229 }
230
231 void logp(unsigned int subsys, char *file, int line, int cont,
232           const char *format, ...)
233 {
234         va_list ap;
235
236         va_start(ap, format);
237         _logp(subsys, LOGL_DEBUG, file, line, cont, format, ap);
238         va_end(ap);
239 }
240
241 void logp2(unsigned int subsys, unsigned int level, char *file, int line, int cont, const char *format, ...)
242 {
243         va_list ap;
244
245         va_start(ap, format);
246         _logp(subsys, level, file, line, cont, format, ap);
247         va_end(ap);
248 }
249
250 void log_add_target(struct log_target *target)
251 {
252         llist_add_tail(&target->entry, &osmo_log_target_list);
253 }
254
255 void log_del_target(struct log_target *target)
256 {
257         llist_del(&target->entry);
258 }
259
260 void log_reset_context(void)
261 {
262         memset(&log_context, 0, sizeof(log_context));
263 }
264
265 int log_set_context(uint8_t ctx_nr, void *value)
266 {
267         if (ctx_nr > LOG_MAX_CTX)
268                 return -EINVAL;
269
270         log_context.ctx[ctx_nr] = value;
271
272         return 0;
273 }
274
275 void log_set_all_filter(struct log_target *target, int all)
276 {
277         if (all)
278                 target->filter_map |= LOG_FILTER_ALL;
279         else
280                 target->filter_map &= ~LOG_FILTER_ALL;
281 }
282
283 void log_set_use_color(struct log_target *target, int use_color)
284 {
285         target->use_color = use_color;
286 }
287
288 void log_set_print_timestamp(struct log_target *target, int print_timestamp)
289 {
290         target->print_timestamp = print_timestamp;
291 }
292
293 void log_set_log_level(struct log_target *target, int log_level)
294 {
295         target->loglevel = log_level;
296 }
297
298 void log_set_category_filter(struct log_target *target, int category,
299                                int enable, int level)
300 {
301         if (category >= osmo_log_info->num_cat)
302                 return;
303         target->categories[category].enabled = !!enable;
304         target->categories[category].loglevel = level;
305 }
306
307 static void _file_output(struct log_target *target, unsigned int level,
308                          const char *log)
309 {
310         fprintf(target->tgt_file.out, "%s", log);
311         fflush(target->tgt_file.out);
312 }
313
314 struct log_target *log_target_create(void)
315 {
316         struct log_target *target;
317         unsigned int i;
318
319         target = talloc_zero(tall_log_ctx, struct log_target);
320         if (!target)
321                 return NULL;
322
323         INIT_LLIST_HEAD(&target->entry);
324
325         /* initialize the per-category enabled/loglevel from defaults */
326         for (i = 0; i < osmo_log_info->num_cat; i++) {
327                 struct log_category *cat = &target->categories[i];
328                 cat->enabled = osmo_log_info->cat[i].enabled;
329                 cat->loglevel = osmo_log_info->cat[i].loglevel;
330         }
331
332         /* global settings */
333         target->use_color = 1;
334         target->print_timestamp = 0;
335
336         /* global log level */
337         target->loglevel = 0;
338         return target;
339 }
340
341 struct log_target *log_target_create_stderr(void)
342 {
343 /* since C89/C99 says stderr is a macro, we can safely do this! */
344 #ifdef stderr
345         struct log_target *target;
346
347         target = log_target_create();
348         if (!target)
349                 return NULL;
350
351         target->type = LOG_TGT_TYPE_STDERR;
352         target->tgt_file.out = stderr;
353         target->output = _file_output;
354         return target;
355 #else
356         return NULL;
357 #endif /* stderr */
358 }
359
360 struct log_target *log_target_create_file(const char *fname)
361 {
362         struct log_target *target;
363
364         target = log_target_create();
365         if (!target)
366                 return NULL;
367
368         target->type = LOG_TGT_TYPE_FILE;
369         target->tgt_file.out = fopen(fname, "a");
370         if (!target->tgt_file.out)
371                 return NULL;
372
373         target->output = _file_output;
374
375         target->tgt_file.fname = talloc_strdup(target, fname);
376
377         return target;
378 }
379
380 struct log_target *log_target_find(int type, const char *fname)
381 {
382         struct log_target *tgt;
383
384         llist_for_each_entry(tgt, &osmo_log_target_list, entry) {
385                 if (tgt->type != type)
386                         continue;
387                 if (tgt->type == LOG_TGT_TYPE_FILE) {
388                         if (!strcmp(fname, tgt->tgt_file.fname))
389                                 return tgt;
390                 } else
391                         return tgt;
392         }
393         return NULL;
394 }
395
396 void log_target_destroy(struct log_target *target)
397 {
398
399         /* just in case, to make sure we don't have any references */
400         log_del_target(target);
401
402         if (target->output == &_file_output) {
403 /* since C89/C99 says stderr is a macro, we can safely do this! */
404 #ifdef stderr
405                 /* don't close stderr */
406                 if (target->tgt_file.out != stderr)
407 #endif
408                 {
409                         fclose(target->tgt_file.out);
410                         target->tgt_file.out = NULL;
411                 }
412         }
413
414         talloc_free(target);
415 }
416
417 /* close and re-open a log file (for log file rotation) */
418 int log_target_file_reopen(struct log_target *target)
419 {
420         fclose(target->tgt_file.out);
421
422         target->tgt_file.out = fopen(target->tgt_file.fname, "a");
423         if (!target->tgt_file.out)
424                 return -errno;
425
426         /* we assume target->output already to be set */
427
428         return 0;
429 }
430
431 /* This generates the logging command string for VTY. */
432 const char *log_vty_command_string(const struct log_info *info)
433 {
434         int len = 0, offset = 0, ret, i, rem;
435         int size = strlen("logging level () ()") + 1;
436         char *str;
437
438         for (i = 0; i < info->num_cat; i++)
439                 size += strlen(info->cat[i].name) + 1;
440
441         for (i = 0; i < LOGLEVEL_DEFS; i++)
442                 size += strlen(loglevel_strs[i].str) + 1;
443
444         rem = size;
445         str = talloc_zero_size(tall_log_ctx, size);
446         if (!str)
447                 return NULL;
448
449         ret = snprintf(str + offset, rem, "logging level (all|");
450         if (ret < 0)
451                 goto err;
452         OSMO_SNPRINTF_RET(ret, rem, offset, len);
453
454         for (i = 0; i < info->num_cat; i++) {
455                 int j, name_len = strlen(info->cat[i].name)+1;
456                 char name[name_len];
457
458                 for (j = 0; j < name_len; j++)
459                         name[j] = tolower(info->cat[i].name[j]);
460
461                 name[name_len-1] = '\0';
462                 ret = snprintf(str + offset, rem, "%s|", name+1);
463                 if (ret < 0)
464                         goto err;
465                 OSMO_SNPRINTF_RET(ret, rem, offset, len);
466         }
467         offset--;       /* to remove the trailing | */
468         rem++;
469
470         ret = snprintf(str + offset, rem, ") (");
471         if (ret < 0)
472                 goto err;
473         OSMO_SNPRINTF_RET(ret, rem, offset, len);
474
475         for (i = 0; i < LOGLEVEL_DEFS; i++) {
476                 int j, loglevel_str_len = strlen(loglevel_strs[i].str)+1;
477                 char loglevel_str[loglevel_str_len];
478
479                 for (j = 0; j < loglevel_str_len; j++)
480                         loglevel_str[j] = tolower(loglevel_strs[i].str[j]);
481
482                 loglevel_str[loglevel_str_len-1] = '\0';
483                 ret = snprintf(str + offset, rem, "%s|", loglevel_str);
484                 if (ret < 0)
485                         goto err;
486                 OSMO_SNPRINTF_RET(ret, rem, offset, len);
487         }
488         offset--;       /* to remove the trailing | */
489         rem++;
490
491         ret = snprintf(str + offset, rem, ")");
492         if (ret < 0)
493                 goto err;
494         OSMO_SNPRINTF_RET(ret, rem, offset, len);
495 err:
496         str[size-1] = '\0';
497         return str;
498 }
499
500 /* This generates the logging command description for VTY. */
501 const char *log_vty_command_description(const struct log_info *info)
502 {
503         char *str;
504         int i, ret, len = 0, offset = 0, rem;
505         unsigned int size =
506                 strlen(LOGGING_STR
507                        "Set the log level for a specified category\n") + 1;
508
509         for (i = 0; i < info->num_cat; i++)
510                 size += strlen(info->cat[i].description) + 1;
511
512         for (i = 0; i < LOGLEVEL_DEFS; i++)
513                 size += strlen(loglevel_descriptions[i]) + 1;
514
515         size += strlen("Global setting for all subsystems") + 1;
516         rem = size;
517         str = talloc_zero_size(tall_log_ctx, size);
518         if (!str)
519                 return NULL;
520
521         ret = snprintf(str + offset, rem, LOGGING_STR
522                         "Set the log level for a specified category\n");
523         if (ret < 0)
524                 goto err;
525         OSMO_SNPRINTF_RET(ret, rem, offset, len);
526
527         ret = snprintf(str + offset, rem,
528                         "Global setting for all subsystems\n");
529         if (ret < 0)
530                 goto err;
531         OSMO_SNPRINTF_RET(ret, rem, offset, len);
532
533         for (i = 0; i < info->num_cat; i++) {
534                 ret = snprintf(str + offset, rem, "%s\n",
535                                 info->cat[i].description);
536                 if (ret < 0)
537                         goto err;
538                 OSMO_SNPRINTF_RET(ret, rem, offset, len);
539         }
540         for (i = 0; i < LOGLEVEL_DEFS; i++) {
541                 ret = snprintf(str + offset, rem, "%s\n",
542                                 loglevel_descriptions[i]);
543                 if (ret < 0)
544                         goto err;
545                 OSMO_SNPRINTF_RET(ret, rem, offset, len);
546         }
547 err:
548         str[size-1] = '\0';
549         return str;
550 }
551
552 void log_init(const struct log_info *cat)
553 {
554         tall_log_ctx = talloc_named_const(NULL, 1, "logging");
555         osmo_log_info = cat;
556 }