Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions collector/receiver/telemetryapireceiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ Supported events:
* `platform.initStart` - The receiver uses this event to record the start time of the function initialization period. Once both start and end times are recorded, the receiver generates a span named `platform.initRuntimeDone` to record the event.
* `platform.initRuntimeDone` - The receiver uses this event to record the end time of the function initialization period. Once both start and end times are recorded, the receiver generates a span named `platform.initRuntimeDone` to record the event.

## Logs metadata reserved fields

The following field names are reserved for internal use in logs metadata and must not be used as custom metadata keys:

| Field | Description |
|-------------|------------------------------------------|
| `level` | Log severity level |
| `message` | Log message body |
| `requestId` | AWS Lambda request identifier |
| `timestamp` | Time of the log event |
| `type` | Telemetry API event type |

> **Note:** These fields are populated internally by the receiver and will take priority over any user-provided metadata with the same name. You should be aware of these limitations and handle conflicts at the business logic level — for example, by renaming custom fields that collide with reserved names before they are emitted as log metadata.

## Configuration

| Field | Default | Description |
Expand Down
14 changes: 14 additions & 0 deletions collector/receiver/telemetryapireceiver/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,20 @@ func (r *telemetryAPIReceiver) createLogs(slice []event) (plog.Logs, error) {
}
} else if line, ok := record["message"].(string); ok {
logRecord.Body().SetStr(line)

for key, value := range record {
switch key {
case "level", "message", "requestId", "timestamp", "type":
continue
default:
attr, _ := logRecord.Attributes().GetOrPutEmpty(key)
if err := attr.FromRaw(value); err != nil {
logRecord.Attributes().Remove(key)
r.logger.Warn("Failed while converting field to attribute", zap.String("key", key), zap.Error(err))
continue
}
Comment thread
tomsobpl marked this conversation as resolved.
}
}
}
} else {
if requestId := r.getCurrentRequestId(); requestId != "" {
Expand Down
139 changes: 139 additions & 0 deletions collector/receiver/telemetryapireceiver/receiver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ func TestCreateLogs(t *testing.T) {
containsRequestId bool
requestId string
severityNumber plog.SeverityNumber
attributes map[string]interface{}
}

testCases := []struct {
Expand Down Expand Up @@ -479,6 +480,137 @@ func TestCreateLogs(t *testing.T) {
},
expectError: false,
},
{
desc: "function json with extra fields",
slice: []event{
{
Time: "2026-02-26T20:15:32.000Z",
Type: "function",
Record: map[string]any{
"timestamp": "2026-02-26T20:15:32.000Z",
"level": "INFO",
"requestId": "79b4f56e-95b1-4643-9700-2807f4e6",
"message": "Hello world, I am a function with extra data!",
"extraString": "stringValue",
"extraNumber": float64(2217),
"extraFloat": 3.14,
"extraBoolean": true,
"extraNull": nil,
"extraArrayOfStrings": []any{"stringValue", "stringValue"},
"extraArrayOfNumbers": []any{float64(2217), float64(2217)},
"extraArrayOfMixedTypes": []any{"stringValue", float64(2217), true, nil},
"extraArrayWithNesting": []any{"stringValue", []any{float64(2217), []any{true, nil}}},
"extraObject": map[string]any{
"stringValue": "stringValue",
"numberValue": float64(2217),
"booleanValue": true,
"nullValue": nil,
},
"extraObjectWithNesting": map[string]any{
"stringValue": "stringValue",
"numberValue": float64(2217),
"booleanValue": true,
"nullValue": nil,
"arrayValue": []any{"stringValue", float64(2217)},
"objectValue": map[string]any{
"stringValue": "stringValue",
"numberValue": float64(2217),
},
},
},
},
},
expectedLogs: []logInfo{
{
logType: "function",
timestamp: "2026-02-26T20:15:32.000Z",
body: "Hello world, I am a function with extra data!",
containsRequestId: true,
requestId: "79b4f56e-95b1-4643-9700-2807f4e6",
severityText: "Info",
severityNumber: plog.SeverityNumberInfo,
attributes: map[string]any{
"extraString": "stringValue",
"extraNumber": float64(2217),
"extraFloat": float64(3.14),
"extraBoolean": true,
"extraNull": nil,
"extraArrayOfStrings": []any{
"stringValue",
"stringValue",
},
"extraArrayOfNumbers": []any{
float64(2217),
float64(2217),
},
"extraArrayOfMixedTypes": []any{
"stringValue",
float64(2217),
true,
nil,
},
"extraArrayWithNesting": []any{
"stringValue",
[]any{
float64(2217),
[]any{
true, nil,
},
},
},
"extraObject": map[string]any{
"stringValue": "stringValue",
"numberValue": float64(2217),
"booleanValue": true,
"nullValue": nil,
},
"extraObjectWithNesting": map[string]any{
"stringValue": "stringValue",
"numberValue": float64(2217),
"booleanValue": true,
"nullValue": nil,
"arrayValue": []any{
"stringValue",
float64(2217),
},
"objectValue": map[string]any{
"stringValue": "stringValue",
"numberValue": float64(2217),
},
},
},
},
},
expectError: false,
},
{
desc: "function json with keeping reserved fields intact",
slice: []event{
{
Time: "2026-02-26T20:15:32.000Z",
Type: "function",
Record: map[string]any{
"timestamp": "2026-02-26T20:15:32.000Z",
"level": "INFO",
"requestId": "79b4f56e-95b1-4643-9700-2807f4e6",
"message": "Hello world, I am a function with overriden type field!",
"type": "override",
},
},
},
expectedLogs: []logInfo{
{
logType: "function",
timestamp: "2026-02-26T20:15:32.000Z",
body: "Hello world, I am a function with overriden type field!",
containsRequestId: true,
requestId: "79b4f56e-95b1-4643-9700-2807f4e6",
severityText: "Info",
severityNumber: plog.SeverityNumberInfo,
},
},
expectError: false,
},
{
desc: "extension text",
slice: []event{
Expand Down Expand Up @@ -820,6 +952,13 @@ func TestCreateLogs(t *testing.T) {
require.Equal(t, expected.severityText, logRecord.SeverityText())
require.Equal(t, expected.severityNumber, logRecord.SeverityNumber())
require.Equal(t, expected.body, logRecord.Body().Str())

// Check expected attributes
for key, value := range expected.attributes {
attr, ok := logRecord.Attributes().Get(key)
require.True(t, ok, "expected attribute %s not found", key)
require.Equal(t, value, attr.AsRaw(), "expected attribute %s to have value %v, got %v", key, value, attr.AsRaw())
}
}
})
}
Expand Down