Skip to content

Commit

Permalink
Fix race condition in tracing package. Update documentation. (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
Baliedge authored Mar 2, 2022
1 parent b1ccaed commit b0f70ff
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 12 deletions.
18 changes: 18 additions & 0 deletions tracing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,24 @@ tracing.SetDefaultTracer(tracer)
tracing.CloseTracing(context.Background())
```

### Tracer Lifecycle
The common use case is to call `InitTracing()` to build a single default tracer
that the application uses througout its lifetime, then call `CloseTracing()` on
shutdown.

The default tracer is stored globally in the tracer package for use by tracing
functions.

The tracer object identifies itself by a library name, which can be seen in Jaeger
traces as attribute `otel.library.name`. This value is typically the module
name of the application.

If it's necessary to create traces with a different library name, additional
tracer objects may be created by `NewTracer()` which returns a context with the
tracer object embedded in it. This context object must be passed to tracing
functions use that tracer in particular, otherwise the default tracer will be
selected.

### Setting Resources
OpenTelemetry is configured by environment variables and supplemental resource
settings. Some of these resources also map to environment variables.
Expand Down
11 changes: 5 additions & 6 deletions tracing/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,11 @@ func startSpan(ctx context.Context, spanName, fileTag string, opts ...trace.Span
tracer, ok := ctx.Value(tracerKey{}).(trace.Tracer)
if !ok {
// No tracer embedded. Fall back to default tracer.
tracer = defaultTracer

// Else, omit tracing.
if tracer == nil {
return ctx
}
tracer = GetDefaultTracer()
}
// Else, omit tracing.
if tracer == nil {
return ctx
}

opts = append(opts, trace.WithAttributes(
Expand Down
32 changes: 26 additions & 6 deletions tracing/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tracing

import (
"context"
"sync"
"time"

"github.com/mailgun/holster/v4/errors"
Expand All @@ -23,6 +24,7 @@ var logLevels = []logrus.Level{
logrus.TraceLevel,
}

var globalMutex sync.RWMutex
var defaultTracer trace.Tracer

// InitTracing initializes a global OpenTelemetry tracer provider singleton.
Expand Down Expand Up @@ -66,7 +68,9 @@ func InitTracing(ctx context.Context, libraryName string, opts ...sdktrace.Trace
return ctx, nil, errors.Wrap(err, "error in NewTracer")
}

SetDefaultTracer(tracer)
if GetDefaultTracer() == nil {
SetDefaultTracer(tracer)
}

// Required for trace propagation between services.
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
Expand All @@ -81,16 +85,25 @@ func InitTracing(ctx context.Context, libraryName string, opts ...sdktrace.Trace
func NewTracer(ctx context.Context, libraryName string) (context.Context, trace.Tracer, error) {
tp, ok := otel.GetTracerProvider().(*sdktrace.TracerProvider)
if !ok {
return nil, nil, errors.New("OpenTelemetry global tracer provider has not be initialized")
return nil, nil, errors.New("OpenTelemetry global tracer provider has not been initialized")
}

tracer := tp.Tracer(libraryName)
ctx = context.WithValue(ctx, tracerKey{}, tracer)
ctx = ContextWithTracer(ctx, tracer)
return ctx, tracer, nil
}

// GetDefaultTracer gets the global tracer used as a default by this package.
func GetDefaultTracer() trace.Tracer {
globalMutex.RLock()
defer globalMutex.RUnlock()
return defaultTracer
}

// SetDefaultTracer sets the global tracer used as a default by this package.
func SetDefaultTracer(tracer trace.Tracer) {
globalMutex.Lock()
defer globalMutex.Unlock()
defaultTracer = tracer
}

Expand All @@ -99,13 +112,13 @@ func SetDefaultTracer(tracer trace.Tracer) {
func CloseTracing(ctx context.Context) error {
tp, ok := otel.GetTracerProvider().(*sdktrace.TracerProvider)
if !ok {
return errors.New("OpenTelemetry global tracer provider has not be initialized")
return errors.New("OpenTelemetry global tracer provider has not been initialized")
}

defaultTracer = nil

SetDefaultTracer(nil)
ctx, cancel := context.WithTimeout(ctx, 5 * time.Second)
defer cancel()

err := tp.Shutdown(ctx)
if err != nil {
return errors.Wrap(err, "error in tp.Shutdown")
Expand All @@ -114,6 +127,13 @@ func CloseTracing(ctx context.Context) error {
return nil
}

// ContextWithTracer creates a context with a tracer object embedded.
// This value is used by scope functions or use TracerFromContext() to retrieve
// it.
func ContextWithTracer(ctx context.Context, tracer trace.Tracer) context.Context {
return context.WithValue(ctx, tracerKey{}, tracer)
}

// TracerFromContext gets embedded `Tracer` from context.
// Returns nil if not found.
func TracerFromContext(ctx context.Context) trace.Tracer {
Expand Down

0 comments on commit b0f70ff

Please sign in to comment.