Skip to content

Commit 707bcb9

Browse files
[exporter/syslog] Fix handling of multiple structured data elements (#37927)
#### Description As reported in issue #33300 this fixes the issue when there are more than one structured data block in the structured_data attributes object. Say you had a structured block like this: ```json { "structured_data": { "a@123": { "a": "123" }, "b@321": { "b": "321" } } } ``` The expected output would then be two sd blocks, like [a@193 a="123"][b@193 b="321"] , but instead the code returns one array, like [a@193 a="123" b@193 b="321"] which is wrong according to the [RFC5424](https://datatracker.ietf.org/doc/html/rfc5424#section-6.3.5) #### Link to tracking issue Fixes #33300 #### Testing A new test for "more than one SD field" was added in the commit, which failed for the original code, but passes now with the fix. #### Documentation No documentation change needed. --------- Co-authored-by: Andrzej Stencel <[email protected]>
1 parent e3062ec commit 707bcb9

File tree

3 files changed

+95
-3
lines changed

3 files changed

+95
-3
lines changed

.chloggen/issue_33300.yaml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: bug_fix
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: syslogexporter
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Fixes handling of multiple structured data elements
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [33300]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: Previous version added all structured data within one bracket pair. According to the RFC each structured data element should have its own bracket pair.
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: []

exporter/syslogexporter/rfc5424_formatter.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package syslogexporter // import "github.com/open-telemetry/opentelemetry-collec
66
import (
77
"fmt"
88
"strconv"
9+
"strings"
910
"time"
1011

1112
"go.opentelemetry.io/collector/pdata/pcommon"
@@ -78,9 +79,9 @@ func (f *rfc5424Formatter) formatStructuredData(logRecord plog.LogRecord) string
7879
return emptyValue
7980
}
8081

81-
sdElements := []string{}
82+
var sdBuilder strings.Builder
8283
for key, val := range structuredDataAttributeValue.Map().AsRaw() {
83-
sdElements = append(sdElements, key)
84+
sdElements := []string{key}
8485
vval, ok := val.(map[string]any)
8586
if !ok {
8687
continue
@@ -92,8 +93,9 @@ func (f *rfc5424Formatter) formatStructuredData(logRecord plog.LogRecord) string
9293
}
9394
sdElements = append(sdElements, fmt.Sprintf("%s=\"%s\"", k, vv))
9495
}
96+
sdBuilder.WriteString(fmt.Sprint(sdElements))
9597
}
96-
return fmt.Sprint(sdElements)
98+
return sdBuilder.String()
9799
}
98100

99101
func (f *rfc5424Formatter) formatMessage(logRecord plog.LogRecord) string {

exporter/syslogexporter/rfc5424_formatter_test.go

+63
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package syslogexporter
66
import (
77
"fmt"
88
"regexp"
9+
"strings"
910
"testing"
1011
"time"
1112

@@ -85,6 +86,68 @@ func TestRFC5424Formatter(t *testing.T) {
8586
assert.Contains(t, actual, "UserID=\"Tester2\"")
8687
assert.Contains(t, actual, "PEN=\"27389\"")
8788

89+
// Test structured data (more than one field)
90+
expectedRegex = "\\<165\\>1 2003-08-24T12:14:15.000003Z 192\\.0\\.2\\.1 myproc 8710 - " +
91+
"\\[(\\S+ \\S+ \\S+)\\]" +
92+
"\\[(\\S+ \\S+ \\S+)\\]" +
93+
" It's time to make the do-nuts\\.\n"
94+
logRecord = plog.NewLogRecord()
95+
logRecord.Attributes().PutStr("appname", "myproc")
96+
logRecord.Attributes().PutStr("hostname", "192.0.2.1")
97+
logRecord.Attributes().PutStr("message", "It's time to make the do-nuts.")
98+
logRecord.Attributes().PutInt("priority", 165)
99+
logRecord.Attributes().PutStr("proc_id", "8710")
100+
logRecord.Attributes().PutEmptyMap("structured_data")
101+
structuredData, found = logRecord.Attributes().Get("structured_data")
102+
require.True(t, found)
103+
structuredData.Map().PutEmptyMap("A@123")
104+
structuredDataSubmap, found = structuredData.Map().Get("A@123")
105+
require.True(t, found)
106+
structuredDataSubmap.Map().PutStr("A", "123")
107+
structuredDataSubmap.Map().PutStr("UserHostAddress", "192.168.2.132")
108+
structuredData.Map().PutEmptyMap("B@321")
109+
structuredDataSubmap, found = structuredData.Map().Get("B@321")
110+
require.True(t, found)
111+
structuredDataSubmap.Map().PutStr("B", "321")
112+
structuredDataSubmap.Map().PutStr("UserHostAddress", "192.168.2.132")
113+
logRecord.Attributes().PutInt("version", 1)
114+
115+
timestamp, err = time.Parse(time.RFC3339Nano, "2003-08-24T05:14:15.000003-07:00")
116+
require.NoError(t, err)
117+
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(timestamp))
118+
119+
actual = newRFC5424Formatter(false).format(logRecord)
120+
assert.NoError(t, err)
121+
122+
// check that the output message is of the right form
123+
matched, err = regexp.MatchString(expectedRegex, actual)
124+
assert.NoError(t, err)
125+
assert.Truef(t, matched, "unexpected form of formatted message, formatted message: %s, regexp: %s", actual, expectedRegex)
126+
regex := regexp.MustCompile(expectedRegex)
127+
submatches := regex.FindStringSubmatch(actual)
128+
// make sure we have received exactly two SD-elements
129+
assert.Lenf(t, submatches, 3,
130+
"expected 2 SD-elements but got %d", len(submatches)-1)
131+
132+
// validate all found SD-elements
133+
for _, submatch := range submatches[1:] {
134+
elements := strings.Split(submatch, " ")
135+
// make sure that the element has three substrings
136+
// one for the key and two key-value pairs
137+
assert.Len(t, elements, 3, "expected 3 elements in SD-element")
138+
139+
// make sure the first element is the key in the structured data map
140+
submap, found := structuredData.Map().Get(elements[0])
141+
require.True(t, found)
142+
143+
// make sure each element in the found submap is rendered
144+
// somewhere in the found SD-element string
145+
for k, v := range submap.Map().AsRaw() {
146+
attribute := fmt.Sprintf("%s=\"%v\"", k, v)
147+
assert.Contains(t, submatch, attribute)
148+
}
149+
}
150+
88151
// Test defaults
89152
expected = "<165>1 2003-08-24T12:14:15.000003Z - - - - -\n"
90153
logRecord = plog.NewLogRecord()

0 commit comments

Comments
 (0)