Index: configs/cdr_syslog.conf.sample =================================================================== --- configs/cdr_syslog.conf.sample (revision 0) +++ configs/cdr_syslog.conf.sample (revision 0) @@ -0,0 +1,60 @@ +; +; Syslog mappings for custom CDR logging +; +; The cdr_syslog module sends CDR records to syslog +; +; XXX: Documentation will be finished before commit. You should be able to do +; something like this in /etc/syslog.conf: +; +; local0.info /var/log/asterisk-cdr.log +; +; And uncomment the [cdr-master] section and be good to go. +; + +[general] +; The syslog 'facility' to use when logging messages. This can be any of the +; following: +; +; auth +; authpriv +; cron +; daemon +; ftp +; kern +; local0, local1, local2, local3, local4, local5, local6, local7 +; lpr +; mail +; news +; syslog +; user +; uucp +; +; The default is 'daemon' +; +;facility=local0 + +; +; The level signifies the importance of the logged message, but we consider +; these informational (info) messages by default. We allow customization so that +; your syslog.conf settings can more easily filter CDR logging. This can be any +; of the following: +; +; emerg +; alert +; crit +; error +; warning +; notice +; info +; debug +; +; The default is 'info' +;level=warn + +;[cdr-master] +;facility = local0 +;level = info +;template = "${CDR(clid)}","${CDR(src)}","${CDR(dst)}","${CDR(dcontext)}","${CDR(channel)}","${CDR(dstchannel)}","${CDR(lastapp)}","${CDR(lastdata)}","${CDR(start)}","${CDR(answer)}","${CDR(end)}","${CDR(duration)}","${CDR(billsec)}","${CDR(disposition)}","${CDR(amaflags)}","${CDR(accountcode)}","${CDR(uniqueid)}","${CDR(userfield)}" + +;[cdr-simple] +;template = "${EPOCH}","${CDR(src)}","${CDR(dst)}" Index: cdr/cdr_syslog.c =================================================================== --- cdr/cdr_syslog.c (revision 0) +++ cdr/cdr_syslog.c (revision 0) @@ -0,0 +1,344 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2009, Sean Bright + * + * 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 syslog CDR logger + * + * See also + * \arg \ref Config_cdr + * \ingroup cdr_drivers + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/cdr.h" +#include "asterisk/pbx.h" + +#include + +#define CONFIG "cdr_syslog.conf" + +AST_THREADSTORAGE(syslog_buf); + +static const char name[] = "cdr-syslog"; + +struct cdr_config { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(ident); + AST_STRING_FIELD(format); + ); + int facility; + int level; + ast_mutex_t lock; + AST_LIST_ENTRY(cdr_config) list; +}; + +static AST_RWLIST_HEAD_STATIC(sinks, cdr_config); + +struct name_to_id_map { + const char * const name; + int value; +}; + +static const int INVALID_NAME = ~0; + +static const struct name_to_id_map facilities[] = { + { "auth", LOG_AUTH }, + { "authpriv", LOG_AUTHPRIV }, + { "cron", LOG_CRON }, + { "daemon", LOG_DAEMON }, + { "ftp", LOG_FTP }, + { "kern", LOG_KERN }, + { "local0", LOG_LOCAL0 }, + { "local1", LOG_LOCAL1 }, + { "local2", LOG_LOCAL2 }, + { "local3", LOG_LOCAL3 }, + { "local4", LOG_LOCAL4 }, + { "local5", LOG_LOCAL5 }, + { "local6", LOG_LOCAL6 }, + { "local7", LOG_LOCAL7 }, + { "lpr", LOG_LPR }, + { "mail", LOG_MAIL }, + { "news", LOG_NEWS }, + { "syslog", LOG_SYSLOG }, + { "user", LOG_USER }, + { "uucp", LOG_UUCP }, +}; + +static const struct name_to_id_map levels[] = { + { "emerg", LOG_EMERG }, + { "alert", LOG_ALERT }, + { "crit", LOG_CRIT }, + { "err", LOG_ERR }, + { "warning", LOG_WARNING }, + { "notice", LOG_NOTICE }, + { "info", LOG_INFO }, + { "debug", LOG_DEBUG }, +}; + +static int map_facility_name(const char *facility) +{ + size_t i; + for (i = 0; i < ARRAY_LEN(facilities); i++) { + if (!strcasecmp(facility, facilities[i].name)) { + return facilities[i].value; + } + } + return INVALID_NAME; +} + +static const char *map_facility_value(int facility) +{ + size_t i; + for (i = 0; i < ARRAY_LEN(facilities); i++) { + if (facility == facilities[i].value) { + return facilities[i].name; + } + } + return NULL; +} + +static int map_level_name(const char *level) +{ + size_t i; + for (i = 0; i < ARRAY_LEN(levels); i++) { + if (!strcasecmp(level, levels[i].name)) { + return levels[i].value; + } + } + return INVALID_NAME; +} + +static const char *map_level_value(int level) +{ + size_t i; + for (i = 0; i < ARRAY_LEN(levels); i++) { + if (level == levels[i].value) { + return levels[i].name; + } + } + return NULL; +} + +static void free_config(void) +{ + struct cdr_config *sink; + while ((sink = AST_RWLIST_REMOVE_HEAD(&sinks, list))) { + ast_mutex_destroy(&sink->lock); + ast_free(sink); + } +} + +static int syslog_log(struct ast_cdr *cdr) +{ + struct ast_channel *dummy; + struct ast_str *str; + struct cdr_config *sink; + + /* Batching saves memory management here. Otherwise, it's the same as doing an allocation and free each time. */ + if (!(str = ast_str_thread_get(&syslog_buf, 16))) { + return -1; + } + + dummy = ast_channel_alloc(0, 0, "", "", "", "", "", 0, "Substitution/%p", cdr); + + if (!dummy) { + ast_log(AST_LOG_ERROR, "Unable to allocate channel for variable substitution.\n"); + return -1; + } + + /* We need to dup here since the cdr actually belongs to the other channel, + so when we release this channel we don't want the CDR getting cleaned + up prematurely. */ + dummy->cdr = ast_cdr_dup(cdr); + + AST_RWLIST_RDLOCK(&sinks); + + AST_LIST_TRAVERSE(&sinks, sink, list) { + + ast_str_substitute_variables(&str, 0, dummy, sink->format); + + /* Even though we have a lock on the list, we could be being chased by + another thread and this lock ensures that we won't step on anyone's + toes. Once each CDR backend gets it's own thread, this lock can be + removed. */ + ast_mutex_lock(&sink->lock); + + openlog(sink->ident, LOG_CONS, sink->facility); + syslog(sink->level, "%s", ast_str_buffer(str)); + closelog(); + + ast_mutex_unlock(&sink->lock); + } + + AST_RWLIST_UNLOCK(&sinks); + + ast_channel_release(dummy); + + return 0; +} + +static int load_config(void) +{ + struct ast_config *cfg; + struct ast_flags config_flags = { 0 }; + int default_facility = LOG_LOCAL4; + int default_level = LOG_INFO; + int res = 0; + const char *catg, *tmp; + + cfg = ast_config_load(CONFIG, config_flags); + if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) { + ast_log(AST_LOG_ERROR, "Unable to load " CONFIG ". Not logging custom CSV CDRs to syslog.\n"); + return -1; + } + + if (!(ast_strlen_zero(tmp = ast_variable_retrieve(cfg, "general", "facility")))) { + int facility = map_facility_name(tmp); if (facility == INVALID_NAME) { + ast_log(AST_LOG_WARNING, "Invalid facility '%s' specified, defaulting to '%s'\n", + tmp, + map_facility_value(default_facility)); + } else { + default_facility = facility; + } + } + + if (!(ast_strlen_zero(tmp = ast_variable_retrieve(cfg, "general", "level")))) { + int level = map_level_name(tmp); + if (level == INVALID_NAME) { + ast_log(AST_LOG_WARNING, "Invalid level '%s' specified, defaulting to '%s'\n", + tmp, + map_level_value(default_level)); + } else { + default_level = level; + } + } + + for (catg = ast_category_browse(cfg, NULL); catg; catg = ast_category_browse(cfg, catg)) { + struct ast_variable *var; + struct cdr_config *sink; + + if (!strcasecmp(catg, "general")) { + continue; + } + + if (!(var = ast_variable_browse(cfg, catg))) { + continue; + } + + if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "template"))) { + ast_log(AST_LOG_WARNING, "No 'template' parameter found for '%s'. Skipping.\n", catg); + continue; + } + + sink = ast_calloc_with_stringfields(1, struct cdr_config, 1024); + + if (!sink) { + ast_log(AST_LOG_ERROR, "Unable to allocate memory for configuration settings.\n"); + free_config(); + break; + } + + ast_string_field_set(sink, ident, catg); + ast_string_field_set(sink, format, tmp); + + if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "facility"))) { + sink->facility = default_facility; + } else { + int facility = map_facility_name(tmp); + if (facility == INVALID_NAME) { + ast_log(AST_LOG_WARNING, "Invalid facility '%s' specified for '%s,' defaulting to '%s'\n", + tmp, + catg, + map_facility_value(default_facility)); + } else { + sink->facility = facility; + } + } + + if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "level"))) { + sink->level = default_level; + } else { + int level = map_level_name(tmp); + if (level == INVALID_NAME) { + ast_log(AST_LOG_WARNING, "Invalid level '%s' specified for '%s,' defaulting to '%s'\n", + tmp, + catg, + map_level_value(default_level)); + } else { + sink->level = level; + } + } + + AST_RWLIST_INSERT_TAIL(&sinks, sink, list); + } + + ast_config_destroy(cfg); + + return res; +} + +static int unload_module(void) +{ + ast_cdr_unregister(name); + + if (AST_RWLIST_WRLOCK(&sinks)) { + ast_cdr_register(name, ast_module_info->description, syslog_log); + ast_log(AST_LOG_ERROR, "Unable to lock sink list. Unload failed.\n"); + return -1; + } + + free_config(); + AST_RWLIST_UNLOCK(&sinks); + return 0; +} + +static enum ast_module_load_result load_module(void) +{ + if (AST_RWLIST_WRLOCK(&sinks)) { + ast_log(AST_LOG_ERROR, "Unable to lock sink list. Load failed.\n"); + return AST_MODULE_LOAD_FAILURE; + } + + load_config(); + AST_RWLIST_UNLOCK(&sinks); + ast_cdr_register(name, ast_module_info->description, syslog_log); + return AST_MODULE_LOAD_SUCCESS; +} + +static int reload(void) +{ + if (AST_RWLIST_WRLOCK(&sinks)) { + ast_log(AST_LOG_ERROR, "Unable to lock sink list. Load failed.\n"); + return AST_MODULE_LOAD_FAILURE; + } + + free_config(); + load_config(); + AST_RWLIST_UNLOCK(&sinks); + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Customizable syslog CDR Backend", + .load = load_module, + .unload = unload_module, + .reload = reload, + );