Skip to main content

Chapter 27: Call Detail Records

FreeSWITCH generates one Call Detail Record (CDR) per call leg at hangup. This chapter covers the three CDR modules most commonly deployed: mod_cdr_csv writes delimited text files, mod_xml_cdr HTTP-POSTs an XML document to a remote endpoint, and mod_cdr_sqlite inserts rows into a local SQLite database. Each module is configured independently and can be loaded alongside the others. Several additional bundled CDR backends are documented in Part 9 (see Other CDR Backends below).

How CDRs Are Produced

CDR generation is triggered by the on_reporting state-handler callback that each CDR module registers. The callback fires once per call leg at the moment the channel transitions to the reporting state, which occurs immediately after hangup processing completes.

A bridged call produces two legs: the A-leg (originating channel) and the B-leg (originated channel). Each CDR module exposes a legs parameter that controls which legs are recorded. Recording only the A-leg is the default for mod_cdr_csv and mod_cdr_sqlite.

Channel variables are expanded at record time. Any channel variable accessible on the session at hangup can be referenced in a template using ${variable_name} syntax.


mod_cdr_csv

mod_cdr_csv appends one line per call leg to flat CSV files. It is loaded by default in the vanilla configuration.

Configuration file: conf/autoload_configs/cdr_csv.conf.xml

Configuration Parameters

ParameterPurposeAccepted ValuesDefault
log-baseParent directory for CDR output. The string cdr-csv is always appended to form the actual directory path.Absolute pathFreeSWITCH log directory
default-templateName of the template to use for all records that do not match an account-code-specific template.Any named templatedefault
legsWhich call legs to record.a, b, or aba
rotate-on-hupWhen true, rotate all open log files when the process receives SIGHUP. When false, SIGHUP still triggers close-and-reopen without renaming.true, falsefalse (source default; vanilla conf.xml sets true)
master-file-onlyWhen true, suppress per-account-code CSV files; write only to Master.csv.true, falsefalse
debugLog the full channel variable set to the FreeSWITCH log at INFO level for each CDR event.true, falsefalse

Templates

A template is a string in which channel variables are referenced as ${variable_name}. At call teardown the module expands all variables against the live channel and appends the resulting line to the output file.

Templates are defined inside the <templates> block of the configuration file:

<templates>
<template name="my_template">"${caller_id_name}","${caller_id_number}","${destination_number}"</template>
</templates>

The template name default is reserved. If no <template name="default"> element is present, the module falls back to its compiled-in default (see section 2.3).

Account-code routing: when the channel variable ACCOUNTCODE is set, the module looks for a template whose name matches that account code. If found, records for that channel are written to a file named <accountcode>.csv in addition to Master.csv. If not found, the default-template is used for both files.

The vanilla configuration ships these named templates:

Template nameIntended use
exampleGeneral purpose; includes codec information
sqlINSERT statement suitable for direct database import
snomSnom SIP phones; adds user-agent and RTP statistics
linksysLinksys/Cisco ATA devices
asteriskField-compatible with Asterisk CDR output
opencdrrateOpenCDRate billing system format

The Built-in Default Template

At module startup, the module pre-registers one template into the template hash under the key default:

"${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}","${uuid}","${bleg_uuid}", "${accountcode}"

Note the space before "${accountcode}" -- this is a literal space in the compiled-in string, so the field appears as "${accountcode}" (with a leading space) in the CSV output.

If default-template is set to a name that is not found in the template hash at call time, the module substitutes a second compiled-in Asterisk-compatible fallback:

"${accountcode}","${caller_id_number}","${destination_number}","${context}","${caller_id}","${channel_name}","${bridge_channel}","${last_app}","${last_arg}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}","${amaflags}","${uuid}","${userfield}"

This 18-field Asterisk-compatible template is used only when the named template cannot be resolved. It is not the same as the pre-registered default template above.

The vanilla configuration sets default-template to example, so by default the example template from cdr_csv.conf.xml is active, not either compiled-in fallback.

File Output

The module writes two categories of files into the log-base/cdr-csv directory:

  • Master.csv - receives every CDR regardless of account code, using the template selected by default-template.
  • <accountcode>.csv - receives CDRs for calls where ACCOUNTCODE is set, rendered with the matching account-code template if one exists, or the default template otherwise. Suppressed when master-file-only is true.

The channel variable cdr_csv_base overrides the global log-base on a per-channel basis. When cdr_csv_base is set on a session, all CDR files for that call are written to the specified directory instead of the global log-base/cdr-csv path.

Log Rotation

When rotate-on-hup is true, sending SIGHUP to the FreeSWITCH process renames each open file by appending a timestamp suffix in the format YYYY-MM-DD-HH-MM-SS, then reopens a fresh file at the original path. The API command cdr_csv rotate triggers the same rotation without a signal.

When rotate-on-hup is false, a SIGHUP causes the files to be closed and reopened in place (useful when an external log management tool has already moved them).

