Skip to content

Commit 86278d9

Browse files
authored
[exporter/elasticsearch] Support preserving attributes when mapping to ECS (#33670)
**Description:** <Describe what has changed.> <!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> This PR adds support for preserving resource attributes that are valid ECS fields in addition of mapping them. At the moment the `host.name` is mapped to the `host.hostname`. Both are valid ECS fields but can differ in some cases. Since there is no such distinction in SemConv right now, it does makes sense to preserve both. refs: - https://www.elastic.co/guide/en/ecs/current/ecs-host.html#field-host-name - https://www.elastic.co/guide/en/ecs/current/ecs-host.html#field-host-hostname **Link to tracking Issue:** <Issue number if applicable> **Testing:** <Describe what testing was performed and which tests were added.> Using the testing notes from #33622. Stored document: ```json { "app": { "label": { "component": "migration-logger" } }, "kubernetes": { "node": { "name": "kind-control-plane" }, "pod": { "uid": "0eda57cd-a4ae-4e89-88fa-c771d3bf0c77", "name": "daemonset-logs-4sqjq" }, "namespace": "default" }, "agent": { "name": "otlp" }, "@timestamp": "2024-06-20T07:27:42.589678923Z", "log": { "iostream": "stdout", "file": { "path": "/var/log/pods/default_daemonset-logs-4sqjq_0eda57cd-a4ae-4e89-88fa-c771d3bf0c77/busybox/0.log" } }, "service": { "name": "migration-logger" }, "k8s": { "container": { "restart_count": "0", "name": "busybox" }, "pod": { "start_time": "2024-06-20T07:27:21Z" }, "daemonset": { "name": "daemonset-logs" } }, "host": { "hostname": "daemonset-opentelemetry-collector-agent-l6pzp", "os": { "type": "linux", "platform": "linux" }, "name": "daemonset-opentelemetry-collector-agent-l6pzp" }, "time": "2024-06-20T07:27:42.589678923Z", "message": "otel logs at 07:27:42", "logtag": "F" } ``` **Documentation:** <Describe the documentation added.> ~ /cc @lahsivjar @andrzej-stencel @carsonip --------- Signed-off-by: ChrsMark <[email protected]>
1 parent 3e275ab commit 86278d9

File tree

3 files changed

+64
-6
lines changed

3 files changed

+64
-6
lines changed

.chloggen/es_preserve_host_name.yaml

Lines changed: 27 additions & 0 deletions
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: elasticsearchexporter
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Preserve `host.name` resource attribute in ECS mode
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: [33670]
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: [user]

exporter/elasticsearchexporter/model.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ var resourceAttrsConversionMap = map[string]string{
4949
"k8s.pod.uid": "kubernetes.pod.uid",
5050
}
5151

52+
// resourceAttrsToPreserve contains conventions that should be preserved in ECS mode.
53+
// This can happen when an attribute needs to be mapped to an ECS equivalent but
54+
// at the same time be preserved to its original form.
55+
var resourceAttrsToPreserve = map[string]bool{
56+
semconv.AttributeHostName: true,
57+
}
58+
5259
type mappingModel interface {
5360
encodeLog(pcommon.Resource, plog.LogRecord, pcommon.InstrumentationScope) ([]byte, error)
5461
encodeSpan(pcommon.Resource, ptrace.Span, pcommon.InstrumentationScope) ([]byte, error)
@@ -117,13 +124,13 @@ func (m *encodeModel) encodeLogECSMode(resource pcommon.Resource, record plog.Lo
117124
var document objmodel.Document
118125

119126
// First, try to map resource-level attributes to ECS fields.
120-
encodeLogAttributesECSMode(&document, resource.Attributes(), resourceAttrsConversionMap)
127+
encodeLogAttributesECSMode(&document, resource.Attributes(), resourceAttrsConversionMap, resourceAttrsToPreserve)
121128

122129
// Then, try to map scope-level attributes to ECS fields.
123130
scopeAttrsConversionMap := map[string]string{
124131
// None at the moment
125132
}
126-
encodeLogAttributesECSMode(&document, scope.Attributes(), scopeAttrsConversionMap)
133+
encodeLogAttributesECSMode(&document, scope.Attributes(), scopeAttrsConversionMap, resourceAttrsToPreserve)
127134

128135
// Finally, try to map record-level attributes to ECS fields.
129136
recordAttrsConversionMap := map[string]string{
@@ -133,7 +140,7 @@ func (m *encodeModel) encodeLogECSMode(resource pcommon.Resource, record plog.Lo
133140
semconv.AttributeExceptionType: "error.type",
134141
semconv.AttributeExceptionEscaped: "event.error.exception.handled",
135142
}
136-
encodeLogAttributesECSMode(&document, record.Attributes(), recordAttrsConversionMap)
143+
encodeLogAttributesECSMode(&document, record.Attributes(), recordAttrsConversionMap, resourceAttrsToPreserve)
137144

138145
// Handle special cases.
139146
encodeLogAgentNameECSMode(&document, resource)
@@ -230,7 +237,7 @@ func scopeToAttributes(scope pcommon.InstrumentationScope) pcommon.Map {
230237
return attrs
231238
}
232239

233-
func encodeLogAttributesECSMode(document *objmodel.Document, attrs pcommon.Map, conversionMap map[string]string) {
240+
func encodeLogAttributesECSMode(document *objmodel.Document, attrs pcommon.Map, conversionMap map[string]string, preserveMap map[string]bool) {
234241
if len(conversionMap) == 0 {
235242
// No conversions to be done; add all attributes at top level of
236243
// document.
@@ -247,6 +254,9 @@ func encodeLogAttributesECSMode(document *objmodel.Document, attrs pcommon.Map,
247254
}
248255

249256
document.AddAttribute(ecsKey, v)
257+
if preserve := preserveMap[k]; preserve {
258+
document.AddAttribute(k, v)
259+
}
250260
return true
251261
}
252262

exporter/elasticsearchexporter/model_test.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ func TestEncodeLogECSModeDuplication(t *testing.T) {
231231
})
232232
require.NoError(t, err)
233233

234-
want := `{"@timestamp":"2024-03-12T20:00:41.123456789Z","agent":{"name":"otlp"},"container":{"image":{"tag":["v3.4.0"]}},"event":{"action":"user-password-change"},"host":{"hostname":"localhost","os":{"full":"Mac OS Mojave","name":"Mac OS X","platform":"darwin","type":"macos","version":"10.14.1"}},"service":{"name":"foo.bar","version":"1.1.0"}}`
234+
want := `{"@timestamp":"2024-03-12T20:00:41.123456789Z","agent":{"name":"otlp"},"container":{"image":{"tag":["v3.4.0"]}},"event":{"action":"user-password-change"},"host":{"hostname":"localhost","name":"localhost","os":{"full":"Mac OS Mojave","name":"Mac OS X","platform":"darwin","type":"macos","version":"10.14.1"}},"service":{"name":"foo.bar","version":"1.1.0"}}`
235235
require.NoError(t, err)
236236

237237
resourceContainerImageTags := resource.Attributes().PutEmptySlice(semconv.AttributeContainerImageTags)
@@ -336,6 +336,7 @@ func TestEncodeLogECSMode(t *testing.T) {
336336
"container.image.name": "my-app",
337337
"container.runtime": "docker",
338338
"host.hostname": "i-103de39e0a.gke.us-west-1b.cloud.google.com",
339+
"host.name": "i-103de39e0a.gke.us-west-1b.cloud.google.com",
339340
"host.id": "i-103de39e0a",
340341
"host.type": "t2.medium",
341342
"host.architecture": "x86_64",
@@ -671,6 +672,7 @@ func TestMapLogAttributesToECS(t *testing.T) {
671672
tests := map[string]struct {
672673
attrs func() pcommon.Map
673674
conversionMap map[string]string
675+
preserveMap map[string]bool
674676
expectedDoc func() objmodel.Document
675677
}{
676678
"no_attrs": {
@@ -775,12 +777,31 @@ func TestMapLogAttributesToECS(t *testing.T) {
775777
return d
776778
},
777779
},
780+
"preserve_map": {
781+
attrs: func() pcommon.Map {
782+
m := pcommon.NewMap()
783+
m.PutStr("foo.bar", "baz")
784+
return m
785+
},
786+
conversionMap: map[string]string{
787+
"foo.bar": "bar.qux",
788+
"qux": "foo",
789+
}, preserveMap: map[string]bool{
790+
"foo.bar": true,
791+
},
792+
expectedDoc: func() objmodel.Document {
793+
d := objmodel.Document{}
794+
d.AddString("bar.qux", "baz")
795+
d.AddString("foo.bar", "baz")
796+
return d
797+
},
798+
},
778799
}
779800

780801
for name, test := range tests {
781802
t.Run(name, func(t *testing.T) {
782803
var doc objmodel.Document
783-
encodeLogAttributesECSMode(&doc, test.attrs(), test.conversionMap)
804+
encodeLogAttributesECSMode(&doc, test.attrs(), test.conversionMap, test.preserveMap)
784805

785806
doc.Sort()
786807
expectedDoc := test.expectedDoc()

0 commit comments

Comments
 (0)