diff --git a/.chloggen/drosiek-syslog-defaults.yaml b/.chloggen/drosiek-syslog-defaults.yaml new file mode 100755 index 000000000000..14c88929a4bc --- /dev/null +++ b/.chloggen/drosiek-syslog-defaults.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: bug_fix + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: syslogexporter + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: use proper defaults according to RFCs + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [25114] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [user] diff --git a/exporter/syslogexporter/README.md b/exporter/syslogexporter/README.md index c97060612986..d68c1af15723 100644 --- a/exporter/syslogexporter/README.md +++ b/exporter/syslogexporter/README.md @@ -60,5 +60,4 @@ Please see [example configurations](./examples/). [syslog_receiver]: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/syslogreceiver [filelog_receiver]: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/filelogreceiver [cryptoTLS]: https://github.com/golang/go/blob/518889b35cb07f3e71963f2ccfc0f96ee26a51ce/src/crypto/tls/common.go#L706-L709 -[development]: https://github.com/open-telemetry/opentelemetry-collector#development [persistent_queue]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md#persistent-queue diff --git a/exporter/syslogexporter/sender.go b/exporter/syslogexporter/sender.go index e705bc570323..c4caeaadf1db 100644 --- a/exporter/syslogexporter/sender.go +++ b/exporter/syslogexporter/sender.go @@ -32,6 +32,7 @@ const structuredData = "structured_data" const message = "message" const emptyValue = "-" +const emptyMessage = "" type sender struct { network string @@ -131,10 +132,8 @@ func (s *sender) addStructuredData(msg map[string]any) { return } - sd, ok := msg[structuredData].(map[string]map[string]string) - if !ok { - msg[structuredData] = emptyValue - } else { + switch sd := msg[structuredData].(type) { + case map[string]map[string]string: sdElements := []string{} for key, val := range sd { sdElements = append(sdElements, key) @@ -143,38 +142,54 @@ func (s *sender) addStructuredData(msg map[string]any) { } } msg[structuredData] = sdElements + case map[string]interface{}: + sdElements := []string{} + for key, val := range sd { + sdElements = append(sdElements, key) + vval, ok := val.(map[string]interface{}) + if !ok { + continue + } + for k, v := range vval { + vv, ok := v.(string) + if !ok { + continue + } + sdElements = append(sdElements, fmt.Sprintf("%s=\"%s\"", k, vv)) + } + } + msg[structuredData] = sdElements + default: + msg[structuredData] = emptyValue } } func populateDefaults(msg map[string]any, msgProperties []string) { - for _, msgProperty := range msgProperties { - msgValue, ok := msg[msgProperty] - if !ok && msgProperty == priority { - msg[msgProperty] = defaultPriority - return + if _, ok := msg[msgProperty]; ok { + continue } - if !ok && msgProperty == version { + + switch msgProperty { + case priority: + msg[msgProperty] = defaultPriority + case version: msg[msgProperty] = versionRFC5424 - return - } - if !ok && msgProperty == facility { + case facility: msg[msgProperty] = defaultFacility - return - } - if !ok { + case message: + msg[msgProperty] = emptyMessage + default: msg[msgProperty] = emptyValue - return } - msg[msgProperty] = msgValue } } func (s *sender) formatRFC3164(msg map[string]any, timestamp time.Time) string { msgProperties := []string{priority, hostname, message} populateDefaults(msg, msgProperties) - timestampString := timestamp.Format("2006-01-02T15:04:05.000-03:00") - return fmt.Sprintf("<%d>%s %s %s", msg[priority], timestampString, msg[hostname], msg[message]) + timestampString := timestamp.Format("Jan 02 15:04:05") + return fmt.Sprintf("<%d>%s %s%s", msg[priority], timestampString, msg[hostname], formatMessagePart(msg[message])) } func (s *sender) formatRFC5424(msg map[string]any, timestamp time.Time) string { @@ -182,5 +197,15 @@ func (s *sender) formatRFC5424(msg map[string]any, timestamp time.Time) string { populateDefaults(msg, msgProperties) s.addStructuredData(msg) timestampString := timestamp.Format(time.RFC3339) - return fmt.Sprintf("<%d>%d %s %s %s %s %s %s %s", msg[priority], msg[version], timestampString, msg[hostname], msg[app], msg[pid], msg[msgID], msg[structuredData], msg[message]) + + return fmt.Sprintf("<%d>%d %s %s %s %s %s %s%s", msg[priority], msg[version], timestampString, msg[hostname], msg[app], msg[pid], msg[msgID], msg[structuredData], formatMessagePart(msg[message])) +} + +func formatMessagePart(message any) string { + msg := message.(string) + if msg != emptyMessage { + msg = " " + msg + } + + return msg } diff --git a/exporter/syslogexporter/sender_test.go b/exporter/syslogexporter/sender_test.go index da161866064b..1620a028a912 100644 --- a/exporter/syslogexporter/sender_test.go +++ b/exporter/syslogexporter/sender_test.go @@ -31,7 +31,7 @@ func TestFormatRFC5424(t *testing.T) { expected := "<165>1 2003-08-24T05:14:15-07:00 192.0.2.1 myproc 8710 - - It's time to make the do-nuts." timeObj1, err := time.Parse(time.RFC3339, "2003-08-24T05:14:15.000003-07:00") - assert.Equal(t, expected, s.formatRFC5424(msg, timeObj1)) + assert.Equal(t, expected, s.formatMsg(msg, timeObj1)) assert.Nil(t, err) msg2 := map[string]any{ @@ -50,7 +50,7 @@ func TestFormatRFC5424(t *testing.T) { expected2 := "<165>1 2003-10-11T22:14:15Z mymachine.example.com evntslog 111 ID47 - BOMAn application event log entry..." timeObj2, err := time.Parse(time.RFC3339, "2003-10-11T22:14:15.003Z") assert.Nil(t, err) - assert.Equal(t, expected2, s.formatRFC5424(msg2, timeObj2)) + assert.Equal(t, expected2, s.formatMsg(msg2, timeObj2)) msg3 := map[string]any{ "timestamp": "2003-08-24T05:14:15.000003-07:00", @@ -76,7 +76,7 @@ func TestFormatRFC5424(t *testing.T) { "\\[\\S+ \\S+ \\S+ \\S+ \\S+\\] It's time to make the do-nuts\\." timeObj3, err := time.Parse(time.RFC3339, "2003-08-24T05:14:15.000003-07:00") assert.Nil(t, err) - formattedMsg := s.formatRFC5424(msg3, timeObj3) + formattedMsg := s.formatMsg(msg3, timeObj3) matched, err := regexp.MatchString(expectedForm, formattedMsg) assert.Nil(t, err) assert.Equal(t, true, matched, fmt.Sprintf("unexpected form of formatted message, formatted message: %s, regexp: %s", formattedMsg, expectedForm)) @@ -84,4 +84,56 @@ func TestFormatRFC5424(t *testing.T) { assert.Equal(t, true, strings.Contains(formattedMsg, "UserHostAddress=\"192.168.2.132\"")) assert.Equal(t, true, strings.Contains(formattedMsg, "UserID=\"Tester2\"")) assert.Equal(t, true, strings.Contains(formattedMsg, "PEN=\"27389\"")) + + // Test defaults + msg4 := map[string]any{} + expected = "<165>1 2003-08-24T05:14:15-07:00 - - - - -" + timeObj1, err = time.Parse(time.RFC3339, "2003-08-24T05:14:15.000003-07:00") + assert.Equal(t, expected, s.formatMsg(msg4, timeObj1)) + assert.Nil(t, err) + + msg5 := map[string]any{ + "timestamp": "2003-08-24T05:14:15.000003-07:00", + "appname": "myproc", + "facility": 20, + "hostname": "192.0.2.1", + "log.file.name": "syslog", + "message": "It's time to make the do-nuts.", + "priority": 165, + "proc_id": "8710", + "version": 1, + "structured_data": map[string]interface{}{ + "SecureAuth@27389": map[string]interface{}{ + "PEN": "27389", + "Realm": "SecureAuth0", + "UserHostAddress": "192.168.2.132", + "UserID": "Tester2", + }, + }, + } + + expectedForm = "\\<165\\>1 2003-08-24T05:14:15-07:00 192\\.0\\.2\\.1 myproc 8710 - " + + "\\[\\S+ \\S+ \\S+ \\S+ \\S+\\] It's time to make the do-nuts\\." + timeObj5, err := time.Parse(time.RFC3339, "2003-08-24T05:14:15.000003-07:00") + assert.Nil(t, err) + formattedMsg = s.formatMsg(msg5, timeObj5) + matched, err = regexp.MatchString(expectedForm, formattedMsg) + assert.Nil(t, err) + assert.Equal(t, true, matched, fmt.Sprintf("unexpected form of formatted message, formatted message: %s, regexp: %s", formattedMsg, expectedForm)) + assert.Equal(t, true, strings.Contains(formattedMsg, "Realm=\"SecureAuth0\"")) + assert.Equal(t, true, strings.Contains(formattedMsg, "UserHostAddress=\"192.168.2.132\"")) + assert.Equal(t, true, strings.Contains(formattedMsg, "UserID=\"Tester2\"")) + assert.Equal(t, true, strings.Contains(formattedMsg, "PEN=\"27389\"")) +} + +func TestFormatRFC3164(t *testing.T) { + + s := sender{protocol: protocolRFC3164Str} + + // Test defaults + msg4 := map[string]any{} + expected := "<165>Aug 24 05:14:15 -" + timeObj1, err := time.Parse(time.RFC3339, "2003-08-24T05:14:15.000003-07:00") + assert.Equal(t, expected, s.formatMsg(msg4, timeObj1)) + assert.Nil(t, err) } diff --git a/pkg/stanza/operator/input/syslog/syslog.go b/pkg/stanza/operator/input/syslog/syslog.go index 26636e6ab56b..d97ab1681475 100644 --- a/pkg/stanza/operator/input/syslog/syslog.go +++ b/pkg/stanza/operator/input/syslog/syslog.go @@ -96,7 +96,7 @@ func (c Config) Build(logger *zap.SugaredLogger) (operator.Operator, error) { udpInput, err := udpInputCfg.Build(logger) if err != nil { - return nil, fmt.Errorf("failed to resolve upd config: %w", err) + return nil, fmt.Errorf("failed to resolve udp config: %w", err) } udpInput.SetOutputIDs([]string{syslogParser.ID()})