freeswitch/src/mod/event_handlers/mod_cdr_mongodb/mod_cdr_mongodb.c

419 lines
14 KiB
C

/*
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2005-2012, Anthony Minessale II <anthm@freeswitch.org>
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is
* Anthony Minessale II <anthm@freeswitch.org>
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Daniel Swarbrick <daniel.swarbrick@seventhsignal.de>
*
* mod_cdr_mongodb.c -- MongoDB CDR Module
*
* Derived from:
* mod_xml_cdr.c -- XML CDR Module to files or curl
*
*/
#include <switch.h>
#include <mongo.h>
static struct {
switch_memory_pool_t *pool;
int shutdown;
char *mongo_host;
uint32_t mongo_port;
char *mongo_namespace;
mongo mongo_conn[1];
switch_mutex_t *mongo_mutex;
switch_bool_t log_b;
} globals;
static switch_xml_config_item_t config_settings[] = {
/* key, flags, ptr, default_value, syntax, helptext */
SWITCH_CONFIG_ITEM_STRING_STRDUP("host", CONFIG_REQUIRED, &globals.mongo_host, "127.0.0.1", NULL, "MongoDB server host address"),
SWITCH_CONFIG_ITEM_STRING_STRDUP("namespace", CONFIG_REQUIRED, &globals.mongo_namespace, NULL, "database.collection", "MongoDB namespace"),
/* key, type, flags, ptr, default_value, data, syntax, helptext */
SWITCH_CONFIG_ITEM("port", SWITCH_CONFIG_INT, CONFIG_REQUIRED, &globals.mongo_port, 27017, NULL, NULL, "MongoDB server TCP port"),
SWITCH_CONFIG_ITEM("log-b-leg", SWITCH_CONFIG_BOOL, CONFIG_RELOADABLE, &globals.log_b, SWITCH_TRUE, NULL, NULL, "Log B-leg in addition to A-leg"),
SWITCH_CONFIG_ITEM_END()
};
SWITCH_MODULE_LOAD_FUNCTION(mod_cdr_mongodb_load);
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_cdr_mongodb_shutdown);
SWITCH_MODULE_DEFINITION(mod_cdr_mongodb, mod_cdr_mongodb_load, mod_cdr_mongodb_shutdown, NULL);
static void set_bson_profile_data(bson *b, switch_caller_profile_t *caller_profile)
{
bson_append_string(b, "username", caller_profile->username);
bson_append_string(b, "dialplan", caller_profile->dialplan);
bson_append_string(b, "caller_id_name", caller_profile->caller_id_name);
bson_append_string(b, "ani", caller_profile->ani);
bson_append_string(b, "aniii", caller_profile->aniii);
bson_append_string(b, "caller_id_number", caller_profile->caller_id_number);
bson_append_string(b, "network_addr", caller_profile->network_addr);
bson_append_string(b, "rdnis", caller_profile->rdnis);
bson_append_string(b, "destination_number", caller_profile->destination_number);
bson_append_string(b, "uuid", caller_profile->uuid);
bson_append_string(b, "source", caller_profile->source);
bson_append_string(b, "context", caller_profile->context);
bson_append_string(b, "chan_name", caller_profile->chan_name);
}
static switch_status_t my_on_reporting(switch_core_session_t *session)
{
switch_status_t status = SWITCH_STATUS_SUCCESS;
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_event_header_t *hi;
switch_caller_profile_t *caller_profile;
switch_app_log_t *app_log;
bson cdr;
int is_b;
char *tmp;
if (globals.shutdown) {
return SWITCH_STATUS_SUCCESS;
}
is_b = channel && switch_channel_get_originator_caller_profile(channel);
if (!globals.log_b && is_b) {
const char *force_cdr = switch_channel_get_variable(channel, SWITCH_FORCE_PROCESS_CDR_VARIABLE);
if (!switch_true(force_cdr)) {
return SWITCH_STATUS_SUCCESS;
}
}
bson_init(&cdr);
/* Channel data */
bson_append_start_object(&cdr, "channel_data");
bson_append_string(&cdr, "state", switch_channel_state_name(switch_channel_get_state(channel)));
bson_append_string(&cdr, "direction", switch_channel_direction(channel) == SWITCH_CALL_DIRECTION_OUTBOUND ? "outbound" : "inbound");
bson_append_int(&cdr, "state_number", switch_channel_get_state(channel));
if ((tmp = switch_channel_get_flag_string(channel))) {
bson_append_string(&cdr, "flags", tmp);
free(tmp);
}
if ((tmp = switch_channel_get_cap_string(channel))) {
bson_append_string(&cdr, "caps", tmp);
free(tmp);
}
bson_append_finish_object(&cdr); /* channel_data */
/* Channel variables */
bson_append_start_object(&cdr, "variables");
if ((hi = switch_channel_variable_first(channel))) {
for (; hi; hi = hi->next) {
if (!zstr(hi->name) && !zstr(hi->value)) {
bson_append_string(&cdr, hi->name, hi->value);
}
}
switch_channel_variable_last(channel);
}
bson_append_finish_object(&cdr); /* variables */
/* App log */
if ((app_log = switch_core_session_get_app_log(session))) {
switch_app_log_t *ap;
bson_append_start_object(&cdr, "app_log");
for (ap = app_log; ap; ap = ap->next) {
bson_append_start_object(&cdr, "application");
bson_append_string(&cdr, "app_name", ap->app);
bson_append_string(&cdr, "app_data", switch_str_nil(ap->arg));
bson_append_long(&cdr, "app_stamp", ap->stamp);
bson_append_finish_object(&cdr); /* application */
}
bson_append_finish_object(&cdr); /* app_log */
}
/* Callflow */
caller_profile = switch_channel_get_caller_profile(channel);
while (caller_profile) {
bson_append_start_object(&cdr, "callflow");
if (!zstr(caller_profile->dialplan)) {
bson_append_string(&cdr, "dialplan", caller_profile->dialplan);
}
if (!zstr(caller_profile->profile_index)) {
bson_append_string(&cdr, "profile_index", caller_profile->profile_index);
}
if (caller_profile->caller_extension) {
switch_caller_application_t *ap;
bson_append_start_object(&cdr, "extension");
bson_append_string(&cdr, "name", caller_profile->caller_extension->extension_name);
bson_append_string(&cdr, "number", caller_profile->caller_extension->extension_number);
if (caller_profile->caller_extension->current_application) {
bson_append_string(&cdr, "current_app", caller_profile->caller_extension->current_application->application_name);
}
for (ap = caller_profile->caller_extension->applications; ap; ap = ap->next) {
bson_append_start_object(&cdr, "application");
if (ap == caller_profile->caller_extension->current_application) {
bson_append_bool(&cdr, "last_executed", 1);
}
bson_append_string(&cdr, "app_name", ap->application_name);
bson_append_string(&cdr, "app_data", switch_str_nil(ap->application_data));
bson_append_finish_object(&cdr);
}
if (caller_profile->caller_extension->children) {
switch_caller_profile_t *cp = NULL;
for (cp = caller_profile->caller_extension->children; cp; cp = cp->next) {
if (!cp->caller_extension) {
continue;
}
bson_append_start_object(&cdr, "sub_extensions");
bson_append_start_object(&cdr, "extension");
bson_append_string(&cdr, "name", cp->caller_extension->extension_name);
bson_append_string(&cdr, "number", cp->caller_extension->extension_number);
bson_append_string(&cdr, "dialplan", cp->dialplan);
if (cp->caller_extension->current_application) {
bson_append_string(&cdr, "current_app", cp->caller_extension->current_application->application_name);
}
for (ap = cp->caller_extension->applications; ap; ap = ap->next) {
bson_append_start_object(&cdr, "application");
if (ap == cp->caller_extension->current_application) {
bson_append_bool(&cdr, "last_executed", 1);
}
bson_append_string(&cdr, "app_name", ap->application_name);
bson_append_string(&cdr, "app_data", switch_str_nil(ap->application_data));
bson_append_finish_object(&cdr);
}
bson_append_finish_object(&cdr); /* extension */
bson_append_finish_object(&cdr); /* sub_extensions */
}
}
bson_append_finish_object(&cdr); /* extension */
}
bson_append_start_object(&cdr, "caller_profile");
set_bson_profile_data(&cdr, caller_profile);
if (caller_profile->origination_caller_profile) {
switch_caller_profile_t *cp = NULL;
bson_append_start_object(&cdr, "origination");
for (cp = caller_profile->origination_caller_profile; cp; cp = cp->next) {
bson_append_start_object(&cdr, "origination_caller_profile");
set_bson_profile_data(&cdr, cp);
bson_append_finish_object(&cdr);
}
bson_append_finish_object(&cdr); /* origination */
}
if (caller_profile->originator_caller_profile) {
switch_caller_profile_t *cp = NULL;
bson_append_start_object(&cdr, "originator");
for (cp = caller_profile->originator_caller_profile; cp; cp = cp->next) {
bson_append_start_object(&cdr, "originator_caller_profile");
set_bson_profile_data(&cdr, cp);
bson_append_finish_object(&cdr);
}
bson_append_finish_object(&cdr); /* originator */
}
if (caller_profile->originatee_caller_profile) {
switch_caller_profile_t *cp = NULL;
bson_append_start_object(&cdr, "originatee");
for (cp = caller_profile->originatee_caller_profile; cp; cp = cp->next) {
bson_append_start_object(&cdr, "originatee_caller_profile");
set_bson_profile_data(&cdr, cp);
bson_append_finish_object(&cdr);
}
bson_append_finish_object(&cdr); /* originatee */
}
bson_append_finish_object(&cdr); /* caller_profile */
/* Timestamps */
if (caller_profile->times) {
bson_append_start_object(&cdr, "times");
/* Insert timestamps as long ints (microseconds) to preserve accuracy */
bson_append_long(&cdr, "created_time", caller_profile->times->created);
bson_append_long(&cdr, "profile_created_time", caller_profile->times->profile_created);
bson_append_long(&cdr, "progress_time", caller_profile->times->progress);
bson_append_long(&cdr, "progress_media_time", caller_profile->times->progress_media);
bson_append_long(&cdr, "answered_time", caller_profile->times->answered);
bson_append_long(&cdr, "bridged_time", caller_profile->times->bridged);
bson_append_long(&cdr, "last_hold_time", caller_profile->times->last_hold);
bson_append_long(&cdr, "hold_accum_time", caller_profile->times->hold_accum);
bson_append_long(&cdr, "hangup_time", caller_profile->times->hungup);
bson_append_long(&cdr, "resurrect_time", caller_profile->times->resurrected);
bson_append_long(&cdr, "transfer_time", caller_profile->times->transferred);
bson_append_finish_object(&cdr); /* times */
}
bson_append_finish_object(&cdr); /* callflow */
caller_profile = caller_profile->next;
}
bson_finish(&cdr);
switch_mutex_lock(globals.mongo_mutex);
if (mongo_insert(globals.mongo_conn, globals.mongo_namespace, &cdr) != MONGO_OK) {
if (globals.mongo_conn->err == MONGO_IO_ERROR) {
mongo_error_t db_status;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "MongoDB connection failed; attempting reconnect...\n");
db_status = mongo_reconnect(globals.mongo_conn);
if (db_status != MONGO_OK) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "MongoDB reconnect failed with error code %d\n", db_status);
status = SWITCH_STATUS_FALSE;
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "MongoDB connection re-established.\n");
if (mongo_insert(globals.mongo_conn, globals.mongo_namespace, &cdr) != MONGO_OK) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_insert: error code %d\n", globals.mongo_conn->err);
status = SWITCH_STATUS_FALSE;
}
}
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_insert: error code %d\n", globals.mongo_conn->err);
status = SWITCH_STATUS_FALSE;
}
}
switch_mutex_unlock(globals.mongo_mutex);
bson_destroy(&cdr);
return status;
}
static switch_state_handler_table_t state_handlers = {
/*.on_init */ NULL,
/*.on_routing */ NULL,
/*.on_execute */ NULL,
/*.on_hangup */ NULL,
/*.on_exchange_media */ NULL,
/*.on_soft_execute */ NULL,
/*.on_consume_media */ NULL,
/*.on_hibernate */ NULL,
/*.on_reset */ NULL,
/*.on_park */ NULL,
/*.on_reporting */ my_on_reporting
};
static switch_status_t load_config(switch_memory_pool_t *pool)
{
switch_status_t status = SWITCH_STATUS_SUCCESS;
if (switch_xml_config_parse_module_settings("cdr_mongodb.conf", SWITCH_FALSE, config_settings) != SWITCH_STATUS_SUCCESS) {
return SWITCH_STATUS_FALSE;
}
return status;
}
SWITCH_MODULE_LOAD_FUNCTION(mod_cdr_mongodb_load)
{
switch_status_t status = SWITCH_STATUS_SUCCESS;
mongo_error_t db_status;
memset(&globals, 0, sizeof(globals));
globals.pool = pool;
if (load_config(pool) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Unable to load or parse config!\n");
}
db_status = mongo_connect(globals.mongo_conn, globals.mongo_host, globals.mongo_port);
if (db_status != MONGO_OK) {
switch (globals.mongo_conn->err) {
case MONGO_CONN_NO_SOCKET:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_connect: no socket\n");
break;
case MONGO_CONN_FAIL:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_connect: connection failed\n");
break;
case MONGO_CONN_NOT_MASTER:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_connect: not master\n");
break;
default:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_connect: unknown error %d\n", db_status);
}
return SWITCH_STATUS_FALSE;
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Connected to MongoDB server %s:%d\n", globals.mongo_host, globals.mongo_port);
}
switch_mutex_init(&globals.mongo_mutex, SWITCH_MUTEX_NESTED, pool);
switch_core_add_state_handler(&state_handlers);
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
return status;
}
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_cdr_mongodb_shutdown)
{
globals.shutdown = 1;
switch_core_remove_state_handler(&state_handlers);
switch_mutex_destroy(globals.mongo_mutex);
mongo_destroy(globals.mongo_conn);
return SWITCH_STATUS_SUCCESS;
}
/* For Emacs:
* Local Variables:
* mode:c
* indent-tabs-mode:t
* tab-width:4
* c-basic-offset:4
* End:
* For VIM:
* vim:set softtabstop=4 shiftwidth=4 tabstop=4:
*/