Skip to content

Commit ac72fb4

Browse files
authored
Receive all logs if no attributes are specified (#37721)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description I'm proposing a new convention where if the attributes field is empty, then it indicates we want to ingest everything that is sent. This helps users avoid having to modify which fields they want in two places. Happy to submit a PR myself. Please check out the linked issue and let me know if you approve or want any changes. Then I will finish implementing whatever else is necessary (e.g docs, tests). <!-- Issue number (e.g. #1234) or full URL to issue, if applicable. --> #### Link to tracking issue Fixes #37720 <!--Describe what testing was performed and which tests were added.--> #### Testing I added a `TestEmptyAttributes()` test to verify the behavior in `logs_test.go` <!--Describe the documentation added.--> #### Documentation I documented the new behavior in the `README.md` and added a separate config example of how it can be used <!--Please delete paragraphs that you did not use before submitting.-->
1 parent 5346f7b commit ac72fb4

File tree

4 files changed

+155
-15
lines changed

4 files changed

+155
-15
lines changed

.chloggen/receive-all-cloudflare.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: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: cloudflarereceiver
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Ingest all attributes by default when attributes map is empty
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: [37720]
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:
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: []

receiver/cloudflarereceiver/README.md

+16
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ If the receiver will be handling TLS termination:
4848
- This receiver was built with the Cloudflare `http_requests` dataset in mind, but should be able to support any Cloudflare dataset. If using another dataset, you will need to set the `timestamp_field` appropriately in order to have the log record be associated with the correct timestamp. the timestamp must be formatted RFC3339, as stated in the Getting Started section.
4949
- `attributes`
5050
- This parameter allows the receiver to be configured to set log record attributes based on fields found in the log message. The fields are not removed from the log message when set in this way. Only string, boolean, integer or float fields can be mapped using this parameter.
51+
- When the `attributes` configuration is empty, the receiver will automatically ingest all fields from the log messages as attributes, using the original field names as attribute names.
5152

5253

5354
### Example:
@@ -66,3 +67,18 @@ receivers:
6667
ClientIP: http_request.client_ip
6768
ClientRequestURI: http_request.uri
6869
```
70+
71+
### Example with automatic attribute ingestion:
72+
```yaml
73+
receivers:
74+
cloudflare:
75+
logs:
76+
tls:
77+
key_file: some_key_file
78+
cert_file: some_cert_file
79+
endpoint: 0.0.0.0:12345
80+
secret: 1234567890abcdef1234567890abcdef
81+
timestamp_field: EdgeStartTimestamp
82+
attributes:
83+
# Specifying no attributes ingests them all
84+
```

receiver/cloudflarereceiver/logs.go

+28-15
Original file line numberDiff line numberDiff line change
@@ -272,22 +272,35 @@ func (l *logsReceiver) processLogs(now pcommon.Timestamp, logs []map[string]any)
272272
}
273273

274274
attrs := logRecord.Attributes()
275-
for field, attribute := range l.cfg.Attributes {
276-
if v, ok := log[field]; ok {
277-
switch v := v.(type) {
278-
case string:
279-
attrs.PutStr(attribute, v)
280-
case int:
281-
attrs.PutInt(attribute, int64(v))
282-
case int64:
283-
attrs.PutInt(attribute, v)
284-
case float64:
285-
attrs.PutDouble(attribute, v)
286-
case bool:
287-
attrs.PutBool(attribute, v)
288-
default:
289-
l.logger.Warn("unable to translate field to attribute, unsupported type", zap.String("field", field), zap.Any("value", v), zap.String("type", fmt.Sprintf("%T", v)))
275+
for field, v := range log {
276+
attrName := field
277+
if len(l.cfg.Attributes) != 0 {
278+
// Only process fields that are in the config mapping
279+
mappedAttr, ok := l.cfg.Attributes[field]
280+
if !ok {
281+
// Skip fields not in mapping when we have a config
282+
continue
290283
}
284+
attrName = mappedAttr
285+
}
286+
// else if l.cfg.Attributes is empty, default to processing all fields with no renaming
287+
288+
switch v := v.(type) {
289+
case string:
290+
attrs.PutStr(attrName, v)
291+
case int:
292+
attrs.PutInt(attrName, int64(v))
293+
case int64:
294+
attrs.PutInt(attrName, v)
295+
case float64:
296+
attrs.PutDouble(attrName, v)
297+
case bool:
298+
attrs.PutBool(attrName, v)
299+
default:
300+
l.logger.Warn("unable to translate field to attribute, unsupported type",
301+
zap.String("field", field),
302+
zap.Any("value", v),
303+
zap.String("type", fmt.Sprintf("%T", v)))
291304
}
292305
}
293306

receiver/cloudflarereceiver/logs_test.go

+84
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,90 @@ func TestHandleRequest(t *testing.T) {
353353
}
354354
}
355355

356+
func TestEmptyAttributes(t *testing.T) {
357+
now := time.Time{}
358+
359+
testCases := []struct {
360+
name string
361+
payload string
362+
attributes map[string]string
363+
}{
364+
{
365+
name: "Empty Attributes Map",
366+
payload: `{ "ClientIP": "89.163.253.200", "ClientRequestHost": "www.theburritobot0.com", "ClientRequestMethod": "GET", "ClientRequestURI": "/static/img/testimonial-hipster.png", "EdgeEndTimestamp": "2023-03-03T05:30:05Z", "EdgeResponseBytes": "69045", "EdgeResponseStatus": "200", "EdgeStartTimestamp": "2023-03-03T05:29:05Z", "RayID": "3a6050bcbe121a87" }
367+
{ "ClientIP" : "89.163.253.201", "ClientRequestHost": "www.theburritobot1.com", "ClientRequestMethod": "GET", "ClientRequestURI": "/static/img/testimonial-hipster.png", "EdgeEndTimestamp": "2023-03-03T05:30:05Z", "EdgeResponseBytes": "69045", "EdgeResponseStatus": "200", "EdgeStartTimestamp": "2023-03-03T05:29:05Z", "RayID": "3a6050bcbe121a87" }`,
368+
attributes: map[string]string{},
369+
},
370+
{
371+
name: "nil Attributes",
372+
payload: `{ "ClientIP": "89.163.253.200", "ClientRequestHost": "www.theburritobot0.com", "ClientRequestMethod": "GET", "ClientRequestURI": "/static/img/testimonial-hipster.png", "EdgeEndTimestamp": "2023-03-03T05:30:05Z", "EdgeResponseBytes": "69045", "EdgeResponseStatus": "200", "EdgeStartTimestamp": "2023-03-03T05:29:05Z", "RayID": "3a6050bcbe121a87" }
373+
{ "ClientIP" : "89.163.253.201", "ClientRequestHost": "www.theburritobot1.com", "ClientRequestMethod": "GET", "ClientRequestURI": "/static/img/testimonial-hipster.png", "EdgeEndTimestamp": "2023-03-03T05:30:05Z", "EdgeResponseBytes": "69045", "EdgeResponseStatus": "200", "EdgeStartTimestamp": "2023-03-03T05:29:05Z", "RayID": "3a6050bcbe121a87" }`,
374+
attributes: nil,
375+
},
376+
}
377+
378+
expectedLogs := func(t *testing.T, payload string) plog.Logs {
379+
logs := plog.NewLogs()
380+
rl := logs.ResourceLogs().AppendEmpty()
381+
sl := rl.ScopeLogs().AppendEmpty()
382+
sl.Scope().SetName("github.com/open-telemetry/opentelemetry-collector-contrib/receiver/cloudflarereceiver")
383+
384+
for idx, line := range strings.Split(payload, "\n") {
385+
lr := sl.LogRecords().AppendEmpty()
386+
387+
require.NoError(t, lr.Attributes().FromRaw(map[string]any{
388+
"ClientIP": fmt.Sprintf("89.163.253.%d", 200+idx),
389+
"ClientRequestHost": fmt.Sprintf("www.theburritobot%d.com", idx),
390+
"ClientRequestMethod": "GET",
391+
"ClientRequestURI": "/static/img/testimonial-hipster.png",
392+
"EdgeEndTimestamp": "2023-03-03T05:30:05Z",
393+
"EdgeResponseBytes": "69045",
394+
"EdgeResponseStatus": "200",
395+
"EdgeStartTimestamp": "2023-03-03T05:29:05Z",
396+
"RayID": "3a6050bcbe121a87",
397+
}))
398+
399+
lr.SetObservedTimestamp(pcommon.NewTimestampFromTime(now))
400+
ts, err := time.Parse(time.RFC3339, "2023-03-03T05:29:05Z")
401+
require.NoError(t, err)
402+
lr.SetTimestamp(pcommon.NewTimestampFromTime(ts))
403+
lr.SetSeverityNumber(plog.SeverityNumberInfo)
404+
lr.SetSeverityText(plog.SeverityNumberInfo.String())
405+
406+
var log map[string]any
407+
err = json.Unmarshal([]byte(line), &log)
408+
require.NoError(t, err)
409+
410+
payloadToExpectedBody(t, line, lr)
411+
}
412+
413+
return logs
414+
}
415+
416+
for _, tc := range testCases {
417+
t.Run(tc.name, func(t *testing.T) {
418+
recv := newReceiver(t, &Config{
419+
Logs: LogsConfig{
420+
Endpoint: "localhost:0",
421+
TLS: &configtls.ServerConfig{},
422+
TimestampField: "EdgeStartTimestamp",
423+
Attributes: tc.attributes,
424+
},
425+
},
426+
&consumertest.LogsSink{},
427+
)
428+
var logs plog.Logs
429+
rawLogs, err := parsePayload([]byte(tc.payload))
430+
if err == nil {
431+
logs = recv.processLogs(pcommon.NewTimestampFromTime(time.Now()), rawLogs)
432+
}
433+
require.NoError(t, err)
434+
require.NotNil(t, logs)
435+
require.NoError(t, plogtest.CompareLogs(expectedLogs(t, tc.payload), logs, plogtest.IgnoreObservedTimestamp()))
436+
})
437+
}
438+
}
439+
356440
func gzippedMessage(message string) string {
357441
var b bytes.Buffer
358442
w := gzip.NewWriter(&b)

0 commit comments

Comments
 (0)