telegraf/plugins/outputs/syslog/syslog_mapper.go

200 lines
4.9 KiB
Go

package syslog
import (
"errors"
"math"
"os"
"strconv"
"strings"
"time"
"github.com/influxdata/go-syslog/v2/rfc5424"
"github.com/influxdata/telegraf"
)
type SyslogMapper struct {
DefaultSdid string
DefaultSeverityCode uint8
DefaultFacilityCode uint8
DefaultAppname string
Sdids []string
Separator string
reservedKeys map[string]bool
}
// MapMetricToSyslogMessage maps metrics tags/fields to syslog messages
func (sm *SyslogMapper) MapMetricToSyslogMessage(metric telegraf.Metric) (*rfc5424.SyslogMessage, error) {
msg := &rfc5424.SyslogMessage{}
sm.mapPriority(metric, msg)
sm.mapStructuredData(metric, msg)
sm.mapAppname(metric, msg)
mapHostname(metric, msg)
mapTimestamp(metric, msg)
mapMsgID(metric, msg)
mapVersion(metric, msg)
mapProcID(metric, msg)
mapMsg(metric, msg)
if !msg.Valid() {
return nil, errors.New("metric could not produce valid syslog message")
}
return msg, nil
}
func (sm *SyslogMapper) mapStructuredData(metric telegraf.Metric, msg *rfc5424.SyslogMessage) {
for _, tag := range metric.TagList() {
sm.mapStructuredDataItem(tag.Key, tag.Value, msg)
}
for _, field := range metric.FieldList() {
sm.mapStructuredDataItem(field.Key, formatValue(field.Value), msg)
}
}
func (sm *SyslogMapper) mapStructuredDataItem(key string, value string, msg *rfc5424.SyslogMessage) {
if sm.reservedKeys[key] {
return
}
isExplicitSdid := false
for _, sdid := range sm.Sdids {
k := strings.TrimLeft(key, sdid+sm.Separator)
if len(key) > len(k) {
isExplicitSdid = true
msg.SetParameter(sdid, k, value)
break
}
}
if !isExplicitSdid && len(sm.DefaultSdid) > 0 {
k := strings.TrimPrefix(key, sm.DefaultSdid+sm.Separator)
msg.SetParameter(sm.DefaultSdid, k, value)
}
}
func (sm *SyslogMapper) mapAppname(metric telegraf.Metric, msg *rfc5424.SyslogMessage) {
if value, ok := metric.GetTag("appname"); ok {
msg.SetAppname(formatValue(value))
} else {
//Use default appname
msg.SetAppname(sm.DefaultAppname)
}
}
func mapMsgID(metric telegraf.Metric, msg *rfc5424.SyslogMessage) {
if value, ok := metric.GetField("msgid"); ok {
msg.SetMsgID(formatValue(value))
} else {
// We default to metric name
msg.SetMsgID(metric.Name())
}
}
func mapVersion(metric telegraf.Metric, msg *rfc5424.SyslogMessage) {
if value, ok := metric.GetField("version"); ok {
switch v := value.(type) {
case uint64:
msg.SetVersion(uint16(v))
return
}
}
msg.SetVersion(1)
}
func mapMsg(metric telegraf.Metric, msg *rfc5424.SyslogMessage) {
if value, ok := metric.GetField("msg"); ok {
msg.SetMessage(formatValue(value))
}
}
func mapProcID(metric telegraf.Metric, msg *rfc5424.SyslogMessage) {
if value, ok := metric.GetField("procid"); ok {
msg.SetProcID(formatValue(value))
}
}
func (sm *SyslogMapper) mapPriority(metric telegraf.Metric, msg *rfc5424.SyslogMessage) {
severityCode := sm.DefaultSeverityCode
facilityCode := sm.DefaultFacilityCode
if value, ok := getFieldCode(metric, "severity_code"); ok {
severityCode = *value
}
if value, ok := getFieldCode(metric, "facility_code"); ok {
facilityCode = *value
}
priority := (8 * facilityCode) + severityCode
msg.SetPriority(priority)
}
func mapHostname(metric telegraf.Metric, msg *rfc5424.SyslogMessage) {
// Try with hostname, then with source, then with host tags, then take OS Hostname
if value, ok := metric.GetTag("hostname"); ok {
msg.SetHostname(formatValue(value))
} else if value, ok := metric.GetTag("source"); ok {
msg.SetHostname(formatValue(value))
} else if value, ok := metric.GetTag("host"); ok {
msg.SetHostname(formatValue(value))
} else if value, err := os.Hostname(); err == nil {
msg.SetHostname(value)
}
}
func mapTimestamp(metric telegraf.Metric, msg *rfc5424.SyslogMessage) {
timestamp := metric.Time()
if value, ok := metric.GetField("timestamp"); ok {
switch v := value.(type) {
case int64:
timestamp = time.Unix(0, v).UTC()
}
}
msg.SetTimestamp(timestamp.Format(time.RFC3339))
}
func formatValue(value interface{}) string {
switch v := value.(type) {
case string:
return v
case bool:
if v {
return "1"
}
return "0"
case uint64:
return strconv.FormatUint(v, 10)
case int64:
return strconv.FormatInt(v, 10)
case float64:
if math.IsNaN(v) {
return ""
}
if math.IsInf(v, 0) {
return ""
}
return strconv.FormatFloat(v, 'f', -1, 64)
}
return ""
}
func getFieldCode(metric telegraf.Metric, fieldKey string) (*uint8, bool) {
if value, ok := metric.GetField(fieldKey); ok {
if v, err := strconv.ParseUint(formatValue(value), 10, 8); err == nil {
r := uint8(v)
return &r, true
}
}
return nil, false
}
func newSyslogMapper() *SyslogMapper {
return &SyslogMapper{
reservedKeys: map[string]bool{
"version": true, "severity_code": true, "facility_code": true,
"procid": true, "msgid": true, "msg": true, "timestamp": true, "sdid": true,
"hostname": true, "source": true, "host": true, "severity": true,
"facility": true, "appname": true},
}
}