Skip to content

Commit 347784d

Browse files
authored
Extend logs v2 intake API to support ECS logging fields (#9349)
1 parent c5bfef1 commit 347784d

28 files changed

+2700
-144
lines changed

changelogs/head.asciidoc

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Set error.id for OpenTelemetry exception span events {pull}9372[9372]
1717

1818
[float]
1919
==== Intake API Changes
20+
- experimental:[] Extend logs v2 intake API to support ECS logging fields {pull}9349[9349]
2021
- experimental:[] Add support for string timestamp format(`2006-01-02T15:04:05.999-0700`) {pull}9376[9376]
2122

2223
[float]

docs/spec/v2/log.json

-87
This file was deleted.

internal/model/error.go

+10
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ type Error struct {
4242
// This may be set when a stack trace cannot be parsed.
4343
StackTrace string
4444

45+
// Message holds an error message.
46+
//
47+
// Message is the ECS field equivalent of the APM field `error.log.message`.
48+
Message string
49+
50+
// Type holds the type of the error.
51+
Type string
52+
4553
Exception *Exception
4654
Log *ErrorLog
4755
}
@@ -72,6 +80,8 @@ func (e *Error) fields() mapstr.M {
7280
exceptionFields := e.Exception.appendFields(nil, 0)
7381
errorFields.set("exception", exceptionFields)
7482
}
83+
errorFields.maybeSetString("message", e.Message)
84+
errorFields.maybeSetString("type", e.Type)
7585
errorFields.maybeSetMapStr("log", e.logFields())
7686
errorFields.maybeSetString("culprit", e.Culprit)
7787
errorFields.maybeSetMapStr("custom", customFields(e.Custom))

internal/model/error_test.go

+10
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,16 @@ func TestErrorFields(t *testing.T) {
213213
},
214214
},
215215
},
216+
"withLogMessageAndType": {
217+
Error: Error{
218+
Message: "error log message",
219+
Type: "IllegalArgumentException",
220+
},
221+
Output: mapstr.M{
222+
"message": "error log message",
223+
"type": "IllegalArgumentException",
224+
},
225+
},
216226
}
217227

