diff --git a/internal/container/logfmt.go b/internal/container/logfmt.go index de155131..1527af88 100644 --- a/internal/container/logfmt.go +++ b/internal/container/logfmt.go @@ -2,6 +2,8 @@ package container import ( "errors" + "strconv" + "strings" orderedmap "github.com/wk8/go-ordered-map/v2" ) @@ -36,7 +38,7 @@ func ParseLogFmt(log string) (*orderedmap.OrderedMap[string, string], error) { } else if char == '\\' { escaping = true } else if char == '"' { - value = log[start:i] + value = unescapeQuoted(log[start-1 : i+1]) result.Set(key, value) inQuotes = false isKey = true @@ -77,3 +79,19 @@ func ParseLogFmt(log string) (*orderedmap.OrderedMap[string, string], error) { return result, nil } + +// unescapeQuoted decodes a quoted logfmt value (including the surrounding +// quotes) so escape sequences like \" and \\ produced by logfmt encoders +// (logrus, go-kit, go-logfmt) yield the original text instead of leaking +// backslashes into the parsed value. If the sequence is not decodable, the +// raw content between the quotes is returned unchanged. +func unescapeQuoted(quoted string) string { + raw := quoted[1 : len(quoted)-1] + if !strings.ContainsRune(raw, '\\') { + return raw + } + if unquoted, err := strconv.Unquote(quoted); err == nil { + return unquoted + } + return raw +} diff --git a/internal/container/logfmt_test.go b/internal/container/logfmt_test.go index 4e4003e9..507a4e24 100644 --- a/internal/container/logfmt_test.go +++ b/internal/container/logfmt_test.go @@ -67,6 +67,28 @@ func TestParseLog(t *testing.T) { ), wantErr: false, }, + { + name: "Escaped quotes and backslashes in quoted values", + log: `level=info msg="failed to open \"config.yml\"" path="C:\\temp"`, + want: orderedmap.New[string, string]( + orderedmap.WithInitialData( + orderedmap.Pair[string, string]{Key: "level", Value: "info"}, + orderedmap.Pair[string, string]{Key: "msg", Value: `failed to open "config.yml"`}, + orderedmap.Pair[string, string]{Key: "path", Value: `C:\temp`}, + ), + ), + wantErr: false, + }, + { + name: "Quoted value with undecodable escape is kept as-is", + log: `msg="foo \q bar"`, + want: orderedmap.New[string, string]( + orderedmap.WithInitialData( + orderedmap.Pair[string, string]{Key: "msg", Value: `foo \q bar`}, + ), + ), + wantErr: false, + }, { name: "Broken format with unexpected quotes", log: `key1=value"1"= key2="value2"`,