From 78c6aec64bbe3382ea89fb0f5c0f1bdaa0685676 Mon Sep 17 00:00:00 2001 From: seanbright Date: Fri, 26 Jun 2009 22:08:05 +0000 Subject: Add a new module, cdr_syslog, which allows writing CDRs to syslog. The original patch for this was written by Brett Bryant, and I split it out into it's own module. (closes issue #12876) Reported by: bbryant Patches: 06162008_cdr_custom_syslog.diff uploaded by bbryant (license 36) 05212009_cdr_syslog.patch uploaded by seanbright (license 71) Tested by: seanbright Review: https://reviewboard.asterisk.org/r/297/ git-svn-id: http://svn.digium.com/svn/asterisk/trunk@203846 f38db490-d61c-443f-a65b-d21fe96a405b --- CHANGES | 2 + build_tools/menuselect-deps.in | 1 + cdr/cdr_syslog.c | 277 +++++++++++++++++++++++++++++++++++++++++ configs/cdr_syslog.conf.sample | 81 ++++++++++++ configure | 11 +- configure.ac | 5 + 6 files changed, 375 insertions(+), 2 deletions(-) create mode 100644 cdr/cdr_syslog.c create mode 100644 configs/cdr_syslog.conf.sample diff --git a/CHANGES b/CHANGES index 95b26bf3c..721411ede 100644 --- a/CHANGES +++ b/CHANGES @@ -188,6 +188,8 @@ CDR linkedid is based on uniqueID, but spreads to other channels as transfers, dials, etc are performed. Thus the peices of CDR can be grouped into multilegged sets. * Multiple files and formats can now be specified in cdr_custom.conf. + * cdr_syslog has been added which allows CDRs to be written directly to syslog. + See configs/cdr_syslog.conf.sample for more information. Calendaring for Asterisk ------------------------ diff --git a/build_tools/menuselect-deps.in b/build_tools/menuselect-deps.in index 7204162e1..2c0b2f9a3 100644 --- a/build_tools/menuselect-deps.in +++ b/build_tools/menuselect-deps.in @@ -49,6 +49,7 @@ SQLITE=@PBX_SQLITE@ SS7=@PBX_SS7@ OPENSSL=@PBX_OPENSSL@ SUPPSERV=@PBX_SUPPSERV@ +SYSLOG=@PBX_SYSLOG@ TONEZONE=@PBX_TONEZONE@ UNIXODBC=@PBX_UNIXODBC@ USB=@PBX_USB@ diff --git a/cdr/cdr_syslog.c b/cdr/cdr_syslog.c new file mode 100644 index 000000000..9455c7b24 --- /dev/null +++ b/cdr/cdr_syslog.c @@ -0,0 +1,277 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2009, malleable, LLC. + * + * 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 cdr_syslog.c + * + * \brief syslog CDR logger + * \author Sean Bright + * + * See also + * \arg \ref Config_cdr + * \ingroup cdr_drivers + */ + +/*** MODULEINFO + syslog +***/ + +#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 + +#include "asterisk/syslog.h" + +static const char 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 priority; + ast_mutex_t lock; + AST_LIST_ENTRY(cdr_config) list; +}; + +static AST_RWLIST_HEAD_STATIC(sinks, cdr_config); + +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; + } + + if (!(dummy = ast_dummy_channel_alloc())) { + 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->priority, "%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(int reload) +{ + struct ast_config *cfg; + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + int default_facility = LOG_LOCAL4; + int default_priority = LOG_INFO; + const char *catg = NULL, *tmp; + + cfg = ast_config_load(CONFIG, config_flags); + if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) { + ast_log(AST_LOG_ERROR, + "Unable to load %s. Not logging custom CSV CDRs to syslog.\n", CONFIG); + return -1; + } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) { + return 0; + } + + if (reload) { + free_config(); + } + + if (!(ast_strlen_zero(tmp = ast_variable_retrieve(cfg, "general", "facility")))) { + int facility = ast_syslog_facility(tmp); + if (facility < 0) { + ast_log(AST_LOG_WARNING, + "Invalid facility '%s' specified, defaulting to '%s'\n", + tmp, ast_syslog_facility_name(default_facility)); + } else { + default_facility = facility; + } + } + + if (!(ast_strlen_zero(tmp = ast_variable_retrieve(cfg, "general", "priority")))) { + int priority = ast_syslog_priority(tmp); + if (priority < 0) { + ast_log(AST_LOG_WARNING, + "Invalid priority '%s' specified, defaulting to '%s'\n", + tmp, ast_syslog_priority_name(default_priority)); + } else { + default_priority = priority; + } + } + + while ((catg = ast_category_browse(cfg, catg))) { + struct cdr_config *sink; + + if (!strcasecmp(catg, "general")) { + 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_mutex_init(&sink->lock); + 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 = ast_syslog_facility(tmp); + if (facility < 0) { + ast_log(AST_LOG_WARNING, + "Invalid facility '%s' specified for '%s,' defaulting to '%s'\n", + tmp, catg, ast_syslog_facility_name(default_facility)); + } else { + sink->facility = facility; + } + } + + if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "priority"))) { + sink->priority = default_priority; + } else { + int priority = ast_syslog_priority(tmp); + if (priority < 0) { + ast_log(AST_LOG_WARNING, + "Invalid priority '%s' specified for '%s,' defaulting to '%s'\n", + tmp, catg, ast_syslog_priority_name(default_priority)); + } else { + sink->priority = priority; + } + } + + AST_RWLIST_INSERT_TAIL(&sinks, sink, list); + } + + ast_config_destroy(cfg); + + return AST_RWLIST_EMPTY(&sinks) ? -1 : 0; +} + +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) +{ + int res; + + if (AST_RWLIST_WRLOCK(&sinks)) { + ast_log(AST_LOG_ERROR, "Unable to lock sink list. Load failed.\n"); + return AST_MODULE_LOAD_DECLINE; + } + + res = load_config(0); + AST_RWLIST_UNLOCK(&sinks); + if (res) { + return AST_MODULE_LOAD_DECLINE; + } + ast_cdr_register(name, ast_module_info->description, syslog_log); + return AST_MODULE_LOAD_SUCCESS; +} + +static int reload(void) +{ + int res; + if (AST_RWLIST_WRLOCK(&sinks)) { + ast_log(AST_LOG_ERROR, "Unable to lock sink list. Load failed.\n"); + return AST_MODULE_LOAD_DECLINE; + } + + free_config(); + res = load_config(1); + AST_RWLIST_UNLOCK(&sinks); + + return res ? AST_MODULE_LOAD_DECLINE : 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, +); diff --git a/configs/cdr_syslog.conf.sample b/configs/cdr_syslog.conf.sample new file mode 100644 index 000000000..70c95680a --- /dev/null +++ b/configs/cdr_syslog.conf.sample @@ -0,0 +1,81 @@ +; +; Asterisk Call Detail Records (CDR) - Syslog Backend +; + +; The cdr_syslog module writes CDRs using the facilities provided by syslog. +; +; Not only must you configure cdr_syslog from this file (cdr_syslog.conf) but +; you will also need to make changes to your /etc/syslog.conf before CDRs will +; be written to syslog. +; +; As an example, you can add the following to /etc/syslog.conf: +; +; local4.info /var/log/asterisk-cdr.log +; +; And then instruct syslogd to re-read the configuration file by sending it a +; HUP signal. On Linux this can be done like this: +; +; kill -HUP `cat /var/run/syslogd.pid` +; +; Finally, you will need to uncomment the [cdr-simple] section below, and restart +; Asterisk. When calls are placed, you should start seeing records appear in +; /var/log/asterisk-cdr.log. + +[general] +; Facility +; +; The 'facility' keyword specifies the syslog facility to use when writing out +; CDRs. +; +; Accepted values: One of the following: +; user, local0, local1, local2, local3, local4, local5, local6 +; and local7. +; +; Note: Depending on your platform, the following may also be +; available: +; auth, authpriv, cron, daemon, ftp, kern, lpr, mail, +; news, syslog, and uucp. +; +; Default value: local4 + +;facility=local0 + +; Priority +; +; Use the 'priority' keyword to select which of the syslog priority levels to +; use when logging CDRs. +; +; Accepted values: One of the following: +; alert, crit, debug, emerg, err, info, notice, warning +; Default value: info + +;priority=warn + +; Note: The settings for 'facility' and 'priority' in the [general] section +; define the default values for all of the logging locations created +; below in separate sections. + +;[cdr-master] +;facility = local5 +;priority = debug + +; Template +; +; The 'template' value allows you to specify a custom format for messages +; written to syslog. This is similar to how cdr_custom is configured. +; +; Allowed values: A diaplan style string. +; Default value: None, this is required field. +; +; Note: Because of the way substitution is done, the only meaningful values +; available when the record is logged are those available via the CDR() +; dialplan function. All other channel variables will be unavailable. + +;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] + +; Since we don't specify a facility or priority for this logging location, the +; records will use the defaults specified in the [general] section. + +;template = "We received a call from ${CDR(src)}" diff --git a/configure b/configure index 09db92696..37be13d34 100755 --- a/configure +++ b/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.ac Revision: 201137 . +# From configure.ac Revision: 203569 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.61 for asterisk 1.6. # @@ -1046,6 +1046,7 @@ PBX_SYSLOG_FACILITY_LOG_MAIL PBX_SYSLOG_FACILITY_LOG_NEWS PBX_SYSLOG_FACILITY_LOG_SYSLOG PBX_SYSLOG_FACILITY_LOG_UUCP +PBX_SYSLOG LTLIBOBJS' ac_subst_files='' ac_precious_vars='build_alias @@ -54504,6 +54505,8 @@ fi +PBX_SYSLOG=0 + if test "${ac_cv_header_syslog_h}" = "yes"; then # syslog facilities @@ -55342,8 +55345,11 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi + PBX_SYSLOG=1 fi + + ac_config_files="$ac_config_files build_tools/menuselect-deps makeopts channels/h323/Makefile" @@ -56592,10 +56598,11 @@ PBX_SYSLOG_FACILITY_LOG_MAIL!$PBX_SYSLOG_FACILITY_LOG_MAIL$ac_delim PBX_SYSLOG_FACILITY_LOG_NEWS!$PBX_SYSLOG_FACILITY_LOG_NEWS$ac_delim PBX_SYSLOG_FACILITY_LOG_SYSLOG!$PBX_SYSLOG_FACILITY_LOG_SYSLOG$ac_delim PBX_SYSLOG_FACILITY_LOG_UUCP!$PBX_SYSLOG_FACILITY_LOG_UUCP$ac_delim +PBX_SYSLOG!$PBX_SYSLOG$ac_delim LTLIBOBJS!$LTLIBOBJS$ac_delim _ACEOF - if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 40; then + if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 41; then break elif $ac_last_try; then { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5 diff --git a/configure.ac b/configure.ac index 27e1323b7..4ea4236a5 100644 --- a/configure.ac +++ b/configure.ac @@ -1747,6 +1747,8 @@ AC_SUBST([GENERIC_ODBC_LIB]) AC_SUBST([GENERIC_ODBC_INCLUDE]) AC_SUBST([PBX_GENERIC_ODBC]) +PBX_SYSLOG=0 + if test "${ac_cv_header_syslog_h}" = "yes"; then # syslog facilities AST_C_DEFINE_CHECK([SYSLOG_FACILITY_LOG_AUTH], [LOG_AUTH], [syslog.h]) @@ -1760,8 +1762,11 @@ if test "${ac_cv_header_syslog_h}" = "yes"; then AST_C_DEFINE_CHECK([SYSLOG_FACILITY_LOG_NEWS], [LOG_NEWS], [syslog.h]) AST_C_DEFINE_CHECK([SYSLOG_FACILITY_LOG_SYSLOG], [LOG_SYSLOG], [syslog.h]) AST_C_DEFINE_CHECK([SYSLOG_FACILITY_LOG_UUCP], [LOG_UUCP], [syslog.h]) + PBX_SYSLOG=1 fi +AC_SUBST([PBX_SYSLOG]) + AC_CONFIG_FILES([build_tools/menuselect-deps makeopts channels/h323/Makefile]) AST_CHECK_MANDATORY -- cgit v1.2.3