Import 'debug' support from OpenBSC into libosmocore
authorHarald Welte <laforge@gnumonks.org>
Fri, 26 Mar 2010 01:33:40 +0000 (09:33 +0800)
committerHarald Welte <laforge@gnumonks.org>
Fri, 26 Mar 2010 13:26:01 +0000 (21:26 +0800)
include/osmocore/Makefile.am
include/osmocore/debug.h [new file with mode: 0644]
src/Makefile.am
src/debug.c [new file with mode: 0644]

index fb4f089..e1ba2c6 100644 (file)
@@ -1,7 +1,7 @@
 osmocore_HEADERS = signal.h linuxlist.h timer.h select.h msgb.h \
                   tlv.h bitvec.h comp128.h statistics.h gsm_utils.h utils.h \
                   gsmtap.h write_queue.h rsl.h gsm48.h rxlev_stat.h mncc.h \
-                  gsm48_ie.h
+                  gsm48_ie.h debug.h
 
 if ENABLE_TALLOC
 osmocore_HEADERS += talloc.h
diff --git a/include/osmocore/debug.h b/include/osmocore/debug.h
new file mode 100644 (file)
index 0000000..a165831
--- /dev/null
@@ -0,0 +1,131 @@
+#ifndef _OSMOCORE_DEBUG_H
+#define _OSMOCORE_DEBUG_H
+
+#include <stdio.h>
+#include <stdint.h>
+#include <osmocore/linuxlist.h>
+
+#define DEBUG_MAX_CATEGORY     32
+#define DEBUG_MAX_CTX          8
+#define DEBUG_MAX_FILTERS      8
+
+#define DEBUG
+
+#ifdef DEBUG
+#define DEBUGP(ss, fmt, args...) debugp(ss, __FILE__, __LINE__, 0, fmt, ## args)
+#define DEBUGPC(ss, fmt, args...) debugp(ss, __FILE__, __LINE__, 1, fmt, ## args)
+#else
+#define DEBUGP(xss, fmt, args...)
+#define DEBUGPC(ss, fmt, args...)
+#endif
+
+#define static_assert(exp, name) typedef int dummy##name [(exp) ? 1 : -1];
+
+char *hexdump(const unsigned char *buf, int len);
+void debugp(unsigned int subsys, char *file, int line, int cont, const char *format, ...) __attribute__ ((format (printf, 5, 6)));
+
+/* new logging interface */
+#define LOGP(ss, level, fmt, args...) \
+       debugp2(ss, level, __FILE__, __LINE__, 0, fmt, ##args)
+#define LOGPC(ss, level, fmt, args...) \
+       debugp2(ss, level, __FILE__, __LINE__, 1, fmt, ##args)
+
+/* different levels */
+#define LOGL_DEBUG     1       /* debugging information */
+#define LOGL_INFO      3
+#define LOGL_NOTICE    5       /* abnormal/unexpected condition */
+#define LOGL_ERROR     7       /* error condition, requires user action */
+#define LOGL_FATAL     8       /* fatal, program aborted */
+
+#define DEBUG_FILTER_ALL       0x0001
+
+struct debug_category {
+       uint8_t loglevel;
+       uint8_t enabled;
+};
+
+struct debug_info_cat {
+       const char *name;
+       const char *color;
+       const char *description;
+       int number;
+       uint8_t loglevel;
+       uint8_t enabled;
+};
+
+/* debug context information, passed to filter */
+struct debug_context {
+       void *ctx[DEBUG_MAX_CTX+1];
+};
+
+struct debug_target;
+
+typedef int debug_filter(const struct debug_context *ctx,
+                        struct debug_target *target);
+
+struct debug_info {
+       /* filter callback function */
+       debug_filter *filter_fn;
+
+       /* per-category information */
+       const struct debug_info_cat *cat;
+       unsigned int num_cat;
+};
+
+struct debug_target {
+        struct llist_head entry;
+
+       int filter_map;
+       void *filter_data[DEBUG_MAX_FILTERS+1];
+
+       struct debug_category categories[DEBUG_MAX_CATEGORY+1];
+       uint8_t loglevel;
+       int use_color:1;
+       int print_timestamp:1;
+
+       union {
+               struct {
+                       FILE *out;
+               } tgt_stdout;
+
+               struct {
+                       int priority;
+               } tgt_syslog;
+
+               struct {
+                       void *vty;
+               } tgt_vty;
+       };
+
+        void (*output) (struct debug_target *target, const char *string);
+};
+
+/* use the above macros */
+void debugp2(unsigned int subsys, unsigned int level, char *file,
+            int line, int cont, const char *format, ...)
+                               __attribute__ ((format (printf, 6, 7)));
+void debug_init(const struct debug_info *cat);
+
+/* context management */
+void debug_reset_context(void);
+int debug_set_context(uint8_t ctx, void *value);
+
+/* filter on the targets */
+void debug_set_all_filter(struct debug_target *target, int);
+
+void debug_set_use_color(struct debug_target *target, int);
+void debug_set_print_timestamp(struct debug_target *target, int);
+void debug_set_log_level(struct debug_target *target, int log_level);
+void debug_parse_category_mask(struct debug_target *target, const char* mask);
+int debug_parse_level(const char *lvl);
+int debug_parse_category(const char *category);
+void debug_set_category_filter(struct debug_target *target, int category,
+                              int enable, int level);
+
+/* management of the targets */
+struct debug_target *debug_target_create(void);
+struct debug_target *debug_target_create_stderr(void);
+void debug_add_target(struct debug_target *target);
+void debug_del_target(struct debug_target *target);
+
+#endif /* _OSMOCORE_DEBUG_H */
index f0effa2..75a66bf 100644 (file)
@@ -9,7 +9,8 @@ lib_LTLIBRARIES = libosmocore.la
 
 libosmocore_la_SOURCES = timer.c select.c signal.c msgb.c rxlev_stat.c \
                         tlv_parser.c bitvec.c comp128.c gsm_utils.c statistics.c \
-                        write_queue.c utils.c rsl.c gsm48.c gsm48_ie.c
+                        write_queue.c utils.c rsl.c gsm48.c gsm48_ie.c \
+                        debug.c
 
 if ENABLE_TALLOC
 libosmocore_la_SOURCES += talloc.c
diff --git a/src/debug.c b/src/debug.c
new file mode 100644 (file)
index 0000000..934f229
--- /dev/null
@@ -0,0 +1,347 @@
+/* Debugging/Logging support code */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <errno.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/utils.h>
+#include <osmocore/debug.h>
+
+static const struct debug_info *debug_info;
+
+static struct debug_context debug_context;
+static void *tall_dbg_ctx = NULL;
+static LLIST_HEAD(target_list);
+
+static const struct value_string loglevel_strs[] = {
+       { 0,            "EVERYTHING" },
+       { LOGL_DEBUG,   "DEBUG" },
+       { LOGL_INFO,    "INFO" },
+       { LOGL_NOTICE,  "NOTICE" },
+       { LOGL_ERROR,   "ERROR" },
+       { LOGL_FATAL,   "FATAL" },
+       { 0, NULL },
+};
+
+int debug_parse_level(const char *lvl)
+{
+       return get_string_value(loglevel_strs, lvl);
+}
+
+int debug_parse_category(const char *category)
+{
+       int i;
+
+       for (i = 0; i < debug_info->num_cat; ++i) {
+               if (!strcasecmp(debug_info->cat[i].name+1, category))
+                       return debug_info->cat[i].number;
+       }
+
+       return -EINVAL;
+}
+
+/*
+ * Parse the category mask.
+ * The format can be this: category1:category2:category3
+ * or category1,2:category2,3:...
+ */
+void debug_parse_category_mask(struct debug_target* target, const char *_mask)
+{
+       int i = 0;
+       char *mask = strdup(_mask);
+       char *category_token = NULL;
+
+       /* Disable everything to enable it afterwards */
+       for (i = 0; i < ARRAY_SIZE(target->categories); ++i)
+               target->categories[i].enabled = 0;
+
+       category_token = strtok(mask, ":");
+       do {
+               for (i = 0; i < debug_info->num_cat; ++i) {
+                       char* colon = strstr(category_token, ",");
+                       int length = strlen(category_token);
+
+                       if (colon)
+                           length = colon - category_token;
+
+                       if (strncasecmp(debug_info->cat[i].name, category_token,
+                                       length) == 0) {
+                               int number = debug_info->cat[i].number;
+                               int level = 0;
+
+                               if (colon)
+                                       level = atoi(colon+1);
+
+                               target->categories[number].enabled = 1;
+                               target->categories[number].loglevel = level;
+                       }
+               }
+       } while ((category_token = strtok(NULL, ":")));
+
+       free(mask);
+}
+
+static const char* color(int subsys)
+{
+       int i = 0;
+
+       for (i = 0; i < debug_info->num_cat; ++i) {
+               if (debug_info->cat[i].number == subsys)
+                       return debug_info->cat[i].color;
+       }
+
+       return "";
+}
+
+static void _output(struct debug_target *target, unsigned int subsys,
+                   char *file, int line, int cont, const char *format,
+                   va_list ap)
+{
+       char col[30];
+       char sub[30];
+       char tim[30];
+       char buf[4096];
+       char final[4096];
+
+       /* prepare the data */
+       col[0] = '\0';
+       sub[0] = '\0';
+       tim[0] = '\0';
+       buf[0] = '\0';
+
+       /* are we using color */
+       if (target->use_color) {
+               snprintf(col, sizeof(col), "%s", color(subsys));
+               col[sizeof(col)-1] = '\0';
+       }
+       vsnprintf(buf, sizeof(buf), format, ap);
+       buf[sizeof(buf)-1] = '\0';
+
+       if (!cont) {
+               if (target->print_timestamp) {
+                       char *timestr;
+                       time_t tm;
+                       tm = time(NULL);
+                       timestr = ctime(&tm);
+                       timestr[strlen(timestr)-1] = '\0';
+                       snprintf(tim, sizeof(tim), "%s ", timestr);
+                       tim[sizeof(tim)-1] = '\0';
+               }
+               snprintf(sub, sizeof(sub), "<%4.4x> %s:%d ", subsys, file, line);
+               sub[sizeof(sub)-1] = '\0';
+       }
+
+       snprintf(final, sizeof(final), "%s%s%s%s\033[0;m", col, tim, sub, buf);
+       final[sizeof(final)-1] = '\0';
+       target->output(target, final);
+}
+
+
+static void _debugp(unsigned int subsys, int level, char *file, int line,
+                   int cont, const char *format, va_list ap)
+{
+       struct debug_target *tar;
+
+       llist_for_each_entry(tar, &target_list, entry) {
+               struct debug_category *category;
+               int output = 0;
+
+               category = &tar->categories[subsys];
+               /* subsystem is not supposed to be debugged */
+               if (!category->enabled)
+                       continue;
+
+               /* Check the global log level */
+               if (tar->loglevel != 0 && level < tar->loglevel)
+                       continue;
+
+               /* Check the category log level */
+               if (tar->loglevel == 0 && category->loglevel != 0 &&
+                   level < category->loglevel)
+                       continue;
+
+               /* Apply filters here... if that becomes messy we will
+                * need to put filters in a list and each filter will
+                * say stop, continue, output */
+               if ((tar->filter_map & DEBUG_FILTER_ALL) != 0)
+                       output = 1;
+               else if (debug_info->filter_fn)
+                       output = debug_info->filter_fn(&debug_context,
+                                                      tar);
+
+               if (output) {
+                       /* FIXME: copying the va_list is an ugly
+                        * workaround against a bug hidden somewhere in
+                        * _output.  If we do not copy here, the first
+                        * call to _output() will corrupt the va_list
+                        * contents, and any further _output() calls
+                        * with the same va_list will segfault */
+                       va_list bp;
+                       va_copy(bp, ap);
+                       _output(tar, subsys, file, line, cont, format, bp);
+                       va_end(bp);
+               }
+       }
+}
+
+void debugp(unsigned int subsys, char *file, int line, int cont,
+           const char *format, ...)
+{
+       va_list ap;
+
+       va_start(ap, format);
+       _debugp(subsys, LOGL_DEBUG, file, line, cont, format, ap);
+       va_end(ap);
+}
+
+void debugp2(unsigned int subsys, unsigned int level, char *file, int line, int cont, const char *format, ...)
+{
+       va_list ap;
+
+       va_start(ap, format);
+       _debugp(subsys, level, file, line, cont, format, ap);
+       va_end(ap);
+}
+
+static char hexd_buff[4096];
+
+char *hexdump(const unsigned char *buf, int len)
+{
+       int i;
+       char *cur = hexd_buff;
+
+       hexd_buff[0] = 0;
+       for (i = 0; i < len; i++) {
+               int len_remain = sizeof(hexd_buff) - (cur - hexd_buff);
+               int rc = snprintf(cur, len_remain, "%02x ", buf[i]);
+               if (rc <= 0)
+                       break;
+               cur += rc;
+       }
+       hexd_buff[sizeof(hexd_buff)-1] = 0;
+       return hexd_buff;
+}
+
+void debug_add_target(struct debug_target *target)
+{
+       llist_add_tail(&target->entry, &target_list);
+}
+
+void debug_del_target(struct debug_target *target)
+{
+       llist_del(&target->entry);
+}
+
+void debug_reset_context(void)
+{
+       memset(&debug_context, 0, sizeof(debug_context));
+}
+
+int debug_set_context(uint8_t ctx_nr, void *value)
+{
+       if (ctx_nr > DEBUG_MAX_CTX)
+               return -EINVAL;
+
+       debug_context.ctx[ctx_nr] = value;
+
+       return 0;
+}
+
+void debug_set_all_filter(struct debug_target *target, int all)
+{
+       if (all)
+               target->filter_map |= DEBUG_FILTER_ALL;
+       else
+               target->filter_map &= ~DEBUG_FILTER_ALL;
+}
+
+void debug_set_use_color(struct debug_target *target, int use_color)
+{
+       target->use_color = use_color;
+}
+
+void debug_set_print_timestamp(struct debug_target *target, int print_timestamp)
+{
+       target->print_timestamp = print_timestamp;
+}
+
+void debug_set_log_level(struct debug_target *target, int log_level)
+{
+       target->loglevel = log_level;
+}
+
+void debug_set_category_filter(struct debug_target *target, int category,
+                              int enable, int level)
+{
+       if (category >= debug_info->num_cat)
+               return;
+       target->categories[category].enabled = !!enable;
+       target->categories[category].loglevel = level;
+}
+
+static void _stderr_output(struct debug_target *target, const char *log)
+{
+       fprintf(target->tgt_stdout.out, "%s", log);
+       fflush(target->tgt_stdout.out);
+}
+
+struct debug_target *debug_target_create(void)
+{
+       struct debug_target *target;
+
+       target = talloc_zero(tall_dbg_ctx, struct debug_target);
+       if (!target)
+               return NULL;
+
+       INIT_LLIST_HEAD(&target->entry);
+       memcpy(target->categories, debug_info->cat,
+               sizeof(struct debug_category)*debug_info->num_cat);
+       target->use_color = 1;
+       target->print_timestamp = 0;
+       target->loglevel = 0;
+       return target;
+}
+
+struct debug_target *debug_target_create_stderr(void)
+{
+       struct debug_target *target;
+
+       target = debug_target_create();
+       if (!target)
+               return NULL;
+
+       target->tgt_stdout.out = stderr;
+       target->output = _stderr_output;
+       return target;
+}
+
+void debug_init(const struct debug_info *cat)
+{
+       tall_dbg_ctx = talloc_named_const(NULL, 1, "debug");
+       debug_info = cat;
+}