diff --git a/include/asterisk/logger.h b/include/asterisk/logger.h index 785c3e8de576eacd485536b086f0470583a9e83e..4f6dfc4e91410f64570a6274661947e4df438d8f 100644 --- a/include/asterisk/logger.h +++ b/include/asterisk/logger.h @@ -194,6 +194,37 @@ unsigned int ast_debug_get_by_file(const char *file); */ unsigned int ast_verbose_get_by_file(const char *file); +/*! + * \brief Register a new logger level + * \param name The name of the level to be registered + * \retval -1 if an error occurs + * \retval non-zero level to be used with ast_log for sending messages to this level + * \since 1.6.3 + */ +int ast_logger_register_level(const char *name); + +/*! + * \brief Unregister a previously registered logger level + * \param name The name of the level to be unregistered + * \return nothing + * \since 1.6.3 + */ +void ast_logger_unregister_level(const char *name); + +/*! + * \brief Send a log message to a dynamically registered log level + * \param level The log level to send the message to + * + * Like ast_log, the log message may include printf-style formats, and + * the data for these must be provided as additional parameters after + * the log message. + * + * \return nothing + * \since 1.6.3 + */ + +#define ast_log_dynamic_level(level, ...) ast_log(level, __FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__) + /*! * \brief Log a DEBUG message * \param level The minimum value of option_debug for this message diff --git a/main/logger.c b/main/logger.c index 91ae572f90b1a026b482db74bd74dd9b63365063..8794ef82b7dd7663c5a2d0da6846945752fe3202 100644 --- a/main/logger.c +++ b/main/logger.c @@ -54,7 +54,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") from <syslog.h> which is included by logger.h */ #include <syslog.h> -static int syslog_level_map[] = { +static const int syslog_level_map[] = { LOG_DEBUG, LOG_INFO, /* arbitrary equivalent of LOG_EVENT */ LOG_NOTICE, @@ -89,14 +89,13 @@ static int syslog_level_map[] = { #else #define GETTID() getpid() #endif - static char dateformat[256] = "%b %e %T"; /* Original Asterisk Format */ static char queue_log_name[256] = QUEUELOG; static char exec_after_rotate[256] = ""; static int filesize_reload_needed; -static int global_logmask = -1; +static unsigned int global_logmask = 0xFFFF; enum rotatestrategy { SEQUENTIAL = 1 << 0, /* Original method - create a new file, in order */ @@ -117,13 +116,24 @@ enum logtypes { }; struct logchannel { - int logmask; /* What to log to this channel */ - int disabled; /* If this channel is disabled or not */ - int facility; /* syslog facility */ - enum logtypes type; /* Type of log channel */ - FILE *fileptr; /* logfile logging file pointer */ - char filename[256]; /* Filename */ + /*! What to log to this channel */ + unsigned int logmask; + /*! If this channel is disabled or not */ + int disabled; + /*! syslog facility */ + int facility; + /*! Type of log channel */ + enum logtypes type; + /*! logfile logging file pointer */ + FILE *fileptr; + /*! Filename */ + char filename[PATH_MAX]; + /*! field for linking to list */ AST_LIST_ENTRY(logchannel) list; + /*! Line number from configuration file */ + int lineno; + /*! Components (levels) from last config load */ + char components[0]; }; static AST_RWLIST_HEAD_STATIC(logchannels, logchannel); @@ -135,14 +145,17 @@ enum logmsgtypes { struct logmsg { enum logmsgtypes type; - char date[256]; int level; - char file[80]; int line; - char function[80]; long process_id; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(date); + AST_STRING_FIELD(file); + AST_STRING_FIELD(function); + AST_STRING_FIELD(message); + AST_STRING_FIELD(level_name); + ); AST_LIST_ENTRY(logmsg) list; - char str[0]; }; static AST_LIST_HEAD_STATIC(logmsgs, logmsg); @@ -152,26 +165,61 @@ static int close_logger_thread; static FILE *qlog; -/*! \brief Logging channels used in the Asterisk logging system */ -static char *levels[] = { +/*! \brief Logging channels used in the Asterisk logging system + * + * The first 16 levels are reserved for system usage, and the remaining + * levels are reserved for usage by dynamic levels registered via + * ast_logger_register_level. + */ + +/* Modifications to this array are protected by the rwlock in the + * logchannels list. + */ + +static char *levels[32] = { "DEBUG", "---EVENT---", /* no longer used */ "NOTICE", "WARNING", "ERROR", "VERBOSE", - "DTMF" + "DTMF", }; /*! \brief Colors used in the console for logging */ -static int colors[] = { +static const int colors[32] = { COLOR_BRGREEN, - COLOR_BRBLUE, + COLOR_BRBLUE, /* no longer used */ COLOR_YELLOW, COLOR_BRRED, COLOR_RED, COLOR_GREEN, - COLOR_BRGREEN + COLOR_BRGREEN, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, + COLOR_BRBLUE, }; AST_THREADSTORAGE(verbose_buf); @@ -180,28 +228,24 @@ AST_THREADSTORAGE(verbose_buf); AST_THREADSTORAGE(log_buf); #define LOG_BUF_INIT_SIZE 256 -static int make_components(const char *s, int lineno) +static unsigned int make_components(const char *s, int lineno) { char *w; - int res = 0; + unsigned int res = 0; char *stringp = ast_strdupa(s); + unsigned int x; while ((w = strsep(&stringp, ","))) { + int found = 0; + w = ast_skip_blanks(w); - if (!strcasecmp(w, "error")) - res |= (1 << __LOG_ERROR); - else if (!strcasecmp(w, "warning")) - res |= (1 << __LOG_WARNING); - else if (!strcasecmp(w, "notice")) - res |= (1 << __LOG_NOTICE); - else if (!strcasecmp(w, "debug")) - res |= (1 << __LOG_DEBUG); - else if (!strcasecmp(w, "verbose")) - res |= (1 << __LOG_VERBOSE); - else if (!strcasecmp(w, "dtmf")) - res |= (1 << __LOG_DTMF); - else { - fprintf(stderr, "Logfile Warning: Unknown keyword '%s' at line %d of logger.conf\n", w, lineno); + + for (x = 0; x < ARRAY_LEN(levels); x++) { + if (levels[x] && !strcasecmp(w, levels[x])) { + res |= (1 << x); + found = 1; + break; + } } } @@ -216,9 +260,12 @@ static struct logchannel *make_logchannel(const char *channel, const char *compo CODE *cptr; #endif - if (ast_strlen_zero(channel) || !(chan = ast_calloc(1, sizeof(*chan)))) + if (ast_strlen_zero(channel) || !(chan = ast_calloc(1, sizeof(*chan) + strlen(components) + 1))) return NULL; + strcpy(chan->components, components); + chan->lineno = lineno; + if (!strcasecmp(channel, "console")) { chan->type = LOGTYPE_CONSOLE; } else if (!strncasecmp(channel, "syslog", 6)) { @@ -292,7 +339,7 @@ static struct logchannel *make_logchannel(const char *channel, const char *compo } chan->type = LOGTYPE_SYSLOG; - snprintf(chan->filename, sizeof(chan->filename), "%s", channel); + ast_copy_string(chan->filename, channel, sizeof(chan->filename)); openlog("asterisk", LOG_PID, chan->facility); } else { if (!ast_strlen_zero(hostname)) { @@ -302,14 +349,14 @@ static struct logchannel *make_logchannel(const char *channel, const char *compo snprintf(chan->filename, sizeof(chan->filename), "%s/%s", channel[0] != '/' ? ast_config_AST_LOG_DIR : "", channel); } - chan->fileptr = fopen(chan->filename, "a"); - if (!chan->fileptr) { + if (!(chan->fileptr = fopen(chan->filename, "a"))) { /* Can't log here, since we're called with a lock */ fprintf(stderr, "Logger Warning: Unable to open log file '%s': %s\n", chan->filename, strerror(errno)); } chan->type = LOGTYPE_FILE; } - chan->logmask = make_components(components, lineno); + chan->logmask = make_components(chan->components, lineno); + return chan; } @@ -329,10 +376,10 @@ static void init_logger_chain(int locked) AST_RWLIST_WRLOCK(&logchannels); while ((chan = AST_RWLIST_REMOVE_HEAD(&logchannels, list))) ast_free(chan); + global_logmask = 0; if (!locked) AST_RWLIST_UNLOCK(&logchannels); - global_logmask = 0; errno = 0; /* close syslog */ closelog(); @@ -346,13 +393,13 @@ static void init_logger_chain(int locked) if (!(chan = ast_calloc(1, sizeof(*chan)))) return; chan->type = LOGTYPE_CONSOLE; - chan->logmask = 28; /*warning,notice,error */ + chan->logmask = __LOG_WARNING | __LOG_NOTICE | __LOG_ERROR; if (!locked) AST_RWLIST_WRLOCK(&logchannels); AST_RWLIST_INSERT_HEAD(&logchannels, chan, list); + global_logmask |= chan->logmask; if (!locked) AST_RWLIST_UNLOCK(&logchannels); - global_logmask |= chan->logmask; return; } @@ -711,13 +758,17 @@ static char *handle_logger_set_level(struct ast_cli_entry *e, int cmd, struct as if (a->argc < 5) return CLI_SHOWUSAGE; - for (x = 0; x <= NUMLOGLEVELS; x++) { - if (!strcasecmp(a->argv[3], levels[x])) { + AST_RWLIST_WRLOCK(&logchannels); + + for (x = 0; x < ARRAY_LEN(levels); x++) { + if (levels[x] && !strcasecmp(a->argv[3], levels[x])) { level = x; break; } } + AST_RWLIST_UNLOCK(&logchannels); + state = ast_true(a->argv[4]) ? 1 : 0; if (level != -1) { @@ -750,21 +801,16 @@ static char *handle_logger_show_channels(struct ast_cli_entry *e, int cmd, struc ast_cli(a->fd, "-------------\n"); AST_RWLIST_RDLOCK(&logchannels); AST_RWLIST_TRAVERSE(&logchannels, chan, list) { + unsigned int level; + ast_cli(a->fd, FORMATL, chan->filename, chan->type == LOGTYPE_CONSOLE ? "Console" : (chan->type == LOGTYPE_SYSLOG ? "Syslog" : "File"), chan->disabled ? "Disabled" : "Enabled"); ast_cli(a->fd, " - "); - if (chan->logmask & (1 << __LOG_DEBUG)) - ast_cli(a->fd, "Debug "); - if (chan->logmask & (1 << __LOG_DTMF)) - ast_cli(a->fd, "DTMF "); - if (chan->logmask & (1 << __LOG_VERBOSE)) - ast_cli(a->fd, "Verbose "); - if (chan->logmask & (1 << __LOG_WARNING)) - ast_cli(a->fd, "Warning "); - if (chan->logmask & (1 << __LOG_NOTICE)) - ast_cli(a->fd, "Notice "); - if (chan->logmask & (1 << __LOG_ERROR)) - ast_cli(a->fd, "Error "); + for (level = 0; level < ARRAY_LEN(levels); level++) { + if (chan->logmask & (1 << level)) { + ast_cli(a->fd, "%s ", levels[level]); + } + } ast_cli(a->fd, "\n"); } AST_RWLIST_UNLOCK(&logchannels); @@ -794,7 +840,7 @@ static int handle_SIGXFSZ(int sig) return 0; } -static void ast_log_vsyslog(int level, const char *file, int line, const char *function, char *str, long pid) +static void ast_log_vsyslog(int level, const char *file, int line, const char *function, const char *str, long pid) { char buf[BUFSIZ]; @@ -834,7 +880,7 @@ static void logger_print_normal(struct logmsg *logmsg) continue; /* Check syslog channels */ if (chan->type == LOGTYPE_SYSLOG && (chan->logmask & (1 << logmsg->level))) { - ast_log_vsyslog(logmsg->level, logmsg->file, logmsg->line, logmsg->function, logmsg->str, logmsg->process_id); + ast_log_vsyslog(logmsg->level, logmsg->file, logmsg->line, logmsg->function, logmsg->message, logmsg->process_id); /* Console channels */ } else if (chan->type == LOGTYPE_CONSOLE && (chan->logmask & (1 << logmsg->level))) { char linestr[128]; @@ -849,12 +895,12 @@ static void logger_print_normal(struct logmsg *logmsg) /* Build string to print out */ snprintf(buf, sizeof(buf), "[%s] %s[%ld]: %s:%s %s: %s", logmsg->date, - term_color(tmp1, levels[logmsg->level], colors[logmsg->level], 0, sizeof(tmp1)), + term_color(tmp1, logmsg->level_name, colors[logmsg->level], 0, sizeof(tmp1)), logmsg->process_id, term_color(tmp2, logmsg->file, COLOR_BRWHITE, 0, sizeof(tmp2)), term_color(tmp3, linestr, COLOR_BRWHITE, 0, sizeof(tmp3)), term_color(tmp4, logmsg->function, COLOR_BRWHITE, 0, sizeof(tmp4)), - logmsg->str); + logmsg->message); /* Print out */ ast_console_puts_mutable(buf, logmsg->level); /* File channels */ @@ -867,8 +913,8 @@ static void logger_print_normal(struct logmsg *logmsg) /* Print out to the file */ res = fprintf(chan->fileptr, "[%s] %s[%ld] %s: %s", - logmsg->date, levels[logmsg->level], logmsg->process_id, logmsg->file, logmsg->str); - if (res <= 0 && !ast_strlen_zero(logmsg->str)) { + logmsg->date, logmsg->level_name, logmsg->process_id, logmsg->file, logmsg->message); + if (res <= 0 && !ast_strlen_zero(logmsg->message)) { fprintf(stderr, "**** Asterisk Logging Error: ***********\n"); if (errno == ENOMEM || errno == ENOSPC) fprintf(stderr, "Asterisk logging error: Out of disk space, can't log to log file %s\n", chan->filename); @@ -882,7 +928,7 @@ static void logger_print_normal(struct logmsg *logmsg) } } } else if (logmsg->level != __LOG_VERBOSE) { - fputs(logmsg->str, stdout); + fputs(logmsg->message, stdout); } AST_RWLIST_UNLOCK(&logchannels); @@ -904,7 +950,7 @@ static void logger_print_verbose(struct logmsg *logmsg) /* Iterate through the list of verbosers and pass them the log message string */ AST_RWLIST_RDLOCK(&verbosers); AST_RWLIST_TRAVERSE(&verbosers, v, list) - v->verboser(logmsg->str); + v->verboser(logmsg->message); AST_RWLIST_UNLOCK(&verbosers); return; @@ -941,6 +987,7 @@ static void *logger_thread(void *data) logger_print_verbose(msg); /* Free the data since we are done */ + ast_string_field_free_memory(msg); ast_free(msg); } @@ -1028,6 +1075,7 @@ void ast_log(int level, const char *file, int line, const char *function, const struct timeval now = ast_tvnow(); int res = 0; va_list ap; + char datestring[256]; if (!(buf = ast_str_thread_get(&log_buf, LOG_BUF_INIT_SIZE))) return; @@ -1073,24 +1121,26 @@ void ast_log(int level, const char *file, int line, const char *function, const return; /* Create a new logging message */ - if (!(logmsg = ast_calloc(1, sizeof(*logmsg) + res + 1))) + if (!(logmsg = ast_calloc_with_stringfields(1, struct logmsg, res + 128))) return; /* Copy string over */ - strcpy(logmsg->str, ast_str_buffer(buf)); + ast_string_field_set(logmsg, message, ast_str_buffer(buf)); /* Set type to be normal */ logmsg->type = LOGMSG_NORMAL; /* Create our date/time */ ast_localtime(&now, &tm, NULL); - ast_strftime(logmsg->date, sizeof(logmsg->date), dateformat, &tm); + ast_strftime(datestring, sizeof(datestring), dateformat, &tm); + ast_string_field_set(logmsg, date, datestring); /* Copy over data */ logmsg->level = level; logmsg->line = line; - ast_copy_string(logmsg->file, file, sizeof(logmsg->file)); - ast_copy_string(logmsg->function, function, sizeof(logmsg->function)); + ast_string_field_set(logmsg, level_name, levels[level]); + ast_string_field_set(logmsg, file, file); + ast_string_field_set(logmsg, function, function); logmsg->process_id = (long) GETTID(); /* If the logger thread is active, append it to the tail end of the list - otherwise skip that step */ @@ -1206,12 +1256,12 @@ void __ast_verbose_ap(const char *file, int line, const char *func, const char * if (res == AST_DYNSTR_BUILD_FAILED) return; - if (!(logmsg = ast_calloc(1, sizeof(*logmsg) + res + 1))) + if (!(logmsg = ast_calloc_with_stringfields(1, struct logmsg, res + 128))) return; - strcpy(logmsg->str, ast_str_buffer(buf)); + ast_string_field_set(logmsg, message, ast_str_buffer(buf)); - ast_log(__LOG_VERBOSE, file, line, func, "%s", logmsg->str + 1); + ast_log(__LOG_VERBOSE, file, line, func, "%s", logmsg->message + 1); /* Set type */ logmsg->type = LOGMSG_VERBOSE; @@ -1231,6 +1281,7 @@ void __ast_verbose_ap(const char *file, int line, const char *func, const char * void __ast_verbose(const char *file, int line, const char *func, const char *fmt, ...) { va_list ap; + va_start(ap, fmt); __ast_verbose_ap(file, line, func, fmt, ap); va_end(ap); @@ -1242,6 +1293,7 @@ void __attribute__((format(printf, 1,2))) ast_verbose(const char *fmt, ...); void ast_verbose(const char *fmt, ...) { va_list ap; + va_start(ap, fmt); __ast_verbose_ap("", 0, "", fmt, ap); va_end(ap); @@ -1280,3 +1332,102 @@ int ast_unregister_verbose(void (*v)(const char *string)) return cur ? 0 : -1; } + +static void update_logchannels(void) +{ + struct logchannel *cur; + + AST_RWLIST_WRLOCK(&logchannels); + + global_logmask = 0; + + AST_RWLIST_TRAVERSE(&logchannels, cur, list) { + cur->logmask = make_components(cur->components, cur->lineno); + global_logmask |= cur->logmask; + } + + AST_RWLIST_UNLOCK(&logchannels); +} + +int ast_logger_register_level(const char *name) +{ + unsigned int level; + unsigned int available = 0; + + AST_RWLIST_WRLOCK(&logchannels); + + for (level = 0; level < ARRAY_LEN(levels); level++) { + if ((level >= 16) && !available && !levels[level]) { + available = level; + continue; + } + + if (levels[level] && !strcasecmp(levels[level], name)) { + ast_log(LOG_WARNING, + "Unable to register dynamic logger level '%s': a standard logger level uses that name.\n", + name); + AST_RWLIST_UNLOCK(&logchannels); + + return -1; + } + } + + if (!available) { + ast_log(LOG_WARNING, + "Unable to register dynamic logger level '%s'; maximum number of levels registered.\n", + name); + AST_RWLIST_UNLOCK(&logchannels); + + return -1; + } + + levels[available] = ast_strdup(name); + + AST_RWLIST_UNLOCK(&logchannels); + + ast_debug(1, "Registered dynamic logger level '%s' with index %d.\n", name, available); + + update_logchannels(); + + return available; +} + +void ast_logger_unregister_level(const char *name) +{ + unsigned int found = 0; + unsigned int x; + + AST_RWLIST_WRLOCK(&logchannels); + + for (x = 16; x < ARRAY_LEN(levels); x++) { + if (!levels[x]) { + continue; + } + + if (strcasecmp(levels[x], name)) { + continue; + } + + found = 1; + break; + } + + if (found) { + /* take this level out of the global_logmask, to ensure that no new log messages + * will be queued for it + */ + + global_logmask &= ~(1 << x); + + free(levels[x]); + levels[x] = NULL; + AST_RWLIST_UNLOCK(&logchannels); + + ast_debug(1, "Unregistered dynamic logger level '%s' with index %d.\n", name, x); + + update_logchannels(); + } else { + AST_RWLIST_UNLOCK(&logchannels); + } +} + diff --git a/tests/test_logger.c b/tests/test_logger.c new file mode 100644 index 0000000000000000000000000000000000000000..04b5a10587ae9f8f100914f1e0091eaae1ab6ffb --- /dev/null +++ b/tests/test_logger.c @@ -0,0 +1,209 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2009, Digium, Inc. + * + * Kevin P. Fleming <kpfleming@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Test module for the logging subsystem + * + * \author\verbatim Kevin P. Fleming <kpfleming@digium.com> \endverbatim + * + * \ingroup tests + */ + +/*** MODULEINFO + <defaultenabled>no</defaultenabled> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/app.h" +#include "asterisk/cli.h" + +struct test { + const char *name; + unsigned int x_success; + unsigned int x_failure; + unsigned int u_success; + unsigned int u_failure; +}; + +static void output_tests(struct test *tests, size_t num_tests, int fd) +{ + unsigned int x; + + for (x = 0; x < num_tests; x++) { + ast_cli(fd, "Test %d: %s\n", x + 1, tests[x].name); + ast_cli(fd, "\tExpected Successes: %d\n", tests[x].x_success); + ast_cli(fd, "\tExpected Failures: %d\n", tests[x].x_failure); + ast_cli(fd, "\tUnexpected Successes: %d\n", tests[x].u_success); + ast_cli(fd, "\tUnexpected Failures: %d\n", tests[x].u_failure); + ast_cli(fd, "Test %d Result: %s\n", x + 1, (tests[x].u_success + tests[x].u_failure) ? "FAIL" : "PASS"); + } +} + +static char *handle_cli_dynamic_level_test(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + unsigned int level; + unsigned int x; + unsigned int test; + struct test tests[] = { + { .name = "Simple register/message/unregister", + }, + { .name = "Register multiple levels", + }, + }; + + switch (cmd) { + case CLI_INIT: + e->command = "logger test dynamic"; + e->usage = "" + "Usage: logger test dynamic\n" + ""; + return NULL; + case CLI_GENERATE: + return NULL; + } + + for (test = 0; test < ARRAY_LEN(tests); test++) { + ast_cli(a->fd, "Test %d: %s.\n", test + 1, tests[test].name); + switch (test) { + case 0: + if ((level = ast_logger_register_level("test")) != -1) { + ast_cli(a->fd, "Test: got level %d\n", level); + ast_log_dynamic_level(level, "Logger Dynamic Test: Test 1\n"); + ast_logger_unregister_level("test"); + tests[test].x_success++; + } else { + ast_cli(a->fd, "Test: Failed, could not register level 'test'.\n"); + tests[test].u_failure++; + } + break; + case 1: + { + char level_name[18][8]; + + for (x = 0; x < ARRAY_LEN(level_name); x++) { + sprintf(level_name[x], "level%02d", x); + if ((level = ast_logger_register_level(level_name[x])) == -1) { + if (x < 16) { + tests[test].u_failure++; + } else { + tests[test].x_failure++; + } + level_name[x][0] = '\0'; + } else { + ast_cli(a->fd, "Test: registered '%s', got level %d\n", level_name[x], level); + if (x < 16) { + tests[test].x_success++; + } else { + tests[test].u_success++; + } + } + } + + for (x = 0; x < ARRAY_LEN(level_name); x++) { + if (!ast_strlen_zero(level_name[x])) { + ast_logger_unregister_level(level_name[x]); + } + } + } + } + } + + output_tests(tests, ARRAY_LEN(tests), a->fd); + + return CLI_SUCCESS; +} + +static char *handle_cli_performance_test(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + unsigned int level; + unsigned int test; + struct test tests[] = { + { .name = "Log 10,000 messages", + }, + }; + + switch (cmd) { + case CLI_INIT: + e->command = "logger test performance"; + e->usage = "" + "Usage: logger test performance\n" + ""; + return NULL; + case CLI_GENERATE: + return NULL; + } + + for (test = 0; test < ARRAY_LEN(tests); test++) { + ast_cli(a->fd, "Test %d: %s.\n", test + 1, tests[test].name); + switch (test) { + case 0: + if ((level = ast_logger_register_level("perftest")) != -1) { + unsigned int x; + struct timeval start, end; + int elapsed; + + ast_cli(a->fd, "Test: got level %d\n", level); + start = ast_tvnow(); + for (x = 0; x < 10000; x++) { + ast_log_dynamic_level(level, "Performance test log message\n"); + } + end = ast_tvnow(); + elapsed = ast_tvdiff_ms(end, start); + ast_cli(a->fd, "Test: 10,000 messages in %f seconds.\n", (float) elapsed / 1000); + ast_logger_unregister_level("perftest"); + tests[test].x_success++; + } else { + ast_cli(a->fd, "Test: Failed, could not register level 'perftest'.\n"); + tests[test].u_failure++; + } + break; + } + } + + output_tests(tests, ARRAY_LEN(tests), a->fd); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_logger[] = { + AST_CLI_DEFINE(handle_cli_dynamic_level_test, "Test the dynamic logger level implementation"), + AST_CLI_DEFINE(handle_cli_performance_test, "Test the logger performance"), +}; + +static int unload_module(void) +{ + ast_cli_unregister_multiple(cli_logger, ARRAY_LEN(cli_logger)); + return 0; +} + +static int load_module(void) +{ + ast_cli_register_multiple(cli_logger, ARRAY_LEN(cli_logger)); + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Logger Test Module");