diff --git a/.chloggen/mackjmr_add-support-for-nested-log-attributes.yaml b/.chloggen/mackjmr_add-support-for-nested-log-attributes.yaml new file mode 100755 index 00000000..231e6f62 --- /dev/null +++ b/.chloggen/mackjmr_add-support-for-nested-log-attributes.yaml @@ -0,0 +1,16 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component (e.g. pkg/quantile) +component: pkg/otlp/logs + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add support for nested log attributes. + +# The PR related to this change +issues: [207] + +# (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: diff --git a/pkg/otlp/logs/logs_translator.go b/pkg/otlp/logs/logs_translator.go index 02e6b09e..9735cef4 100644 --- a/pkg/otlp/logs/logs_translator.go +++ b/pkg/otlp/logs/logs_translator.go @@ -114,7 +114,10 @@ func Transform(lr plog.LogRecord, res pcommon.Resource, logger *zap.Logger) data tagStr := strings.Join(tags, ",") l.Ddtags = datadog.PtrString(tagStr) default: - l.AdditionalProperties[k] = v.AsString() + m := flattenAttribute(k, v, 1) + for k, v := range m { + l.AdditionalProperties[k] = v + } } return true }) @@ -172,6 +175,26 @@ func Transform(lr plog.LogRecord, res pcommon.Resource, logger *zap.Logger) data return l } +func flattenAttribute(key string, val pcommon.Value, depth int) map[string]string { + result := make(map[string]string) + + if val.Type() != pcommon.ValueTypeMap || depth == 10 { + result[key] = val.AsString() + return result + } + + val.Map().Range(func(k string, v pcommon.Value) bool { + newKey := key + "." + k + nestedResult := flattenAttribute(newKey, v, depth+1) + for nk, nv := range nestedResult { + result[nk] = nv + } + return true + }) + + return result +} + func extractHostNameAndServiceName(resourceAttrs pcommon.Map, logAttrs pcommon.Map) (host string, service string) { if src, ok := attributes.SourceFromAttrs(resourceAttrs); ok && src.Kind == source.HostnameKind { host = src.Identifier diff --git a/pkg/otlp/logs/logs_translator_test.go b/pkg/otlp/logs/logs_translator_test.go index 4fd4dfcc..768ecf1f 100644 --- a/pkg/otlp/logs/logs_translator_test.go +++ b/pkg/otlp/logs/logs_translator_test.go @@ -410,6 +410,84 @@ func TestTransform(t *testing.T) { }, }, }, + { + name: "Nestings", + args: args{ + lr: func() plog.LogRecord { + l := plog.NewLogRecord() + sl := l.Attributes().PutEmptyMap("root") + sl.FromRaw(map[string]any{ + "nest1": map[string]any{ + "nest2": "val", + }, + "nest12": map[string]any{ + "nest22": map[string]any{ + "nest3": "val2", + }, + }, + }) + return l + }(), + res: func() pcommon.Resource { + r := pcommon.NewResource() + return r + }(), + }, + want: datadogV2.HTTPLogItem{ + Ddtags: datadog.PtrString(""), + Message: *datadog.PtrString(""), + AdditionalProperties: map[string]string{ + "root.nest1.nest2": "val", + "root.nest12.nest22.nest3": "val2", + "status": "", + }, + }, + }, + { + name: "Too many nestings", + args: args{ + lr: func() plog.LogRecord { + l := plog.NewLogRecord() + sl := l.Attributes().PutEmptyMap("nest1") + sl.FromRaw(map[string]any{ + "nest2": map[string]any{ + "nest3": map[string]any{ + "nest4": map[string]any{ + "nest5": map[string]any{ + "nest6": map[string]any{ + "nest7": map[string]any{ + "nest8": map[string]any{ + "nest9": map[string]any{ + "nest10": map[string]any{ + "nest11": map[string]any{ + "nest12": "ok", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }) + return l + }(), + res: func() pcommon.Resource { + r := pcommon.NewResource() + return r + }(), + }, + want: datadogV2.HTTPLogItem{ + Ddtags: datadog.PtrString(""), + Message: *datadog.PtrString(""), + AdditionalProperties: map[string]string{ + "nest1.nest2.nest3.nest4.nest5.nest6.nest7.nest8.nest9.nest10": "{\"nest11\":{\"nest12\":\"ok\"}}", + "status": "", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {