1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-12-12 10:04:29 +02:00
opentelemetry-go/oteltest/span.go
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

229 lines
6.2 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 // import "go.opentelemetry.io/otel/oteltest"
import (
"fmt"
"reflect"
"sync"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/semconv"
"go.opentelemetry.io/otel/trace"
)
var _ trace.Span = (*Span)(nil)
// Span is an OpenTelemetry Span used for testing.
type Span struct {
lock sync.RWMutex
tracer *Tracer
spanContext trace.SpanContext
parentSpanID trace.SpanID
ended bool
name string
startTime time.Time
endTime time.Time
statusCode codes.Code
statusMessage string
attributes map[attribute.Key]attribute.Value
events []Event
links []trace.Link
spanKind trace.SpanKind
}
// Tracer returns the Tracer that created s.
func (s *Span) Tracer() trace.Tracer {
return s.tracer
}
// End ends s. If the Tracer that created s was configured with a
// SpanRecorder, that recorder's OnEnd method is called as the final part of
// this method.
func (s *Span) End(opts ...trace.SpanOption) {
s.lock.Lock()
defer s.lock.Unlock()
if s.ended {
return
}
c := trace.NewSpanConfig(opts...)
s.endTime = time.Now()
if endTime := c.Timestamp; !endTime.IsZero() {
s.endTime = endTime
}
s.ended = true
if s.tracer.config.SpanRecorder != nil {
s.tracer.config.SpanRecorder.OnEnd(s)
}
}
// RecordError records an error as an exception Span event.
func (s *Span) RecordError(err error, opts ...trace.EventOption) {
if err == nil || s.ended {
return
}
errType := reflect.TypeOf(err)
errTypeString := fmt.Sprintf("%s.%s", errType.PkgPath(), errType.Name())
if errTypeString == "." {
errTypeString = errType.String()
}
opts = append(opts, trace.WithAttributes(
semconv.ExceptionTypeKey.String(errTypeString),
semconv.ExceptionMessageKey.String(err.Error()),
))
s.AddEvent(semconv.ExceptionEventName, opts...)
}
// AddEvent adds an event to s.
func (s *Span) AddEvent(name string, o ...trace.EventOption) {
s.lock.Lock()
defer s.lock.Unlock()
if s.ended {
return
}
c := trace.NewEventConfig(o...)
var attributes map[attribute.Key]attribute.Value
if l := len(c.Attributes); l > 0 {
attributes = make(map[attribute.Key]attribute.Value, l)
for _, attr := range c.Attributes {
attributes[attr.Key] = attr.Value
}
}
s.events = append(s.events, Event{
Timestamp: c.Timestamp,
Name: name,
Attributes: attributes,
})
}
// IsRecording returns the recording state of s.
func (s *Span) IsRecording() bool {
return true
}
// SpanContext returns the SpanContext of s.
func (s *Span) SpanContext() trace.SpanContext {
return s.spanContext
}
// SetStatus sets the status of s in the form of a code and a message.
func (s *Span) SetStatus(code codes.Code, msg string) {
s.lock.Lock()
defer s.lock.Unlock()
if s.ended {
return
}
s.statusCode = code
s.statusMessage = msg
}
// SetName sets the name of s.
func (s *Span) SetName(name string) {
s.lock.Lock()
defer s.lock.Unlock()
if s.ended {
return
}
s.name = name
}
// SetAttributes sets attrs as attributes of s.
func (s *Span) SetAttributes(attrs ...attribute.KeyValue) {
s.lock.Lock()
defer s.lock.Unlock()
if s.ended {
return
}
for _, attr := range attrs {
s.attributes[attr.Key] = attr.Value
}
}
// Name returns the name most recently set on s, either at or after creation
// time. It cannot be change after End has been called on s.
func (s *Span) Name() string { return s.name }
// ParentSpanID returns the SpanID of the parent Span. If s is a root Span,
// and therefore does not have a parent, the returned SpanID will be invalid
// (i.e., it will contain all zeroes).
func (s *Span) ParentSpanID() trace.SpanID { return s.parentSpanID }
// Attributes returns the attributes set on s, either at or after creation
// time. If the same attribute key was set multiple times, the last call will
// be used. Attributes cannot be changed after End has been called on s.
func (s *Span) Attributes() map[attribute.Key]attribute.Value {
s.lock.RLock()
defer s.lock.RUnlock()
attributes := make(map[attribute.Key]attribute.Value)
for k, v := range s.attributes {
attributes[k] = v
}
return attributes
}
// Events returns the events set on s. Events cannot be changed after End has
// been called on s.
func (s *Span) Events() []Event { return s.events }
// Links returns the links set on s at creation time. If multiple links for
// the same SpanContext were set, the last link will be used.
func (s *Span) Links() []trace.Link { return s.links }
// StartTime returns the time at which s was started. This will be the
// wall-clock time unless a specific start time was provided.
func (s *Span) StartTime() time.Time { return s.startTime }
// EndTime returns the time at which s was ended if at has been ended, or
// false otherwise. If the span has been ended, the returned time will be the
// wall-clock time unless a specific end time was provided.
func (s *Span) EndTime() (time.Time, bool) { return s.endTime, s.ended }
// Ended returns whether s has been ended, i.e. whether End has been called at
// least once on s.
func (s *Span) Ended() bool { return s.ended }
// StatusCode returns the code of the status most recently set on s, or
// codes.OK if no status has been explicitly set. It cannot be changed after
// End has been called on s.
func (s *Span) StatusCode() codes.Code { return s.statusCode }
// StatusMessage returns the status message most recently set on s or the
// empty string if no status message was set.
func (s *Span) StatusMessage() string { return s.statusMessage }
// SpanKind returns the span kind of s.
func (s *Span) SpanKind() trace.SpanKind { return s.spanKind }