Skip to content

Commit af79e10

Browse files
committed
fix the changes
1 parent e61f5cb commit af79e10

4 files changed

Lines changed: 133 additions & 104 deletions

File tree

pkg/stanza/docs/operators/otelcol.md

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ Configuration:
168168
</tr>
169169
</table>
170170

171-
#### Malformed trailing fields in a console-encoded line
171+
#### A message that legitimately contains a brace
172172

173173
Configuration:
174174
```yaml
@@ -182,7 +182,7 @@ Configuration:
182182

183183
```json
184184
{
185-
"body": "2026-07-06T22:56:21Z\twarn\tbroken message\t{\"resource\": invalid}"
185+
"body": "2026-07-06T22:56:21Z\twarn\tConfig value for pool {default} is missing\t{\"resource\":{\"k8s.pod.name\":\"otel-agent\"}}"
186186
}
187187
```
188188

@@ -193,9 +193,9 @@ Configuration:
193193
{
194194
"timestamp": "2026-07-06T22:56:21Z",
195195
"severity": "warn",
196-
"body": "broken message\t{\"resource\": invalid}",
197-
"attributes": {
198-
"otelcol.self_log.malformed_trailing_json": true
196+
"body": "Config value for pool {default} is missing",
197+
"resource": {
198+
"k8s.pod.name": "otel-agent"
199199
}
200200
}
201201
```
@@ -204,4 +204,39 @@ Configuration:
204204
</tr>
205205
</table>
206206

