Mon Jun 27 16:50:49 2011

Asterisk developer's documentation


cdr_csv.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2005, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * Includes code and algorithms from the Zapata library.
00009  *
00010  * See http://www.asterisk.org for more information about
00011  * the Asterisk project. Please do not directly contact
00012  * any of the maintainers of this project for assistance;
00013  * the project provides a web site, mailing lists and IRC
00014  * channels for your use.
00015  *
00016  * This program is free software, distributed under the terms of
00017  * the GNU General Public License Version 2. See the LICENSE file
00018  * at the top of the source tree.
00019  */
00020 
00021 /*!
00022  * \file
00023  * \brief Comma Separated Value CDR records.
00024  *
00025  * \author Mark Spencer <markster@digium.com>
00026  *
00027  * \arg See also \ref AstCDR
00028  * \ingroup cdr_drivers
00029  */
00030 
00031 #include "asterisk.h"
00032 
00033 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 278132 $")
00034 
00035 #include "asterisk/paths.h"   /* use ast_config_AST_LOG_DIR */
00036 #include "asterisk/config.h"
00037 #include "asterisk/channel.h"
00038 #include "asterisk/cdr.h"
00039 #include "asterisk/module.h"
00040 #include "asterisk/utils.h"
00041 #include "asterisk/lock.h"
00042 
00043 #define CSV_LOG_DIR "/cdr-csv"
00044 #define CSV_MASTER  "/Master.csv"
00045 
00046 #define DATE_FORMAT "%Y-%m-%d %T"
00047 
00048 static int usegmtime = 0;
00049 static int accountlogs;
00050 static int loguniqueid = 0;
00051 static int loguserfield = 0;
00052 static int loaded = 0;
00053 static const char config[] = "cdr.conf";
00054 
00055 /* #define CSV_LOGUNIQUEID 1 */
00056 /* #define CSV_LOGUSERFIELD 1 */
00057 
00058 /*----------------------------------------------------
00059   The values are as follows:
00060 
00061 
00062   "accountcode",  accountcode is the account name of detail records, Master.csv contains all records *
00063          Detail records are configured on a channel basis, IAX and SIP are determined by user *
00064          DAHDI is determined by channel in dahdi.conf
00065   "source",
00066   "destination",
00067   "destination context",
00068   "callerid",
00069   "channel",
00070   "destination channel",   (if applicable)
00071   "last application",   Last application run on the channel
00072   "last app argument",  argument to the last channel
00073   "start time",
00074   "answer time",
00075   "end time",
00076   duration,       Duration is the whole length that the entire call lasted. ie. call rx'd to hangup
00077          "end time" minus "start time"
00078   billable seconds,  the duration that a call was up after other end answered which will be <= to duration
00079          "end time" minus "answer time"
00080   "disposition",     ANSWERED, NO ANSWER, BUSY
00081   "amaflags",        DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode.
00082   "uniqueid",           unique call identifier
00083   "userfield"     user field set via SetCDRUserField
00084 ----------------------------------------------------------*/
00085 
00086 static char *name = "csv";
00087 
00088 AST_MUTEX_DEFINE_STATIC(mf_lock);
00089 AST_MUTEX_DEFINE_STATIC(acf_lock);
00090 
00091 static int load_config(int reload)
00092 {
00093    struct ast_config *cfg;
00094    struct ast_variable *var;
00095    const char *tmp;
00096    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
00097 
00098    if (!(cfg = ast_config_load(config, config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
00099       ast_log(LOG_WARNING, "unable to load config: %s\n", config);
00100       return 0;
00101    } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
00102       return 1;
00103 
00104    accountlogs = 1;
00105    usegmtime = 0;
00106    loguniqueid = 0;
00107    loguserfield = 0;
00108 
00109    if (!(var = ast_variable_browse(cfg, "csv"))) {
00110       ast_config_destroy(cfg);
00111       return 0;
00112    }
00113 
00114    if ((tmp = ast_variable_retrieve(cfg, "csv", "usegmtime"))) {
00115       usegmtime = ast_true(tmp);
00116       if (usegmtime)
00117          ast_debug(1, "logging time in GMT\n");
00118    }
00119 
00120    /* Turn on/off separate files per accountcode. Default is on (as before) */
00121    if ((tmp = ast_variable_retrieve(cfg, "csv", "accountlogs"))) {
00122       accountlogs = ast_true(tmp);
00123       if (accountlogs) {
00124          ast_debug(1, "logging in separate files per accountcode\n");
00125       }
00126    }
00127 
00128    if ((tmp = ast_variable_retrieve(cfg, "csv", "loguniqueid"))) {
00129       loguniqueid = ast_true(tmp);
00130       if (loguniqueid)
00131          ast_debug(1, "logging CDR field UNIQUEID\n");
00132    }
00133 
00134    if ((tmp = ast_variable_retrieve(cfg, "csv", "loguserfield"))) {
00135       loguserfield = ast_true(tmp);
00136       if (loguserfield)
00137          ast_debug(1, "logging CDR user-defined field\n");
00138    }
00139 
00140    ast_config_destroy(cfg);
00141    return 1;
00142 }
00143 
00144 static int append_string(char *buf, const char *s, size_t bufsize)
00145 {
00146    int pos = strlen(buf), spos = 0, error = -1;
00147 
00148    if (pos >= bufsize - 4)
00149       return -1;
00150 
00151    buf[pos++] = '\"';
00152 
00153    while(pos < bufsize - 3) {
00154       if (!s[spos]) {
00155          error = 0;
00156          break;
00157       }
00158       if (s[spos] == '\"')
00159          buf[pos++] = '\"';
00160       buf[pos++] = s[spos];
00161       spos++;
00162    }
00163 
00164    buf[pos++] = '\"';
00165    buf[pos++] = ',';
00166    buf[pos++] = '\0';
00167 
00168    return error;
00169 }
00170 
00171 static int append_int(char *buf, int s, size_t bufsize)
00172 {
00173    char tmp[32];
00174    int pos = strlen(buf);
00175 
00176    snprintf(tmp, sizeof(tmp), "%d", s);
00177 
00178    if (pos + strlen(tmp) > bufsize - 3)
00179       return -1;
00180 
00181    strncat(buf, tmp, bufsize - strlen(buf) - 1);
00182    pos = strlen(buf);
00183    buf[pos++] = ',';
00184    buf[pos++] = '\0';
00185 
00186    return 0;
00187 }
00188 
00189 static int append_date(char *buf, struct timeval when, size_t bufsize)
00190 {
00191    char tmp[80] = "";
00192    struct ast_tm tm;
00193 
00194    if (strlen(buf) > bufsize - 3)
00195       return -1;
00196 
00197    if (ast_tvzero(when)) {
00198       strncat(buf, ",", bufsize - strlen(buf) - 1);
00199       return 0;
00200    }
00201 
00202    ast_localtime(&when, &tm, usegmtime ? "GMT" : NULL);
00203    ast_strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
00204 
00205    return append_string(buf, tmp, bufsize);
00206 }
00207 
00208 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
00209 {
00210 
00211    buf[0] = '\0';
00212    /* Account code */
00213    append_string(buf, cdr->accountcode, bufsize);
00214    /* Source */
00215    append_string(buf, cdr->src, bufsize);
00216    /* Destination */
00217    append_string(buf, cdr->dst, bufsize);
00218    /* Destination context */
00219    append_string(buf, cdr->dcontext, bufsize);
00220    /* Caller*ID */
00221    append_string(buf, cdr->clid, bufsize);
00222    /* Channel */
00223    append_string(buf, cdr->channel, bufsize);
00224    /* Destination Channel */
00225    append_string(buf, cdr->dstchannel, bufsize);
00226    /* Last Application */
00227    append_string(buf, cdr->lastapp, bufsize);
00228    /* Last Data */
00229    append_string(buf, cdr->lastdata, bufsize);
00230    /* Start Time */
00231    append_date(buf, cdr->start, bufsize);
00232    /* Answer Time */
00233    append_date(buf, cdr->answer, bufsize);
00234    /* End Time */
00235    append_date(buf, cdr->end, bufsize);
00236    /* Duration */
00237    append_int(buf, cdr->duration, bufsize);
00238    /* Billable seconds */
00239    append_int(buf, cdr->billsec, bufsize);
00240    /* Disposition */
00241    append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
00242    /* AMA Flags */
00243    append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
00244    /* Unique ID */
00245    if (loguniqueid)
00246       append_string(buf, cdr->uniqueid, bufsize);
00247    /* append the user field */
00248    if(loguserfield)
00249       append_string(buf, cdr->userfield,bufsize);
00250    /* If we hit the end of our buffer, log an error */
00251    if (strlen(buf) < bufsize - 5) {
00252       /* Trim off trailing comma */
00253       buf[strlen(buf) - 1] = '\0';
00254       strncat(buf, "\n", bufsize - strlen(buf) - 1);
00255       return 0;
00256    }
00257    return -1;
00258 }
00259 
00260 static int writefile(char *s, char *acc)
00261 {
00262    char tmp[PATH_MAX];
00263    FILE *f;
00264 
00265    if (strchr(acc, '/') || (acc[0] == '.')) {
00266       ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
00267       return -1;
00268    }
00269 
00270    snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
00271 
00272    ast_mutex_lock(&acf_lock);
00273    if (!(f = fopen(tmp, "a"))) {
00274       ast_mutex_unlock(&acf_lock);
00275       ast_log(LOG_ERROR, "Unable to open file %s : %s\n", tmp, strerror(errno));
00276       return -1;
00277    }
00278    fputs(s, f);
00279    fflush(f);
00280    fclose(f);
00281    ast_mutex_unlock(&acf_lock);
00282 
00283    return 0;
00284 }
00285 
00286 
00287 static int csv_log(struct ast_cdr *cdr)
00288 {
00289    FILE *mf = NULL;
00290    /* Make sure we have a big enough buf */
00291    char buf[1024];
00292    char csvmaster[PATH_MAX];
00293    snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
00294 #if 0
00295    printf("[CDR] %s ('%s' -> '%s') Dur: %ds Bill: %ds Disp: %s Flags: %s Account: [%s]\n", cdr->channel, cdr->src, cdr->dst, cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), cdr->accountcode);
00296 #endif
00297    if (build_csv_record(buf, sizeof(buf), cdr)) {
00298       ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes.  CDR not recorded!\n", (int)sizeof(buf));
00299       return 0;
00300    }
00301 
00302    /* because of the absolutely unconditional need for the
00303       highest reliability possible in writing billing records,
00304       we open write and close the log file each time */
00305    ast_mutex_lock(&mf_lock);
00306    if ((mf = fopen(csvmaster, "a"))) {
00307       fputs(buf, mf);
00308       fflush(mf); /* be particularly anal here */
00309       fclose(mf);
00310       mf = NULL;
00311       ast_mutex_unlock(&mf_lock);
00312    } else {
00313       ast_mutex_unlock(&mf_lock);
00314       ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
00315    }
00316 
00317    if (accountlogs && !ast_strlen_zero(cdr->accountcode)) {
00318       if (writefile(buf, cdr->accountcode))
00319          ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
00320    }
00321 
00322    return 0;
00323 }
00324 
00325 static int unload_module(void)
00326 {
00327    ast_cdr_unregister(name);
00328    loaded = 0;
00329    return 0;
00330 }
00331 
00332 static int load_module(void)
00333 {
00334    int res;
00335 
00336    if(!load_config(0))
00337       return AST_MODULE_LOAD_DECLINE;
00338 
00339    if ((res = ast_cdr_register(name, ast_module_info->description, csv_log))) {
00340       ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
00341    } else {
00342       loaded = 1;
00343    }
00344    return res;
00345 }
00346 
00347 static int reload(void)
00348 {
00349    if (load_config(1)) {
00350       loaded = 1;
00351    } else {
00352       loaded = 0;
00353       ast_log(LOG_WARNING, "No [csv] section in cdr.conf.  Unregistering backend.\n");
00354       ast_cdr_unregister(name);
00355    }
00356 
00357    return 0;
00358 }
00359 
00360 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Comma Separated Values CDR Backend",
00361       .load = load_module,
00362       .unload = unload_module,
00363       .reload = reload,
00364       .load_pri = AST_MODPRI_CDR_DRIVER,
00365           );

Generated on Mon Jun 27 16:50:49 2011 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7