diff --git a/CHANGELOG.md b/CHANGELOG.md index 508b6b30baa..9e4a8df6440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Support Go 1.19. Include compatibility testing and document support. (#3077) - Upgrade go.opentelemetry.io/proto/otlp from v0.18.0 to v0.19.0 (#3107) +- Add an `Attribute` field to the `Scope` type in `go.opentelemetry.io/otel/sdk/instrumentation`. (#3131) +- Add the `WithScopeAttributes` `TracerOption` to the `go.opentelemetry.io/otel/trace` package. (#3131) +- Add the `WithScopeAttributes` `MeterOption` to the `go.opentelemetry.io/otel/metric` package. (#3132) ### Changed @@ -20,6 +23,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - The exponential histogram mapping functions have been updated with exact upper-inclusive boundary support following the [corresponding specification change](https://github.com/open-telemetry/opentelemetry-specification/pull/2633). (#2982) +- Attempting to start a span with a nil `context` will no longer cause a panic. (#3110) +- Export scope attributes for all exporters provided by `go.opentelemetry.io/otel/exporters/otlp/otlptrace`. (#3131) ## [1.9.0/0.0.3] - 2022-08-01 diff --git a/bridge/opentracing/bridge_test.go b/bridge/opentracing/bridge_test.go index a64d6f73919..7286bd77823 100644 --- a/bridge/opentracing/bridge_test.go +++ b/bridge/opentracing/bridge_test.go @@ -22,9 +22,11 @@ import ( "testing" ot "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/bridge/opentracing/internal" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) @@ -425,3 +427,44 @@ func TestBridgeTracer_StartSpan(t *testing.T) { }) } } + +func Test_otTagsToOTelAttributesKindAndError(t *testing.T) { + tracer := internal.NewMockTracer() + sc := &bridgeSpanContext{} + + testCases := []struct { + name string + opt []ot.StartSpanOption + expected trace.SpanKind + }{ + { + name: "client", + opt: []ot.StartSpanOption{ext.SpanKindRPCClient}, + expected: trace.SpanKindClient, + }, + { + name: "server", + opt: []ot.StartSpanOption{ext.RPCServerOption(sc)}, + expected: trace.SpanKindServer, + }, + { + name: "client string", + opt: []ot.StartSpanOption{ot.Tag{Key: "span.kind", Value: "client"}}, + expected: trace.SpanKindClient, + }, + { + name: "server string", + opt: []ot.StartSpanOption{ot.Tag{Key: "span.kind", Value: "server"}}, + expected: trace.SpanKindServer, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + b, _ := NewTracerPair(tracer) + + s := b.StartSpan(tc.name, tc.opt...) + assert.Equal(t, s.(*bridgeSpan).otelSpan.(*internal.MockSpan).SpanKind, tc.expected) + }) + } +} diff --git a/exporters/otlp/otlptrace/internal/tracetransform/instrumentation.go b/exporters/otlp/otlptrace/internal/tracetransform/instrumentation.go index 7aaec38d22a..4dcddb17809 100644 --- a/exporters/otlp/otlptrace/internal/tracetransform/instrumentation.go +++ b/exporters/otlp/otlptrace/internal/tracetransform/instrumentation.go @@ -24,7 +24,8 @@ func InstrumentationScope(il instrumentation.Scope) *commonpb.InstrumentationSco return nil } return &commonpb.InstrumentationScope{ - Name: il.Name, - Version: il.Version, + Name: il.Name, + Version: il.Version, + Attributes: Iterator(il.Attributes.Iter()), } } diff --git a/exporters/otlp/otlptrace/internal/tracetransform/instrumentation_test.go b/exporters/otlp/otlptrace/internal/tracetransform/instrumentation_test.go new file mode 100644 index 00000000000..634d1dfaf00 --- /dev/null +++ b/exporters/otlp/otlptrace/internal/tracetransform/instrumentation_test.go @@ -0,0 +1,52 @@ +// 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 tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform" + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/instrumentation" + commonpb "go.opentelemetry.io/proto/otlp/common/v1" +) + +func TestInstrumentationScope(t *testing.T) { + t.Run("Empty", func(t *testing.T) { + assert.Nil(t, InstrumentationScope(instrumentation.Scope{})) + }) + + t.Run("Mapping", func(t *testing.T) { + var ( + name = "instrumentation name" + version = "v0.1.0" + attr = attribute.NewSet(attribute.String("domain", "trace")) + attrPb = Iterator(attr.Iter()) + ) + expected := &commonpb.InstrumentationScope{ + Name: name, + Version: version, + Attributes: attrPb, + } + actual := InstrumentationScope(instrumentation.Scope{ + Name: name, + Version: version, + SchemaURL: "http://this.is.mapped.elsewhere.com", + Attributes: attr, + }) + assert.Equal(t, expected, actual) + }) +} diff --git a/exporters/stdout/stdoutmetric/example_test.go b/exporters/stdout/stdoutmetric/example_test.go index 8a2528661dd..6dcf163c47f 100644 --- a/exporters/stdout/stdoutmetric/example_test.go +++ b/exporters/stdout/stdoutmetric/example_test.go @@ -142,7 +142,9 @@ func Example() { // { // "Scope": { // "Name": "example", - // "Version": "v0.0.1" + // "Version": "v0.0.1", + // "SchemaURL": "", + // "Attributes": null // }, // "Metrics": [ // { diff --git a/exporters/stdout/stdouttrace/trace_test.go b/exporters/stdout/stdouttrace/trace_test.go index 8f056460cdb..82bd2fcabd7 100644 --- a/exporters/stdout/stdouttrace/trace_test.go +++ b/exporters/stdout/stdouttrace/trace_test.go @@ -185,7 +185,9 @@ func expectedJSON(now time.Time) string { ], "InstrumentationLibrary": { "Name": "", - "Version": "" + "Version": "", + "SchemaURL": "", + "Attributes": null } } ` diff --git a/metric/config.go b/metric/config.go index 621e4c5fcb8..ddadd62f9f9 100644 --- a/metric/config.go +++ b/metric/config.go @@ -14,10 +14,13 @@ package metric // import "go.opentelemetry.io/otel/metric" +import "go.opentelemetry.io/otel/attribute" + // MeterConfig contains options for Meters. type MeterConfig struct { instrumentationVersion string schemaURL string + attributes attribute.Set } // InstrumentationVersion is the version of the library providing instrumentation. @@ -30,6 +33,11 @@ func (cfg MeterConfig) SchemaURL() string { return cfg.schemaURL } +// Attributes returns the scope attribute set of the Meter. +func (t MeterConfig) Attributes() attribute.Set { + return t.attributes +} + // MeterOption is an interface for applying Meter options. type MeterOption interface { // applyMeter is used to set a MeterOption value of a MeterConfig. @@ -67,3 +75,13 @@ func WithSchemaURL(schemaURL string) MeterOption { return config }) } + +// WithScopeAttributes sets the attributes for the scope of a Meter. The +// attributes are stored as an attribute set. Duplicate values are removed, the +// last value is used. +func WithScopeAttributes(attr ...attribute.KeyValue) MeterOption { + return meterOptionFunc(func(cfg MeterConfig) MeterConfig { + cfg.attributes = attribute.NewSet(attr...) + return cfg + }) +} diff --git a/metric/config_test.go b/metric/config_test.go new file mode 100644 index 00000000000..359e14c0603 --- /dev/null +++ b/metric/config_test.go @@ -0,0 +1,69 @@ +// 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 metric // import "go.opentelemetry.io/otel/metric" + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/otel/attribute" +) + +func TestMeterConfig(t *testing.T) { + t.Run("Empty", func(t *testing.T) { + assert.Equal(t, NewMeterConfig(), MeterConfig{}) + }) + + t.Run("InstrumentationVersion", func(t *testing.T) { + v0, v1 := "v0.1.0", "v1.0.0" + + assert.Equal(t, NewMeterConfig( + WithInstrumentationVersion(v0), + ).InstrumentationVersion(), v0) + + assert.Equal(t, NewMeterConfig( + WithInstrumentationVersion(v0), + WithInstrumentationVersion(v1), + ).InstrumentationVersion(), v1, "last option has precedence") + }) + + t.Run("SchemaURL", func(t *testing.T) { + s120 := "https://opentelemetry.io/schemas/1.2.0" + s130 := "https://opentelemetry.io/schemas/1.3.0" + + assert.Equal(t, NewMeterConfig( + WithSchemaURL(s120), + ).SchemaURL(), s120) + + assert.Equal(t, NewMeterConfig( + WithSchemaURL(s120), + WithSchemaURL(s130), + ).SchemaURL(), s130, "last option has precedence") + }) + + t.Run("Attributes", func(t *testing.T) { + one, two := attribute.Int("key", 1), attribute.Int("key", 2) + + assert.Equal(t, NewMeterConfig( + WithScopeAttributes(one, two), + ).Attributes(), attribute.NewSet(two), "last attribute is used") + + assert.Equal(t, NewMeterConfig( + WithScopeAttributes(two), + WithScopeAttributes(one), + ).Attributes(), attribute.NewSet(one), "last option has precedence") + }) +} diff --git a/metric/meter.go b/metric/meter.go index 21fc1c499fb..b548405353c 100644 --- a/metric/meter.go +++ b/metric/meter.go @@ -24,15 +24,50 @@ import ( "go.opentelemetry.io/otel/metric/instrument/syncint64" ) -// MeterProvider provides access to named Meter instances, for instrumenting -// an application or library. +// MeterProvider provides Meters that are used by instrumentation code to +// create instruments that measure code operations. +// +// A MeterProvider is the collection destination of all measurements made from +// instruments the provided Meters created, it represents a unique telemetry +// collection pipeline. How that pipeline is defined, meaning how those +// measurements are collected, processed, and where they are exported, depends +// on its implementation. Instrumentation authors do not need to define this +// implementation, rather just use the provided Meters to instrument code. +// +// Commonly, instrumentation code will accept a MeterProvider implementation at +// runtime from its users or it can simply use the globally registered one (see +// https://pkg.go.dev/go.opentelemetry.io/otel/metric/global#MeterProvider). +// +// Warning: methods may be added to this interface in minor releases. type MeterProvider interface { - // Meter creates an instance of a `Meter` interface. The instrumentationName - // must be the name of the library providing instrumentation. This name may - // be the same as the instrumented code only if that code provides built-in - // instrumentation. If the instrumentationName is empty, then a - // implementation defined default name will be used instead. - Meter(instrumentationName string, opts ...MeterOption) Meter + // Meter returns a unique Meter scoped to be used by instrumentation code + // to measure code operations. The scope and identity of that + // instrumentation code is uniquely defined by the name and options passed. + // + // The passed name needs to uniquely identify instrumentation code. + // Therefore, it is recommended that name is the Go package name of the + // library providing instrumentation (note: not the code being + // instrumented). Instrumentation libraries can have multiple versions, + // therefore, the WithInstrumentationVersion option should be used to + // distinguish these different codebases. Additionally, instrumentation + // libraries may sometimes use metric measurements to communicate different + // domains of code operations data (i.e. using different Meters to + // communicate user experience and back-end operations). If this is the + // case, the WithScopeAttributes option should be used to uniquely identify + // Meters that handle the different domains of code operations data. + // + // If the same name and options are passed multiple times, the same Meter + // will be returned (it is up to the implementation if this will be the + // same underlying instance of that Meter or not). It is not necessary to + // call this multiple times with the same name and options to get an + // up-to-date Meter. All implementations will ensure any MeterProvider + // configuration changes are propagated to all provided Meters. + // + // If name is empty, then an implementation defined default name will be + // used instead. + // + // This method is safe to call concurrently. + Meter(name string, options ...MeterOption) Meter } // Meter provides access to instrument instances for recording metrics. diff --git a/sdk/instrumentation/doc.go b/sdk/instrumentation/doc.go new file mode 100644 index 00000000000..6e923acab43 --- /dev/null +++ b/sdk/instrumentation/doc.go @@ -0,0 +1,24 @@ +// 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 instrumentation provides types to represent the code libraries that +// provide OpenTelemetry instrumentation. These types are used in the +// OpenTelemetry signal pipelines to identify the source of telemetry. +// +// See +// https://github.com/open-telemetry/oteps/blob/d226b677d73a785523fe9b9701be13225ebc528d/text/0083-component.md +// and +// https://github.com/open-telemetry/oteps/blob/d226b677d73a785523fe9b9701be13225ebc528d/text/0201-scope-attributes.md +// for more information. +package instrumentation // import "go.opentelemetry.io/otel/sdk/instrumentation" diff --git a/sdk/instrumentation/library.go b/sdk/instrumentation/library.go index 246873345de..39f025a1715 100644 --- a/sdk/instrumentation/library.go +++ b/sdk/instrumentation/library.go @@ -12,13 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -/* -Package instrumentation provides an instrumentation library structure to be -passed to both the OpenTelemetry Tracer and Meter components. - -For more information see -[this](https://github.com/open-telemetry/oteps/blob/main/text/0083-component.md). -*/ package instrumentation // import "go.opentelemetry.io/otel/sdk/instrumentation" // Library represents the instrumentation library. diff --git a/sdk/instrumentation/scope.go b/sdk/instrumentation/scope.go index bbbed01c5b3..3001b6cc907 100644 --- a/sdk/instrumentation/scope.go +++ b/sdk/instrumentation/scope.go @@ -12,16 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -/* -Package instrumentation provides an instrumentation scope structure to be -passed to both the OpenTelemetry Tracer and Meter components. - -For more information see -[this](https://github.com/open-telemetry/oteps/blob/main/text/0083-component.md). -*/ package instrumentation // import "go.opentelemetry.io/otel/sdk/instrumentation" -// Scope represents the instrumentation scope. +import "go.opentelemetry.io/otel/attribute" + +// Scope represents the instrumentation source of OpenTelemetry data. +// +// Code that uses OpenTelemetry APIs or data-models to produce telemetry needs +// to be identifiable by the receiver of that data. A Scope is used for this +// purpose, it uniquely identifies that code as the source and the extent to +// which it is relevant. type Scope struct { // Name is the name of the instrumentation scope. This should be the // Go package name of that scope. @@ -29,5 +29,14 @@ type Scope struct { // Version is the version of the instrumentation scope. Version string // SchemaURL of the telemetry emitted by the scope. - SchemaURL string `json:",omitempty"` + SchemaURL string + // Attributes describe the unique attributes of an instrumentation scope. + // + // These attributes are used to differentiate an instrumentation scope when + // it emits data that belong to different domains. For example, if both + // profiling data and client-side data are emitted as log records from the + // same instrumentation library, they may need to be differentiated by a + // telemetry receiver. In that case, these attributes are used to scope and + // differentiate the data. + Attributes attribute.Set } diff --git a/sdk/trace/provider.go b/sdk/trace/provider.go index 3c8abb8c1aa..7498017903a 100644 --- a/sdk/trace/provider.go +++ b/sdk/trace/provider.go @@ -142,9 +142,10 @@ func (p *TracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.T name = defaultTracerName } is := instrumentation.Scope{ - Name: name, - Version: c.InstrumentationVersion(), - SchemaURL: c.SchemaURL(), + Name: name, + Version: c.InstrumentationVersion(), + SchemaURL: c.SchemaURL(), + Attributes: c.Attributes(), } t, ok := p.namedTracer[is] if !ok { @@ -153,7 +154,13 @@ func (p *TracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.T instrumentationScope: is, } p.namedTracer[is] = t - global.Info("Tracer created", "name", name, "version", c.InstrumentationVersion(), "schemaURL", c.SchemaURL()) + global.Info( + "Tracer created", + "name", name, + "version", c.InstrumentationVersion(), + "schemaURL", c.SchemaURL(), + "attributes", c.Attributes(), + ) } return t } @@ -241,10 +248,7 @@ func (p *TracerProvider) Shutdown(ctx context.Context) error { if !ok { return fmt.Errorf("failed to load span processors") } - if len(spss) == 0 { - return nil - } - + var retErr error for _, sps := range spss { select { case <-ctx.Done(): @@ -257,10 +261,15 @@ func (p *TracerProvider) Shutdown(ctx context.Context) error { err = sps.sp.Shutdown(ctx) }) if err != nil { - return err + if retErr == nil { + retErr = err + } else { + // Poor man's list of errors + retErr = fmt.Errorf("%v; %v", retErr, err) + } } } - return nil + return retErr } // TracerProviderOption configures a TracerProvider. diff --git a/sdk/trace/provider_test.go b/sdk/trace/provider_test.go index 39caf5a91df..6ec3df6794d 100644 --- a/sdk/trace/provider_test.go +++ b/sdk/trace/provider_test.go @@ -72,6 +72,28 @@ func TestFailedProcessorShutdown(t *testing.T) { assert.Equal(t, err, spErr) } +func TestFailedProcessorsShutdown(t *testing.T) { + stp := NewTracerProvider() + spErr1 := errors.New("basic span processor shutdown failure1") + spErr2 := errors.New("basic span processor shutdown failure2") + sp1 := &basicSpanProcesor{ + running: true, + injectShutdownError: spErr1, + } + sp2 := &basicSpanProcesor{ + running: true, + injectShutdownError: spErr2, + } + stp.RegisterSpanProcessor(sp1) + stp.RegisterSpanProcessor(sp2) + + err := stp.Shutdown(context.Background()) + assert.Error(t, err) + assert.EqualError(t, err, "basic span processor shutdown failure1; basic span processor shutdown failure2") + assert.False(t, sp1.running) + assert.False(t, sp2.running) +} + func TestFailedProcessorShutdownInUnregister(t *testing.T) { handler.Reset() stp := NewTracerProvider() diff --git a/sdk/trace/trace_test.go b/sdk/trace/trace_test.go index 8d95782115b..8badccc4240 100644 --- a/sdk/trace/trace_test.go +++ b/sdk/trace/trace_test.go @@ -363,6 +363,16 @@ func TestStartSpanWithParent(t *testing.T) { } } +// Test we get a successful span as a new root if a nil context is sent in, as opposed to a panic. +// See https://github.com/open-telemetry/opentelemetry-go/issues/3109 +func TestStartSpanWithNilContext(t *testing.T) { + tp := NewTracerProvider() + tr := tp.Tracer("NoPanic") + + // nolint:staticcheck // no nil context, but that's the point of the test. + assert.NotPanics(t, func() { tr.Start(nil, "should-not-panic") }) +} + func TestStartSpanNewRootNotSampled(t *testing.T) { alwaysSampleTp := NewTracerProvider() sampledTr := alwaysSampleTp.Tracer("AlwaysSampled") @@ -901,6 +911,8 @@ func TestSetSpanStatusWithoutMessageWhenStatusIsNotError(t *testing.T) { func cmpDiff(x, y interface{}) string { return cmp.Diff(x, y, cmp.AllowUnexported(snapshot{}), + cmp.AllowUnexported(attribute.Set{}), + cmp.AllowUnexported(attribute.Distinct{}), cmp.AllowUnexported(attribute.Value{}), cmp.AllowUnexported(Event{}), cmp.AllowUnexported(trace.TraceState{})) diff --git a/sdk/trace/tracer.go b/sdk/trace/tracer.go index f4a1f96f3d6..7b11fc465c6 100644 --- a/sdk/trace/tracer.go +++ b/sdk/trace/tracer.go @@ -37,6 +37,11 @@ var _ trace.Tracer = &tracer{} func (tr *tracer) Start(ctx context.Context, name string, options ...trace.SpanStartOption) (context.Context, trace.Span) { config := trace.NewSpanStartConfig(options...) + if ctx == nil { + // Prevent trace.ContextWithSpan from panicking. + ctx = context.Background() + } + // For local spans created by this SDK, track child span count. if p := trace.SpanFromContext(ctx); p != nil { if sdkSpan, ok := p.(*recordingSpan); ok { diff --git a/trace/config.go b/trace/config.go index f058cc781e0..a7b7eab2172 100644 --- a/trace/config.go +++ b/trace/config.go @@ -24,7 +24,8 @@ import ( type TracerConfig struct { instrumentationVersion string // Schema URL of the telemetry emitted by the Tracer. - schemaURL string + schemaURL string + attributes attribute.Set } // InstrumentationVersion returns the version of the library providing instrumentation. @@ -37,6 +38,11 @@ func (t *TracerConfig) SchemaURL() string { return t.schemaURL } +// Attributes returns the scope attribute set of the Tracer. +func (t *TracerConfig) Attributes() attribute.Set { + return t.attributes +} + // NewTracerConfig applies all the options to a returned TracerConfig. func NewTracerConfig(options ...TracerOption) TracerConfig { var config TracerConfig @@ -314,3 +320,13 @@ func WithSchemaURL(schemaURL string) TracerOption { return cfg }) } + +// WithScopeAttributes sets the attributes for the scope of a Tracer. The +// attributes are stored as an attribute set. Duplicate values are removed, the +// last value is used. +func WithScopeAttributes(attr ...attribute.KeyValue) TracerOption { + return tracerOptionFunc(func(cfg TracerConfig) TracerConfig { + cfg.attributes = attribute.NewSet(attr...) + return cfg + }) +} diff --git a/trace/config_test.go b/trace/config_test.go index a4cafcbcd09..d46a8e137c6 100644 --- a/trace/config_test.go +++ b/trace/config_test.go @@ -211,6 +211,7 @@ func TestTracerConfig(t *testing.T) { v1 := "semver:0.0.1" v2 := "semver:1.0.0" schemaURL := "https://opentelemetry.io/schemas/1.2.0" + one, two := attribute.Int("key", 1), attribute.Int("key", 2) tests := []struct { options []TracerOption expected TracerConfig @@ -246,6 +247,24 @@ func TestTracerConfig(t *testing.T) { schemaURL: schemaURL, }, }, + + { + []TracerOption{ + WithScopeAttributes(one, two), + }, + TracerConfig{ + attributes: attribute.NewSet(two), + }, + }, + { + []TracerOption{ + WithScopeAttributes(two), + WithScopeAttributes(one), + }, + TracerConfig{ + attributes: attribute.NewSet(one), + }, + }, } for _, test := range tests { config := NewTracerConfig(test.options...) diff --git a/trace/trace.go b/trace/trace.go index 3e009873219..97f3d83855b 100644 --- a/trace/trace.go +++ b/trace/trace.go @@ -503,17 +503,48 @@ type Tracer interface { Start(ctx context.Context, spanName string, opts ...SpanStartOption) (context.Context, Span) } -// TracerProvider provides access to instrumentation Tracers. +// TracerProvider provides Tracers that are used by instrumentation code to +// trace computational workflows. +// +// A TracerProvider is the collection destination of all Spans from Tracers it +// provides, it represents a unique telemetry collection pipeline. How that +// pipeline is defined, meaning how those Spans are collected, processed, and +// where they are exported, depends on its implementation. Instrumentation +// authors do not need to define this implementation, rather just use the +// provided Tracers to instrument code. +// +// Commonly, instrumentation code will accept a TracerProvider implementation +// at runtime from its users or it can simply use the globally registered one +// (see https://pkg.go.dev/go.opentelemetry.io/otel#GetTracerProvider). // // Warning: methods may be added to this interface in minor releases. type TracerProvider interface { - // Tracer creates an implementation of the Tracer interface. - // The instrumentationName must be the name of the library providing - // instrumentation. This name may be the same as the instrumented code - // only if that code provides built-in instrumentation. If the - // instrumentationName is empty, then a implementation defined default - // name will be used instead. + // Tracer returns a unique Tracer scoped to be used by instrumentation code + // to trace computational workflows. The scope and identity of that + // instrumentation code is uniquely defined by the name and options passed. + // + // The passed name needs to uniquely identify instrumentation code. + // Therefore, it is recommended that name is the Go package name of the + // library providing instrumentation (note: not the code being + // instrumented). Instrumentation libraries can have multiple versions, + // therefore, the WithInstrumentationVersion option should be used to + // distinguish these different codebases. Additionally, instrumentation + // libraries may sometimes use traces to communicate different domains of + // workflow data (i.e. using spans to communicate workflow events only). If + // this is the case, the WithScopeAttributes option should be used to + // uniquely identify Tracers that handle the different domains of workflow + // data. + // + // If the same name and options are passed multiple times, the same Tracer + // will be returned (it is up to the implementation if this will be the + // same underlying instance of that Tracer or not). It is not necessary to + // call this multiple times with the same name and options to get an + // up-to-date Tracer. All implementations will ensure any TracerProvider + // configuration changes are propagated to all provided Tracers. + // + // If name is empty, then an implementation defined default name will be + // used instead. // - // This method must be concurrency safe. - Tracer(instrumentationName string, opts ...TracerOption) Tracer + // This method is safe to call concurrently. + Tracer(name string, options ...TracerOption) Tracer } diff --git a/website_docs/_index.md b/website_docs/_index.md index a21cc50329d..a1ab8723779 100644 --- a/website_docs/_index.md +++ b/website_docs/_index.md @@ -19,7 +19,7 @@ This is the OpenTelemetry for Go documentation. OpenTelemetry is an observabilit The current status of the major functional components for OpenTelemetry Go is as follows: -| Tracing | Metrics | Logging | +| Traces | Metrics | Logs | | ------- | ------- | ------- | | Stable | Alpha | Not Yet Implemented |