// Compose and cache the trailer func (cdre *CdrExporter) composeTrailer() error { for _, cfgFld := range cdre.exportTemplate.TrailerFields { var outVal string switch cfgFld.Type { case utils.META_FILLER: outVal = cfgFld.Value.Id() cfgFld.Padding = "right" case utils.META_CONSTANT: outVal = cfgFld.Value.Id() case utils.META_HANDLER: outVal, err = cdre.metaHandler(cfgFld.Value.Id(), cfgFld.Layout) default: return fmt.Errorf("Unsupported field type: %s", cfgFld.Type) } if err != nil { utils.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.Tag, err.Error())) return err } fmtOut := outVal if fmtOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { utils.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.Tag, err.Error())) return err } cdre.trailer = append(cdre.trailer, fmtOut) } return nil }
// Extracts data out of CCR into a SMGenericEvent based on the configured template func (self *CCR) AsSMGenericEvent(cfgFlds []*config.CfgCdrField) (sessionmanager.SMGenericEvent, error) { outMap := make(map[string]string) // work with it so we can append values to keys outMap[utils.EVENT_NAME] = DIAMETER_CCR for _, cfgFld := range cfgFlds { var outVal string var err error switch cfgFld.Type { case utils.META_FILLER: outVal = cfgFld.Value.Id() cfgFld.Padding = "right" case utils.META_CONSTANT: outVal = cfgFld.Value.Id() case utils.META_HANDLER: outVal, err = self.metaHandler(cfgFld.HandlerId, cfgFld.Layout) if err != nil { utils.Logger.Warning(fmt.Sprintf("<Diameter> Ignoring processing of metafunction: %s, error: %s", cfgFld.HandlerId, err.Error())) } case utils.META_COMPOSED: outVal = self.eventFieldValue(cfgFld.Value) } fmtOut := outVal if fmtOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { utils.Logger.Warning(fmt.Sprintf("<Diameter> Error when processing field template with tag: %s, error: %s", cfgFld.Tag, err.Error())) return nil, err } if _, hasKey := outMap[cfgFld.FieldId]; !hasKey { outMap[cfgFld.FieldId] = fmtOut } else { // If already there, postpend outMap[cfgFld.FieldId] += fmtOut } } return sessionmanager.SMGenericEvent(utils.ConvertMapValStrIf(outMap)), nil }
func (cdr *CDR) formatField(cfgFld *config.CfgCdrField, httpSkipTlsCheck bool, groupedCDRs []*CDR) (fmtOut string, err error) { layout := cfgFld.Layout if layout == "" { layout = time.RFC3339 } var outVal string switch cfgFld.Type { case utils.META_FILLER: outVal = cfgFld.Value.Id() cfgFld.Padding = "right" case utils.META_CONSTANT: outVal = cfgFld.Value.Id() case utils.MetaDateTime: // Convert the requested field value into datetime with layout rawVal, err := cdr.exportFieldValue(cfgFld) if err != nil { return "", err } if dtFld, err := utils.ParseTimeDetectLayout(rawVal, cfgFld.Timezone); err != nil { // Only one rule makes sense here return "", err } else { outVal = dtFld.Format(layout) } case utils.META_HTTP_POST: var outValByte []byte httpAddr := cfgFld.Value.Id() jsn, err := json.Marshal(cdr) if err != nil { return "", err } if len(httpAddr) == 0 { err = fmt.Errorf("Empty http address for field %s type %s", cfgFld.Tag, cfgFld.Type) } else if outValByte, err = utils.HttpJsonPost(httpAddr, httpSkipTlsCheck, jsn); err == nil { outVal = string(outValByte) if len(outVal) == 0 && cfgFld.Mandatory { err = fmt.Errorf("Empty result for http_post field: %s", cfgFld.Tag) } } case utils.META_COMBIMED: outVal, err = cdr.combimedCdrFieldVal(cfgFld, groupedCDRs) case utils.META_COMPOSED: outVal, err = cdr.exportFieldValue(cfgFld) case utils.MetaMaskedDestination: if len(cfgFld.MaskDestID) != 0 && CachedDestHasPrefix(cfgFld.MaskDestID, cdr.Destination) { outVal = "1" } else { outVal = "0" } } if err != nil { return "", err } return utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory) }
func fieldOutVal(m *diam.Message, cfgFld *config.CfgCdrField, extraParam interface{}, processorVars map[string]string) (fmtValOut string, err error) { var outVal string passAtIndex := -1 passedAllFilters := true for _, fldFilter := range cfgFld.FieldFilter { var pass bool if pass, passAtIndex = passesFieldFilter(m, fldFilter, processorVars); !pass { passedAllFilters = false break } } if !passedAllFilters { return "", ErrFilterNotPassing // Not matching field filters, will have it empty } if passAtIndex == -1 { passAtIndex = 0 // No filter } switch cfgFld.Type { case utils.META_FILLER: outVal = cfgFld.Value.Id() cfgFld.Padding = "right" case utils.META_CONSTANT: outVal = cfgFld.Value.Id() case utils.META_HANDLER: switch cfgFld.HandlerId { case META_VALUE_EXPONENT: outVal, err = metaValueExponent(m, cfgFld.Value, 10) // FixMe: add here configured number of decimals case META_SUM: outVal, err = metaSum(m, cfgFld.Value, passAtIndex, 10) default: outVal, err = metaHandler(m, cfgFld.HandlerId, cfgFld.Layout, extraParam.(time.Duration)) if err != nil { utils.Logger.Warning(fmt.Sprintf("<Diameter> Ignoring processing of metafunction: %s, error: %s", cfgFld.HandlerId, err.Error())) } } case utils.META_COMPOSED: outVal = composedFieldvalue(m, cfgFld.Value, 0, processorVars) case utils.MetaGrouped: // GroupedAVP outVal = composedFieldvalue(m, cfgFld.Value, passAtIndex, processorVars) } if fmtValOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { utils.Logger.Warning(fmt.Sprintf("<Diameter> Error when processing field template with tag: %s, error: %s", cfgFld.Tag, err.Error())) return "", err } return fmtValOut, nil }
func fieldOutVal(m *diam.Message, cfgFld *config.CfgCdrField, extraParam interface{}) (fmtValOut string, err error) { var outVal string passAtIndex := -1 passedAllFilters := true for _, fldFilter := range cfgFld.FieldFilter { var pass bool if pass, passAtIndex = passesFieldFilter(m, fldFilter); !pass { passedAllFilters = false break } } if !passedAllFilters { return "", ErrFilterNotPassing // Not matching field filters, will have it empty } if passAtIndex == -1 { passAtIndex = 0 // No filter } switch cfgFld.Type { case utils.META_FILLER: outVal = cfgFld.Value.Id() cfgFld.Padding = "right" case utils.META_CONSTANT: outVal = cfgFld.Value.Id() case utils.META_HANDLER: if cfgFld.HandlerId == META_CCA_USAGE { // Exception, usage is passed in the dur variable by CCA outVal = strconv.FormatFloat(extraParam.(float64), 'f', -1, 64) } else { outVal, err = metaHandler(m, cfgFld.HandlerId, cfgFld.Layout, extraParam.(time.Duration)) if err != nil { utils.Logger.Warning(fmt.Sprintf("<Diameter> Ignoring processing of metafunction: %s, error: %s", cfgFld.HandlerId, err.Error())) } } case utils.META_COMPOSED: outVal = composedFieldvalue(m, cfgFld.Value, 0) case utils.MetaGrouped: // GroupedAVP outVal = composedFieldvalue(m, cfgFld.Value, passAtIndex) } if fmtValOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { utils.Logger.Warning(fmt.Sprintf("<Diameter> Error when processing field template with tag: %s, error: %s", cfgFld.Tag, err.Error())) return "", err } return fmtValOut, nil }
// Write individual cdr into content buffer, build stats func (cdre *CdrExporter) processCdr(cdr *engine.CDR) error { if cdr == nil || len(cdr.CGRID) == 0 { // We do not export empty CDRs return nil } else if cdr.ExtraFields == nil { // Avoid assignment in nil map if not initialized cdr.ExtraFields = make(map[string]string) } // Cost multiply if cdre.dataUsageMultiplyFactor != 0.0 && cdr.ToR == utils.DATA { cdr.UsageMultiply(cdre.dataUsageMultiplyFactor, cdre.cgrPrecision) } else if cdre.smsUsageMultiplyFactor != 0 && cdr.ToR == utils.SMS { cdr.UsageMultiply(cdre.smsUsageMultiplyFactor, cdre.cgrPrecision) } else if cdre.mmsUsageMultiplyFactor != 0 && cdr.ToR == utils.MMS { cdr.UsageMultiply(cdre.mmsUsageMultiplyFactor, cdre.cgrPrecision) } else if cdre.genericUsageMultiplyFactor != 0 && cdr.ToR == utils.GENERIC { cdr.UsageMultiply(cdre.genericUsageMultiplyFactor, cdre.cgrPrecision) } if cdre.costMultiplyFactor != 0.0 { cdr.CostMultiply(cdre.costMultiplyFactor, cdre.cgrPrecision) } var err error cdrRow := make([]string, len(cdre.exportTemplate.ContentFields)) for idx, cfgFld := range cdre.exportTemplate.ContentFields { var outVal string switch cfgFld.Type { case utils.META_FILLER: outVal = cfgFld.Value.Id() cfgFld.Padding = "right" case utils.META_CONSTANT: outVal = cfgFld.Value.Id() case utils.META_COMPOSED: outVal, err = cdre.cdrFieldValue(cdr, cfgFld) case META_DATETIME: outVal, err = cdre.getDateTimeFieldVal(cdr, cfgFld) case utils.META_HTTP_POST: var outValByte []byte httpAddr := cfgFld.Value.Id() if len(httpAddr) == 0 { err = fmt.Errorf("Empty http address for field %s type %s", cfgFld.Tag, cfgFld.Type) } else if outValByte, err = utils.HttpJsonPost(httpAddr, cdre.httpSkipTlsCheck, cdr); err == nil { outVal = string(outValByte) if len(outVal) == 0 && cfgFld.Mandatory { err = fmt.Errorf("Empty result for http_post field: %s", cfgFld.Tag) } } case utils.META_COMBIMED: outVal, err = cdre.getCombimedCdrFieldVal(cdr, cfgFld) case utils.META_HANDLER: if cfgFld.Value.Id() == META_MASKDESTINATION { outVal, err = cdre.metaHandler(cfgFld.Value.Id(), cdr.FieldAsString(&utils.RSRField{Id: utils.DESTINATION})) } else { outVal, err = cdre.metaHandler(cfgFld.Value.Id(), cfgFld.Layout) } } if err != nil { utils.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR with CGRID: %s and runid: %s, error: %s", cdr.CGRID, cdr.RunID, err.Error())) return err } fmtOut := outVal if fmtOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { utils.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR with CGRID: %s, runid: %s, fieldName: %s, fieldValue: %s, error: %s", cdr.CGRID, cdr.RunID, cfgFld.Tag, outVal, err.Error())) return err } cdrRow[idx] += fmtOut } if len(cdrRow) == 0 { // No CDR data, most likely no configuration fields defined return nil } else { cdre.content = append(cdre.content, cdrRow) } // Done with writing content, compute stats here if cdre.firstCdrATime.IsZero() || cdr.AnswerTime.Before(cdre.firstCdrATime) { cdre.firstCdrATime = cdr.AnswerTime } if cdr.AnswerTime.After(cdre.lastCdrATime) { cdre.lastCdrATime = cdr.AnswerTime } cdre.numberOfRecords += 1 if cdr.ToR == utils.VOICE { // Only count duration for non data cdrs cdre.totalDuration += cdr.Usage } if cdr.ToR == utils.SMS { // Count usage for SMS cdre.totalSmsUsage += cdr.Usage } if cdr.ToR == utils.MMS { // Count usage for MMS cdre.totalMmsUsage += cdr.Usage } if cdr.ToR == utils.GENERIC { // Count usage for GENERIC cdre.totalGenericUsage += cdr.Usage } if cdr.ToR == utils.DATA { // Count usage for DATA cdre.totalDataUsage += cdr.Usage } if cdr.Cost != -1 { cdre.totalCost += cdr.Cost cdre.totalCost = utils.Round(cdre.totalCost, cdre.roundDecimals, utils.ROUNDING_MIDDLE) } if cdre.firstExpOrderId > cdr.OrderID || cdre.firstExpOrderId == 0 { cdre.firstExpOrderId = cdr.OrderID } if cdre.lastExpOrderId < cdr.OrderID { cdre.lastExpOrderId = cdr.OrderID } return nil }
// Used in place where we need to export the CDR based on an export template // ExportRecord is a []string to keep it compatible with encoding/csv Writer func (cdr *CDR) AsExportRecord(exportFields []*config.CfgCdrField, costShiftDigits, roundDecimals int, timezone string, httpSkipTlsCheck bool, maskLen int, maskDestID string, groupedCDRs []*CDR) ([]string, error) { var err error expRecord := make([]string, len(exportFields)) for idx, cfgFld := range exportFields { layout := cfgFld.Layout if len(layout) == 0 { layout = time.RFC3339 } var outVal string switch cfgFld.Type { case utils.META_FILLER: outVal = cfgFld.Value.Id() cfgFld.Padding = "right" case utils.META_CONSTANT: outVal = cfgFld.Value.Id() case utils.MetaDateTime: // Convert the requested field value into datetime with layout rawVal, err := cdr.exportFieldValue(cfgFld, costShiftDigits, roundDecimals, layout, maskLen, maskDestID) if err != nil { return nil, err } if dtFld, err := utils.ParseTimeDetectLayout(rawVal, timezone); err != nil { // Only one rule makes sense here return nil, err } else { outVal = dtFld.Format(layout) } case utils.META_HTTP_POST: var outValByte []byte httpAddr := cfgFld.Value.Id() jsn, err := json.Marshal(cdr) if err != nil { return nil, err } if len(httpAddr) == 0 { err = fmt.Errorf("Empty http address for field %s type %s", cfgFld.Tag, cfgFld.Type) } else if outValByte, err = utils.HttpJsonPost(httpAddr, httpSkipTlsCheck, jsn); err == nil { outVal = string(outValByte) if len(outVal) == 0 && cfgFld.Mandatory { err = fmt.Errorf("Empty result for http_post field: %s", cfgFld.Tag) } } case utils.META_COMBIMED: outVal, err = cdr.combimedCdrFieldVal(cfgFld, groupedCDRs) case utils.META_COMPOSED: outVal, err = cdr.exportFieldValue(cfgFld, costShiftDigits, roundDecimals, layout, maskLen, maskDestID) case utils.MetaMaskedDestination: if len(maskDestID) != 0 && CachedDestHasPrefix(maskDestID, cdr.Destination) { outVal = "1" } else { outVal = "0" } } if err != nil { return nil, err } fmtOut := outVal if fmtOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { return nil, err } expRecord[idx] += fmtOut } return expRecord, nil }