2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * Includes code and algorithms from the Zapata library.
10 * See http://www.asterisk.org for more information about
11 * the Asterisk project. Please do not directly contact
12 * any of the maintainers of this project for assistance;
13 * the project provides a web site, mailing lists and IRC
14 * channels for your use.
16 * This program is free software, distributed under the terms of
17 * the GNU General Public License Version 2. See the LICENSE file
18 * at the top of the source tree.
23 * \brief Comma Separated Value CDR records.
25 * \author Mark Spencer <markster@digium.com>
27 * \arg See also \ref AstCDR
28 * \ingroup cdr_drivers
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
37 #include "asterisk/paths.h" /* use ast_config_AST_LOG_DIR */
38 #include "asterisk/config.h"
39 #include "asterisk/channel.h"
40 #include "asterisk/cdr.h"
41 #include "asterisk/module.h"
42 #include "asterisk/utils.h"
43 #include "asterisk/lock.h"
45 #define CSV_LOG_DIR "/cdr-csv"
46 #define CSV_MASTER "/Master.csv"
48 #define DATE_FORMAT "%Y-%m-%d %T"
50 static int usegmtime = 0;
51 static int loguniqueid = 0;
52 static int loguserfield = 0;
53 static char *config = "cdr.conf";
55 /* #define CSV_LOGUNIQUEID 1 */
56 /* #define CSV_LOGUSERFIELD 1 */
58 /*----------------------------------------------------
59 The values are as follows:
62 "accountcode", accountcode is the account name of detail records, Master.csv contains all records *
63 Detail records are configured on a channel basis, IAX and SIP are determined by user *
64 Zap is determined by channel in zaptel.conf
67 "destination context",
70 "destination channel", (if applicable)
71 "last application", Last application run on the channel
72 "last app argument", argument to the last channel
76 duration, Duration is the whole length that the entire call lasted. ie. call rx'd to hangup
77 "end time" minus "start time"
78 billable seconds, the duration that a call was up after other end answered which will be <= to duration
79 "end time" minus "answer time"
80 "disposition", ANSWERED, NO ANSWER, BUSY
81 "amaflags", DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode.
82 "uniqueid", unique call identifier
83 "userfield" user field set via SetCDRUserField
84 ----------------------------------------------------------*/
86 static char *name = "csv";
88 AST_MUTEX_DEFINE_STATIC(mf_lock);
89 AST_MUTEX_DEFINE_STATIC(acf_lock);
91 static int load_config(int reload)
93 struct ast_config *cfg;
94 struct ast_variable *var;
96 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
102 if (!(cfg = ast_config_load(config, config_flags))) {
103 ast_log(LOG_WARNING, "unable to load config: %s\n", config);
105 } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
108 if (!(var = ast_variable_browse(cfg, "csv"))) {
109 ast_config_destroy(cfg);
113 if ((tmp = ast_variable_retrieve(cfg, "csv", "usegmtime"))) {
114 usegmtime = ast_true(tmp);
116 ast_debug(1, "logging time in GMT\n");
119 if ((tmp = ast_variable_retrieve(cfg, "csv", "loguniqueid"))) {
120 loguniqueid = ast_true(tmp);
122 ast_debug(1, "logging CDR field UNIQUEID\n");
125 if ((tmp = ast_variable_retrieve(cfg, "csv", "loguserfield"))) {
126 loguserfield = ast_true(tmp);
128 ast_debug(1, "logging CDR user-defined field\n");
131 ast_config_destroy(cfg);
135 static int append_string(char *buf, char *s, size_t bufsize)
137 int pos = strlen(buf), spos = 0, error = -1;
139 if (pos >= bufsize - 4)
144 while(pos < bufsize - 3) {
151 buf[pos++] = s[spos];
162 static int append_int(char *buf, int s, size_t bufsize)
165 int pos = strlen(buf);
167 snprintf(tmp, sizeof(tmp), "%d", s);
169 if (pos + strlen(tmp) > bufsize - 3)
172 strncat(buf, tmp, bufsize - strlen(buf) - 1);
180 static int append_date(char *buf, struct timeval tv, size_t bufsize)
185 if (strlen(buf) > bufsize - 3)
188 if (ast_tvzero(tv)) {
189 strncat(buf, ",", bufsize - strlen(buf) - 1);
193 ast_localtime(&tv, &tm, usegmtime ? "GMT" : NULL);
194 ast_strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
196 return append_string(buf, tmp, bufsize);
199 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
204 append_string(buf, cdr->accountcode, bufsize);
206 append_string(buf, cdr->src, bufsize);
208 append_string(buf, cdr->dst, bufsize);
209 /* Destination context */
210 append_string(buf, cdr->dcontext, bufsize);
212 append_string(buf, cdr->clid, bufsize);
214 append_string(buf, cdr->channel, bufsize);
215 /* Destination Channel */
216 append_string(buf, cdr->dstchannel, bufsize);
217 /* Last Application */
218 append_string(buf, cdr->lastapp, bufsize);
220 append_string(buf, cdr->lastdata, bufsize);
222 append_date(buf, cdr->start, bufsize);
224 append_date(buf, cdr->answer, bufsize);
226 append_date(buf, cdr->end, bufsize);
228 append_int(buf, cdr->duration, bufsize);
229 /* Billable seconds */
230 append_int(buf, cdr->billsec, bufsize);
232 append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
234 append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
237 append_string(buf, cdr->uniqueid, bufsize);
238 /* append the user field */
240 append_string(buf, cdr->userfield,bufsize);
241 /* If we hit the end of our buffer, log an error */
242 if (strlen(buf) < bufsize - 5) {
243 /* Trim off trailing comma */
244 buf[strlen(buf) - 1] = '\0';
245 strncat(buf, "\n", bufsize - strlen(buf) - 1);
251 static int writefile(char *s, char *acc)
256 if (strchr(acc, '/') || (acc[0] == '.')) {
257 ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
261 snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", (char *)ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
263 ast_mutex_lock(&acf_lock);
264 if (!(f = fopen(tmp, "a"))) {
265 ast_mutex_unlock(&acf_lock);
266 ast_log(LOG_ERROR, "Unable to open file %s : %s\n", tmp, strerror(errno));
272 ast_mutex_unlock(&acf_lock);
278 static int csv_log(struct ast_cdr *cdr)
281 /* Make sure we have a big enough buf */
283 char csvmaster[PATH_MAX];
284 snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
286 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);
288 if (build_csv_record(buf, sizeof(buf), cdr)) {
289 ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes. CDR not recorded!\n", (int)sizeof(buf));
293 /* because of the absolutely unconditional need for the
294 highest reliability possible in writing billing records,
295 we open write and close the log file each time */
296 ast_mutex_lock(&mf_lock);
297 if ((mf = fopen(csvmaster, "a"))) {
299 fflush(mf); /* be particularly anal here */
302 ast_mutex_unlock(&mf_lock);
304 ast_mutex_unlock(&mf_lock);
305 ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
308 if (!ast_strlen_zero(cdr->accountcode)) {
309 if (writefile(buf, cdr->accountcode))
310 ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
316 static int unload_module(void)
318 ast_cdr_unregister(name);
322 static int load_module(void)
327 return AST_MODULE_LOAD_DECLINE;
329 if ((res = ast_cdr_register(name, ast_module_info->description, csv_log)))
330 ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
335 static int reload(void)
342 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Comma Separated Values CDR Backend",
344 .unload = unload_module,