diff --git a/apier/v1/apier.go b/apier/v1/apier.go
index a304c24ee..62b1b714f 100644
--- a/apier/v1/apier.go
+++ b/apier/v1/apier.go
@@ -24,6 +24,7 @@ import (
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/scheduler"
"github.com/cgrates/cgrates/utils"
+ "github.com/cgrates/cgrates/config"
)
const (
@@ -33,7 +34,9 @@ const (
type ApierV1 struct {
StorDb engine.LoadStorage
DataDb engine.DataStorage
+ CdrDb engine.CdrStorage
Sched *scheduler.Scheduler
+ Config *config.CGRConfig
}
type AttrDestination struct {
diff --git a/apier/v1/cdrs.go b/apier/v1/cdrs.go
new file mode 100644
index 000000000..eac6699a2
--- /dev/null
+++ b/apier/v1/cdrs.go
@@ -0,0 +1,72 @@
+/*
+Rating system designed to be used in VoIP Carriers World
+Copyright (C) 2013 ITsysCOM
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see
+*/
+
+package apier
+
+import (
+ "fmt"
+ "os"
+ "time"
+ "path"
+ "github.com/cgrates/cgrates/utils"
+ "github.com/cgrates/cgrates/cdrexporter"
+)
+
+type AttrExpCsvCdrs struct {
+ TimeStart string // If provided, will represent the starting of the CDRs interval (>=)
+ TimeEnd string // If provided, will represent the end of the CDRs interval (<)
+}
+
+func (self *ApierV1) ExportCsvCdrs(attr *AttrExpCsvCdrs, reply *string) error {
+ var tStart, tEnd time.Time
+ var err error
+ if len(attr.TimeStart) !=0 {
+ if tStart, err = utils.ParseDate( attr.TimeStart ); err != nil {
+ return err
+ }
+ }
+ if len(attr.TimeEnd) !=0 {
+ if tEnd, err = utils.ParseDate( attr.TimeEnd ); err != nil {
+ return err
+ }
+ }
+ cdrs, err := self.CdrDb.GetRatedCdrs(tStart, tEnd)
+ if err != nil {
+ return err
+ }
+ fileName := path.Join(self.Config.CDRSExportPath, "cgr", "csv", fmt.Sprintf("cdrs_%d.csv",time.Now().Unix()))
+ fileOut, err := os.Create(fileName)
+ if err != nil {
+ return err
+ } else {
+ defer fileOut.Close()
+ }
+ csvWriter := cdrexporter.NewCsvCdrWriter(fileOut, self.Config.RoundingDecimals, self.Config.CDRSExportExtraFields)
+ for _, cdr := range cdrs {
+ if err := csvWriter.Write( cdr.(*utils.RatedCDR) ); err != nil {
+ os.Remove(fileName)
+ return err
+ }
+ }
+ csvWriter.Close()
+ *reply = fileName
+ return nil
+}
+
+
+
diff --git a/cdrexporter/cdrexporter.go b/cdrexporter/cdrexporter.go
index 1a0963e3c..f5915cff5 100644
--- a/cdrexporter/cdrexporter.go
+++ b/cdrexporter/cdrexporter.go
@@ -18,7 +18,11 @@ along with this program. If not, see
package cdrexporter
+import (
+ "github.com/cgrates/cgrates/utils"
+)
+
type CdrWriter interface {
- Write(cdr map[string]string) string
+ Write(cdr utils.RatedCDR) string
Close()
}
diff --git a/cdrexporter/csv.go b/cdrexporter/csv.go
index 38dd50a15..43ed23dcf 100644
--- a/cdrexporter/csv.go
+++ b/cdrexporter/csv.go
@@ -21,20 +21,35 @@ package cdrexporter
import (
"encoding/csv"
"io"
+ "sort"
+ "strconv"
+ "github.com/cgrates/cgrates/utils"
)
type CsvCdrWriter struct {
writer *csv.Writer
+ roundDecimals int // Round floats like Cost using this number of decimals
+ extraFields []string // Extra fields to append after primary ones, order important
}
-func NewCsvCdrWriter(writer io.Writer) *CsvCdrWriter {
- return &CsvCdrWriter{csv.NewWriter(writer)}
+func NewCsvCdrWriter(writer io.Writer, roundDecimals int, extraFields []string) *CsvCdrWriter {
+ return &CsvCdrWriter{csv.NewWriter(writer), roundDecimals, extraFields}
}
-func (dcw *CsvCdrWriter) Write(cdr map[string]string) error {
- var row []string
- for _, v := range cdr {
- row = append(row, v)
+func (dcw *CsvCdrWriter) Write(cdr *utils.RatedCDR) error {
+ primaryFields := []string{cdr.CgrId, cdr.AccId, cdr.CdrHost, cdr.ReqType, cdr.Direction, cdr.Tenant, cdr.TOR, cdr.Account, cdr.Subject,
+ cdr.Destination, cdr.AnswerTime.String(), strconv.Itoa(int(cdr.Duration)), strconv.FormatFloat(cdr.Cost, 'f', dcw.roundDecimals, 64)}
+ if len(dcw.extraFields) == 0 {
+ dcw.extraFields = utils.MapKeys(cdr.ExtraFields)
+ sort.Strings(dcw.extraFields) // Controlled order in case of dynamic extra fields
+ }
+ lenPrimary := len(primaryFields)
+ row := make([]string, lenPrimary+len(dcw.extraFields))
+ for idx, fld := range primaryFields { // Add primary fields
+ row[idx] = fld
+ }
+ for idx, fldKey := range dcw.extraFields { // Add extra fields
+ row[lenPrimary+idx] = cdr.ExtraFields[fldKey]
}
return dcw.writer.Write(row)
}
diff --git a/cmd/cgr-engine/cgr-engine.go b/cmd/cgr-engine/cgr-engine.go
index b3696af89..f23c5b819 100644
--- a/cmd/cgr-engine/cgr-engine.go
+++ b/cmd/cgr-engine/cgr-engine.go
@@ -346,7 +346,7 @@ func main() {
go stopRaterSingnalHandler()
}
responder := &engine.Responder{ExitChan: exitChan}
- apier := &apier.ApierV1{StorDb: loadDb, DataDb: dataDb}
+ apier := &apier.ApierV1{StorDb: loadDb, DataDb: dataDb, CdrDb: cdrDb, Config: cfg}
if cfg.RaterEnabled && !cfg.BalancerEnabled && cfg.RaterListen != INTERNAL {
engine.Logger.Info(fmt.Sprintf("Starting CGRateS Rater on %s.", cfg.RaterListen))
go listenToRPCRequests(responder, apier, cfg.RaterListen, cfg.RPCEncoding, dataDb, logDb)
diff --git a/config/config.go b/config/config.go
index 372dc262b..d05986809 100644
--- a/config/config.go
+++ b/config/config.go
@@ -70,6 +70,8 @@ type CGRConfig struct {
CDRSListen string // CDRS's listening interface: .
CDRSExtraFields []string //Extra fields to store in CDRs
CDRSMediator string // Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
+ CDRSExportPath string // Path towards exported cdrs
+ CDRSExportExtraFields []string // Extra fields list to add in exported CDRs
SMEnabled bool
SMSwitchType string
SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
@@ -133,6 +135,8 @@ func (self *CGRConfig) setDefaults() error {
self.CDRSListen = "127.0.0.1:2022"
self.CDRSExtraFields = []string{}
self.CDRSMediator = ""
+ self.CDRSExportPath = "/var/log/cgrates/cdr/out"
+ self.CDRSExportExtraFields = []string{}
self.MediatorEnabled = false
self.MediatorListen = "127.0.0.1:2032"
self.MediatorRater = "127.0.0.1:2012"
@@ -287,6 +291,14 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("cdrs", "mediator"); hasOpt {
cfg.CDRSMediator, _ = c.GetString("cdrs", "mediator")
}
+ if hasOpt = c.HasOption("cdrs", "export_path"); hasOpt {
+ cfg.CDRSExportPath, _ = c.GetString("cdrs", "export_path")
+ }
+ if hasOpt = c.HasOption("cdrs", "export_extra_fields"); hasOpt {
+ if cfg.CDRSExportExtraFields, errParse = ConfigSlice(c, "cdrs", "export_extra_fields"); errParse != nil {
+ return nil, errParse
+ }
+ }
if hasOpt = c.HasOption("mediator", "enabled"); hasOpt {
cfg.MediatorEnabled, _ = c.GetBool("mediator", "enabled")
}
diff --git a/config/config_test.go b/config/config_test.go
index 58266f88b..4622b1269 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -64,6 +64,8 @@ func TestDefaults(t *testing.T) {
eCfg.CDRSListen = "127.0.0.1:2022"
eCfg.CDRSExtraFields = []string{}
eCfg.CDRSMediator = ""
+ eCfg.CDRSExportPath = "/var/log/cgrates/cdr/out"
+ eCfg.CDRSExportExtraFields = []string{}
eCfg.MediatorEnabled = false
eCfg.MediatorListen = "127.0.0.1:2032"
eCfg.MediatorRater = "127.0.0.1:2012"
@@ -160,6 +162,8 @@ func TestConfigFromFile(t *testing.T) {
eCfg.CDRSListen = "test"
eCfg.CDRSExtraFields = []string{"test"}
eCfg.CDRSMediator = "test"
+ eCfg.CDRSExportPath = "test"
+ eCfg.CDRSExportExtraFields = []string{"test"}
eCfg.MediatorEnabled = true
eCfg.MediatorListen = "test"
eCfg.MediatorRater = "test"
diff --git a/config/test_data.txt b/config/test_data.txt
index 006d72f5b..79e0f7c58 100644
--- a/config/test_data.txt
+++ b/config/test_data.txt
@@ -41,6 +41,8 @@ enabled = true # Start the CDR Server service: .
listen=test # CDRS's listening interface: .
extra_fields = test # Extra fields to store in CDRs
mediator = test # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
+export_path = test # Path where exported cdrs will be written
+export_extra_fields = test # Extra fields list to be exported
[mediator]
enabled = true # Starts Mediator service: .
diff --git a/data/conf/cgrates.cfg b/data/conf/cgrates.cfg
index b8d1c0de0..957bba74f 100644
--- a/data/conf/cgrates.cfg
+++ b/data/conf/cgrates.cfg
@@ -5,82 +5,85 @@
# [global] must exist in all files, rest of the configuration is inter-changeable.
[global]
-# datadb_type = redis # The main database: .
-# datadb_host = 127.0.0.1 # Database host address.
-# datadb_port = 6379 # Port to reach the database.
-# datadb_name = 10 # The name of the database to connect to.
-# datadb_user = # Username to use when connecting to database.
-# datadb_passwd = # Password to use when connecting to database.
-# stordb_type = mysql # Log/stored database type to use:
-# stordb_host = 127.0.0.1 # The host to connect to. Values that start with / are for UNIX domain sockets.
-# stordb_port = 3306 # The port to reach the logdb.
-# stordb_name = cgrates # The name of the log database to connect to.
-# stordb_user = cgrates # Username to use when connecting to stordb.
-# stordb_passwd = CGRateS.org # Password to use when connecting to stordb.
-# dbdata_encoding = msgpack # The encoding used to store object data in strings:
-# rpc_encoding = json # RPC encoding used on APIs: .
-# default_reqtype = rated # Default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated>.
-# default_tor = 0 # Default Type of Record to consider when missing from requests.
-# default_tenant = 0 # Default Tenant to consider when missing from requests.
-# default_subject = 0 # Default rating Subject to consider when missing from requests.
-# rounding_method = *middle # Rounding method for floats/costs: <*up|*middle|*down>
-# rounding_decimals = 4 # Number of decimals to round float/costs at
+# datadb_type = redis # The main database: .
+# datadb_host = 127.0.0.1 # Database host address.
+# datadb_port = 6379 # Port to reach the database.
+# datadb_name = 10 # The name of the database to connect to.
+# datadb_user = # Username to use when connecting to database.
+# datadb_passwd = # Password to use when connecting to database.
+# stordb_type = mysql # Log/stored database type to use:
+# stordb_host = 127.0.0.1 # The host to connect to. Values that start with / are for UNIX domain sockets.
+# stordb_port = 3306 # The port to reach the logdb.
+# stordb_name = cgrates # The name of the log database to connect to.
+# stordb_user = cgrates # Username to use when connecting to stordb.
+# stordb_passwd = CGRateS.org # Password to use when connecting to stordb.
+# dbdata_encoding = msgpack # The encoding used to store object data in strings:
+# rpc_encoding = json # RPC encoding used on APIs: .
+# default_reqtype = rated # Default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated>.
+# default_tor = 0 # Default Type of Record to consider when missing from requests.
+# default_tenant = 0 # Default Tenant to consider when missing from requests.
+# default_subject = 0 # Default rating Subject to consider when missing from requests.
+# rounding_method = *middle # Rounding method for floats/costs: <*up|*middle|*down>
+# rounding_decimals = 4 # Number of decimals to round float/costs at
[balancer]
-# enabled = false # Start Balancer service: .
-# listen = 127.0.0.1:2012 # Balancer listen interface: .
+# enabled = false # Start Balancer service: .
+# listen = 127.0.0.1:2012 # Balancer listen interface: .
[rater]
-# enabled = false # Enable Rater service: .
-# balancer = disabled # Register to Balancer as worker: .
-# listen = 127.0.0.1:2012 # Rater's listening interface: .
+# enabled = false # Enable RaterCDRSExportPath service: .
+# balancer = disabled # Register to Balancer as worker: .
+# listen = 127.0.0.1:2012 # Rater's listening interface: .
[scheduler]
-# enabled = false # Starts Scheduler service: .
+# enabled = false # Starts Scheduler service: .
[cdrs]
-# enabled = false # Start the CDR Server service: .
-# listen=127.0.0.1:2022 # CDRS's listening interface: .
-# extra_fields = # Extra fields to store in CDRs
-# mediator = # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
+# enabled = false # Start the CDR Server service: .
+# listen=127.0.0.1:2022 # CDRS's listening interface: .
+# extra_fields = # Extra fields to store in CDRs
+# mediator = # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
+# export_path = /var/log/cgrates/cdr/out # Path where the exported CDRs will be placed
+# export_extra_fields = # List of extra fields to be exported out in CDRs
[mediator]
-# enabled = false # Starts Mediator service: .
-# listen=internal # Mediator's listening interface: .
-# rater = 127.0.0.1:2012 # Address where to reach the Rater:
-# rater_reconnects = 3 # Number of reconnects to rater before giving up.
-# accid_field = accid # Name of field identifying accounting id used during mediation. Use index number in case of .csv cdrs.
-# subject_fields = subject # Name of subject fields to be used during mediation. Use index numbers in case of .csv cdrs.
-# reqtype_fields = reqtype # Name of request type fields to be used during mediation. Use index number in case of .csv cdrs.
-# direction_fields = direction # Name of direction fields to be used during mediation. Use index numbers in case of .csv cdrs.
-# tenant_fields = tenant # Name of tenant fields to be used during mediation. Use index numbers in case of .csv cdrs.
-# tor_fields = tor # Name of tor fields to be used during mediation. Use index numbers in case of .csv cdrs.
-# account_fields = account # Name of account fields to be used during mediation. Use index numbers in case of .csv cdrs.
-# destination_fields = destination # Name of destination fields to be used during mediation. Use index numbers in case of .csv cdrs.
-# time_answer_fields = time_answer # Name of time_answer fields to be used during mediation. Use index numbers in case of .csv cdrs.
-# duration_fields = duration # Name of duration fields to be used during mediation. Use index numbers in case of .csv cdrs.
-# cdr_type = # CDR type, used when running mediator as service .
-# cdr_in_dir = /var/log/freeswitch/cdr-csv # Absolute path towards the directory where the CDRs are kept (file stored CDRs).
-# cdr_out_dir = /var/log/cgrates/cdr/out/freeswitch/csv # Absolute path towards the directory where processed CDRs will be exported (file stored CDRs).
+# enabled = false # Starts Mediator service: .
+# listen=internal # Mediator's listening interface: .
+# rater = 127.0.0.1:2012 # Address where to reach the Rater:
+# rater_reconnects = 3 # Number of reconnects to rater before giving up.
+# accid_field = accid # Name of field identifying accounting id used during mediation. Use index number in case of .csv cdrs.
+# subject_fields = subject # Name of sCDRSExportPathubject fields to be used during mediation. Use index numbers in case of .csv cdrs.
+# reqtype_fields = reqtype # Name of request type fields to be used during mediation. Use index number in case of .csv cdrs.
+# direction_fields = direction # Name of direction fields to be used during mediation. Use index numbers in case of .csv cdrs.
+# tenant_fields = tenant # Name of tenant fields to be used during mediation. Use index numbers in case of .csv cdrs.
+# tor_fields = tor # Name of tor fields to be used during mediation. Use index numbers in case of .csv cdrs.
+# account_fields = account # Name of account fields to be used during mediation. Use index numbers in case of .csv cdrs.
+# destination_fields = destination # Name of destination fields to be used during mediation. Use index numbers in case of .csv cdrs.
+# time_answer_fields = time_answer # Name of time_answer fields to be used during mediation. Use index numbers in case of .csv cdrs.
+# duration_fields = duration # Name of duration fields to be used during mediation. Use index numbers in case of .csv cdrs.
+# cdr_type = # CDR type, used when running mediator as service .
+# cdr_in_dir = /var/log/freeswitch/cdr-csv # Absolute path towards the directory where the CDRs are kept (file stored CDRs).
+# cdr_out_dir = /var/log/cgrates/cdr/out/freeswitch/csv
+ # Absolute path towards the directory where processed CDRs will be exported (file stored CDRs).
[session_manager]
-# enabled = false # Starts SessionManager service: .
-# switch_type = freeswitch # Defines the type of switch behind: .
-# rater = 127.0.0.1:2012 # Address where to reach the Rater.
-# rater_reconnects = 3 # Number of reconnects to rater before giving up.
-# debit_interval = 5 # Interval to perform debits on.
+# enabled = false # Starts SessionManager service: .
+# switch_type = freeswitch # Defines the type of switch behind: .
+# rater = 127.0.0.1:2012 # Address where to reach the Rater.
+# rater_reconnects = 3 # Number of reconnects to rater before giving up.
+# debit_interval = 5 # Interval to perform debits on.
[freeswitch]
-# server = 127.0.0.1:8021 # Adress where to connect to FreeSWITCH socket.
-# passwd = ClueCon # FreeSWITCH socket password.
-# reconnects = 5 # Number of attempts on connect failure.
+# server = 127.0.0.1:8021 # Adress where to connect to FreeSWITCH socket.
+# passwd = ClueCon # FreeSWITCH socket password.
+# reconnects = 5 # Number of attempts on connect failure.
[history_agent]
-#enabled = false # Starts History as a client: .
-#server = 127.0.0.1:2013 # Address where to reach the master history server:
+#enabled = false # Starts History as a client: .
+#server = 127.0.0.1:2013 # Address where to reach the master history server:
[history_server]
-#enabled = false # Starts History service: .
-#listen = 127.0.0.1:2013 # Listening addres for history server:
-#path = /var/log/cgrates/history # Location on disk where to store history files.
+#enabled = false # Starts History service: .
+#listen = 127.0.0.1:2013 # Listening addres for history server:
+#path = /var/log/cgrates/history # Location on disk where to store history files.
diff --git a/data/pkg/pkg_cgrates_deb.sh b/data/pkg/pkg_cgrates_deb.sh
index d96d8b278..2980fc02b 100755
--- a/data/pkg/pkg_cgrates_deb.sh
+++ b/data/pkg/pkg_cgrates_deb.sh
@@ -18,5 +18,6 @@ mkdir -p $PKG_DIR/usr/share/cgrates
cp -r ../../data/ $PKG_DIR/usr/share/cgrates/
mkdir -p $PKG_DIR/usr/bin
cp /usr/local/goapps/bin/cgr-* $PKG_DIR/usr/bin/
+mkdir -p $PKG_DIR/var/log/cgrates/cdr/out
dpkg-deb --build $PKG_DIR