Allow cdr_custom to write to multiple files instead of just one.

Up to now, cdr_custom would only accept a single filename/format from
cdr_custom.conf.  This change allows you to specify multiple filename
& format directives.


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@195165 65c4cc65-6c06-0410-ace0-fbb531ad65f3
certified/1.8.6
Sean Bright 17 years ago
parent d24179825f
commit f223598207

@ -148,6 +148,10 @@ Logger
users of this channel in the tree have been converted to LOG_NOTICE or removed users of this channel in the tree have been converted to LOG_NOTICE or removed
(in cases where the same message was already generated to another channel). (in cases where the same message was already generated to another channel).
CDR
---
* Multiple files and formats can now be specified in cdr_custom.conf.
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
--- Functionality changes from Asterisk 1.6.1 to Asterisk 1.6.2 ------------- --- Functionality changes from Asterisk 1.6.1 to Asterisk 1.6.2 -------------
------------------------------------------------------------------------------ ------------------------------------------------------------------------------

@ -1,7 +1,7 @@
/* /*
* Asterisk -- An open source telephony toolkit. * Asterisk -- An open source telephony toolkit.
* *
* Copyright (C) 1999 - 2005, Digium, Inc. * Copyright (C) 1999 - 2009, Digium, Inc.
* *
* Mark Spencer <markster@digium.com> * Mark Spencer <markster@digium.com>
* *
@ -48,103 +48,116 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/strings.h" #include "asterisk/strings.h"
#define CUSTOM_LOG_DIR "/cdr_custom" #define CUSTOM_LOG_DIR "/cdr_custom"
#define CONFIG "cdr_custom.conf"
#define DATE_FORMAT "%Y-%m-%d %T" #define DATE_FORMAT "%Y-%m-%d %T"
AST_MUTEX_DEFINE_STATIC(lock);
AST_MUTEX_DEFINE_STATIC(mf_lock);
AST_THREADSTORAGE(custom_buf); AST_THREADSTORAGE(custom_buf);
static char *name = "cdr-custom"; static char *name = "cdr-custom";
static char master[PATH_MAX]; struct cdr_config {
static char format[1024]=""; AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(filename);
AST_STRING_FIELD(format);
);
ast_mutex_t lock;
AST_RWLIST_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 load_config(int reload) static int load_config(void)
{ {
struct ast_config *cfg; struct ast_config *cfg;
struct ast_variable *var; struct ast_variable *var;
struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; struct ast_flags config_flags = { 0 };
int res = -1; int res = 0;
if ((cfg = ast_config_load("cdr_custom.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
return 0;
if (cfg == CONFIG_STATUS_FILEINVALID) { cfg = ast_config_load(CONFIG, config_flags);
ast_log(LOG_ERROR, "Invalid config file\n"); if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
return 1; ast_log(LOG_ERROR, "Unable to load " CONFIG ". Not custom CSV CDRs.\n");
return -1;
} }
strcpy(format, ""); var = ast_variable_browse(cfg, "mappings");
strcpy(master, ""); while (var) {
ast_mutex_lock(&lock); if (!ast_strlen_zero(var->name) && !ast_strlen_zero(var->value)) {
if (cfg) { struct cdr_config *sink = ast_calloc_with_stringfields(1, struct cdr_config, 1024);
var = ast_variable_browse(cfg, "mappings");
while(var) { if (!sink) {
if (!ast_strlen_zero(var->name) && !ast_strlen_zero(var->value)) { ast_log(LOG_ERROR, "Unable to allocate memory for configuration settings.\n");
if (strlen(var->value) > (sizeof(format) - 1)) res = -2;
ast_log(LOG_WARNING, "Format string too long, will be truncated, at line %d\n", var->lineno); break;
ast_copy_string(format, var->value, sizeof(format) - 1); }
strcat(format,"\n");
snprintf(master, sizeof(master),"%s/%s/%s", ast_config_AST_LOG_DIR, name, var->name); ast_string_field_build(sink, format, "%s\n", var->value);
if (var->next) { ast_string_field_build(sink, filename, "%s/%s/%s", ast_config_AST_LOG_DIR, name, var->name);
ast_log(LOG_NOTICE, "Sorry, only one mapping is supported at this time, mapping '%s' will be ignored at line %d.\n", var->next->name, var->next->lineno); ast_mutex_init(&sink->lock);
break;
} AST_RWLIST_INSERT_TAIL(&sinks, sink, list);
} else } else {
ast_log(LOG_NOTICE, "Mapping must have both filename and format at line %d\n", var->lineno); ast_log(LOG_NOTICE, "Mapping must have both a filename and a format at line %d\n", var->lineno);
var = var->next;
} }
ast_config_destroy(cfg); var = var->next;
res = 0;
} else {
if (reload)
ast_log(LOG_WARNING, "Failed to reload configuration file.\n");
else
ast_log(LOG_WARNING, "Failed to load configuration file. Module not activated.\n");
} }
ast_mutex_unlock(&lock); ast_config_destroy(cfg);
return res; return res;
} }
static int custom_log(struct ast_cdr *cdr) static int custom_log(struct ast_cdr *cdr)
{ {
FILE *mf = NULL;
struct ast_channel dummy; struct ast_channel dummy;
struct ast_str *str; struct ast_str *str;
struct cdr_config *config;
/* Abort if no master file is specified */
if (ast_strlen_zero(master)) {
return 0;
}
/* Batching saves memory management here. Otherwise, it's the same as doing an allocation and free each time. */ /* Batching saves memory management here. Otherwise, it's the same as doing an allocation and free each time. */
if (!(str = ast_str_thread_get(&custom_buf, 16))) { if (!(str = ast_str_thread_get(&custom_buf, 16))) {
return -1; return -1;
} }
ast_str_reset(str);
/* Quite possibly the first use of a static struct ast_channel, we need it so the var funcs will work */ /* Quite possibly the first use of a static struct ast_channel, we need it so the var funcs will work */
memset(&dummy, 0, sizeof(dummy)); memset(&dummy, 0, sizeof(dummy));
dummy.cdr = cdr; dummy.cdr = cdr;
ast_str_substitute_variables(&str, 0, &dummy, format);
AST_RWLIST_RDLOCK(&sinks);
/* because of the absolutely unconditional need for the
highest reliability possible in writing billing records, AST_LIST_TRAVERSE(&sinks, config, list) {
we open write and close the log file each time */ FILE *out;
ast_mutex_lock(&mf_lock);
if ((mf = fopen(master, "a"))) { ast_str_reset(str);
fputs(ast_str_buffer(str), mf); ast_str_substitute_variables(&str, 0, &dummy, config->format);
fflush(mf); /* be particularly anal here */
fclose(mf); /* Even though we have a lock on the list, we could be being chased by
} else { another thread and this lock ensures that we won't step on anyone's
ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", master, strerror(errno)); toes. Once each CDR backend gets it's own thread, this lock can be
removed. */
ast_mutex_lock(&config->lock);
/* Because of the absolutely unconditional need for the
highest reliability possible in writing billing records,
we open write and close the log file each time */
if ((out = fopen(config->filename, "a"))) {
fputs(ast_str_buffer(str), out);
fflush(out); /* be particularly anal here */
fclose(out);
} else {
ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", config->filename, strerror(errno));
}
ast_mutex_unlock(&config->lock);
} }
ast_mutex_unlock(&mf_lock);
AST_RWLIST_UNLOCK(&sinks);
return 0; return 0;
} }
@ -152,25 +165,42 @@ static int custom_log(struct ast_cdr *cdr)
static int unload_module(void) static int unload_module(void)
{ {
ast_cdr_unregister(name); ast_cdr_unregister(name);
if (AST_RWLIST_WRLOCK(&sinks)) {
ast_cdr_register(name, ast_module_info->description, custom_log);
ast_log(LOG_ERROR, "Unable to lock sink list. Unload failed.\n");
return -1;
}
free_config();
AST_RWLIST_UNLOCK(&sinks);
return 0; return 0;
} }
static int load_module(void) static int load_module(void)
{ {
int res = 0; if (AST_RWLIST_WRLOCK(&sinks)) {
ast_log(LOG_ERROR, "Unable to lock sink list. Load failed.\n");
return AST_MODULE_LOAD_FAILURE;
}
if (!load_config(0)) { load_config();
res = ast_cdr_register(name, ast_module_info->description, custom_log); AST_RWLIST_UNLOCK(&sinks);
if (res) ast_cdr_register(name, ast_module_info->description, custom_log);
ast_log(LOG_ERROR, "Unable to register custom CDR handling\n"); return AST_MODULE_LOAD_SUCCESS;
return res;
} else
return AST_MODULE_LOAD_DECLINE;
} }
static int reload(void) static int reload(void)
{ {
return load_config(1); if (AST_RWLIST_WRLOCK(&sinks)) {
ast_log(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 Comma Separated Values CDR Backend", AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Customizable Comma Separated Values CDR Backend",

@ -1,10 +1,11 @@
; ;
; Mappings for custom config file ; Mappings for custom config file
; ;
; to get your csv output in a format tailored to your liking, uncomment the following ; To get your CSV output in a format tailored to your liking, uncomment the
; and look for the output in the cdr-custom/Master.csv file (usually in /var/log/asterisk). ; following look for the output in the cdr-custom/Master.csv file (usually
; ; in /var/log/asterisk).
; ;
;[mappings] ;[mappings]
;Master.csv => "${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)}" ;Master.csv => "${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)}"
;Simple.csv => "${EPOCH}","${CDR(src)}","${CDR(dst)}"

Loading…
Cancel
Save