mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-01-24 03:47:19 +02:00
33699d242d
Adds support for the opentelemetry exceptions semantic conventions. In short, this has RecordError produce an exception event with exception attributes instead of using the error event and error attributes. While golang does not have exceptions, the spec itself does not differentiate between errors and exceptions for recording purposes. RecordError was kept as the method name, both for backwards compatibility and to reduce confusion (the method signature takes in a golang error object). The spec appears to allow this, as it suggests the method is optional and signature may reflect whatever is most appropriate for the language implementing it. It may seem non-intuitive to log an exception event from a method called RecordError, but it's beneficial to have consistent behavior across all opentelemetry SDKs. Downstream projects like the opentelemetry-collector can build off of the published API and not special case behaviors from individual languages.
587 lines
14 KiB
Go
587 lines
14 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package oteltest_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
ottest "go.opentelemetry.io/otel/internal/internaltest"
|
|
"go.opentelemetry.io/otel/internal/matchers"
|
|
"go.opentelemetry.io/otel/oteltest"
|
|
"go.opentelemetry.io/otel/semconv"
|
|
"go.opentelemetry.io/otel/trace"
|
|
)
|
|
|
|
func TestSpan(t *testing.T) {
|
|
t.Run("#Tracer", func(t *testing.T) {
|
|
tp := oteltest.NewTracerProvider()
|
|
t.Run("returns the tracer used to start the span", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, subject := tracer.Start(context.Background(), "test")
|
|
|
|
e.Expect(subject.Tracer()).ToEqual(tracer)
|
|
})
|
|
})
|
|
|
|
t.Run("#End", func(t *testing.T) {
|
|
tp := oteltest.NewTracerProvider()
|
|
t.Run("ends the span", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
e.Expect(subject.Ended()).ToBeFalse()
|
|
|
|
_, ok = subject.EndTime()
|
|
e.Expect(ok).ToBeFalse()
|
|
|
|
start := time.Now()
|
|
|
|
subject.End()
|
|
|
|
end := time.Now()
|
|
|
|
e.Expect(subject.Ended()).ToBeTrue()
|
|
|
|
endTime, ok := subject.EndTime()
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
e.Expect(endTime).ToBeTemporally(matchers.AfterOrSameTime, start)
|
|
e.Expect(endTime).ToBeTemporally(matchers.BeforeOrSameTime, end)
|
|
})
|
|
|
|
t.Run("only takes effect the first time it is called", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
subject.End()
|
|
|
|
expectedEndTime, ok := subject.EndTime()
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
subject.End()
|
|
|
|
endTime, ok := subject.EndTime()
|
|
e.Expect(ok).ToBeTrue()
|
|
e.Expect(endTime).ToEqual(expectedEndTime)
|
|
})
|
|
|
|
t.Run("uses the time from WithTimestamp", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
expectedEndTime := time.Now().AddDate(5, 0, 0)
|
|
subject.End(trace.WithTimestamp(expectedEndTime))
|
|
|
|
e.Expect(subject.Ended()).ToBeTrue()
|
|
|
|
endTime, ok := subject.EndTime()
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
e.Expect(endTime).ToEqual(expectedEndTime)
|
|
})
|
|
})
|
|
|
|
t.Run("#RecordError", func(t *testing.T) {
|
|
tp := oteltest.NewTracerProvider()
|
|
t.Run("records an error", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
scenarios := []struct {
|
|
err error
|
|
typ string
|
|
msg string
|
|
}{
|
|
{
|
|
err: ottest.NewTestError("test error"),
|
|
typ: "go.opentelemetry.io/otel/internal/internaltest.TestError",
|
|
msg: "test error",
|
|
},
|
|
{
|
|
err: errors.New("test error 2"),
|
|
typ: "*errors.errorString",
|
|
msg: "test error 2",
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
testTime := time.Now()
|
|
subject.RecordError(s.err, trace.WithTimestamp(testTime))
|
|
|
|
expectedEvents := []oteltest.Event{{
|
|
Timestamp: testTime,
|
|
Name: semconv.ExceptionEventName,
|
|
Attributes: map[attribute.Key]attribute.Value{
|
|
semconv.ExceptionTypeKey: attribute.StringValue(s.typ),
|
|
semconv.ExceptionMessageKey: attribute.StringValue(s.msg),
|
|
},
|
|
}}
|
|
e.Expect(subject.Events()).ToEqual(expectedEvents)
|
|
e.Expect(subject.StatusCode()).ToEqual(codes.Unset)
|
|
e.Expect(subject.StatusMessage()).ToEqual("")
|
|
}
|
|
})
|
|
|
|
t.Run("cannot be set after the span has ended", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
subject.End()
|
|
subject.RecordError(errors.New("ignored error"))
|
|
|
|
e.Expect(len(subject.Events())).ToEqual(0)
|
|
})
|
|
|
|
t.Run("has no effect with nil error", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
subject.RecordError(nil)
|
|
|
|
e.Expect(len(subject.Events())).ToEqual(0)
|
|
})
|
|
})
|
|
|
|
t.Run("#IsRecording", func(t *testing.T) {
|
|
tp := oteltest.NewTracerProvider()
|
|
t.Run("returns true", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, subject := tracer.Start(context.Background(), "test")
|
|
|
|
e.Expect(subject.IsRecording()).ToBeTrue()
|
|
})
|
|
})
|
|
|
|
t.Run("#SpanContext", func(t *testing.T) {
|
|
tp := oteltest.NewTracerProvider()
|
|
t.Run("returns a valid SpanContext", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, subject := tracer.Start(context.Background(), "test")
|
|
|
|
e.Expect(subject.SpanContext().IsValid()).ToBeTrue()
|
|
})
|
|
|
|
t.Run("returns a consistent value", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, subject := tracer.Start(context.Background(), "test")
|
|
|
|
e.Expect(subject.SpanContext()).ToEqual(subject.SpanContext())
|
|
})
|
|
})
|
|
|
|
t.Run("#Name", func(t *testing.T) {
|
|
tp := oteltest.NewTracerProvider()
|
|
t.Run("returns the most recently set name on the span", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
originalName := "test"
|
|
_, span := tracer.Start(context.Background(), originalName)
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
e.Expect(subject.Name()).ToEqual(originalName)
|
|
|
|
subject.SetName("in-between")
|
|
|
|
newName := "new name"
|
|
|
|
subject.SetName(newName)
|
|
|
|
e.Expect(subject.Name()).ToEqual(newName)
|
|
})
|
|
|
|
t.Run("cannot be changed after the span has been ended", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
originalName := "test"
|
|
_, span := tracer.Start(context.Background(), originalName)
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
subject.End()
|
|
subject.SetName("new name")
|
|
|
|
e.Expect(subject.Name()).ToEqual(originalName)
|
|
})
|
|
})
|
|
|
|
t.Run("#Attributes", func(t *testing.T) {
|
|
tp := oteltest.NewTracerProvider()
|
|
t.Run("returns an empty map by default", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
e.Expect(subject.Attributes()).ToEqual(map[attribute.Key]attribute.Value{})
|
|
})
|
|
|
|
t.Run("returns the most recently set attributes", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
attr1 := attribute.String("key1", "value1")
|
|
attr2 := attribute.String("key2", "value2")
|
|
attr3 := attribute.String("key3", "value3")
|
|
unexpectedAttr := attr2.Key.String("unexpected")
|
|
|
|
subject.SetAttributes(attr1, unexpectedAttr, attr3)
|
|
subject.SetAttributes(attr2)
|
|
|
|
attributes := subject.Attributes()
|
|
|
|
e.Expect(attributes[attr1.Key]).ToEqual(attr1.Value)
|
|
e.Expect(attributes[attr2.Key]).ToEqual(attr2.Value)
|
|
e.Expect(attributes[attr3.Key]).ToEqual(attr3.Value)
|
|
})
|
|
|
|
t.Run("cannot be changed after the span has been ended", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
expectedAttr := attribute.String("key", "value")
|
|
subject.SetAttributes(expectedAttr)
|
|
subject.End()
|
|
|
|
unexpectedAttr := expectedAttr.Key.String("unexpected")
|
|
subject.SetAttributes(unexpectedAttr)
|
|
subject.End()
|
|
|
|
attributes := subject.Attributes()
|
|
e.Expect(attributes[expectedAttr.Key]).ToEqual(expectedAttr.Value)
|
|
})
|
|
|
|
t.Run("can be used concurrently with setter", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(2)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
subject.SetAttributes(attribute.String("key", "value"))
|
|
}()
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
subject.Attributes()
|
|
}()
|
|
|
|
wg.Wait()
|
|
})
|
|
})
|
|
|
|
t.Run("#Links", func(t *testing.T) {
|
|
tp := oteltest.NewTracerProvider()
|
|
t.Run("returns an empty map by default", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
e.Expect(len(subject.Links())).ToEqual(0)
|
|
})
|
|
})
|
|
|
|
t.Run("#Events", func(t *testing.T) {
|
|
tp := oteltest.NewTracerProvider()
|
|
t.Run("returns an empty slice by default", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
e.Expect(len(subject.Events())).ToEqual(0)
|
|
})
|
|
|
|
t.Run("returns all of the added events", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
event1Name := "event1"
|
|
event1Attributes := []attribute.KeyValue{
|
|
attribute.String("event1Attr1", "foo"),
|
|
attribute.String("event1Attr2", "bar"),
|
|
}
|
|
|
|
event1Start := time.Now()
|
|
subject.AddEvent(event1Name, trace.WithAttributes(event1Attributes...))
|
|
event1End := time.Now()
|
|
|
|
event2Timestamp := time.Now().AddDate(5, 0, 0)
|
|
event2Name := "event1"
|
|
event2Attributes := []attribute.KeyValue{
|
|
attribute.String("event2Attr", "abc"),
|
|
}
|
|
|
|
subject.AddEvent(event2Name, trace.WithTimestamp(event2Timestamp), trace.WithAttributes(event2Attributes...))
|
|
|
|
events := subject.Events()
|
|
|
|
e.Expect(len(events)).ToEqual(2)
|
|
|
|
event1 := events[0]
|
|
|
|
e.Expect(event1.Timestamp).ToBeTemporally(matchers.AfterOrSameTime, event1Start)
|
|
e.Expect(event1.Timestamp).ToBeTemporally(matchers.BeforeOrSameTime, event1End)
|
|
e.Expect(event1.Name).ToEqual(event1Name)
|
|
|
|
for _, attr := range event1Attributes {
|
|
e.Expect(event1.Attributes[attr.Key]).ToEqual(attr.Value)
|
|
}
|
|
|
|
event2 := events[1]
|
|
|
|
e.Expect(event2.Timestamp).ToEqual(event2Timestamp)
|
|
e.Expect(event2.Name).ToEqual(event2Name)
|
|
|
|
for _, attr := range event2Attributes {
|
|
e.Expect(event2.Attributes[attr.Key]).ToEqual(attr.Value)
|
|
}
|
|
})
|
|
|
|
t.Run("cannot be changed after the span has been ended", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
subject.AddEvent("test")
|
|
|
|
e.Expect(len(subject.Events())).ToEqual(1)
|
|
|
|
expectedEvent := subject.Events()[0]
|
|
|
|
subject.End()
|
|
subject.AddEvent("should not occur")
|
|
|
|
e.Expect(len(subject.Events())).ToEqual(1)
|
|
e.Expect(subject.Events()[0]).ToEqual(expectedEvent)
|
|
})
|
|
})
|
|
|
|
t.Run("#Status", func(t *testing.T) {
|
|
tp := oteltest.NewTracerProvider()
|
|
t.Run("defaults to OK", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
e.Expect(subject.StatusCode()).ToEqual(codes.Unset)
|
|
|
|
subject.End()
|
|
|
|
e.Expect(subject.StatusCode()).ToEqual(codes.Unset)
|
|
})
|
|
|
|
statuses := []codes.Code{
|
|
codes.Unset,
|
|
codes.Error,
|
|
codes.Ok,
|
|
}
|
|
|
|
for _, status := range statuses {
|
|
t.Run("returns the most recently set status on the span", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
subject.SetStatus(codes.Ok, "Ok")
|
|
subject.SetStatus(status, "Yo!")
|
|
|
|
e.Expect(subject.StatusCode()).ToEqual(status)
|
|
e.Expect(subject.StatusMessage()).ToEqual("Yo!")
|
|
})
|
|
|
|
t.Run("cannot be changed after the span has been ended", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
|
|
originalStatus := codes.Ok
|
|
|
|
subject.SetStatus(originalStatus, "OK")
|
|
subject.End()
|
|
subject.SetStatus(status, fmt.Sprint(status))
|
|
|
|
e.Expect(subject.StatusCode()).ToEqual(originalStatus)
|
|
e.Expect(subject.StatusMessage()).ToEqual("OK")
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("#SpanKind", func(t *testing.T) {
|
|
tp := oteltest.NewTracerProvider()
|
|
t.Run("returns the value given at start", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := matchers.NewExpecter(t)
|
|
|
|
tracer := tp.Tracer(t.Name())
|
|
_, span := tracer.Start(context.Background(), "test",
|
|
trace.WithSpanKind(trace.SpanKindConsumer))
|
|
|
|
subject, ok := span.(*oteltest.Span)
|
|
e.Expect(ok).ToBeTrue()
|
|
subject.End()
|
|
|
|
e.Expect(subject.SpanKind()).ToEqual(trace.SpanKindConsumer)
|
|
})
|
|
})
|
|
}
|