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 int load_config(int reload) 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(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, "");
strcpy(master, "");
ast_mutex_lock(&lock);
if (cfg) {
var = ast_variable_browse(cfg, "mappings"); var = ast_variable_browse(cfg, "mappings");
while (var) { while (var) {
if (!ast_strlen_zero(var->name) && !ast_strlen_zero(var->value)) { if (!ast_strlen_zero(var->name) && !ast_strlen_zero(var->value)) {
if (strlen(var->value) > (sizeof(format) - 1)) struct cdr_config *sink = ast_calloc_with_stringfields(1, struct cdr_config, 1024);
ast_log(LOG_WARNING, "Format string too long, will be truncated, at line %d\n", var->lineno);
ast_copy_string(format, var->value, sizeof(format) - 1); if (!sink) {
strcat(format,"\n"); ast_log(LOG_ERROR, "Unable to allocate memory for configuration settings.\n");
snprintf(master, sizeof(master),"%s/%s/%s", ast_config_AST_LOG_DIR, name, var->name); res = -2;
if (var->next) {
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);
break; break;
} }
} else
ast_log(LOG_NOTICE, "Mapping must have both filename and format at line %d\n", var->lineno); ast_string_field_build(sink, format, "%s\n", var->value);
ast_string_field_build(sink, filename, "%s/%s/%s", ast_config_AST_LOG_DIR, name, var->name);
ast_mutex_init(&sink->lock);
AST_RWLIST_INSERT_TAIL(&sinks, sink, list);
} else {
ast_log(LOG_NOTICE, "Mapping must have both a filename and a format at line %d\n", var->lineno);
}
var = var->next; var = var->next;
} }
ast_config_destroy(cfg); ast_config_destroy(cfg);
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);
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);
/* because of the absolutely unconditional need for the AST_RWLIST_RDLOCK(&sinks);
AST_LIST_TRAVERSE(&sinks, config, list) {
FILE *out;
ast_str_reset(str);
ast_str_substitute_variables(&str, 0, &dummy, config->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(&config->lock);
/* Because of the absolutely unconditional need for the
highest reliability possible in writing billing records, highest reliability possible in writing billing records,
we open write and close the log file each time */ we open write and close the log file each time */
ast_mutex_lock(&mf_lock); if ((out = fopen(config->filename, "a"))) {
if ((mf = fopen(master, "a"))) { fputs(ast_str_buffer(str), out);
fputs(ast_str_buffer(str), mf); fflush(out); /* be particularly anal here */
fflush(mf); /* be particularly anal here */ fclose(out);
fclose(mf);
} else { } else {
ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", master, strerror(errno)); 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