1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-01-28 03:57:09 +02:00
Jack Wink 33699d242d
Adds semantic conventions for exceptions (#1492)
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.
2021-04-01 13:07:46 -07:00

283 lines
7.0 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 internal
import (
"context"
"math/rand"
"reflect"
"sync"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/internal/baggage"
"go.opentelemetry.io/otel/semconv"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/bridge/opentracing/migration"
)
var (
ComponentKey = attribute.Key("component")
ServiceKey = attribute.Key("service")
StatusCodeKey = attribute.Key("status.code")
StatusMessageKey = attribute.Key("status.message")
ErrorKey = attribute.Key("error")
NameKey = attribute.Key("name")
)
type MockContextKeyValue struct {
Key interface{}
Value interface{}
}
type MockTracer struct {
Resources baggage.Map
FinishedSpans []*MockSpan
SpareTraceIDs []trace.TraceID
SpareSpanIDs []trace.SpanID
SpareContextKeyValues []MockContextKeyValue
randLock sync.Mutex
rand *rand.Rand
}
var _ trace.Tracer = &MockTracer{}
var _ migration.DeferredContextSetupTracerExtension = &MockTracer{}
func NewMockTracer() *MockTracer {
return &MockTracer{
Resources: baggage.NewEmptyMap(),
FinishedSpans: nil,
SpareTraceIDs: nil,
SpareSpanIDs: nil,
SpareContextKeyValues: nil,
rand: rand.New(rand.NewSource(time.Now().Unix())),
}
}
func (t *MockTracer) Start(ctx context.Context, name string, opts ...trace.SpanOption) (context.Context, trace.Span) {
config := trace.NewSpanConfig(opts...)
startTime := config.Timestamp
if startTime.IsZero() {
startTime = time.Now()
}
spanContext := trace.NewSpanContext(trace.SpanContextConfig{
TraceID: t.getTraceID(ctx, config),
SpanID: t.getSpanID(),
TraceFlags: 0,
})
span := &MockSpan{
mockTracer: t,
officialTracer: t,
spanContext: spanContext,
Attributes: baggage.NewMap(baggage.MapUpdate{
MultiKV: config.Attributes,
}),
StartTime: startTime,
EndTime: time.Time{},
ParentSpanID: t.getParentSpanID(ctx, config),
Events: nil,
SpanKind: trace.ValidateSpanKind(config.SpanKind),
}
if !migration.SkipContextSetup(ctx) {
ctx = trace.ContextWithSpan(ctx, span)
ctx = t.addSpareContextValue(ctx)
}
return ctx, span
}
func (t *MockTracer) addSpareContextValue(ctx context.Context) context.Context {
if len(t.SpareContextKeyValues) > 0 {
pair := t.SpareContextKeyValues[0]
t.SpareContextKeyValues[0] = MockContextKeyValue{}
t.SpareContextKeyValues = t.SpareContextKeyValues[1:]
if len(t.SpareContextKeyValues) == 0 {
t.SpareContextKeyValues = nil
}
ctx = context.WithValue(ctx, pair.Key, pair.Value)
}
return ctx
}
func (t *MockTracer) getTraceID(ctx context.Context, config *trace.SpanConfig) trace.TraceID {
if parent := t.getParentSpanContext(ctx, config); parent.IsValid() {
return parent.TraceID()
}
if len(t.SpareTraceIDs) > 0 {
traceID := t.SpareTraceIDs[0]
t.SpareTraceIDs = t.SpareTraceIDs[1:]
if len(t.SpareTraceIDs) == 0 {
t.SpareTraceIDs = nil
}
return traceID
}
return t.getRandTraceID()
}
func (t *MockTracer) getParentSpanID(ctx context.Context, config *trace.SpanConfig) trace.SpanID {
if parent := t.getParentSpanContext(ctx, config); parent.IsValid() {
return parent.SpanID()
}
return trace.SpanID{}
}
func (t *MockTracer) getParentSpanContext(ctx context.Context, config *trace.SpanConfig) trace.SpanContext {
if !config.NewRoot {
return trace.SpanContextFromContext(ctx)
}
return trace.SpanContext{}
}
func (t *MockTracer) getSpanID() trace.SpanID {
if len(t.SpareSpanIDs) > 0 {
spanID := t.SpareSpanIDs[0]
t.SpareSpanIDs = t.SpareSpanIDs[1:]
if len(t.SpareSpanIDs) == 0 {
t.SpareSpanIDs = nil
}
return spanID
}
return t.getRandSpanID()
}
func (t *MockTracer) getRandSpanID() trace.SpanID {
t.randLock.Lock()
defer t.randLock.Unlock()
sid := trace.SpanID{}
t.rand.Read(sid[:])
return sid
}
func (t *MockTracer) getRandTraceID() trace.TraceID {
t.randLock.Lock()
defer t.randLock.Unlock()
tid := trace.TraceID{}
t.rand.Read(tid[:])
return tid
}
func (t *MockTracer) DeferredContextSetupHook(ctx context.Context, span trace.Span) context.Context {
return t.addSpareContextValue(ctx)
}
type MockEvent struct {
Timestamp time.Time
Name string
Attributes baggage.Map
}
type MockSpan struct {
mockTracer *MockTracer
officialTracer trace.Tracer
spanContext trace.SpanContext
SpanKind trace.SpanKind
recording bool
Attributes baggage.Map
StartTime time.Time
EndTime time.Time
ParentSpanID trace.SpanID
Events []MockEvent
}
var _ trace.Span = &MockSpan{}
var _ migration.OverrideTracerSpanExtension = &MockSpan{}
func (s *MockSpan) SpanContext() trace.SpanContext {
return s.spanContext
}
func (s *MockSpan) IsRecording() bool {
return s.recording
}
func (s *MockSpan) SetStatus(code codes.Code, msg string) {
s.SetAttributes(StatusCodeKey.Int(int(code)), StatusMessageKey.String(msg))
}
func (s *MockSpan) SetName(name string) {
s.SetAttributes(NameKey.String(name))
}
func (s *MockSpan) SetError(v bool) {
s.SetAttributes(ErrorKey.Bool(v))
}
func (s *MockSpan) SetAttributes(attributes ...attribute.KeyValue) {
s.applyUpdate(baggage.MapUpdate{
MultiKV: attributes,
})
}
func (s *MockSpan) applyUpdate(update baggage.MapUpdate) {
s.Attributes = s.Attributes.Apply(update)
}
func (s *MockSpan) End(options ...trace.SpanOption) {
if !s.EndTime.IsZero() {
return // already finished
}
config := trace.NewSpanConfig(options...)
endTime := config.Timestamp
if endTime.IsZero() {
endTime = time.Now()
}
s.EndTime = endTime
s.mockTracer.FinishedSpans = append(s.mockTracer.FinishedSpans, s)
}
func (s *MockSpan) RecordError(err error, opts ...trace.EventOption) {
if err == nil {
return // no-op on nil error
}
if !s.EndTime.IsZero() {
return // already finished
}
s.SetStatus(codes.Error, "")
opts = append(opts, trace.WithAttributes(
semconv.ExceptionTypeKey.String(reflect.TypeOf(err).String()),
semconv.ExceptionMessageKey.String(err.Error()),
))
s.AddEvent(semconv.ExceptionEventName, opts...)
}
func (s *MockSpan) Tracer() trace.Tracer {
return s.officialTracer
}
func (s *MockSpan) AddEvent(name string, o ...trace.EventOption) {
c := trace.NewEventConfig(o...)
s.Events = append(s.Events, MockEvent{
Timestamp: c.Timestamp,
Name: name,
Attributes: baggage.NewMap(baggage.MapUpdate{
MultiKV: c.Attributes,
}),
})
}
func (s *MockSpan) OverrideTracer(tracer trace.Tracer) {
s.officialTracer = tracer
}