diff --git a/configs/samples/cdr.conf.sample b/configs/samples/cdr.conf.sample index 6331760764..46ddb96b7d 100644 --- a/configs/samples/cdr.conf.sample +++ b/configs/samples/cdr.conf.sample @@ -32,6 +32,17 @@ ; is "no". ;congestion = no +; Define whether or not to ignore bridging changes in CDRs. This prevents +; bridging changes from resulting in multiple CDRs for different parts of +; a call. Default is "no". This setting cannot be changed on a reload. +;ignorestatechanges = no + +; Define whether or not to ignore dial updates in CDRs. This prevents +; dial updates from resulting in multiple CDRs for different parts of +; a call. The last disposition on the channel will be used for the CDR. +; Use with caution. Default is "no". +;ignoredialchanges = no + ; Normally, CDR's are not closed out until after all extensions are finished ; executing. By enabling this option, the CDR will be ended before executing ; the "h" extension and hangup handlers so that CDR values such as "end" and diff --git a/doc/CHANGES-staging/cdr_ignore.txt b/doc/CHANGES-staging/cdr_ignore.txt new file mode 100644 index 0000000000..e82f40415a --- /dev/null +++ b/doc/CHANGES-staging/cdr_ignore.txt @@ -0,0 +1,6 @@ +Subject: cdr + +Two new options have been added which allow +bridging and dial state changes to be ignored +in CDRs, which can be useful if a single CDR +is desired for a channel. diff --git a/include/asterisk/cdr.h b/include/asterisk/cdr.h index 2bbfbdb28b..06307c91f2 100644 --- a/include/asterisk/cdr.h +++ b/include/asterisk/cdr.h @@ -225,6 +225,8 @@ enum ast_cdr_settings { CDR_INITIATED_SECONDS = 1 << 5, /*!< Include microseconds into the billing time */ CDR_DEBUG = 1 << 6, /*!< Enables extra debug statements */ CDR_CHANNEL_DEFAULT_ENABLED = 1 << 7, /*!< Whether CDR is enabled for each channel by default */ + CDR_IGNORE_STATE_CHANGES = 1 << 8, /*!< Whether to ignore bridge and other call state change events */ + CDR_IGNORE_DIAL_CHANGES = 1 << 9, /*!< Whether to ignore dial state changes */ }; /*! \brief CDR Batch Mode settings */ diff --git a/main/cdr.c b/main/cdr.c index b05908245f..8f1f30b0a1 100644 --- a/main/cdr.c +++ b/main/cdr.c @@ -111,6 +111,29 @@ to undisable (enable) CDR for a call. + + Whether CDR is updated or forked by bridging changes. + Define whether or not CDR should be updated by bridging changes. + This includes entering and leaving bridges and call parking. + If this is set to "no", bridging changes will be ignored for all CDRs. + This should only be done if these events should not affect CDRs and are undesired, + such as to use a single CDR for the lifetime of the channel. + This setting cannot be changed on a reload. + + + + Whether CDR is updated or forked by dial updates. + Define whether or not CDR should be updated by dial updates. + If this is set to "no", a single CDR will be used for the channel, even if + multiple endpoints or destinations are dialed sequentially. Note that you will also + lose detailed nonanswer dial dispositions if this option is enabled, which may not be acceptable, + e.g. instead of detailed no-answer dispositions like BUSY and CONGESTION, the disposition + will always be NO ANSWER if the channel was unanswered (it will still be ANSWERED + if the channel was answered). + This option should be enabled if a single CDR is desired for the lifetime of + the channel. + + Log calls that are never answered and don't set an outgoing party. @@ -208,6 +231,8 @@ #define DEFAULT_END_BEFORE_H_EXTEN "1" #define DEFAULT_INITIATED_SECONDS "0" #define DEFAULT_CHANNEL_ENABLED "1" +#define DEFAULT_IGNORE_STATE_CHANGES "0" +#define DEFAULT_IGNORE_DIAL_CHANGES "0" #define DEFAULT_BATCH_SIZE "100" #define MAX_BATCH_SIZE 1000 @@ -222,6 +247,7 @@ } while (0) static int cdr_debug_enabled; +static int dial_changes_ignored; #define CDR_DEBUG(fmt, ...) \ do { \ @@ -2170,6 +2196,10 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str if (!it_cdr->fn_table->process_dial_begin) { continue; } + if (dial_changes_ignored) { + CDR_DEBUG("%p - Ignoring Dial Begin message\n", it_cdr); + continue; + } CDR_DEBUG("%p - Processing Dial Begin message for channel %s, peer %s\n", it_cdr, caller ? caller->base->name : "(none)", @@ -2181,6 +2211,12 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str if (!it_cdr->fn_table->process_dial_end) { continue; } + if (dial_changes_ignored) { + /* Set the disposition, and do nothing else. */ + it_cdr->disposition = dial_status_to_disposition(dial_status); + CDR_DEBUG("%p - Setting disposition and that's it (%s)\n", it_cdr, dial_status); + continue; + } CDR_DEBUG("%p - Processing Dial End message for channel %s, peer %s\n", it_cdr, caller ? caller->base->name : "(none)", @@ -2192,15 +2228,19 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str } } - /* If no CDR handled a dial begin message, make a new one */ - if (res && ast_strlen_zero(dial_status)) { - struct cdr_object *new_cdr; + /* If we're ignoring dial changes, don't allow multiple CDRs for this channel. */ + if (!dial_changes_ignored) { + /* If no CDR handled a dial begin message, make a new one */ + if (res && ast_strlen_zero(dial_status)) { + struct cdr_object *new_cdr; - new_cdr = cdr_object_create_and_append(cdr, stasis_message_timestamp(message)); - if (new_cdr) { - new_cdr->fn_table->process_dial_begin(new_cdr, caller, peer); + new_cdr = cdr_object_create_and_append(cdr, stasis_message_timestamp(message)); + if (new_cdr) { + new_cdr->fn_table->process_dial_begin(new_cdr, caller, peer); + } } } + ao2_unlock(cdr); ao2_cleanup(cdr); } @@ -4200,6 +4240,8 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_ ast_cli(a->fd, " Log calls by default: %s\n", ast_test_flag(&mod_cfg->general->settings, CDR_CHANNEL_DEFAULT_ENABLED) ? "Yes" : "No"); ast_cli(a->fd, " Log unanswered calls: %s\n", ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED) ? "Yes" : "No"); ast_cli(a->fd, " Log congestion: %s\n\n", ast_test_flag(&mod_cfg->general->settings, CDR_CONGESTION) ? "Yes" : "No"); + ast_cli(a->fd, " Ignore bridging changes: %s\n\n", ast_test_flag(&mod_cfg->general->settings, CDR_IGNORE_STATE_CHANGES) ? "Yes" : "No"); + ast_cli(a->fd, " Ignore dial state changes: %s\n\n", ast_test_flag(&mod_cfg->general->settings, CDR_IGNORE_DIAL_CHANGES) ? "Yes" : "No"); if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) { ast_cli(a->fd, "* Batch Mode Settings\n"); ast_cli(a->fd, " -------------------\n"); @@ -4379,6 +4421,8 @@ static int process_config(int reload) aco_option_register(&cfg_info, "size", ACO_EXACT, general_options, DEFAULT_BATCH_SIZE, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_cdr_config, batch_settings.size), 0, MAX_BATCH_SIZE); aco_option_register(&cfg_info, "time", ACO_EXACT, general_options, DEFAULT_BATCH_TIME, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_cdr_config, batch_settings.time), 1, MAX_BATCH_TIME); aco_option_register(&cfg_info, "channeldefaultenabled", ACO_EXACT, general_options, DEFAULT_CHANNEL_ENABLED, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_CHANNEL_DEFAULT_ENABLED); + aco_option_register(&cfg_info, "ignorestatechanges", ACO_EXACT, general_options, DEFAULT_IGNORE_STATE_CHANGES, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_IGNORE_STATE_CHANGES); + aco_option_register(&cfg_info, "ignoredialchanges", ACO_EXACT, general_options, DEFAULT_IGNORE_DIAL_CHANGES, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_IGNORE_DIAL_CHANGES); } if (aco_process_config(&cfg_info, reload) == ACO_PROCESS_ERROR) { @@ -4541,6 +4585,7 @@ static int unload_module(void) static int load_module(void) { + struct module_config *mod_cfg = NULL; if (process_config(0)) { return AST_MODULE_LOAD_FAILURE; } @@ -4561,13 +4606,36 @@ static int load_module(void) return AST_MODULE_LOAD_FAILURE; } + mod_cfg = ao2_global_obj_ref(module_configs); + stasis_message_router_add(stasis_router, ast_channel_snapshot_type(), handle_channel_snapshot_update_message, NULL); + + /* Always process dial messages, because even if we ignore most of it, we do want the dial status for the disposition. */ stasis_message_router_add(stasis_router, ast_channel_dial_type(), handle_dial_message, NULL); - stasis_message_router_add(stasis_router, ast_channel_entered_bridge_type(), handle_bridge_enter_message, NULL); - stasis_message_router_add(stasis_router, ast_channel_left_bridge_type(), handle_bridge_leave_message, NULL); - stasis_message_router_add(stasis_router, ast_parked_call_type(), handle_parked_call_message, NULL); + if (!mod_cfg || !ast_test_flag(&mod_cfg->general->settings, CDR_IGNORE_DIAL_CHANGES)) { + dial_changes_ignored = 0; + } else { + dial_changes_ignored = 1; + CDR_DEBUG("Dial messages will be mostly ignored\n"); + } + + /* If explicitly instructed to ignore call state changes, then ignore bridging events, parking, etc. */ + if (!mod_cfg || !ast_test_flag(&mod_cfg->general->settings, CDR_IGNORE_STATE_CHANGES)) { + stasis_message_router_add(stasis_router, ast_channel_entered_bridge_type(), handle_bridge_enter_message, NULL); + stasis_message_router_add(stasis_router, ast_channel_left_bridge_type(), handle_bridge_leave_message, NULL); + stasis_message_router_add(stasis_router, ast_parked_call_type(), handle_parked_call_message, NULL); + } else { + CDR_DEBUG("All bridge and parking messages will be ignored\n"); + } + stasis_message_router_add(stasis_router, cdr_sync_message_type(), handle_cdr_sync_message, NULL); + if (mod_cfg) { + ao2_cleanup(mod_cfg); + } else { + ast_log(LOG_WARNING, "Unable to obtain CDR configuration during module load?\n"); + } + active_cdrs_master = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, AST_NUM_CHANNEL_BUCKETS, cdr_master_hash_fn, NULL, cdr_master_cmp_fn); if (!active_cdrs_master) {