package trace
import (
"context"
"errors"
"fmt"
"sync"
"sync/atomic"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/internal/global"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace/internal/x"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
"go.opentelemetry.io/otel/trace/noop"
)
const (
defaultTracerName = "go.opentelemetry.io/otel/sdk/tracer"
selfObsScopeName = "go.opentelemetry.io/otel/sdk/trace"
)
type tracerProviderConfig struct {
processors []SpanProcessor
sampler Sampler
idGenerator IDGenerator
spanLimits SpanLimits
resource *resource .Resource
}
func (cfg tracerProviderConfig ) MarshalLog () any {
return struct {
SpanProcessors []SpanProcessor
SamplerType string
IDGeneratorType string
SpanLimits SpanLimits
Resource *resource .Resource
}{
SpanProcessors : cfg .processors ,
SamplerType : fmt .Sprintf ("%T" , cfg .sampler ),
IDGeneratorType : fmt .Sprintf ("%T" , cfg .idGenerator ),
SpanLimits : cfg .spanLimits ,
Resource : cfg .resource ,
}
}
type TracerProvider struct {
embedded .TracerProvider
mu sync .Mutex
namedTracer map [instrumentation .Scope ]*tracer
spanProcessors atomic .Pointer [spanProcessorStates ]
isShutdown atomic .Bool
sampler Sampler
idGenerator IDGenerator
spanLimits SpanLimits
resource *resource .Resource
}
var _ trace .TracerProvider = &TracerProvider {}
func NewTracerProvider (opts ...TracerProviderOption ) *TracerProvider {
o := tracerProviderConfig {
spanLimits : NewSpanLimits (),
}
o = applyTracerProviderEnvConfigs (o )
for _ , opt := range opts {
o = opt .apply (o )
}
o = ensureValidTracerProviderConfig (o )
tp := &TracerProvider {
namedTracer : make (map [instrumentation .Scope ]*tracer ),
sampler : o .sampler ,
idGenerator : o .idGenerator ,
spanLimits : o .spanLimits ,
resource : o .resource ,
}
global .Info ("TracerProvider created" , "config" , o )
spss := make (spanProcessorStates , 0 , len (o .processors ))
for _ , sp := range o .processors {
spss = append (spss , newSpanProcessorState (sp ))
}
tp .spanProcessors .Store (&spss )
return tp
}
func (p *TracerProvider ) Tracer (name string , opts ...trace .TracerOption ) trace .Tracer {
if p .isShutdown .Load () {
return noop .NewTracerProvider ().Tracer (name , opts ...)
}
c := trace .NewTracerConfig (opts ...)
if name == "" {
name = defaultTracerName
}
is := instrumentation .Scope {
Name : name ,
Version : c .InstrumentationVersion (),
SchemaURL : c .SchemaURL (),
Attributes : c .InstrumentationAttributes (),
}
t , ok := func () (trace .Tracer , bool ) {
p .mu .Lock ()
defer p .mu .Unlock ()
if p .isShutdown .Load () {
return noop .NewTracerProvider ().Tracer (name , opts ...), true
}
t , ok := p .namedTracer [is ]
if !ok {
t = &tracer {
provider : p ,
instrumentationScope : is ,
selfObservabilityEnabled : x .SelfObservability .Enabled (),
}
if t .selfObservabilityEnabled {
var err error
t .spanLiveMetric , t .spanStartedMetric , err = newInst ()
if err != nil {
msg := "failed to create self-observability metrics for tracer: %w"
err := fmt .Errorf (msg , err )
otel .Handle (err )
}
}
p .namedTracer [is ] = t
}
return t , ok
}()
if !ok {
global .Info (
"Tracer created" ,
"name" ,
name ,
"version" ,
is .Version ,
"schemaURL" ,
is .SchemaURL ,
"attributes" ,
is .Attributes ,
)
}
return t
}
func newInst() (otelconv .SDKSpanLive , otelconv .SDKSpanStarted , error ) {
m := otel .GetMeterProvider ().Meter (
selfObsScopeName ,
metric .WithInstrumentationVersion (sdk .Version ()),
metric .WithSchemaURL (semconv .SchemaURL ),
)
var err error
spanLiveMetric , e := otelconv .NewSDKSpanLive (m )
err = errors .Join (err , e )
spanStartedMetric , e := otelconv .NewSDKSpanStarted (m )
err = errors .Join (err , e )
return spanLiveMetric , spanStartedMetric , err
}
func (p *TracerProvider ) RegisterSpanProcessor (sp SpanProcessor ) {
if p .isShutdown .Load () {
return
}
p .mu .Lock ()
defer p .mu .Unlock ()
if p .isShutdown .Load () {
return
}
current := p .getSpanProcessors ()
newSPS := make (spanProcessorStates , 0 , len (current )+1 )
newSPS = append (newSPS , current ...)
newSPS = append (newSPS , newSpanProcessorState (sp ))
p .spanProcessors .Store (&newSPS )
}
func (p *TracerProvider ) UnregisterSpanProcessor (sp SpanProcessor ) {
if p .isShutdown .Load () {
return
}
p .mu .Lock ()
defer p .mu .Unlock ()
if p .isShutdown .Load () {
return
}
old := p .getSpanProcessors ()
if len (old ) == 0 {
return
}
spss := make (spanProcessorStates , len (old ))
copy (spss , old )
var stopOnce *spanProcessorState
var idx int
for i , sps := range spss {
if sps .sp == sp {
stopOnce = sps
idx = i
}
}
if stopOnce != nil {
stopOnce .state .Do (func () {
if err := sp .Shutdown (context .Background ()); err != nil {
otel .Handle (err )
}
})
}
if len (spss ) > 1 {
copy (spss [idx :], spss [idx +1 :])
}
spss [len (spss )-1 ] = nil
spss = spss [:len (spss )-1 ]
p .spanProcessors .Store (&spss )
}
func (p *TracerProvider ) ForceFlush (ctx context .Context ) error {
spss := p .getSpanProcessors ()
if len (spss ) == 0 {
return nil
}
for _ , sps := range spss {
select {
case <- ctx .Done ():
return ctx .Err ()
default :
}
if err := sps .sp .ForceFlush (ctx ); err != nil {
return err
}
}
return nil
}
func (p *TracerProvider ) Shutdown (ctx context .Context ) error {
if p .isShutdown .Load () {
return nil
}
p .mu .Lock ()
defer p .mu .Unlock ()
if !p .isShutdown .CompareAndSwap (false , true ) {
return nil
}
var retErr error
for _ , sps := range p .getSpanProcessors () {
select {
case <- ctx .Done ():
return ctx .Err ()
default :
}
var err error
sps .state .Do (func () {
err = sps .sp .Shutdown (ctx )
})
if err != nil {
if retErr == nil {
retErr = err
} else {
retErr = fmt .Errorf ("%w; %w" , retErr , err )
}
}
}
p .spanProcessors .Store (&spanProcessorStates {})
return retErr
}
func (p *TracerProvider ) getSpanProcessors () spanProcessorStates {
return *(p .spanProcessors .Load ())
}
type TracerProviderOption interface {
apply(tracerProviderConfig ) tracerProviderConfig
}
type traceProviderOptionFunc func (tracerProviderConfig ) tracerProviderConfig
func (fn traceProviderOptionFunc ) apply (cfg tracerProviderConfig ) tracerProviderConfig {
return fn (cfg )
}
func WithSyncer (e SpanExporter ) TracerProviderOption {
return WithSpanProcessor (NewSimpleSpanProcessor (e ))
}
func WithBatcher (e SpanExporter , opts ...BatchSpanProcessorOption ) TracerProviderOption {
return WithSpanProcessor (NewBatchSpanProcessor (e , opts ...))
}
func WithSpanProcessor (sp SpanProcessor ) TracerProviderOption {
return traceProviderOptionFunc (func (cfg tracerProviderConfig ) tracerProviderConfig {
cfg .processors = append (cfg .processors , sp )
return cfg
})
}
func WithResource (r *resource .Resource ) TracerProviderOption {
return traceProviderOptionFunc (func (cfg tracerProviderConfig ) tracerProviderConfig {
var err error
cfg .resource , err = resource .Merge (resource .Environment (), r )
if err != nil {
otel .Handle (err )
}
return cfg
})
}
func WithIDGenerator (g IDGenerator ) TracerProviderOption {
return traceProviderOptionFunc (func (cfg tracerProviderConfig ) tracerProviderConfig {
if g != nil {
cfg .idGenerator = g
}
return cfg
})
}
func WithSampler (s Sampler ) TracerProviderOption {
return traceProviderOptionFunc (func (cfg tracerProviderConfig ) tracerProviderConfig {
if s != nil {
cfg .sampler = s
}
return cfg
})
}
func WithSpanLimits (sl SpanLimits ) TracerProviderOption {
if sl .AttributeValueLengthLimit <= 0 {
sl .AttributeValueLengthLimit = DefaultAttributeValueLengthLimit
}
if sl .AttributeCountLimit <= 0 {
sl .AttributeCountLimit = DefaultAttributeCountLimit
}
if sl .EventCountLimit <= 0 {
sl .EventCountLimit = DefaultEventCountLimit
}
if sl .AttributePerEventCountLimit <= 0 {
sl .AttributePerEventCountLimit = DefaultAttributePerEventCountLimit
}
if sl .LinkCountLimit <= 0 {
sl .LinkCountLimit = DefaultLinkCountLimit
}
if sl .AttributePerLinkCountLimit <= 0 {
sl .AttributePerLinkCountLimit = DefaultAttributePerLinkCountLimit
}
return traceProviderOptionFunc (func (cfg tracerProviderConfig ) tracerProviderConfig {
cfg .spanLimits = sl
return cfg
})
}
func WithRawSpanLimits (limits SpanLimits ) TracerProviderOption {
return traceProviderOptionFunc (func (cfg tracerProviderConfig ) tracerProviderConfig {
cfg .spanLimits = limits
return cfg
})
}
func applyTracerProviderEnvConfigs(cfg tracerProviderConfig ) tracerProviderConfig {
for _ , opt := range tracerProviderOptionsFromEnv () {
cfg = opt .apply (cfg )
}
return cfg
}
func tracerProviderOptionsFromEnv() []TracerProviderOption {
var opts []TracerProviderOption
sampler , err := samplerFromEnv ()
if err != nil {
otel .Handle (err )
}
if sampler != nil {
opts = append (opts , WithSampler (sampler ))
}
return opts
}
func ensureValidTracerProviderConfig(cfg tracerProviderConfig ) tracerProviderConfig {
if cfg .sampler == nil {
cfg .sampler = ParentBased (AlwaysSample ())
}
if cfg .idGenerator == nil {
cfg .idGenerator = defaultIDGenerator ()
}
if cfg .resource == nil {
cfg .resource = resource .Default ()
}
return cfg
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .