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 * \arg See also \ref AstCDR
26 * \ingroup cdr_drivers
29 #include <sys/types.h>
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
35 #include "asterisk/channel.h"
36 #include "asterisk/cdr.h"
37 #include "asterisk/module.h"
38 #include "asterisk/logger.h"
39 #include "asterisk/utils.h"
41 #define CSV_LOG_DIR "/cdr-csv"
42 #define CSV_MASTER "/Master.csv"
44 #define DATE_FORMAT "%Y-%m-%d %T"
46 /* #define CSV_LOGUNIQUEID 1 */
47 /* #define CSV_LOGUSERFIELD 1 */
57 /*----------------------------------------------------
58 The values are as follows:
61 "accountcode", accountcode is the account name of detail records, Master.csv contains all records *
62 Detail records are configured on a channel basis, IAX and SIP are determined by user *
63 Zap is determined by channel in zaptel.conf
66 "destination context",
69 "destination channel", (if applicable)
70 "last application", Last application run on the channel
71 "last app argument", argument to the last channel
75 duration, Duration is the whole length that the entire call lasted. ie. call rx'd to hangup
76 "end time" minus "start time"
77 billable seconds, the duration that a call was up after other end answered which will be <= to duration
78 "end time" minus "answer time"
79 "disposition", ANSWERED, NO ANSWER, BUSY
80 "amaflags", DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode.
81 "uniqueid", unique call identifier
82 "userfield" user field set via SetCDRUserField
83 ----------------------------------------------------------*/
85 static char *desc = "Comma Separated Values CDR Backend";
87 static char *name = "csv";
89 static FILE *mf = NULL;
91 static int append_string(char *buf, char *s, size_t bufsize)
93 int pos = strlen(buf);
96 if (pos >= bufsize - 4)
100 while(pos < bufsize - 3) {
107 buf[pos++] = s[spos];
116 static int append_int(char *buf, int s, size_t bufsize)
119 int pos = strlen(buf);
120 snprintf(tmp, sizeof(tmp), "%d", s);
121 if (pos + strlen(tmp) > bufsize - 3)
123 strncat(buf, tmp, bufsize - strlen(buf) - 1);
130 static int append_date(char *buf, struct timeval tv, size_t bufsize)
136 if (strlen(buf) > bufsize - 3)
138 if (ast_tvzero(tv)) {
139 strncat(buf, ",", bufsize - strlen(buf) - 1);
143 strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
144 return append_string(buf, tmp, bufsize);
147 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
152 append_string(buf, cdr->accountcode, bufsize);
154 append_string(buf, cdr->src, bufsize);
156 append_string(buf, cdr->dst, bufsize);
157 /* Destination context */
158 append_string(buf, cdr->dcontext, bufsize);
160 append_string(buf, cdr->clid, bufsize);
162 append_string(buf, cdr->channel, bufsize);
163 /* Destination Channel */
164 append_string(buf, cdr->dstchannel, bufsize);
165 /* Last Application */
166 append_string(buf, cdr->lastapp, bufsize);
168 append_string(buf, cdr->lastdata, bufsize);
170 append_date(buf, cdr->start, bufsize);
172 append_date(buf, cdr->answer, bufsize);
174 append_date(buf, cdr->end, bufsize);
176 append_int(buf, cdr->duration, bufsize);
177 /* Billable seconds */
178 append_int(buf, cdr->billsec, bufsize);
180 append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
182 append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
184 #ifdef CSV_LOGUNIQUEID
186 append_string(buf, cdr->uniqueid, bufsize);
188 #ifdef CSV_LOGUSERFIELD
189 /* append the user field */
190 append_string(buf, cdr->userfield,bufsize);
192 /* If we hit the end of our buffer, log an error */
193 if (strlen(buf) < bufsize - 5) {
194 /* Trim off trailing comma */
195 buf[strlen(buf) - 1] = '\0';
196 strncat(buf, "\n", bufsize - strlen(buf) - 1);
202 static int writefile(char *s, char *acc)
204 char tmp[AST_CONFIG_MAX_PATH];
206 if (strchr(acc, '/') || (acc[0] == '.')) {
207 ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
210 snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", (char *)ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
221 static int csv_log(struct ast_cdr *cdr)
223 /* Make sure we have a big enough buf */
225 char csvmaster[AST_CONFIG_MAX_PATH];
226 snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
228 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);
230 if (build_csv_record(buf, sizeof(buf), cdr)) {
231 ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes. CDR not recorded!\n", (int)sizeof(buf));
233 /* because of the absolutely unconditional need for the
234 highest reliability possible in writing billing records,
235 we open write and close the log file each time */
236 mf = fopen(csvmaster, "a");
238 ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
242 fflush(mf); /* be particularly anal here */
246 if (!ast_strlen_zero(cdr->accountcode)) {
247 if (writefile(buf, cdr->accountcode))
248 ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
254 char *description(void)
259 int unload_module(void)
263 ast_cdr_unregister(name);
267 int load_module(void)
271 res = ast_cdr_register(name, desc, csv_log);
273 ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
292 return ASTERISK_GPL_KEY;