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$")
35 #include <sys/types.h>
44 #include "asterisk/config.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/cdr.h"
47 #include "asterisk/module.h"
48 #include "asterisk/logger.h"
49 #include "asterisk/utils.h"
50 #include "asterisk/lock.h"
52 #define CSV_LOG_DIR "/cdr-csv"
53 #define CSV_MASTER "/Master.csv"
55 #define DATE_FORMAT "%Y-%m-%d %T"
57 static int usegmtime
= 0;
58 static int loguniqueid
= 0;
59 static int loguserfield
= 0;
60 static char *config
= "cdr.conf";
62 /* #define CSV_LOGUNIQUEID 1 */
63 /* #define CSV_LOGUSERFIELD 1 */
65 /*----------------------------------------------------
66 The values are as follows:
69 "accountcode", accountcode is the account name of detail records, Master.csv contains all records *
70 Detail records are configured on a channel basis, IAX and SIP are determined by user *
71 Zap is determined by channel in zaptel.conf
74 "destination context",
77 "destination channel", (if applicable)
78 "last application", Last application run on the channel
79 "last app argument", argument to the last channel
83 duration, Duration is the whole length that the entire call lasted. ie. call rx'd to hangup
84 "end time" minus "start time"
85 billable seconds, the duration that a call was up after other end answered which will be <= to duration
86 "end time" minus "answer time"
87 "disposition", ANSWERED, NO ANSWER, BUSY
88 "amaflags", DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode.
89 "uniqueid", unique call identifier
90 "userfield" user field set via SetCDRUserField
91 ----------------------------------------------------------*/
93 static char *name
= "csv";
95 AST_MUTEX_DEFINE_STATIC(mf_lock
);
96 AST_MUTEX_DEFINE_STATIC(acf_lock
);
98 static int load_config(void)
100 struct ast_config
*cfg
;
101 struct ast_variable
*var
;
108 cfg
= ast_config_load(config
);
111 ast_log(LOG_WARNING
, "unable to load config: %s\n", config
);
115 var
= ast_variable_browse(cfg
, "csv");
117 ast_config_destroy(cfg
);
121 tmp
= ast_variable_retrieve(cfg
, "csv", "usegmtime");
123 usegmtime
= ast_true(tmp
);
125 ast_log(LOG_DEBUG
, "logging time in GMT\n");
129 tmp
= ast_variable_retrieve(cfg
, "csv", "loguniqueid");
131 loguniqueid
= ast_true(tmp
);
133 ast_log(LOG_DEBUG
, "logging CDR field UNIQUEID\n");
137 tmp
= ast_variable_retrieve(cfg
, "csv", "loguserfield");
139 loguserfield
= ast_true(tmp
);
141 ast_log(LOG_DEBUG
, "logging CDR user-defined field\n");
145 ast_config_destroy(cfg
);
149 static int append_string(char *buf
, char *s
, size_t bufsize
)
151 int pos
= strlen(buf
);
154 if (pos
>= bufsize
- 4)
158 while(pos
< bufsize
- 3) {
165 buf
[pos
++] = s
[spos
];
174 static int append_int(char *buf
, int s
, size_t bufsize
)
177 int pos
= strlen(buf
);
178 snprintf(tmp
, sizeof(tmp
), "%d", s
);
179 if (pos
+ strlen(tmp
) > bufsize
- 3)
181 strncat(buf
, tmp
, bufsize
- strlen(buf
) - 1);
188 static int append_date(char *buf
, struct timeval tv
, size_t bufsize
)
194 if (strlen(buf
) > bufsize
- 3)
196 if (ast_tvzero(tv
)) {
197 strncat(buf
, ",", bufsize
- strlen(buf
) - 1);
203 ast_localtime(&t
, &tm
, NULL
);
205 strftime(tmp
, sizeof(tmp
), DATE_FORMAT
, &tm
);
206 return append_string(buf
, tmp
, bufsize
);
209 static int build_csv_record(char *buf
, size_t bufsize
, struct ast_cdr
*cdr
)
214 append_string(buf
, cdr
->accountcode
, bufsize
);
216 append_string(buf
, cdr
->src
, bufsize
);
218 append_string(buf
, cdr
->dst
, bufsize
);
219 /* Destination context */
220 append_string(buf
, cdr
->dcontext
, bufsize
);
222 append_string(buf
, cdr
->clid
, bufsize
);
224 append_string(buf
, cdr
->channel
, bufsize
);
225 /* Destination Channel */
226 append_string(buf
, cdr
->dstchannel
, bufsize
);
227 /* Last Application */
228 append_string(buf
, cdr
->lastapp
, bufsize
);
230 append_string(buf
, cdr
->lastdata
, bufsize
);
232 append_date(buf
, cdr
->start
, bufsize
);
234 append_date(buf
, cdr
->answer
, bufsize
);
236 append_date(buf
, cdr
->end
, bufsize
);
238 append_int(buf
, cdr
->duration
, bufsize
);
239 /* Billable seconds */
240 append_int(buf
, cdr
->billsec
, bufsize
);
242 append_string(buf
, ast_cdr_disp2str(cdr
->disposition
), bufsize
);
244 append_string(buf
, ast_cdr_flags2str(cdr
->amaflags
), bufsize
);
247 append_string(buf
, cdr
->uniqueid
, bufsize
);
248 /* append the user field */
250 append_string(buf
, cdr
->userfield
,bufsize
);
251 /* If we hit the end of our buffer, log an error */
252 if (strlen(buf
) < bufsize
- 5) {
253 /* Trim off trailing comma */
254 buf
[strlen(buf
) - 1] = '\0';
255 strncat(buf
, "\n", bufsize
- strlen(buf
) - 1);
261 static int writefile(char *s
, char *acc
)
265 if (strchr(acc
, '/') || (acc
[0] == '.')) {
266 ast_log(LOG_WARNING
, "Account code '%s' insecure for writing file\n", acc
);
269 snprintf(tmp
, sizeof(tmp
), "%s/%s/%s.csv", (char *)ast_config_AST_LOG_DIR
,CSV_LOG_DIR
, acc
);
271 ast_mutex_lock(&acf_lock
);
274 ast_mutex_unlock(&acf_lock
);
275 ast_log(LOG_ERROR
, "Unable to open file %s : %s\n", tmp
, strerror(errno
));
281 ast_mutex_unlock(&acf_lock
);
287 static int csv_log(struct ast_cdr
*cdr
)
290 /* Make sure we have a big enough buf */
292 char csvmaster
[PATH_MAX
];
293 snprintf(csvmaster
, sizeof(csvmaster
),"%s/%s/%s", ast_config_AST_LOG_DIR
, CSV_LOG_DIR
, CSV_MASTER
);
295 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
);
297 if (build_csv_record(buf
, sizeof(buf
), cdr
)) {
298 ast_log(LOG_WARNING
, "Unable to create CSV record in %d bytes. CDR not recorded!\n", (int)sizeof(buf
));
300 /* because of the absolutely unconditional need for the
301 highest reliability possible in writing billing records,
302 we open write and close the log file each time */
303 ast_mutex_lock(&mf_lock
);
304 mf
= fopen(csvmaster
, "a");
307 fflush(mf
); /* be particularly anal here */
310 ast_mutex_unlock(&mf_lock
);
312 ast_mutex_unlock(&mf_lock
);
313 ast_log(LOG_ERROR
, "Unable to re-open master file %s : %s\n", csvmaster
, strerror(errno
));
316 if (!ast_strlen_zero(cdr
->accountcode
)) {
317 if (writefile(buf
, cdr
->accountcode
))
318 ast_log(LOG_WARNING
, "Unable to write CSV record to account file '%s' : %s\n", cdr
->accountcode
, strerror(errno
));
324 static int unload_module(void)
326 ast_cdr_unregister(name
);
330 static int load_module(void)
335 return AST_MODULE_LOAD_DECLINE
;
337 res
= ast_cdr_register(name
, ast_module_info
->description
, csv_log
);
339 ast_log(LOG_ERROR
, "Unable to register CSV CDR handling\n");
344 static int reload(void)
351 AST_MODULE_INFO(ASTERISK_GPL_KEY
, AST_MODFLAG_DEFAULT
, "Comma Separated Values CDR Backend",
353 .unload
= unload_module
,