218228
for name, tc := range tests {

internal/model/event.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,20 @@ type Event struct {
3838
// Severity holds the numeric severity of the event for log events.
3939
Severity int64
4040

41-
// Severity holds the action captured by the event for log events.
41+
// Action holds the action captured by the event for log events.
4242
Action string
43+
44+
// Dataset holds the the dataset which produces the events. If an event
45+
// source publishes more than one type of log or events (e.g. access log,
46+
// error log), the dataset is used to specify which one the event comes from.
47+
Dataset string
4348
}
4449

4550
func (e *Event) fields() mapstr.M {
4651
var fields mapStr
4752
fields.maybeSetString("outcome", e.Outcome)
4853
fields.maybeSetString("action", e.Action)
54+
fields.maybeSetString("dataset", e.Dataset)
4955
if e.Severity > 0 {
5056
fields.set("severity", e.Severity)
5157
}

internal/model/event_test.go

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package model
19+
20+
import (
21+
"testing"
22+
"time"
23+
24+
"github.com/stretchr/testify/assert"
25+
26+
"github.com/elastic/elastic-agent-libs/mapstr"
27+
)
28+
29+
func TestEventFieldsEmpty(t *testing.T) {
30+
event := APMEvent{}
31+
beatEvent := event.BeatEvent()
32+
assert.Empty(t, beatEvent.Fields)
33+
}
34+
35+
func TestEventFields(t *testing.T) {
36+
tests := map[string]struct {
37+
Event Event
38+
Output mapstr.M
39+
}{
40+
"withOutcome": {
41+
Event: Event{Outcome: "success"},
42+
Output: mapstr.M{
43+
"outcome": "success",
44+
},
45+
},
46+
"withAction": {
47+
Event: Event{Action: "process-started"},
48+
Output: mapstr.M{
49+
"action": "process-started",
50+
},
51+
},
52+
"withDataset": {
53+
Event: Event{Dataset: "access-log"},
54+
Output: mapstr.M{
55+
"dataset": "access-log",
56+
},
57+
},
58+
"withSeverity": {
59+
Event: Event{Severity: 1},
60+
Output: mapstr.M{
61+
"severity": int64(1),
62+
},
63+
},
64+
"withDuration": {
65+
Event: Event{Duration: time.Minute},
66+
Output: mapstr.M{
67+
"duration": int64(60000000000),
68+
},
69+
},
70+
"withOutcomeActionDataset": {
71+
Event: Event{Outcome: "success", Action: "process-started", Dataset: "access-log"},
72+
Output: mapstr.M{
73+
"outcome": "success",
74+
"action": "process-started",
75+
"dataset": "access-log",
76+
},
77+
},
78+
}
79+
80+
for name, tc := range tests {
81+
t.Run(name, func(t *testing.T) {
82+
event := APMEvent{Event: tc.Event}
83+
beatEvent := event.BeatEvent()
84+
assert.Equal(t, tc.Output, beatEvent.Fields["event"])
85+
})
86+
}
87+
}

internal/model/log.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,33 @@ var (
3434
type Log struct {
3535
// Level holds the log level of the log event.
3636
Level string
37+
// Logger holds the name of the logger instance.
38+
Logger string
39+
Origin LogOrigin
40+
}
41+
42+
// LogOrigin holds information about the origin of the log.
43+
type LogOrigin struct {
44+
File LogOriginFile
45+
FunctionName string
46+
}
47+
48+
// LogOriginFile holds information about the file and the line of the origin of the log.
49+
type LogOriginFile struct {
50+
Name string
51+
Line int
3752
}
3853

3954
func (e Log) fields() mapstr.M {
40-
var fields mapStr
55+
var fields, origin, file mapStr
4156
fields.maybeSetString("level", e.Level)
57+
fields.maybeSetString("logger", e.Logger)
58+
origin.maybeSetString("function", e.Origin.FunctionName)
59+
file.maybeSetString("name", e.Origin.File.Name)
60+
if e.Origin.File.Line > 0 {
61+
file.set("line", e.Origin.File.Line)
62+
}
63+
origin.maybeSetMapStr("file", mapstr.M(file))
64+
fields.maybeSetMapStr("origin", mapstr.M(origin))
4265
return mapstr.M(fields)
4366
}

internal/model/log_test.go

+28-2
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,36 @@ func TestLogTransform(t *testing.T) {
3636
},
3737
{
3838
Log: Log{
39-
Level: "warn",
39+
Level: "warn",
40+
Logger: "bootstrap",
4041
},
4142
Output: mapstr.M{
42-
"level": "warn",
43+
"level": "warn",
44+
"logger": "bootstrap",
45+
},
46+
},
47+
{
48+
Log: Log{
49+
Level: "warn",
50+
Logger: "bootstrap",
51+
Origin: LogOrigin{
52+
File: LogOriginFile{
53+
Name: "testFile",
54+
Line: 12,
55+
},
56+
FunctionName: "testFunc",
57+
},
58+
},
59+
Output: mapstr.M{
60+
"level": "warn",
61+
"logger": "bootstrap",
62+
"origin": mapstr.M{
63+
"function": "testFunc",
64+
"file": mapstr.M{
65+
"name": "testFile",
66+
"line": 12,
67+
},
68+
},
4369
},
4470
},
4571
}

internal/model/modeldecoder/generator/cmd/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func generateV2() {
5353
panic(err)
5454
}
5555
generateCode(p, pkg, parsed, []string{"metadataRoot", "errorRoot", "metricsetRoot", "spanRoot", "transactionRoot", "logRoot"})
56-
generateJSONSchema(p, pkg, parsed, []string{"metadata", "errorEvent", "metricset", "span", "transaction", "log"})
56+
generateJSONSchema(p, pkg, parsed, []string{"metadata", "errorEvent", "metricset", "span", "transaction"})
5757
}
5858

5959
func generateV3RUM() {

0 commit comments

Comments
 (0)