diff --git a/libosmocore/include/osmocore/Makefile.am b/libosmocore/include/osmocore/Makefile.am index fb4f089b7..1c3a33f33 100644 --- a/libosmocore/include/osmocore/Makefile.am +++ b/libosmocore/include/osmocore/Makefile.am @@ -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 logging.h if ENABLE_TALLOC osmocore_HEADERS += talloc.h diff --git a/libosmocore/include/osmocore/logging.h b/libosmocore/include/osmocore/logging.h new file mode 100644 index 000000000..93f18a07b --- /dev/null +++ b/libosmocore/include/osmocore/logging.h @@ -0,0 +1,130 @@ +#ifndef _OSMOCORE_LOGGING_H +#define _OSMOCORE_LOGGING_H + +#include +#include +#include + +#define LOG_MAX_CATEGORY 32 +#define LOG_MAX_CTX 8 +#define LOG_MAX_FILTERS 8 + +#define DEBUG + +#ifdef DEBUG +#define DEBUGP(ss, fmt, args...) logp(ss, __FILE__, __LINE__, 0, fmt, ## args) +#define DEBUGPC(ss, fmt, args...) logp(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 logp(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...) \ + logp2(ss, level, __FILE__, __LINE__, 0, fmt, ##args) +#define LOGPC(ss, level, fmt, args...) \ + logp2(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 LOG_FILTER_ALL 0x0001 + +struct log_category { + uint8_t loglevel; + uint8_t enabled; +}; + +struct log_info_cat { + const char *name; + const char *color; + const char *description; + uint8_t loglevel; + uint8_t enabled; +}; + +/* log context information, passed to filter */ +struct log_context { + void *ctx[LOG_MAX_CTX+1]; +}; + +struct log_target; + +typedef int log_filter(const struct log_context *ctx, + struct log_target *target); + +struct log_info { + /* filter callback function */ + log_filter *filter_fn; + + /* per-category information */ + const struct log_info_cat *cat; + unsigned int num_cat; +}; + +struct log_target { + struct llist_head entry; + + int filter_map; + void *filter_data[LOG_MAX_FILTERS+1]; + + struct log_category categories[LOG_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 log_target *target, const char *string); +}; + +/* use the above macros */ +void logp2(unsigned int subsys, unsigned int level, char *file, + int line, int cont, const char *format, ...) + __attribute__ ((format (printf, 6, 7))); +void log_init(const struct log_info *cat); + +/* context management */ +void log_reset_context(void); +int log_set_context(uint8_t ctx, void *value); + +/* filter on the targets */ +void log_set_all_filter(struct log_target *target, int); + +void log_set_use_color(struct log_target *target, int); +void log_set_print_timestamp(struct log_target *target, int); +void log_set_log_level(struct log_target *target, int log_level); +void log_parse_category_mask(struct log_target *target, const char* mask); +int log_parse_level(const char *lvl); +int log_parse_category(const char *category); +void log_set_category_filter(struct log_target *target, int category, + int enable, int level); + +/* management of the targets */ +struct log_target *log_target_create(void); +struct log_target *log_target_create_stderr(void); +void log_add_target(struct log_target *target); +void log_del_target(struct log_target *target); + +#endif /* _OSMOCORE_LOGGING_H */ diff --git a/libosmocore/include/osmocore/write_queue.h b/libosmocore/include/osmocore/write_queue.h index c84000c1e..64d4159a0 100644 --- a/libosmocore/include/osmocore/write_queue.h +++ b/libosmocore/include/osmocore/write_queue.h @@ -38,6 +38,7 @@ struct write_queue { }; void write_queue_init(struct write_queue *queue, int max_length); +void write_queue_clear(struct write_queue *queue); int write_queue_enqueue(struct write_queue *queue, struct msgb *data); int write_queue_bfd_cb(struct bsc_fd *fd, unsigned int what); diff --git a/libosmocore/src/Makefile.am b/libosmocore/src/Makefile.am index f0effa2ea..16978074a 100644 --- a/libosmocore/src/Makefile.am +++ b/libosmocore/src/Makefile.am @@ -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 \ + logging.c if ENABLE_TALLOC libosmocore_la_SOURCES += talloc.c diff --git a/libosmocore/src/logging.c b/libosmocore/src/logging.c new file mode 100644 index 000000000..508ccfd3e --- /dev/null +++ b/libosmocore/src/logging.c @@ -0,0 +1,345 @@ +/* Debugging/Logging support code */ + +/* (C) 2008-2010 by Harald Welte + * (C) 2008 by Holger Hans Peter Freyther + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static const struct log_info *log_info; + +static struct log_context log_context; +static void *tall_log_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 log_parse_level(const char *lvl) +{ + return get_string_value(loglevel_strs, lvl); +} + +int log_parse_category(const char *category) +{ + int i; + + for (i = 0; i < log_info->num_cat; ++i) { + if (!strcasecmp(log_info->cat[i].name+1, category)) + return i; + } + + return -EINVAL; +} + +/* + * Parse the category mask. + * The format can be this: category1:category2:category3 + * or category1,2:category2,3:... + */ +void log_parse_category_mask(struct log_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 < log_info->num_cat; ++i) { + char* colon = strstr(category_token, ","); + int length = strlen(category_token); + + if (colon) + length = colon - category_token; + + if (strncasecmp(log_info->cat[i].name, category_token, + length) == 0) { + int level = 0; + + if (colon) + level = atoi(colon+1); + + target->categories[i].enabled = 1; + target->categories[i].loglevel = level; + } + } + } while ((category_token = strtok(NULL, ":"))); + + free(mask); +} + +static const char* color(int subsys) +{ + if (subsys < log_info->num_cat) + return log_info->cat[subsys].color; + + return NULL; +} + +static void _output(struct log_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) { + const char *c = color(subsys); + if (c) { + 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 _logp(unsigned int subsys, int level, char *file, int line, + int cont, const char *format, va_list ap) +{ + struct log_target *tar; + + llist_for_each_entry(tar, &target_list, entry) { + struct log_category *category; + int output = 0; + + category = &tar->categories[subsys]; + /* subsystem is not supposed to be logged */ + 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 & LOG_FILTER_ALL) != 0) + output = 1; + else if (log_info->filter_fn) + output = log_info->filter_fn(&log_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 logp(unsigned int subsys, char *file, int line, int cont, + const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + _logp(subsys, LOGL_DEBUG, file, line, cont, format, ap); + va_end(ap); +} + +void logp2(unsigned int subsys, unsigned int level, char *file, int line, int cont, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + _logp(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 log_add_target(struct log_target *target) +{ + llist_add_tail(&target->entry, &target_list); +} + +void log_del_target(struct log_target *target) +{ + llist_del(&target->entry); +} + +void log_reset_context(void) +{ + memset(&log_context, 0, sizeof(log_context)); +} + +int log_set_context(uint8_t ctx_nr, void *value) +{ + if (ctx_nr > LOG_MAX_CTX) + return -EINVAL; + + log_context.ctx[ctx_nr] = value; + + return 0; +} + +void log_set_all_filter(struct log_target *target, int all) +{ + if (all) + target->filter_map |= LOG_FILTER_ALL; + else + target->filter_map &= ~LOG_FILTER_ALL; +} + +void log_set_use_color(struct log_target *target, int use_color) +{ + target->use_color = use_color; +} + +void log_set_print_timestamp(struct log_target *target, int print_timestamp) +{ + target->print_timestamp = print_timestamp; +} + +void log_set_log_level(struct log_target *target, int log_level) +{ + target->loglevel = log_level; +} + +void log_set_category_filter(struct log_target *target, int category, + int enable, int level) +{ + if (category >= log_info->num_cat) + return; + target->categories[category].enabled = !!enable; + target->categories[category].loglevel = level; +} + +static void _stderr_output(struct log_target *target, const char *log) +{ + fprintf(target->tgt_stdout.out, "%s", log); + fflush(target->tgt_stdout.out); +} + +struct log_target *log_target_create(void) +{ + struct log_target *target; + + target = talloc_zero(tall_log_ctx, struct log_target); + if (!target) + return NULL; + + INIT_LLIST_HEAD(&target->entry); + memcpy(target->categories, log_info->cat, + sizeof(struct log_category)*log_info->num_cat); + target->use_color = 1; + target->print_timestamp = 0; + target->loglevel = 0; + return target; +} + +struct log_target *log_target_create_stderr(void) +{ + struct log_target *target; + + target = log_target_create(); + if (!target) + return NULL; + + target->tgt_stdout.out = stderr; + target->output = _stderr_output; + return target; +} + +void log_init(const struct log_info *cat) +{ + tall_log_ctx = talloc_named_const(NULL, 1, "logging"); + log_info = cat; +} diff --git a/libosmocore/src/write_queue.c b/libosmocore/src/write_queue.c index 7d908b4ca..a0ac2d6fd 100644 --- a/libosmocore/src/write_queue.c +++ b/libosmocore/src/write_queue.c @@ -72,3 +72,14 @@ int write_queue_enqueue(struct write_queue *queue, struct msgb *data) return 0; } + +void write_queue_clear(struct write_queue *queue) +{ + while (!llist_empty(&queue->msg_queue)) { + struct msgb *msg = msgb_dequeue(&queue->msg_queue); + msgb_free(msg); + } + + queue->current_length = 0; + queue->bfd.when &= ~BSC_FD_WRITE; +}