Merge commit '01fd5cb3f0da802ca82d3b6aef46d2a3d8e6c15c'
[osmocom-bb.git] / src / shared / libosmocore / 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
30 #ifdef HAVE_STRINGS_H
31 #include <strings.h>
32 #endif
33 #include <time.h>
34 #include <errno.h>
35
36 #include <osmocore/talloc.h>
37 #include <osmocore/utils.h>
38 #include <osmocore/logging.h>
39
40 static const struct log_info *log_info;
41
42 static struct log_context log_context;
43 static void *tall_log_ctx = NULL;
44 static LLIST_HEAD(target_list);
45
46 static const struct value_string loglevel_strs[] = {
47         { 0,            "EVERYTHING" },
48         { LOGL_DEBUG,   "DEBUG" },
49         { LOGL_INFO,    "INFO" },
50         { LOGL_NOTICE,  "NOTICE" },
51         { LOGL_ERROR,   "ERROR" },
52         { LOGL_FATAL,   "FATAL" },
53         { 0, NULL },
54 };
55
56 int log_parse_level(const char *lvl)
57 {
58         return get_string_value(loglevel_strs, lvl);
59 }
60
61 int log_parse_category(const char *category)
62 {
63         int i;
64
65         for (i = 0; i < log_info->num_cat; ++i) {
66                 if (!strcasecmp(log_info->cat[i].name+1, category))
67                         return i;
68         }
69
70         return -EINVAL;
71 }
72
73 /*
74  * Parse the category mask.
75  * The format can be this: category1:category2:category3
76  * or category1,2:category2,3:...
77  */
78 void log_parse_category_mask(struct log_target* target, const char *_mask)
79 {
80         int i = 0;
81         char *mask = strdup(_mask);
82         char *category_token = NULL;
83
84         /* Disable everything to enable it afterwards */
85         for (i = 0; i < ARRAY_SIZE(target->categories); ++i)
86                 target->categories[i].enabled = 0;
87
88         category_token = strtok(mask, ":");
89         do {
90                 for (i = 0; i < log_info->num_cat; ++i) {
91                         char* colon = strstr(category_token, ",");
92                         int length = strlen(category_token);
93
94                         if (colon)
95                             length = colon - category_token;
96
97                         if (strncasecmp(log_info->cat[i].name, category_token,
98                                         length) == 0) {
99                                 int level = 0;
100
101                                 if (colon)
102                                         level = atoi(colon+1);
103
104                                 target->categories[i].enabled = 1;
105                                 target->categories[i].loglevel = level;
106                         }
107                 }
108         } while ((category_token = strtok(NULL, ":")));
109
110         free(mask);
111 }
112
113 static const char* color(int subsys)
114 {
115         if (subsys < log_info->num_cat)
116                 return log_info->cat[subsys].color;
117
118         return NULL;
119 }
120
121 static void _output(struct log_target *target, unsigned int subsys,
122                     char *file, int line, int cont, const char *format,
123                     va_list ap)
124 {
125         char col[30];
126         char sub[30];
127         char tim[30];
128         char buf[4096];
129         char final[4096];
130
131         /* prepare the data */
132         col[0] = '\0';
133         sub[0] = '\0';
134         tim[0] = '\0';
135         buf[0] = '\0';
136
137         /* are we using color */
138         if (target->use_color) {
139                 const char *c = color(subsys);
140                 if (c) {
141                         snprintf(col, sizeof(col), "%s", color(subsys));
142                         col[sizeof(col)-1] = '\0';
143                 }
144         }
145         vsnprintf(buf, sizeof(buf), format, ap);
146         buf[sizeof(buf)-1] = '\0';
147
148         if (!cont) {
149                 if (target->print_timestamp) {
150                         char *timestr;
151                         time_t tm;
152                         tm = time(NULL);
153                         timestr = ctime(&tm);
154                         timestr[strlen(timestr)-1] = '\0';
155                         snprintf(tim, sizeof(tim), "%s ", timestr);
156                         tim[sizeof(tim)-1] = '\0';
157                 }
158                 snprintf(sub, sizeof(sub), "<%4.4x> %s:%d ", subsys, file, line);
159                 sub[sizeof(sub)-1] = '\0';
160         }
161
162         snprintf(final, sizeof(final), "%s%s%s%s\033[0;m", col, tim, sub, buf);
163         final[sizeof(final)-1] = '\0';
164         target->output(target, final);
165 }
166
167
168 static void _logp(unsigned int subsys, int level, char *file, int line,
169                   int cont, const char *format, va_list ap)
170 {
171         struct log_target *tar;
172
173         llist_for_each_entry(tar, &target_list, entry) {
174                 struct log_category *category;
175                 int output = 0;
176
177                 category = &tar->categories[subsys];
178                 /* subsystem is not supposed to be logged */
179                 if (!category->enabled)
180                         continue;
181
182                 /* Check the global log level */
183                 if (tar->loglevel != 0 && level < tar->loglevel)
184                         continue;
185
186                 /* Check the category log level */
187                 if (tar->loglevel == 0 && category->loglevel != 0 &&
188                     level < category->loglevel)
189                         continue;
190
191                 /* Apply filters here... if that becomes messy we will
192                  * need to put filters in a list and each filter will
193                  * say stop, continue, output */
194                 if ((tar->filter_map & LOG_FILTER_ALL) != 0)
195                         output = 1;
196                 else if (log_info->filter_fn)
197                         output = log_info->filter_fn(&log_context,
198                                                        tar);
199
200                 if (output) {
201                         /* FIXME: copying the va_list is an ugly
202                          * workaround against a bug hidden somewhere in
203                          * _output.  If we do not copy here, the first
204                          * call to _output() will corrupt the va_list
205                          * contents, and any further _output() calls
206                          * with the same va_list will segfault */
207                         va_list bp;
208                         va_copy(bp, ap);
209                         _output(tar, subsys, file, line, cont, format, bp);
210                         va_end(bp);
211                 }
212         }
213 }
214
215 void logp(unsigned int subsys, char *file, int line, int cont,
216           const char *format, ...)
217 {
218         va_list ap;
219
220         va_start(ap, format);
221         _logp(subsys, LOGL_DEBUG, file, line, cont, format, ap);
222         va_end(ap);
223 }
224
225 void logp2(unsigned int subsys, unsigned int level, char *file, int line, int cont, const char *format, ...)
226 {
227         va_list ap;
228
229         va_start(ap, format);
230         _logp(subsys, level, file, line, cont, format, ap);
231         va_end(ap);
232 }
233
234 static char hexd_buff[4096];
235
236 char *hexdump(const unsigned char *buf, int len)
237 {
238         int i;
239         char *cur = hexd_buff;
240
241         hexd_buff[0] = 0;
242         for (i = 0; i < len; i++) {
243                 int len_remain = sizeof(hexd_buff) - (cur - hexd_buff);
244                 int rc = snprintf(cur, len_remain, "%02x ", buf[i]);
245                 if (rc <= 0)
246                         break;
247                 cur += rc;
248         }
249         hexd_buff[sizeof(hexd_buff)-1] = 0;
250         return hexd_buff;
251 }
252
253 void log_add_target(struct log_target *target)
254 {
255         llist_add_tail(&target->entry, &target_list);
256 }
257
258 void log_del_target(struct log_target *target)
259 {
260         llist_del(&target->entry);
261 }
262
263 void log_reset_context(void)
264 {
265         memset(&log_context, 0, sizeof(log_context));
266 }
267
268 int log_set_context(uint8_t ctx_nr, void *value)
269 {
270         if (ctx_nr > LOG_MAX_CTX)
271                 return -EINVAL;
272
273         log_context.ctx[ctx_nr] = value;
274
275         return 0;
276 }
277
278 void log_set_all_filter(struct log_target *target, int all)
279 {
280         if (all)
281                 target->filter_map |= LOG_FILTER_ALL;
282         else
283                 target->filter_map &= ~LOG_FILTER_ALL;
284 }
285
286 void log_set_use_color(struct log_target *target, int use_color)
287 {
288         target->use_color = use_color;
289 }
290
291 void log_set_print_timestamp(struct log_target *target, int print_timestamp)
292 {
293         target->print_timestamp = print_timestamp;
294 }
295
296 void log_set_log_level(struct log_target *target, int log_level)
297 {
298         target->loglevel = log_level;
299 }
300
301 void log_set_category_filter(struct log_target *target, int category,
302                                int enable, int level)
303 {
304         if (category >= log_info->num_cat)
305                 return;
306         target->categories[category].enabled = !!enable;
307         target->categories[category].loglevel = level;
308 }
309
310 static void _stderr_output(struct log_target *target, const char *log)
311 {
312         fprintf(target->tgt_stdout.out, "%s", log);
313         fflush(target->tgt_stdout.out);
314 }
315
316 struct log_target *log_target_create(void)
317 {
318         struct log_target *target;
319         unsigned int i;
320
321         target = talloc_zero(tall_log_ctx, struct log_target);
322         if (!target)
323                 return NULL;
324
325         INIT_LLIST_HEAD(&target->entry);
326
327         /* initialize the per-category enabled/loglevel from defaults */
328         for (i = 0; i < log_info->num_cat; i++) {
329                 struct log_category *cat = &target->categories[i];
330                 cat->enabled = log_info->cat[i].enabled;
331                 cat->loglevel = log_info->cat[i].loglevel;
332         }
333
334         /* global settings */
335         target->use_color = 1;
336         target->print_timestamp = 0;
337
338         /* global log level */
339         target->loglevel = 0;
340         return target;
341 }
342
343 struct log_target *log_target_create_stderr(void)
344 {
345         struct log_target *target;
346
347         target = log_target_create();
348         if (!target)
349                 return NULL;
350
351         target->tgt_stdout.out = stderr;
352         target->output = _stderr_output;
353         return target;
354 }
355
356 void log_init(const struct log_info *cat)
357 {
358         tall_log_ctx = talloc_named_const(NULL, 1, "logging");
359         log_info = cat;
360 }