diff --git a/.chloggen/46716-cloudflare-array-attrs.yaml b/.chloggen/46716-cloudflare-array-attrs.yaml new file mode 100644 index 0000000000000..2216c05a7450b --- /dev/null +++ b/.chloggen/46716-cloudflare-array-attrs.yaml @@ -0,0 +1,25 @@ +# 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. receiver/filelog) +component: cloudflarereceiver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Preserve array-typed log fields (e.g. BotDetectionIDs, SecurityActions, SecurityRuleIDs) as pcommon.Slice attributes instead of dropping them with an "unsupported type" warning. + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [46716] + +# (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: + +# 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: [] diff --git a/receiver/cloudflarereceiver/logs.go b/receiver/cloudflarereceiver/logs.go index c37f7dc86c7c3..f30916d46c1e0 100644 --- a/receiver/cloudflarereceiver/logs.go +++ b/receiver/cloudflarereceiver/logs.go @@ -361,6 +361,16 @@ func (l *logsReceiver) processLogs(now pcommon.Timestamp, logs []map[string]any) attrs.PutDouble(attrName, v) case bool: attrs.PutBool(attrName, v) + case []any: + // Preserve array-typed fields (e.g. BotDetectionIDs, + // SecurityActions) as a pcommon.Slice attribute + // instead of dropping them with a warning. + if err := attrs.PutEmptySlice(attrName).FromRaw(v); err != nil { + l.logger.Warn("unable to translate array field to attribute", + zap.String("field", field), + zap.Any("value", v), + zap.Error(err)) + } case map[string]any: // Flatten the map and add each field with a prefixed key flattened := make(map[string]any) @@ -377,6 +387,13 @@ func (l *logsReceiver) processLogs(now pcommon.Timestamp, logs []map[string]any) attrs.PutDouble(k, v) case bool: attrs.PutBool(k, v) + case []any: + if err := attrs.PutEmptySlice(k).FromRaw(v); err != nil { + l.logger.Warn("unable to translate flattened array field to attribute", + zap.String("field", k), + zap.Any("value", v), + zap.Error(err)) + } default: l.logger.Warn("unable to translate flattened field to attribute, unsupported type", zap.String("field", k), diff --git a/receiver/cloudflarereceiver/logs_test.go b/receiver/cloudflarereceiver/logs_test.go index 274505df11168..3604b44193e50 100644 --- a/receiver/cloudflarereceiver/logs_test.go +++ b/receiver/cloudflarereceiver/logs_test.go @@ -732,3 +732,44 @@ func newReceiver(t *testing.T, cfg *Config, nextConsumer consumer.Logs) *logsRec require.NoError(t, err) return r } + +// TestArrayAttributes covers #46716: Cloudflare log records carry several +// array-typed fields (BotDetectionIDs, SecurityActions, SecurityRuleIDs, …) +// that used to be dropped with "unsupported type" warnings. They should now +// round-trip as pcommon.Slice attributes. +func TestArrayAttributes(t *testing.T) { + payload := `{"ClientIP":"1.2.3.4","EdgeStartTimestamp":"2023-03-03T05:29:05Z","BotDetectionIDs":[1,2,3],"SecurityActions":["block","log"],"SecurityRuleIDs":["rule-a","rule-b"]}` + + recv := newReceiver(t, &Config{ + Logs: LogsConfig{ + Endpoint: "localhost:0", + TLS: &configtls.ServerConfig{}, + MaxRequestBodySize: 1024, + TimestampField: "EdgeStartTimestamp", + Attributes: map[string]string{}, + Separator: ".", + }, + }, &consumertest.LogsSink{}) + + rawLogs, err := parsePayload([]byte(payload)) + require.NoError(t, err) + logs := recv.processLogs(pcommon.NewTimestampFromTime(time.Now()), rawLogs) + require.Equal(t, 1, logs.ResourceLogs().Len()) + + attrs := logs.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Attributes() + + botIDs, ok := attrs.Get("BotDetectionIDs") + require.True(t, ok, "BotDetectionIDs must be preserved as an attribute") + require.Equal(t, pcommon.ValueTypeSlice, botIDs.Type()) + require.Equal(t, 3, botIDs.Slice().Len()) + + secActions, ok := attrs.Get("SecurityActions") + require.True(t, ok, "SecurityActions must be preserved as an attribute") + require.Equal(t, pcommon.ValueTypeSlice, secActions.Type()) + require.Equal(t, []any{"block", "log"}, secActions.Slice().AsRaw()) + + secRuleIDs, ok := attrs.Get("SecurityRuleIDs") + require.True(t, ok, "SecurityRuleIDs must be preserved as an attribute") + require.Equal(t, pcommon.ValueTypeSlice, secRuleIDs.Type()) + require.Equal(t, []any{"rule-a", "rule-b"}, secRuleIDs.Slice().AsRaw()) +}