Custom CSV Template Example

<configuration name="cdr_csv.conf" description="CDR CSV Format">
<settings>
<param name="default-template" value="my_billing"/>
<param name="legs" value="a"/>
<param name="rotate-on-hup" value="true"/>
<param name="log-base" value="/var/log/freeswitch"/>
</settings>
<templates>
<template name="my_billing">"${uuid}","${caller_id_number}","${destination_number}","${start_stamp}","${answer_stamp}","${end_stamp}","${billsec}","${hangup_cause}","${accountcode}"</template>
</templates>
</configuration>

With this configuration, mod_cdr_csv writes to /var/log/freeswitch/cdr-csv/Master.csv using the my_billing template for every A-leg.


mod_xml_cdr

mod_xml_cdr generates a structured XML document for each call leg and delivers it via HTTP POST to one or more configured URLs. When no URL is configured, or as a fallback on POST failure, it writes the XML to disk. mod_xml_cdr is not loaded by default; enable it in conf/autoload_configs/modules.conf.xml.

Configuration file: conf/autoload_configs/xml_cdr.conf.xml

The module is located under src/mod/xml_int/mod_xml_cdr (not event_handlers).

Configuration Parameters

ParameterPurposeAccepted ValuesDefault
urlHTTP or HTTPS endpoint to POST records to. Up to 20 URLs may be specified; the module cycles through them on failure. Omitting this parameter disables HTTP delivery.URL stringNone
credHTTP credentials in user:password form.StringNone
retriesNumber of additional POST attempts after the first failure. Setting retries without setting delay forces delay to 5.Integer0
delaySeconds to wait between retry attempts. Also used as the TCP connection timeout for each attempt; when delay is 0, the connection timeout defaults to 5 seconds.Integer0 (forced to 5 if retries is set and delay is 0)
timeoutTotal HTTP transaction timeout in seconds. 0 disables the timeout.Integer0
log-dirDirectory for on-disk XML records. An empty value resolves to ${prefix}/logs/xml_cdr. A relative value is interpreted relative to ${prefix}/logs. An absolute path is used as-is. Omitting the parameter entirely disables disk logging (unless HTTP fails).Path or empty stringNot set
err-log-dirDirectory for XML records that could not be delivered via HTTP. Defaults to log-dir when unset. Accepts the same path forms as log-dir.Path or empty stringSame as log-dir
log-http-and-diskWhen true, write every record to disk even when HTTP POST succeeds.true, falsefalse
log-b-legWhen false, suppress CDR generation for B-legs unless the channel variable force_process_cdr is true.true, falsetrue (source default; vanilla conf.xml sets false)
prefix-a-legWhen true, prefix on-disk filenames for A-legs with a_. The channel variable prefix-a-leg on the session overrides this global setting per-call.true, falsefalse (source default; vanilla conf.xml sets true)
encodeEncoding applied to the XML payload before POST. true applies URL encoding (application/x-www-form-urlencoded); base64 applies Base64 encoding; textxml sends raw XML as text/xml; false sends unencoded plaintext with Content-Type: application/x-www-form-plaintext.true, false, base64, textxmlfalse (source default; vanilla conf.xml sets true)
rotateWhen true, on SIGHUP the log directories are rotated into timestamped subdirectories.true, falsefalse
disable-100-continueWhen true, suppress the Expect: 100-continue header. Required for some lighttpd configurations.true, falsefalse
auth-schemeHTTP authentication scheme for credential delivery.basic, digest, NTLM, GSS-NEGOTIATE, anybasic
cookie-filePath to a file used for cookie persistence across requests.Absolute pathNone

TLS and Authentication Parameters

ParameterPurposeAccepted ValuesDefault
enable-cacert-checkInstruct libcurl to verify the server certificate against the system CA bundle. Disabled by default.true, falsefalse
enable-ssl-verifyhostVerify that the server hostname matches the certificate CN.true, falsefalse
ssl-cert-pathPath to the PEM-encoded client public key certificate.Absolute pathNone
ssl-key-pathPath to the PEM-encoded client private key.Absolute pathNone
ssl-key-passwordPassphrase for the private key if it is encrypted.StringNone
ssl-versionForce a specific SSL/TLS version.SSLv3, TLSv1libcurl auto-negotiates
ssl-cacert-filePath to a custom PEM CA certificate for peer verification. Meaningful only when enable-cacert-check is true.Absolute pathNone

Example URL Binding

<configuration name="xml_cdr.conf" description="XML CDR CURL logger">
<settings>
<param name="url" value="https://billing.example.com/cdr"/>
<param name="cred" value="fsuser:secret"/>
<param name="retries" value="2"/>
<param name="delay" value="5"/>
<param name="encode" value="textxml"/>
<param name="log-dir" value="/var/log/freeswitch/xml_cdr"/>
<param name="err-log-dir" value="/var/log/freeswitch/xml_cdr_err"/>
<param name="log-b-leg" value="false"/>
<param name="prefix-a-leg" value="true"/>
<param name="enable-cacert-check" value="true"/>
<param name="auth-scheme" value="basic"/>
</settings>
</configuration>

With encode set to textxml, the module sends the XML document as-is with a Content-Type: text/xml header. Setting encode to true sends URL-encoded data with Content-Type: application/x-www-form-urlencoded. Setting encode to false sends plaintext with Content-Type: application/x-www-form-plaintext. In all non-textxml cases, the body is wrapped as cdr=<payload>.

The UUID of the session is appended to the configured URL as a query parameter: uuid=<uuid> (or uuid=a_<uuid> when prefix-a-leg is active and the leg is an A-leg). If the URL already contains a ?, the parameter is joined with & instead.

When all HTTP attempts fail, the module writes the XML to err-log-dir with the filename <uuid>.cdr.xml (or a_<uuid>.cdr.xml when prefix-a-leg is true and the leg is an A-leg).

The channel variable xml_cdr_base on the session overrides the global log-dir on a per-call basis for on-disk writes.


mod_cdr_sqlite

mod_cdr_sqlite inserts one row per call leg into a SQLite database table. The database file is created automatically if it does not exist; the table is created automatically if it does not exist, using the default schema. mod_cdr_sqlite is not loaded by default; enable it in conf/autoload_configs/modules.conf.xml.

Configuration file: conf/autoload_configs/cdr_sqlite.conf.xml

Configuration Parameters

ParameterPurposeAccepted ValuesDefault
db-nameBase name of the SQLite database file. The .db suffix is appended automatically by the FreeSWITCH DSN layer.Stringcdr
db-tableName of the table within the database that receives CDR rows.Stringcdr
legsWhich call legs to record.a, b, or aba
default-templateName of the template used to generate the VALUES clause of each INSERT statement.Any named templatedefault
debugLog the full channel variable set to the FreeSWITCH log at INFO level for each CDR event.true, falsefalse

Default Table Schema

When the configured table does not exist at startup, the module creates it with the following schema:

CREATE TABLE cdr (
caller_id_name VARCHAR,
caller_id_number VARCHAR,
destination_number VARCHAR,
context VARCHAR,
start_stamp DATETIME,
answer_stamp DATETIME,
end_stamp DATETIME,
duration INTEGER,
billsec INTEGER,
hangup_cause VARCHAR,
uuid VARCHAR,
bleg_uuid VARCHAR,
account_code VARCHAR
);

The table name substituted for cdr is the value of db-table. The module executes INSERT INTO <db-table> VALUES (...) at hangup; the VALUES clause is generated by expanding the active template. Column order in the template must match column order in the table definition, or inserts will fail.

Note that the auto-created schema names the last column account_code (with an underscore), while the channel variable referenced in the built-in default template is ${accountcode} (no underscore). These refer to the same call data; the discrepancy is in the column name vs the channel variable name.

Templates

Templates for mod_cdr_sqlite follow the same ${variable_name} expansion syntax as mod_cdr_csv, but the output is used as the VALUES clause of an SQL INSERT, not as a CSV line. Numeric fields (duration, billsec) must not be quoted in the template; string fields must be quoted.

The built-in default template:

"${caller_id_name}","${caller_id_number}","${destination_number}","${context}",
"${start_stamp}","${answer_stamp}","${end_stamp}",${duration},${billsec},
"${hangup_cause}","${uuid}","${bleg_uuid}","${accountcode}"

Custom templates are defined in the <templates> block and referenced by name with default-template:

<configuration name="cdr_sqlite.conf" description="SQLite CDR">
<settings>
<param name="db-name" value="freeswitch_cdr"/>
<param name="db-table" value="cdr"/>
<param name="legs" value="ab"/>
<param name="default-template" value="example"/>
</settings>
<templates>
<template name="example">"${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}",${duration},${billsec},"${hangup_cause}","${uuid}","${bleg_uuid}","${accountcode}"</template>
</templates>
</configuration>

Module Load Status

ModuleLoaded by default (vanilla conf)
mod_cdr_csvYes
mod_xml_cdrNo
mod_cdr_sqliteNo

To enable a module that is not loaded by default, uncomment or add its <load module="..."/> entry in conf/autoload_configs/modules.conf.xml and reload or restart FreeSWITCH. Multiple CDR modules may be active simultaneously; each fires its own on_reporting callback independently.


Other CDR Backends

Beyond the three modules covered above, FreeSWITCH bundles additional CDR backends that target other storage and delivery mechanisms. These are documented in the Part 9 Module Reference:

  • mod_json_cdr - posts each CDR as a JSON document over HTTP.
  • mod_format_cdr - emits CDRs in a configurable format (JSON or XML) with HTTP posting options.
  • mod_odbc_cdr - inserts CDR rows into a database via ODBC.
  • mod_cdr_pg_csv - writes CDRs directly to PostgreSQL.