Skip to content

Commit b53a3e1

Browse files
authored
[exporter/elasticsearch] Fix unset span status serialization (#40345)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description Fix a bug where unset span status code was being serialized as "Unset" instead of being ignored <!-- Issue number (e.g. #1234) or full URL to issue, if applicable. --> #### Link to tracking issue Fixes #39498 <!--Describe what testing was performed and which tests were added.--> #### Testing Unit tests.
1 parent 1fd9e76 commit b53a3e1

File tree

4 files changed

+135
-2
lines changed

4 files changed

+135
-2
lines changed
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: bug_fix
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: "Fix a bug where unset span status code was being serialized as 'Unset' instead of being ignored"
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: [39498]
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/exporter_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2001,7 +2001,7 @@ func TestExporterTraces(t *testing.T) {
20012001
expected := []itemRequest{
20022002
{
20032003
Action: []byte(`{"create":{"_index":"traces-generic.otel-default"}}`),
2004-
Document: []byte(`{"@timestamp":"3600000.0","attributes":{"attr.foo":"attr.bar"},"data_stream":{"dataset":"generic.otel","namespace":"default","type":"traces"},"dropped_attributes_count":2,"dropped_events_count":3,"dropped_links_count":4,"duration":3600000000000,"kind":"Unspecified","links":[{"attributes":{"link.attr.foo":"link.attr.bar"},"dropped_attributes_count":11,"span_id":"0100000000000000","trace_id":"01000000000000000000000000000000","trace_state":"bar"}],"name":"name","resource":{"attributes":{"resource.foo":"resource.bar"}},"scope":{},"status":{"code":"Unset"},"trace_state":"foo"}`),
2004+
Document: []byte(`{"@timestamp":"3600000.0","attributes":{"attr.foo":"attr.bar"},"data_stream":{"dataset":"generic.otel","namespace":"default","type":"traces"},"dropped_attributes_count":2,"dropped_events_count":3,"dropped_links_count":4,"duration":3600000000000,"kind":"Unspecified","links":[{"attributes":{"link.attr.foo":"link.attr.bar"},"dropped_attributes_count":11,"span_id":"0100000000000000","trace_id":"01000000000000000000000000000000","trace_state":"bar"}],"name":"name","resource":{"attributes":{"resource.foo":"resource.bar"}},"scope":{},"status":{},"trace_state":"foo"}`),
20052005
},
20062006
{
20072007
Action: []byte(`{"create":{"_index":"logs-generic.otel-default"}}`),

exporter/elasticsearchexporter/internal/serializer/otelserializer/traces.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ func writeStatus(v *json.Visitor, status ptrace.Status) {
7272
_ = v.OnKey("status")
7373
_ = v.OnObjectStart(-1, structform.AnyType)
7474
writeStringFieldSkipDefault(v, "message", status.Message())
75-
writeStringFieldSkipDefault(v, "code", status.Code().String())
75+
if code := status.Code(); code != ptrace.StatusCodeUnset {
76+
writeStringFieldSkipDefault(v, "code", code.String())
77+
}
7678
_ = v.OnObjectFinished()
7779
}
7880

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package otelserializer
5+
6+
import (
7+
"bytes"
8+
"encoding/json"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
"go.opentelemetry.io/collector/pdata/pcommon"
14+
"go.opentelemetry.io/collector/pdata/ptrace"
15+
16+
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/elasticsearchexporter/internal/elasticsearch"
17+
)
18+
19+
func TestSerializeTraces(t *testing.T) {
20+
tests := []struct {
21+
name string
22+
spanCustomizer func(resource pcommon.Resource, scope pcommon.InstrumentationScope, span ptrace.Span)
23+
wantErr bool
24+
expected any
25+
}{
26+
{
27+
name: "default",
28+
spanCustomizer: func(resource pcommon.Resource, scope pcommon.InstrumentationScope, span ptrace.Span) {
29+
span.SetStartTimestamp(1721314113467654123)
30+
span.SetEndTimestamp(1721314113469654123)
31+
span.SetKind(ptrace.SpanKindServer)
32+
span.Status().SetCode(ptrace.StatusCodeOk)
33+
span.Status().SetMessage("Hello")
34+
resource.Attributes().PutInt("foo", 123)
35+
scope.Attributes().PutStr("foo", "bar")
36+
},
37+
wantErr: false,
38+
expected: map[string]any{
39+
"@timestamp": "1721314113467.654123",
40+
"duration": json.Number("2000000"),
41+
"kind": "Server",
42+
"links": []any{},
43+
"resource": map[string]any{
44+
"attributes": map[string]any{
45+
"foo": json.Number("123"),
46+
},
47+
},
48+
"scope": map[string]any{
49+
"attributes": map[string]any{
50+
"foo": "bar",
51+
},
52+
},
53+
"status": map[string]any{
54+
"code": "Ok",
55+
"message": "Hello",
56+
},
57+
},
58+
},
59+
{
60+
name: "unset status code",
61+
spanCustomizer: func(_ pcommon.Resource, _ pcommon.InstrumentationScope, span ptrace.Span) {
62+
span.Status().SetCode(ptrace.StatusCodeUnset)
63+
},
64+
wantErr: false,
65+
expected: map[string]any{
66+
"@timestamp": "0.0",
67+
"duration": json.Number("0"),
68+
"kind": "Unspecified",
69+
"links": []any{},
70+
"resource": map[string]any{},
71+
"scope": map[string]any{},
72+
"status": map[string]any{},
73+
},
74+
},
75+
}
76+
for _, tt := range tests {
77+
t.Run(tt.name, func(t *testing.T) {
78+
traces := ptrace.NewTraces()
79+
resourceSpans := traces.ResourceSpans().AppendEmpty()
80+
scopeSpans := resourceSpans.ScopeSpans().AppendEmpty()
81+
span := scopeSpans.Spans().AppendEmpty()
82+
tt.spanCustomizer(resourceSpans.Resource(), scopeSpans.Scope(), span)
83+
traces.MarkReadOnly()
84+
85+
var buf bytes.Buffer
86+
ser, err := New()
87+
require.NoError(t, err)
88+
err = ser.SerializeSpan(resourceSpans.Resource(), "", scopeSpans.Scope(), "", span, elasticsearch.Index{}, &buf)
89+
if (err != nil) != tt.wantErr {
90+
t.Errorf("SerializeSpan() error = %v, wantErr %v", err, tt.wantErr)
91+
}
92+
spanBytes := buf.Bytes()
93+
eventAsJSON := string(spanBytes)
94+
var result any
95+
decoder := json.NewDecoder(bytes.NewBuffer(spanBytes))
96+
decoder.UseNumber()
97+
if err := decoder.Decode(&result); err != nil {
98+
t.Error(err)
99+
}
100+
101+
assert.Equal(t, tt.expected, result, eventAsJSON)
102+
})
103+
}
104+
}

0 commit comments

Comments
 (0)