207-
The line is not dropped or errored on - the raw trailing text is preserved as-is in `body`, and the attribute above signals that the loss is visible rather than silent. This is the only case where malformed input does not follow the operator's normal [on_error](../types/on_error.md) behavior; any other unparseable line (e.g. invalid full-line JSON in `json` mode, or a line that doesn't match the `console` shape at all) still triggers `on_error` as usual.
207+
A `{` appearing in the message itself (not as the start of real trailing JSON) does not get mistaken for structured fields, and the real trailing `resource` object further along the line is still found and promoted correctly.
208+
209+
#### Trailing text that looks like JSON but isn't valid
210+
211+
Configuration:
212+
```yaml
213+
- type: otelcol
214+
```
215+
216+
<table>
217+
<tr><td> Input body </td> <td> Output entry</td></tr>
218+
<tr>
219+
<td>
220+
221+
```json
222+
{
223+
"body": "2026-07-06T22:56:21Z\twarn\tbroken message\t{\"resource\": invalid}"
224+
}
225+
```
226+
227+
</td>
228+
<td>
229+
230+
```json
231+
{
232+
"timestamp": "2026-07-06T22:56:21Z",
233+
"severity": "warn",
234+
"body": "broken message\t{\"resource\": invalid}"
235+
}
236+
```
237+
238+
</td>
239+
</tr>
240+
</table>
241+
242+
The line is not dropped or errored on: the message itself is still valid content, and only the optional trailing metadata failed to parse, so the raw remaining text is kept as-is in `body` rather than discarded. This is the one case where unparseable input does not raise `on_error` - any other failure (e.g. invalid full-line JSON in `json` mode, or a line that doesn't match the `console` shape at all) still triggers `on_error` as usual.

pkg/stanza/operator/parser/otelcol/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,14 @@ type Config struct {
4242

4343
// Build will build an otelcol parser operator.
4444
func (c Config) Build(set component.TelemetrySettings) (operator.Operator, error) {
45+
// Schema is fixed, so none of these are user-configurable.
4546
c.ParseFrom = entry.NewBodyField()
4647
c.ParseTo = entry.RootableField{Field: entry.NewAttributeField()}
48+
c.BodyField = nil
49+
c.TimeParser = nil
50+
c.SeverityConfig = nil
51+
c.TraceParser = nil
52+
c.ScopeNameParser = nil
4753

4854
parserOperator, err := c.ParserConfig.Build(set)
4955
if err != nil {

pkg/stanza/operator/parser/otelcol/parser.go

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,15 @@ const (
2323
formatAuto = "auto"
2424
formatJSON = "json"
2525
formatConsole = "console"
26-
27-
// malformedTrailingFieldsAttr is set on the entry's leftover attributes
28-
// whenever a console-encoded line ended with what looked like an attempt
29-
// at structured trailing fields (i.e. the line ends in "}"), but the
30-
// trailing text never resolved into valid JSON.
31-
malformedTrailingFieldsAttr = "otelcol.self_log.malformed_trailing_json"
3226
)
3327

3428
// consoleLinePrefixRegex matches zap's console encoding up through the message:
3529
// <ts> <level> [<caller>] <rest>. It does not attempt to locate any trailing
3630
// JSON fields - regex alone can't tell whether a "{" is real JSON or just
37-
// part of the message text (e.g. "Config value for pool {default}").
31+
// part of the message text (e.g. "Config value for pool {default}"), since
32+
// that requires tracking brace nesting and string literals. Splitting the
33+
// trailing JSON out of <rest> is handled separately, structurally, by
34+
// splitConsoleMessageAndFields.
3835
var consoleLinePrefixRegex = regexp.MustCompile(`^(\S+)\s+(\S+)\s+(?:(\S+:\d+)\s+)?(.*)$`)
3936

4037
// Parser is an operator that parses otelcol self-logs and promotes the resource field onto the entry's Resource.
@@ -95,6 +92,9 @@ func detectFormat(line string) string {
9592
}
9693

9794
// parseJSONLine parses a line where the entire line is a single JSON object.
95+
// Since the whole line is fed to a real JSON decoder here, braces inside
96+
// string values (e.g. a "msg" field containing "{") are already handled
97+
// correctly - this function was never affected by the console-line bug.
9898
func parseJSONLine(line string) (map[string]any, error) {
9999
var parsed map[string]any
100100
if err := json.Unmarshal([]byte(line), &parsed); err != nil {
@@ -111,7 +111,7 @@ func parseConsoleLine(line string) (map[string]any, error) {
111111
return nil, errors.New("line cannot be parsed as a console-encoded otelcol self-log")
112112
}
113113

114-
msg, fields, malformed := splitConsoleMessageAndFields(match[4])
114+
msg, fields := splitConsoleMessageAndFields(match[4])
115115

116116
result := map[string]any{
117117
"ts": match[1],
@@ -121,27 +121,25 @@ func parseConsoleLine(line string) (map[string]any, error) {
121121
if caller := match[3]; caller != "" {
122122
result["caller"] = caller
123123
}
124-
if malformed {
125-
result[malformedTrailingFieldsAttr] = true
126-
}
127124
maps.Copy(result, fields)
128125

129126
return result, nil
130127
}
131128

132129
// splitConsoleMessageAndFields separates a message from an optional trailing
133-
// JSON object, using a real JSON decoder rather than a regex, so that:
130+
// JSON object, so that:
134131
// - braces inside the message text (e.g. "pool {default} is missing") don't
135132
// get mistaken for the start of structured fields, and
136133
// - nested objects inside the real trailing fields (e.g. "resource":{...})
137134
// are matched to their correct closing brace, not the first "}" found.
138-
139-
func splitConsoleMessageAndFields(rest string) (msg string, fields map[string]any, malformed bool) {
135+
func splitConsoleMessageAndFields(rest string) (msg string, fields map[string]any) {
140136
rest = strings.TrimSpace(rest)
141137

142-
looksLikeTrailingJSON := strings.HasSuffix(rest, "}")
138+
// skip scanning entirely if "}" missing.
139+
if !strings.HasSuffix(rest, "}") {
140+
return rest, nil
141+
}
143142

144-
sawUnresolvedBrace := false
145143
searchFrom := 0
146144
for {
147145
idx := strings.IndexByte(rest[searchFrom:], '{')
@@ -155,17 +153,14 @@ func splitConsoleMessageAndFields(rest string) (msg string, fields map[string]an
155153
var decoded map[string]any
156154
if err := dec.Decode(&decoded); err == nil {
157155
if strings.TrimSpace(candidate[dec.InputOffset():]) == "" {
158-
return strings.TrimSpace(rest[:idx]), decoded, false
156+
return strings.TrimSpace(rest[:idx]), decoded
159157
}
160158
}
161159

162-
if looksLikeTrailingJSON {
163-
sawUnresolvedBrace = true
164-
}
165160
searchFrom = idx + 1
166161
}
167162

168-
return rest, nil, sawUnresolvedBrace
163+
return rest, nil
169164
}
170165

171166
// postProcess promotes the ts, level, msg, and resource fields of a parsed otelcol self-log

0 commit comments

Comments
 (0)