Files
asterisk/res/res_cdrel_custom.c
T
George Joseph b3f6728a28 res_cdrel_custom: Resolve several formatting issues.
Several issues are resolved:

* Internally, floats were used for timestamp values but this could result
in wrapping so they've been changed to doubles.

* Historically, the default CEL eventtime format is `<seconds>.<microseconds>`
with `<microseconds>` always being 6 digits.  This should have continued to be
the case but res_cdrel_custom wasn't checking the `dateformat` setting in
cel.conf and was defaulting to `%F %T`.  res_cdrel_custom now gets the default
date format from cel.conf, which will be whatever the `dateformat` parameter
is set to or `<seconds>.<microseconds>` if not set.

* The timeval field formatter for both CDR and CEL wasn't handling custom
strftime format strings correctly.  This is now fixed so you should be able
to specifiy custom strftime format strings for the CEL `eventtime` and CDR
`start`, `answer` and `end` fields.  For example: `eventtime(%FT%T%z)`.

Resolves: #1844
Resolves: #1845
2026-04-01 13:26:01 -06:00

259 lines
8.5 KiB
C

/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2026, Sangoma Technologies Corporation
*
* George Joseph <gjoseph@sangoma.com>
*
* 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
* \author George Joseph <gjoseph@sangoma.com>
*
* \brief Common logic for the CDR and CEL Custom Backends
*
* All source files are in the res/cdrel_custom directory.
*
* "config.c": Contains common configuration file parsing the ultimate goal
* of which is to create a vector of cdrel_config structures for each of
* the cdr_custom, cdr_sqlite3_custom, cel_custom and cel_sqlite3_custom
* modules. Each cdrel_config object represents an output file defined in
* their respective config files. Each one contains a vector of cdrel_field
* objects, one for each field in the output record, plus settings like
* the output file name, backend type (text file or database), config
* type ((legacy or advanced), the field separator and quote character to
* use.
*
* Each cdrel_field object contains an abstract field id that points to
* a ast_cdr structure member or CEL event field id along with an input
* type and an output type. The registry of cdrel_fields is located in
* registry.c.
*
* "loggers.c": Contains the common "cdrel_logger" entrypoint that the
* individual modules call to log a record. It takes the module's
* cdrel_configs vector and the record to log it got from the core
* cel.c and cdr.c. It then looks up and runs the logger implementation
* based on the backend type (text file or database) and config type
* (legacy or advanced).
*
* "getters_cdr.c", "getters_cel.c": Contain the getters that retrieve
* values from the ast_cdr or ast_event structures based on the field
* id and input type defined for that field and create a cdrel_value
* wrapper object for it.
*
* "writers.c": Contains common backend writers for the text file and
* database backends.
*
* The load-time flow...
*
* Each of the individual cdr/cel custom modules call the common
* cdrel_load_module function with their backend_type, record_type
* (cdr or cel), config file name, and the logging callback that
* should be registered with the core cdr or cel facility.
*
* cdrel_load_module calls the config load function appropriate for
* the backend type, each of which parses the config file and, if
* successful, registers the calling module with the cdr or cel core
* and creates a vector of cdrel_config objects that is passed
* back to the calling module. That vector contains the context for
* all future operations.
*
* The run-time flow...
*
* The core cdr and cel modules use their registries of backends and call
* the callback function registered by the 4 cdr and cel custom modules.
* No changes there.
*
* Each of those modules call the common cdrel_logger function with their
* cdrel_configs vector and the actual ast_cdr or ast_event structure to log.
* The cdrel_logger function iterates over the cdrel_configs vector and for
* each invokes the logger implementation specific to the backend type
* (text file or database) and config type (legacy or advanced).
*
* For legacy config types, the logger implementation simply calls
* ast_str_substitute_variables() on the whole opaque format and writes
* the result to the text file or database.
*
* For advanced configs, the logger implementation iterates over each field
* in the cdrel_config's fields vector and for each, calls the appropriate
* getter based on the record type (cdr or cel) and field id. Each getter
* call returns a cdrel_value object which is then passed to a field formatter
* looked up based on the field's data type (string, int32, etc). The formatter
* is also passed the cdrel_config object and the desired output type and
* returns the final value in another cdrel_value object formatted with any
* quoting, etc. needed. The logger accumulates the output cdrel_values
* (which are all now strings) in another vector and after all fields have
* been processed, hands the vector over to one of the backend writers.
*
* The backend writer concatenates the cdrel_values into an output record
* using the config's separator setting and writes it to the text file
* or database. For the JSON output format, it creates a simple
* name/value pair output record.
*
* The identification of field data types, field ids, record types and
* backend types is all done at config load time and saved in the
* cdrel_config and cdrel_field objects. The callbacks for getters, formatters
* and writers are also loaded when the res_cdrel_custom module loads
* and stored in arrays indexed by their enum values. The result is that
* at run time, simple array indexing is all that's needed to get the
* proper getter, formatter and writer for any logging request.
*
*/
/*** MODULEINFO
<depend>sqlite3</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
#include "asterisk/module.h"
#include "cdrel_custom/cdrel.h"
/*
* Populated by cdrel_custom/getters_cdr.c and cdrel_custom/getters_cel.c.
*/
cdrel_field_getter cdrel_field_getters[cdrel_record_type_end][cdrel_data_type_end];
/*
* Populated by cdrel_custom/formatters.c.
*/
cdrel_field_formatter cdrel_field_formatters[cdrel_data_type_end];
/*
* Populated by cdrel_custom/writers.c.
*/
cdrel_backend_writer cdrel_backend_writers[cdrel_format_type_end];
/*
* Populated by cdrel_custom/getters_cdr.c and cdrel_custom/getters_cel.c.
*/
cdrel_dummy_channel_alloc cdrel_dummy_channel_allocators[cdrel_format_type_end];
/*
* You must ensure that there's an entry for every value in the enum.
*/
const char *cdrel_record_type_map[] = {
[cdrel_record_cdr] = "CDR",
[cdrel_record_cel] = "CEL",
[cdrel_record_type_end] = "!!END!!",
};
const char *cdrel_module_type_map[] = {
[cdrel_backend_text] = "Custom ",
[cdrel_backend_db] = "SQLITE3 Custom",
[cdrel_backend_type_end] = "!!END!!",
};
const char *cdrel_data_type_map[] = {
[cdrel_type_string] = "string",
[cdrel_type_timeval] = "timeval",
[cdrel_type_literal] = "literal",
[cdrel_type_amaflags] = "amaflags",
[cdrel_type_disposition] = "disposition",
[cdrel_type_uservar] = "uservar",
[cdrel_type_event_type] = "event_type",
[cdrel_type_event_enum] = "event_enum",
[cdrel_type_cel_timefmt] = "cel_timefmt",
[cdrel_data_type_strings_end] = "!!STRINGS END!!",
[cdrel_type_int32] = "int32",
[cdrel_type_uint32] = "uint32",
[cdrel_type_int64] = "int64",
[cdrel_type_uint64] = "uint64",
[cdrel_type_double] = "double",
[cdrel_data_type_end] = "!!END!!",
};
enum cdrel_data_type cdrel_data_type_from_str(const char *str)
{
enum cdrel_data_type data_type = 0;
for (data_type = 0; data_type < cdrel_data_type_end; data_type++) {
if (strcasecmp(cdrel_data_type_map[data_type], str) == 0) {
return data_type;
}
}
return cdrel_data_type_end;
}
static const char *cdrel_field_flags_map[] = {
[CDREL_FIELD_FLAG_QUOTE] = "quote",
[CDREL_FIELD_FLAG_NOQUOTE] = "noquote",
[CDREL_FIELD_FLAG_TYPE_FORCED] = "type_forced",
[CDREL_FIELD_FLAG_USERVAR] = "uservar",
[CDREL_FIELD_FLAG_LITERAL] = "literal",
[CDREL_FIELD_FLAG_FORMAT_SPEC] = "format_spec",
[CDREL_FIELD_FLAG_LAST] = "LAST",
};
const char *cdrel_get_field_flags(struct ast_flags *flags, struct ast_str **str)
{
int ix = 0;
int res = 0;
int trues = 0;
for (ix = 0; ix < CDREL_FIELD_FLAG_LAST; ix++) {
if (ast_test_flag(flags, (1 << ix))) {
res = ast_str_append(str, -1, "%s%s", trues++ ? "," : "", cdrel_field_flags_map[ix]);
if (res < 0) {
return "";
}
}
}
return ast_str_buffer(*str);
}
const char *cdrel_basename(const char *path)
{
int i = 0;
const char *basename = path;
if (ast_strlen_zero(path)) {
return path;
}
i = strlen(path) - 1;
while(i >= 0) {
if (path[i] == '/') {
basename = &path[i + 1];
break;
}
i--;
}
return basename;
}
static int unload_module(void)
{
return 0;
}
static enum ast_module_load_result load_module(void)
{
load_cdr();
load_cel();
load_formatters();
load_writers();
return AST_MODULE_LOAD_SUCCESS;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER,
"Combined logic for CDR/CEL Custom modules",
.support_level = AST_MODULE_SUPPORT_CORE,
.load = load_module,
.unload = unload_module,
.load_pri = AST_MODPRI_CDR_DRIVER,
);