dect
/
asterisk
Archived
13
0
Fork 0

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: http://svn.digium.com/svn/asterisk/trunk@195165 f38db490-d61c-443f-a65b-d21fe96a405b
This commit is contained in:
seanbright 2009-05-18 14:54:43 +00:00
parent 924053a6d4
commit e586239816
3 changed files with 112 additions and 77 deletions

View File

@ -148,6 +148,10 @@ Logger
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).
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 -------------
------------------------------------------------------------------------------

View File

@ -1,7 +1,7 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 1999 - 2005, Digium, Inc.
* Copyright (C) 1999 - 2009, Digium, Inc.
*
* Mark Spencer <markster@digium.com>
*
@ -48,103 +48,116 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/strings.h"
#define CUSTOM_LOG_DIR "/cdr_custom"
#define DATE_FORMAT "%Y-%m-%d %T"
AST_MUTEX_DEFINE_STATIC(lock);
AST_MUTEX_DEFINE_STATIC(mf_lock);
#define CONFIG "cdr_custom.conf"
#define DATE_FORMAT "%Y-%m-%d %T"
AST_THREADSTORAGE(custom_buf);
static char *name = "cdr-custom";
static char master[PATH_MAX];
static char format[1024]="";
struct cdr_config {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(filename);
AST_STRING_FIELD(format);
);
ast_mutex_t lock;
AST_RWLIST_ENTRY(cdr_config) list;
};
static int load_config(int reload)
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(void)
{
struct ast_config *cfg;
struct ast_variable *var;
struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
int res = -1;
struct ast_flags config_flags = { 0 };
int res = 0;
if ((cfg = ast_config_load("cdr_custom.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
return 0;
if (cfg == CONFIG_STATUS_FILEINVALID) {
ast_log(LOG_ERROR, "Invalid config file\n");
return 1;
cfg = ast_config_load(CONFIG, config_flags);
if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
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");
while(var) {
if (!ast_strlen_zero(var->name) && !ast_strlen_zero(var->value)) {
if (strlen(var->value) > (sizeof(format) - 1))
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);
strcat(format,"\n");
snprintf(master, sizeof(master),"%s/%s/%s", ast_config_AST_LOG_DIR, name, var->name);
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;
}
} else
ast_log(LOG_NOTICE, "Mapping must have both filename and format at line %d\n", var->lineno);
var = var->next;
var = ast_variable_browse(cfg, "mappings");
while (var) {
if (!ast_strlen_zero(var->name) && !ast_strlen_zero(var->value)) {
struct cdr_config *sink = ast_calloc_with_stringfields(1, struct cdr_config, 1024);
if (!sink) {
ast_log(LOG_ERROR, "Unable to allocate memory for configuration settings.\n");
res = -2;
break;
}
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);
}
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");
var = var->next;
}
ast_mutex_unlock(&lock);
ast_config_destroy(cfg);
return res;
}
static int custom_log(struct ast_cdr *cdr)
{
FILE *mf = NULL;
struct ast_channel dummy;
struct ast_str *str;
/* Abort if no master file is specified */
if (ast_strlen_zero(master)) {
return 0;
}
struct cdr_config *config;
/* 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))) {
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 */
memset(&dummy, 0, sizeof(dummy));
dummy.cdr = cdr;
ast_str_substitute_variables(&str, 0, &dummy, format);
/* 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 */
ast_mutex_lock(&mf_lock);
if ((mf = fopen(master, "a"))) {
fputs(ast_str_buffer(str), mf);
fflush(mf); /* be particularly anal here */
fclose(mf);
} else {
ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", master, strerror(errno));
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,
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;
}
@ -152,25 +165,42 @@ static int custom_log(struct ast_cdr *cdr)
static int unload_module(void)
{
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;
}
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)) {
res = ast_cdr_register(name, ast_module_info->description, custom_log);
if (res)
ast_log(LOG_ERROR, "Unable to register custom CDR handling\n");
return res;
} else
return AST_MODULE_LOAD_DECLINE;
load_config();
AST_RWLIST_UNLOCK(&sinks);
ast_cdr_register(name, ast_module_info->description, custom_log);
return AST_MODULE_LOAD_SUCCESS;
}
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",

View File

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