feat(collector/receiver): support extra fields in structured JSON function logs#2251
feat(collector/receiver): support extra fields in structured JSON function logs#2251tomsobpl wants to merge 5 commits intoopen-telemetry:mainfrom
Conversation
…port-in-json-logging
…port-in-json-logging
| if err := attr.FromRaw(value); err != nil { | ||
| r.logger.Warn(fmt.Sprintf("error creating attribute: %s", key), zap.Error(err)) | ||
| continue | ||
| } |
There was a problem hiding this comment.
If an error is returned out of the FromRaw call you log but shouldn't you also remove the attribute from the map?
| default: | ||
| attr, _ := logRecord.Attributes().GetOrPutEmpty(key) | ||
| if err := attr.FromRaw(value); err != nil { | ||
| r.logger.Warn(fmt.Sprintf("error creating attribute: %s", key), zap.Error(err)) |
There was a problem hiding this comment.
fmt.Sprintf is okay but I'd prefer leveraging zap as much as possible here. Also not sure why we'd prefer logging this as a warning vs an error level but happy to hear your thoughts on that. I'd suggest something like:
| r.logger.Warn(fmt.Sprintf("error creating attribute: %s", key), zap.Error(err)) | |
| r.logger.Warn("Failed to convert field to attribute", zap.String("key", key), zap.Error(err)) |
There was a problem hiding this comment.
I personally do not feel that after emiting an error level process should continue and that's why I chose warning here.
Probably it is more preferred to prioritize handling request correctly instead stopping it due to malformed log metadata. Warning developer that something was not correctly logged should be enough IMHO.
Anyway agree on the zap usage.
|
|
||
| for key, value := range record { | ||
| switch key { | ||
| case "level", "message", "requestId", "timestamp": |
There was a problem hiding this comment.
You really should include "type" here as well. Look at:
Every log record gets an attribute with key "type" and a value like "function" or "platform.start", = the event type from the Telemetry API.
Now what if we have a log line originating from a user's lambda function code:
{
"message": "buy order",
"type": "checkout",
"orderId": "123"
}So now in your newly added code below (in the default branch) you will simply overwrite the type attribute. And its value will now be "checkout" instead of "function"...
Another, maybe better, option would be to prefix the additional fields that are in a user's logs so we can keep both.
There was a problem hiding this comment.
That was an oversight, good catch.
In the first place I was thinking about adding prefixes to the fields but after some thought I abandoned this option. Developers should be familiar with limitations and reserved fields and should handle that themselves on business logic level.
But maybe it's worth to update documentation and point out all the reserved internal fields with info that they will be prioritized over users metadata. WDYT?
| "requestId": "79b4f56e-95b1-4643-9700-2807f4e6", | ||
| "message": "Hello world, I am a function with extra data!", | ||
| "extraString": "stringValue", | ||
| "extraNumber": 2217, |
There was a problem hiding this comment.
This can never happen in production. a Record is always populated via json.Unmarshal into, which always decodes JSON numbers as float64. Consider using float64(2217) as input so the test mirrors reality.
Summary
When a Lambda function emits structured JSON logs (i.e., a log line with a
messagefield), thetelemetryapireceivernow extracts any additional fields from the JSON object and maps them to log record attributes.Previously, only the
messagefield was captured from structured JSON log records. Any extra fields present in the JSON object (e.g.,userId,orderId, custom business data) were silently discarded.{ "message": "order processed", "orderId": "123", "userId": "abc", "level": "info" }Before:
→ Log record body: "order processed", no extra attributes.
After:
→ Log record body: "order processed", attributes: orderId = "123", userId = "abc".
Changes
receiver.go: After setting the log record body from the message field, iterate over all remaining keys in the parsed JSON record and add them as log record attributes. The well-known fields (level, message, requestId, timestamp) are skipped since they are already handled separately.receiver_test.go: Extended test coverage for JSON logs containing extra fields, covering nested values, arrays, and numeric types.Notes: