From fd151925e2d2c32f6893696b961e0763c3ab0bcb Mon Sep 17 00:00:00 2001 From: Tigran Najaryan Date: Wed, 24 Jan 2024 20:39:09 -0500 Subject: [PATCH 01/11] Add Entity --- entity/entity.go | 225 ++++++++++++++++++ .../internal/tracetransform/resource.go | 9 +- sdk/resource/config.go | 19 ++ sdk/resource/resource.go | 79 ++++-- sdk/trace/provider.go | 109 +++++---- 5 files changed, 380 insertions(+), 61 deletions(-) create mode 100644 entity/entity.go diff --git a/entity/entity.go b/entity/entity.go new file mode 100644 index 00000000000..a48e06c6c21 --- /dev/null +++ b/entity/entity.go @@ -0,0 +1,225 @@ +// 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 entity // import "go.opentelemetry.io/otel/sdk/resource" + +import ( + "context" + "errors" + "sync" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" +) + +// Entity describes an entity about which identifying information +// and metadata is exposed. Entity is an immutable object, +// equivalent to a map from key to unique value. +// +// Resources should be passed and stored as pointers +// (`*resource.Entity`). The `nil` value is equivalent to an empty +// Entity. +type Entity struct { + attrs attribute.Set + schemaURL string +} + +var ( + defaultResource *Entity + defaultResourceOnce sync.Once +) + +var errMergeConflictSchemaURL = errors.New("cannot merge resource due to conflicting Schema URL") + +// New returns a Entity combined from the user-provided detectors. +func New(ctx context.Context, opts ...Option) (*Entity, error) { + cfg := config{} + for _, opt := range opts { + cfg = opt.apply(cfg) + } + + r := &Entity{schemaURL: cfg.schemaURL} + return r, detect(ctx, r, cfg.detectors) +} + +// NewWithAttributes creates a resource from attrs and associates the resource with a +// schema URL. If attrs contains duplicate keys, the last value will be used. If attrs +// contains any invalid items those items will be dropped. The attrs are assumed to be +// in a schema identified by schemaURL. +func NewWithAttributes(schemaURL string, attrs ...attribute.KeyValue) *Entity { + resource := NewSchemaless(attrs...) + resource.schemaURL = schemaURL + return resource +} + +// String implements the Stringer interface and provides a +// human-readable form of the resource. +// +// Avoid using this representation as the key in a map of resources, +// use Equivalent() as the key instead. +func (r *Entity) String() string { + if r == nil { + return "" + } + return r.attrs.Encoded(attribute.DefaultEncoder()) +} + +// MarshalLog is the marshaling function used by the logging system to represent this Entity. +func (r *Entity) MarshalLog() interface{} { + return struct { + Attributes attribute.Set + SchemaURL string + }{ + Attributes: r.attrs, + SchemaURL: r.schemaURL, + } +} + +// Attributes returns a copy of attributes from the resource in a sorted order. +// To avoid allocating a new slice, use an iterator. +func (r *Entity) Attributes() []attribute.KeyValue { + if r == nil { + r = Empty() + } + return r.attrs.ToSlice() +} + +// SchemaURL returns the schema URL associated with Entity r. +func (r *Entity) SchemaURL() string { + if r == nil { + return "" + } + return r.schemaURL +} + +// Equal returns true when a Entity is equivalent to this Entity. +func (r *Entity) Equal(eq *Entity) bool { + if r == nil { + r = Empty() + } + if eq == nil { + eq = Empty() + } + return r.Equivalent() == eq.Equivalent() +} + +// Merge creates a new resource by combining resource a and b. +// +// If there are common keys between resource a and b, then the value +// from resource b will overwrite the value from resource a, even +// if resource b's value is empty. +// +// The SchemaURL of the resources will be merged according to the spec rules: +// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/resource/sdk.md#merge +// If the resources have different non-empty schemaURL an empty resource and an error +// will be returned. +func Merge(a, b *Entity) (*Entity, error) { + if a == nil && b == nil { + return Empty(), nil + } + if a == nil { + return b, nil + } + if b == nil { + return a, nil + } + + // Merge the schema URL. + var schemaURL string + switch true { + case a.schemaURL == "": + schemaURL = b.schemaURL + case b.schemaURL == "": + schemaURL = a.schemaURL + case a.schemaURL == b.schemaURL: + schemaURL = a.schemaURL + default: + return Empty(), errMergeConflictSchemaURL + } + + // Note: 'b' attributes will overwrite 'a' with last-value-wins in attribute.Key() + // Meaning this is equivalent to: append(a.Attributes(), b.Attributes()...) + mi := attribute.NewMergeIterator(b.Set(), a.Set()) + combine := make([]attribute.KeyValue, 0, a.Len()+b.Len()) + for mi.Next() { + combine = append(combine, mi.Attribute()) + } + merged := NewWithAttributes(schemaURL, combine...) + return merged, nil +} + +// Empty returns an instance of Entity with no attributes. It is +// equivalent to a `nil` Entity. +func Empty() *Entity { + return &Entity{} +} + +// Default returns an instance of Entity with a default +// "service.name" and OpenTelemetrySDK attributes. +func Default() *Entity { + defaultResourceOnce.Do( + func() { + var err error + defaultResource, err = Detect( + context.Background(), + defaultServiceNameDetector{}, + fromEnv{}, + telemetrySDK{}, + ) + if err != nil { + otel.Handle(err) + } + // If Detect did not return a valid resource, fall back to emptyResource. + if defaultResource == nil { + defaultResource = &Entity{} + } + }, + ) + return defaultResource +} + +// Environment returns an instance of Entity with attributes +// extracted from the OTEL_RESOURCE_ATTRIBUTES environment variable. +func Environment() *Entity { + detector := &fromEnv{} + resource, err := detector.Detect(context.Background()) + if err != nil { + otel.Handle(err) + } + return resource +} + +// Equivalent returns an object that can be compared for equality +// between two resources. This value is suitable for use as a key in +// a map. +func (r *Entity) Equivalent() attribute.Distinct { + return r.Set().Equivalent() +} + +// MarshalJSON encodes the resource attributes as a JSON list of { "Key": +// "...", "Value": ... } pairs in order sorted by key. +func (r *Entity) MarshalJSON() ([]byte, error) { + if r == nil { + r = Empty() + } + return r.attrs.MarshalJSON() +} + +// Encoded returns an encoded representation of the resource. +func (r *Entity) Encoded(enc attribute.Encoder) string { + if r == nil { + return "" + } + return r.attrs.Encoded(enc) +} diff --git a/exporters/otlp/otlptrace/internal/tracetransform/resource.go b/exporters/otlp/otlptrace/internal/tracetransform/resource.go index 05a1f78adbc..47a8e4b0f61 100644 --- a/exporters/otlp/otlptrace/internal/tracetransform/resource.go +++ b/exporters/otlp/otlptrace/internal/tracetransform/resource.go @@ -15,8 +15,9 @@ package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform" import ( - "go.opentelemetry.io/otel/sdk/resource" resourcepb "go.opentelemetry.io/proto/otlp/resource/v1" + + "go.opentelemetry.io/otel/sdk/resource" ) // Resource transforms a Resource into an OTLP Resource. @@ -24,5 +25,11 @@ func Resource(r *resource.Resource) *resourcepb.Resource { if r == nil { return nil } + + entity := r.Entity() + entityId := Iterator(entity.IdIter()) + entityId = entityId + + // TODO: set entityId and entity.Type() in the Resource{} below. return &resourcepb.Resource{Attributes: ResourceAttributes(r)} } diff --git a/sdk/resource/config.go b/sdk/resource/config.go index f263919f6ec..613809f42d2 100644 --- a/sdk/resource/config.go +++ b/sdk/resource/config.go @@ -26,6 +26,9 @@ type config struct { detectors []Detector // SchemaURL to associate with the Resource. schemaURL string + + entityType string + entityId []attribute.KeyValue } // Option is the interface that applies a configuration option. @@ -93,6 +96,22 @@ func (o schemaURLOption) apply(cfg config) config { return cfg } +// WithEntity sets the schema URL for the configured resource. +func WithEntity(entityType string, entityId ...attribute.KeyValue) Option { + return entityOption{entityType, entityId} +} + +type entityOption struct { + entityType string + entityId []attribute.KeyValue +} + +func (o entityOption) apply(cfg config) config { + cfg.entityType = o.entityType + cfg.entityId = o.entityId + return cfg +} + // WithOS adds all the OS attributes to the configured Resource. // See individual WithOS* functions to configure specific attributes. func WithOS() Option { diff --git a/sdk/resource/resource.go b/sdk/resource/resource.go index 8c69ba25a4f..3d46f976e25 100644 --- a/sdk/resource/resource.go +++ b/sdk/resource/resource.go @@ -23,6 +23,23 @@ import ( "go.opentelemetry.io/otel/attribute" ) +type Entity struct { + // Defines the producing entity type of this resource, e.g "service", "k8s.pod", etc. + // Empty for legacy Resources that are not entity-aware. + typ string + // Set of attributes that identify the producing entity. + // Note that the identifying attributes may be also recorded in the "attributes" field. + id attribute.Set +} + +func (e *Entity) Type() string { + return e.typ +} + +func (e *Entity) IdIter() attribute.Iterator { + return e.id.Iter() +} + // Resource describes an entity about which identifying information // and metadata is exposed. Resource is an immutable object, // equivalent to a map from key to unique value. @@ -33,6 +50,8 @@ import ( type Resource struct { attrs attribute.Set schemaURL string + + entity Entity } var ( @@ -49,7 +68,16 @@ func New(ctx context.Context, opts ...Option) (*Resource, error) { cfg = opt.apply(cfg) } - r := &Resource{schemaURL: cfg.schemaURL} + entityId, _ := attribute.NewSetWithFiltered( + cfg.entityId, func(kv attribute.KeyValue) bool { + return kv.Valid() + }, + ) + + r := &Resource{ + schemaURL: cfg.schemaURL, + entity: Entity{typ: cfg.entityType, id: entityId}, + } return r, detect(ctx, r, cfg.detectors) } @@ -74,9 +102,11 @@ func NewSchemaless(attrs ...attribute.KeyValue) *Resource { // Ensure attributes comply with the specification: // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/common/README.md#attribute - s, _ := attribute.NewSetWithFiltered(attrs, func(kv attribute.KeyValue) bool { - return kv.Valid() - }) + s, _ := attribute.NewSetWithFiltered( + attrs, func(kv attribute.KeyValue) bool { + return kv.Valid() + }, + ) // If attrs only contains invalid entries do not allocate a new resource. if s.Len() == 0 { @@ -126,6 +156,13 @@ func (r *Resource) SchemaURL() string { return r.schemaURL } +func (r *Resource) Entity() Entity { + if r == nil { + return Entity{} + } + return r.entity +} + // Iter returns an iterator of the Resource attributes. // This is ideal to use if you do not want a copy of the attributes. func (r *Resource) Iter() attribute.Iterator { @@ -200,22 +237,24 @@ func Empty() *Resource { // Default returns an instance of Resource with a default // "service.name" and OpenTelemetrySDK attributes. func Default() *Resource { - defaultResourceOnce.Do(func() { - var err error - defaultResource, err = Detect( - context.Background(), - defaultServiceNameDetector{}, - fromEnv{}, - telemetrySDK{}, - ) - if err != nil { - otel.Handle(err) - } - // If Detect did not return a valid resource, fall back to emptyResource. - if defaultResource == nil { - defaultResource = &Resource{} - } - }) + defaultResourceOnce.Do( + func() { + var err error + defaultResource, err = Detect( + context.Background(), + defaultServiceNameDetector{}, + fromEnv{}, + telemetrySDK{}, + ) + if err != nil { + otel.Handle(err) + } + // If Detect did not return a valid resource, fall back to emptyResource. + if defaultResource == nil { + defaultResource = &Resource{} + } + }, + ) return defaultResource } diff --git a/sdk/trace/provider.go b/sdk/trace/provider.go index b1ac608464a..108d50e65c0 100644 --- a/sdk/trace/provider.go +++ b/sdk/trace/provider.go @@ -233,11 +233,13 @@ func (p *TracerProvider) UnregisterSpanProcessor(sp SpanProcessor) { } } if stopOnce != nil { - stopOnce.state.Do(func() { - if err := sp.Shutdown(context.Background()); err != nil { - otel.Handle(err) - } - }) + 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:]) @@ -294,9 +296,11 @@ func (p *TracerProvider) Shutdown(ctx context.Context) error { } var err error - sps.state.Do(func() { - err = sps.sp.Shutdown(ctx) - }) + sps.state.Do( + func() { + err = sps.sp.Shutdown(ctx) + }, + ) if err != nil { if retErr == nil { retErr = err @@ -345,10 +349,12 @@ func WithBatcher(e SpanExporter, opts ...BatchSpanProcessorOption) TracerProvide // WithSpanProcessor registers the SpanProcessor with a TracerProvider. func WithSpanProcessor(sp SpanProcessor) TracerProviderOption { - return traceProviderOptionFunc(func(cfg tracerProviderConfig) tracerProviderConfig { - cfg.processors = append(cfg.processors, sp) - return cfg - }) + return traceProviderOptionFunc( + func(cfg tracerProviderConfig) tracerProviderConfig { + cfg.processors = append(cfg.processors, sp) + return cfg + }, + ) } // WithResource returns a TracerProviderOption that will configure the @@ -359,14 +365,29 @@ func WithSpanProcessor(sp SpanProcessor) TracerProviderOption { // If this option is not used, the TracerProvider will use the // resource.Default() Resource by default. 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 - }) + 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 WithEntity(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 + }, + ) } // WithIDGenerator returns a TracerProviderOption that will configure the @@ -377,12 +398,14 @@ func WithResource(r *resource.Resource) TracerProviderOption { // If this option is not used, the TracerProvider will use a random number // IDGenerator by default. func WithIDGenerator(g IDGenerator) TracerProviderOption { - return traceProviderOptionFunc(func(cfg tracerProviderConfig) tracerProviderConfig { - if g != nil { - cfg.idGenerator = g - } - return cfg - }) + return traceProviderOptionFunc( + func(cfg tracerProviderConfig) tracerProviderConfig { + if g != nil { + cfg.idGenerator = g + } + return cfg + }, + ) } // WithSampler returns a TracerProviderOption that will configure the Sampler @@ -396,12 +419,14 @@ func WithIDGenerator(g IDGenerator) TracerProviderOption { // contains invalid/unsupported configuration, the TracerProvider will use a // ParentBased(AlwaysSample) Sampler by default. func WithSampler(s Sampler) TracerProviderOption { - return traceProviderOptionFunc(func(cfg tracerProviderConfig) tracerProviderConfig { - if s != nil { - cfg.sampler = s - } - return cfg - }) + return traceProviderOptionFunc( + func(cfg tracerProviderConfig) tracerProviderConfig { + if s != nil { + cfg.sampler = s + } + return cfg + }, + ) } // WithSpanLimits returns a TracerProviderOption that configures a @@ -438,10 +463,12 @@ func WithSpanLimits(sl SpanLimits) TracerProviderOption { if sl.AttributePerLinkCountLimit <= 0 { sl.AttributePerLinkCountLimit = DefaultAttributePerLinkCountLimit } - return traceProviderOptionFunc(func(cfg tracerProviderConfig) tracerProviderConfig { - cfg.spanLimits = sl - return cfg - }) + return traceProviderOptionFunc( + func(cfg tracerProviderConfig) tracerProviderConfig { + cfg.spanLimits = sl + return cfg + }, + ) } // WithRawSpanLimits returns a TracerProviderOption that configures a @@ -460,10 +487,12 @@ func WithSpanLimits(sl SpanLimits) TracerProviderOption { // limits defined by environment variables, or the defaults if unset. Refer to // the NewSpanLimits documentation for information about this relationship. func WithRawSpanLimits(limits SpanLimits) TracerProviderOption { - return traceProviderOptionFunc(func(cfg tracerProviderConfig) tracerProviderConfig { - cfg.spanLimits = limits - return cfg - }) + return traceProviderOptionFunc( + func(cfg tracerProviderConfig) tracerProviderConfig { + cfg.spanLimits = limits + return cfg + }, + ) } func applyTracerProviderEnvConfigs(cfg tracerProviderConfig) tracerProviderConfig { From 77e60bf19542489bfea8a8901cfdaa5182a8a81f Mon Sep 17 00:00:00 2001 From: Tigran Najaryan Date: Fri, 9 Feb 2024 16:23:25 -0500 Subject: [PATCH 02/11] Encode Entity in JSON console output --- attribute/set.go | 15 ++++++++-- exporters/otlp/otlptrace/go.mod | 2 ++ .../internal/tracetransform/resource.go | 9 ++++-- sdk/resource/resource.go | 29 ++++++++++++++++--- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/attribute/set.go b/attribute/set.go index fb6da51450c..7cb3e720dde 100644 --- a/attribute/set.go +++ b/attribute/set.go @@ -113,9 +113,11 @@ func (l *Set) Value(k Key) (Value, bool) { rValue := l.equivalent.reflectValue() vlen := rValue.Len() - idx := sort.Search(vlen, func(idx int) bool { - return rValue.Index(idx).Interface().(KeyValue).Key >= k - }) + idx := sort.Search( + vlen, func(idx int) bool { + return rValue.Index(idx).Interface().(KeyValue).Key >= k + }, + ) if idx >= vlen { return Value{}, false } @@ -427,6 +429,13 @@ func (l *Set) MarshalJSON() ([]byte, error) { return json.Marshal(l.equivalent.iface) } +// MarshalableToJSON returns a value that is marshalable to JSON form. +// The returned value can be passed as an argument to json.Marshal() or +// be placed in any other structure that itself is passed to json.Marshal(). +func (l *Set) MarshalableToJSON() any { + return l.equivalent.iface +} + // MarshalLog is the marshaling function used by the logging system to represent this Set. func (l Set) MarshalLog() interface{} { kvs := make(map[string]string) diff --git a/exporters/otlp/otlptrace/go.mod b/exporters/otlp/otlptrace/go.mod index 11da29b0356..4059de9b70d 100644 --- a/exporters/otlp/otlptrace/go.mod +++ b/exporters/otlp/otlptrace/go.mod @@ -32,3 +32,5 @@ replace go.opentelemetry.io/otel/sdk => ../../../sdk replace go.opentelemetry.io/otel/trace => ../../../trace replace go.opentelemetry.io/otel/metric => ../../../metric + +replace go.opentelemetry.io/proto/otlp => ../../../../opentelemetry-proto-go/otlp/ diff --git a/exporters/otlp/otlptrace/internal/tracetransform/resource.go b/exporters/otlp/otlptrace/internal/tracetransform/resource.go index 47a8e4b0f61..fa3cd5720b8 100644 --- a/exporters/otlp/otlptrace/internal/tracetransform/resource.go +++ b/exporters/otlp/otlptrace/internal/tracetransform/resource.go @@ -28,8 +28,11 @@ func Resource(r *resource.Resource) *resourcepb.Resource { entity := r.Entity() entityId := Iterator(entity.IdIter()) - entityId = entityId - // TODO: set entityId and entity.Type() in the Resource{} below. - return &resourcepb.Resource{Attributes: ResourceAttributes(r)} + return &resourcepb.Resource{ + Attributes: ResourceAttributes(r), + DroppedAttributesCount: 0, + EntityType: entity.Type(), + EntityId: entityId, + } } diff --git a/sdk/resource/resource.go b/sdk/resource/resource.go index 3d46f976e25..26e75e6fa81 100644 --- a/sdk/resource/resource.go +++ b/sdk/resource/resource.go @@ -16,6 +16,7 @@ package resource // import "go.opentelemetry.io/otel/sdk/resource" import ( "context" + "encoding/json" "errors" "sync" @@ -156,11 +157,11 @@ func (r *Resource) SchemaURL() string { return r.schemaURL } -func (r *Resource) Entity() Entity { +func (r *Resource) Entity() *Entity { if r == nil { - return Entity{} + return &Entity{} } - return r.entity + return &r.entity } // Iter returns an iterator of the Resource attributes. @@ -290,7 +291,27 @@ func (r *Resource) MarshalJSON() ([]byte, error) { if r == nil { r = Empty() } - return r.attrs.MarshalJSON() + + rjson := struct { + Attributes any + SchemaURL string + Entity struct { + Type string + Id any + } + }{ + Attributes: r.attrs.MarshalableToJSON(), + SchemaURL: r.schemaURL, + Entity: struct { + Type string + Id any + }{ + Type: r.entity.Type(), + Id: r.entity.id.MarshalableToJSON(), + }, + } + + return json.Marshal(rjson) } // Len returns the number of unique key-values in this Resource. From 55b13cca495482589dbfa75e941140b20cfec858 Mon Sep 17 00:00:00 2001 From: Tigran Najaryan Date: Fri, 9 Feb 2024 16:36:06 -0500 Subject: [PATCH 03/11] Use Entity in builtin string detector --- sdk/resource/builtin.go | 21 ++++++++++++++++----- sdk/resource/resource.go | 23 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/sdk/resource/builtin.go b/sdk/resource/builtin.go index 6a2c08293a8..0387c362cc1 100644 --- a/sdk/resource/builtin.go +++ b/sdk/resource/builtin.go @@ -41,9 +41,10 @@ type ( host struct{} stringDetector struct { - schemaURL string - K attribute.Key - F func() (string, error) + schemaURL string + K attribute.Key + F func() (string, error) + entityType string } defaultServiceNameDetector struct{} @@ -78,6 +79,14 @@ func StringDetector(schemaURL string, k attribute.Key, f func() (string, error)) return stringDetector{schemaURL: schemaURL, K: k, F: f} } +// StringDetectorWithEntity returns a Detector that will produce a *Resource +// containing the string as a value corresponding to k. The Id of entity of the +// resource will also be set to the same key/value pair. +// The resulting Resource will have the specified schemaURL. +func StringDetectorWithEntity(schemaURL string, entityType string, k attribute.Key, f func() (string, error)) Detector { + return stringDetector{schemaURL: schemaURL, K: k, F: f, entityType: entityType} +} + // Detect returns a *Resource that describes the string as a value // corresponding to attribute.Key as well as the specific schemaURL. func (sd stringDetector) Detect(ctx context.Context) (*Resource, error) { @@ -89,13 +98,15 @@ func (sd stringDetector) Detect(ctx context.Context) (*Resource, error) { if !a.Valid() { return nil, fmt.Errorf("invalid attribute: %q -> %q", a.Key, a.Value.Emit()) } - return NewWithAttributes(sd.schemaURL, sd.K.String(value)), nil + attrs := []attribute.KeyValue{sd.K.String(value)} + return NewWithEntity(sd.schemaURL, sd.entityType, attrs, attrs), nil } // Detect implements Detector. func (defaultServiceNameDetector) Detect(ctx context.Context) (*Resource, error) { - return StringDetector( + return StringDetectorWithEntity( semconv.SchemaURL, + "service", semconv.ServiceNameKey, func() (string, error) { executable, err := os.Executable() diff --git a/sdk/resource/resource.go b/sdk/resource/resource.go index 26e75e6fa81..f784a4ca44e 100644 --- a/sdk/resource/resource.go +++ b/sdk/resource/resource.go @@ -92,6 +92,29 @@ func NewWithAttributes(schemaURL string, attrs ...attribute.KeyValue) *Resource return resource } +// NewWithEntity creates a resource from entity and attrs and associates the resource with a +// schema URL. If attrs or entityId contains duplicate keys, the last value will be used. If attrs or entityId +// contains any invalid items those items will be dropped. The attrs and entityId are assumed to be +// in a schema identified by schemaURL. +func NewWithEntity( + schemaURL string, entityType string, entityId []attribute.KeyValue, attrs []attribute.KeyValue, +) *Resource { + resource := NewSchemaless(attrs...) + resource.schemaURL = schemaURL + resource.entity.typ = entityType + + // Ensure attributes comply with the specification: + // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/common/README.md#attribute + id, _ := attribute.NewSetWithFiltered( + entityId, func(kv attribute.KeyValue) bool { + return kv.Valid() + }, + ) + + resource.entity.id = id + return resource +} + // NewSchemaless creates a resource from attrs. If attrs contains duplicate keys, // the last value will be used. If attrs contains any invalid items those items will // be dropped. The resource will not be associated with a schema URL. If the schema From d71d7741669a624d6c6533ebaddd3936cec5ed57 Mon Sep 17 00:00:00 2001 From: Tigran Najaryan Date: Fri, 9 Feb 2024 16:59:18 -0500 Subject: [PATCH 04/11] Implement entity merging --- entity/entity.go | 225 ---------------------- sdk/resource/resource.go | 41 +++- sdk/resource/resource_test.go | 343 ++++++++++++++++++++-------------- 3 files changed, 243 insertions(+), 366 deletions(-) delete mode 100644 entity/entity.go diff --git a/entity/entity.go b/entity/entity.go deleted file mode 100644 index a48e06c6c21..00000000000 --- a/entity/entity.go +++ /dev/null @@ -1,225 +0,0 @@ -// 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 entity // import "go.opentelemetry.io/otel/sdk/resource" - -import ( - "context" - "errors" - "sync" - - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" -) - -// Entity describes an entity about which identifying information -// and metadata is exposed. Entity is an immutable object, -// equivalent to a map from key to unique value. -// -// Resources should be passed and stored as pointers -// (`*resource.Entity`). The `nil` value is equivalent to an empty -// Entity. -type Entity struct { - attrs attribute.Set - schemaURL string -} - -var ( - defaultResource *Entity - defaultResourceOnce sync.Once -) - -var errMergeConflictSchemaURL = errors.New("cannot merge resource due to conflicting Schema URL") - -// New returns a Entity combined from the user-provided detectors. -func New(ctx context.Context, opts ...Option) (*Entity, error) { - cfg := config{} - for _, opt := range opts { - cfg = opt.apply(cfg) - } - - r := &Entity{schemaURL: cfg.schemaURL} - return r, detect(ctx, r, cfg.detectors) -} - -// NewWithAttributes creates a resource from attrs and associates the resource with a -// schema URL. If attrs contains duplicate keys, the last value will be used. If attrs -// contains any invalid items those items will be dropped. The attrs are assumed to be -// in a schema identified by schemaURL. -func NewWithAttributes(schemaURL string, attrs ...attribute.KeyValue) *Entity { - resource := NewSchemaless(attrs...) - resource.schemaURL = schemaURL - return resource -} - -// String implements the Stringer interface and provides a -// human-readable form of the resource. -// -// Avoid using this representation as the key in a map of resources, -// use Equivalent() as the key instead. -func (r *Entity) String() string { - if r == nil { - return "" - } - return r.attrs.Encoded(attribute.DefaultEncoder()) -} - -// MarshalLog is the marshaling function used by the logging system to represent this Entity. -func (r *Entity) MarshalLog() interface{} { - return struct { - Attributes attribute.Set - SchemaURL string - }{ - Attributes: r.attrs, - SchemaURL: r.schemaURL, - } -} - -// Attributes returns a copy of attributes from the resource in a sorted order. -// To avoid allocating a new slice, use an iterator. -func (r *Entity) Attributes() []attribute.KeyValue { - if r == nil { - r = Empty() - } - return r.attrs.ToSlice() -} - -// SchemaURL returns the schema URL associated with Entity r. -func (r *Entity) SchemaURL() string { - if r == nil { - return "" - } - return r.schemaURL -} - -// Equal returns true when a Entity is equivalent to this Entity. -func (r *Entity) Equal(eq *Entity) bool { - if r == nil { - r = Empty() - } - if eq == nil { - eq = Empty() - } - return r.Equivalent() == eq.Equivalent() -} - -// Merge creates a new resource by combining resource a and b. -// -// If there are common keys between resource a and b, then the value -// from resource b will overwrite the value from resource a, even -// if resource b's value is empty. -// -// The SchemaURL of the resources will be merged according to the spec rules: -// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/resource/sdk.md#merge -// If the resources have different non-empty schemaURL an empty resource and an error -// will be returned. -func Merge(a, b *Entity) (*Entity, error) { - if a == nil && b == nil { - return Empty(), nil - } - if a == nil { - return b, nil - } - if b == nil { - return a, nil - } - - // Merge the schema URL. - var schemaURL string - switch true { - case a.schemaURL == "": - schemaURL = b.schemaURL - case b.schemaURL == "": - schemaURL = a.schemaURL - case a.schemaURL == b.schemaURL: - schemaURL = a.schemaURL - default: - return Empty(), errMergeConflictSchemaURL - } - - // Note: 'b' attributes will overwrite 'a' with last-value-wins in attribute.Key() - // Meaning this is equivalent to: append(a.Attributes(), b.Attributes()...) - mi := attribute.NewMergeIterator(b.Set(), a.Set()) - combine := make([]attribute.KeyValue, 0, a.Len()+b.Len()) - for mi.Next() { - combine = append(combine, mi.Attribute()) - } - merged := NewWithAttributes(schemaURL, combine...) - return merged, nil -} - -// Empty returns an instance of Entity with no attributes. It is -// equivalent to a `nil` Entity. -func Empty() *Entity { - return &Entity{} -} - -// Default returns an instance of Entity with a default -// "service.name" and OpenTelemetrySDK attributes. -func Default() *Entity { - defaultResourceOnce.Do( - func() { - var err error - defaultResource, err = Detect( - context.Background(), - defaultServiceNameDetector{}, - fromEnv{}, - telemetrySDK{}, - ) - if err != nil { - otel.Handle(err) - } - // If Detect did not return a valid resource, fall back to emptyResource. - if defaultResource == nil { - defaultResource = &Entity{} - } - }, - ) - return defaultResource -} - -// Environment returns an instance of Entity with attributes -// extracted from the OTEL_RESOURCE_ATTRIBUTES environment variable. -func Environment() *Entity { - detector := &fromEnv{} - resource, err := detector.Detect(context.Background()) - if err != nil { - otel.Handle(err) - } - return resource -} - -// Equivalent returns an object that can be compared for equality -// between two resources. This value is suitable for use as a key in -// a map. -func (r *Entity) Equivalent() attribute.Distinct { - return r.Set().Equivalent() -} - -// MarshalJSON encodes the resource attributes as a JSON list of { "Key": -// "...", "Value": ... } pairs in order sorted by key. -func (r *Entity) MarshalJSON() ([]byte, error) { - if r == nil { - r = Empty() - } - return r.attrs.MarshalJSON() -} - -// Encoded returns an encoded representation of the resource. -func (r *Entity) Encoded(enc attribute.Encoder) string { - if r == nil { - return "" - } - return r.attrs.Encoded(enc) -} diff --git a/sdk/resource/resource.go b/sdk/resource/resource.go index f784a4ca44e..fad06084830 100644 --- a/sdk/resource/resource.go +++ b/sdk/resource/resource.go @@ -137,7 +137,7 @@ func NewSchemaless(attrs ...attribute.KeyValue) *Resource { return &Resource{} } - return &Resource{attrs: s} //nolint + return &Resource{attrs: s, entity: Entity{id: attribute.NewSet()}} //nolint } // String implements the Stringer interface and provides a @@ -243,13 +243,46 @@ func Merge(a, b *Resource) (*Resource, error) { // Note: 'b' attributes will overwrite 'a' with last-value-wins in attribute.Key() // Meaning this is equivalent to: append(a.Attributes(), b.Attributes()...) - mi := attribute.NewMergeIterator(b.Set(), a.Set()) + combineAttrs := mergeAttrs(b.Set(), a.Set()) + + var combineEntityId []attribute.KeyValue + + var entityType string + if a.entity.typ == b.entity.typ { + entityType = a.entity.typ + combineEntityId = mergeAttrs(&b.entity.id, &a.entity.id) + } else { + if a.entity.typ == "" { + entityType = b.entity.typ + combineEntityId = b.entity.id.ToSlice() + } else if b.entity.typ == "" { + entityType = a.entity.typ + combineEntityId = a.entity.id.ToSlice() + } else { + // Different non-empty entities. + combineEntityId = a.entity.id.ToSlice() + // TODO: merge the id of the updating Entity into the non-identifying + // attributes of the old Resource, attributes from the updating Entity + // take precedence. + panic("not implemented") + } + } + + merged := NewWithEntity(schemaURL, entityType, combineEntityId, combineAttrs) + return merged, nil +} + +func mergeAttrs(a, b *attribute.Set) []attribute.KeyValue { + if a.Len()+b.Len() == 0 { + return nil + } + + mi := attribute.NewMergeIterator(a, b) combine := make([]attribute.KeyValue, 0, a.Len()+b.Len()) for mi.Next() { combine = append(combine, mi.Attribute()) } - merged := NewWithAttributes(schemaURL, combine...) - return merged, nil + return combine } // Empty returns an instance of Resource with no attributes. It is diff --git a/sdk/resource/resource_test.go b/sdk/resource/resource_test.go index baed4a31336..8ba400f0f25 100644 --- a/sdk/resource/resource_test.go +++ b/sdk/resource/resource_test.go @@ -67,15 +67,18 @@ func TestNewWithAttributes(t *testing.T) { }, } for _, c := range cases { - t.Run(fmt.Sprintf("case-%s", c.name), func(t *testing.T) { - res := resource.NewSchemaless(c.in...) - if diff := cmp.Diff( - res.Attributes(), - c.want, - cmp.AllowUnexported(attribute.Value{})); diff != "" { - t.Fatalf("unwanted result: diff %+v,", diff) - } - }) + t.Run( + fmt.Sprintf("case-%s", c.name), func(t *testing.T) { + res := resource.NewSchemaless(c.in...) + if diff := cmp.Diff( + res.Attributes(), + c.want, + cmp.AllowUnexported(attribute.Value{}), + ); diff != "" { + t.Fatalf("unwanted result: diff %+v,", diff) + } + }, + ) } } @@ -188,21 +191,24 @@ func TestMerge(t *testing.T) { }, } for _, c := range cases { - t.Run(fmt.Sprintf("case-%s", c.name), func(t *testing.T) { - res, err := resource.Merge(c.a, c.b) - if c.isErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - assert.EqualValues(t, c.schemaURL, res.SchemaURL()) - if diff := cmp.Diff( - res.Attributes(), - c.want, - cmp.AllowUnexported(attribute.Value{})); diff != "" { - t.Fatalf("unwanted result: diff %+v,", diff) - } - }) + t.Run( + fmt.Sprintf("case-%s", c.name), func(t *testing.T) { + res, err := resource.Merge(c.a, c.b) + if c.isErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.EqualValues(t, c.schemaURL, res.SchemaURL()) + if diff := cmp.Diff( + res.Attributes(), + c.want, + cmp.AllowUnexported(attribute.Value{}), + ); diff != "" { + t.Fatalf("unwanted result: diff %+v,", diff) + } + }, + ) } } @@ -224,8 +230,10 @@ func TestDefault(t *testing.T) { serviceName, _ := res.Set().Value(semconv.ServiceNameKey) require.True(t, strings.HasPrefix(serviceName.AsString(), "unknown_service:")) - require.Greaterf(t, len(serviceName.AsString()), len("unknown_service:"), - "default service.name should include executable name") + require.Greaterf( + t, len(serviceName.AsString()), len("unknown_service:"), + "default service.name should include executable name", + ) require.Contains(t, res.Attributes(), semconv.TelemetrySDKLanguageGo) require.Contains(t, res.Attributes(), semconv.TelemetrySDKVersion(sdk.Version())) @@ -310,9 +318,11 @@ func TestMarshalJSON(t *testing.T) { r := resource.NewSchemaless(attribute.Int64("A", 1), attribute.String("C", "D")) data, err := json.Marshal(r) require.NoError(t, err) - require.Equal(t, - `[{"Key":"A","Value":{"Type":"INT64","Value":1}},{"Key":"C","Value":{"Type":"STRING","Value":"D"}}]`, - string(data)) + require.Equal( + t, + `{"Attributes":[{"Key":"A","Value":{"Type":"INT64","Value":1}},{"Key":"C","Value":{"Type":"STRING","Value":"D"}}],"SchemaURL":"","Entity":{"Type":"","Id":[]}}`, + string(data), + ) } func TestNew(t *testing.T) { @@ -416,7 +426,10 @@ func TestNew(t *testing.T) { options: []resource.Option{ resource.WithDetectors( resource.StringDetector("https://opentelemetry.io/schemas/1.0.0", semconv.HostNameKey, os.Hostname), - resource.StringDetector("https://opentelemetry.io/schemas/1.1.0", semconv.HostNameKey, func() (string, error) { return "", errors.New("fail") }), + resource.StringDetector( + "https://opentelemetry.io/schemas/1.1.0", semconv.HostNameKey, + func() (string, error) { return "", errors.New("fail") }, + ), ), resource.WithSchemaURL("https://opentelemetry.io/schemas/1.2.0"), }, @@ -426,30 +439,34 @@ func TestNew(t *testing.T) { }, } for _, tt := range tc { - t.Run(tt.name, func(t *testing.T) { - store, err := ottest.SetEnvVariables(map[string]string{ - envVar: tt.envars, - }) - require.NoError(t, err) - defer func() { require.NoError(t, store.Restore()) }() - - ctx := context.Background() - res, err := resource.New(ctx, tt.options...) - - if tt.isErr { - require.Error(t, err) - } else { + t.Run( + tt.name, func(t *testing.T) { + store, err := ottest.SetEnvVariables( + map[string]string{ + envVar: tt.envars, + }, + ) require.NoError(t, err) - } + defer func() { require.NoError(t, store.Restore()) }() - require.EqualValues(t, tt.resourceValues, toMap(res)) + ctx := context.Background() + res, err := resource.New(ctx, tt.options...) - // TODO: do we need to ensure that resource is never nil and eliminate the - // following if? - if res != nil { - assert.EqualValues(t, tt.schemaURL, res.SchemaURL()) - } - }) + if tt.isErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + require.EqualValues(t, tt.resourceValues, toMap(res)) + + // TODO: do we need to ensure that resource is never nil and eliminate the + // following if? + if res != nil { + assert.EqualValues(t, tt.schemaURL, res.SchemaURL()) + } + }, + ) } } @@ -458,12 +475,16 @@ func TestNewWrapedError(t *testing.T) { _, err := resource.New( context.Background(), resource.WithDetectors( - resource.StringDetector("", "", func() (string, error) { - return "", localErr - }), - resource.StringDetector("", "", func() (string, error) { - return "", assert.AnError - }), + resource.StringDetector( + "", "", func() (string, error) { + return "", localErr + }, + ), + resource.StringDetector( + "", "", func() (string, error) { + return "", assert.AnError + }, + ), ), ) @@ -478,14 +499,17 @@ func TestWithHostID(t *testing.T) { ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithHostID(), ) require.NoError(t, err) - require.EqualValues(t, map[string]string{ - "host.id": "f2c668b579780554f70f72a063dc0864", - }, toMap(res)) + require.EqualValues( + t, map[string]string{ + "host.id": "f2c668b579780554f70f72a063dc0864", + }, toMap(res), + ) } func TestWithHostIDError(t *testing.T) { @@ -494,7 +518,8 @@ func TestWithHostIDError(t *testing.T) { ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithHostID(), ) @@ -508,14 +533,17 @@ func TestWithOSType(t *testing.T) { ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithOSType(), ) require.NoError(t, err) - require.EqualValues(t, map[string]string{ - "os.type": "linux", - }, toMap(res)) + require.EqualValues( + t, map[string]string{ + "os.type": "linux", + }, toMap(res), + ) } func TestWithOSDescription(t *testing.T) { @@ -524,14 +552,17 @@ func TestWithOSDescription(t *testing.T) { ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithOSDescription(), ) require.NoError(t, err) - require.EqualValues(t, map[string]string{ - "os.description": "Test", - }, toMap(res)) + require.EqualValues( + t, map[string]string{ + "os.description": "Test", + }, toMap(res), + ) } func TestWithOS(t *testing.T) { @@ -540,148 +571,178 @@ func TestWithOS(t *testing.T) { ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithOS(), ) require.NoError(t, err) - require.EqualValues(t, map[string]string{ - "os.type": "linux", - "os.description": "Test", - }, toMap(res)) + require.EqualValues( + t, map[string]string{ + "os.type": "linux", + "os.description": "Test", + }, toMap(res), + ) } func TestWithProcessPID(t *testing.T) { mockProcessAttributesProvidersWithErrors() ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithProcessPID(), ) require.NoError(t, err) - require.EqualValues(t, map[string]string{ - "process.pid": fmt.Sprint(fakePID), - }, toMap(res)) + require.EqualValues( + t, map[string]string{ + "process.pid": fmt.Sprint(fakePID), + }, toMap(res), + ) } func TestWithProcessExecutableName(t *testing.T) { mockProcessAttributesProvidersWithErrors() ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithProcessExecutableName(), ) require.NoError(t, err) - require.EqualValues(t, map[string]string{ - "process.executable.name": fakeExecutableName, - }, toMap(res)) + require.EqualValues( + t, map[string]string{ + "process.executable.name": fakeExecutableName, + }, toMap(res), + ) } func TestWithProcessExecutablePath(t *testing.T) { mockProcessAttributesProviders() ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithProcessExecutablePath(), ) require.NoError(t, err) - require.EqualValues(t, map[string]string{ - "process.executable.path": fakeExecutablePath, - }, toMap(res)) + require.EqualValues( + t, map[string]string{ + "process.executable.path": fakeExecutablePath, + }, toMap(res), + ) } func TestWithProcessCommandArgs(t *testing.T) { mockProcessAttributesProvidersWithErrors() ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithProcessCommandArgs(), ) require.NoError(t, err) - require.EqualValues(t, map[string]string{ - "process.command_args": fmt.Sprint(fakeCommandArgs), - }, toMap(res)) + require.EqualValues( + t, map[string]string{ + "process.command_args": fmt.Sprint(fakeCommandArgs), + }, toMap(res), + ) } func TestWithProcessOwner(t *testing.T) { mockProcessAttributesProviders() ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithProcessOwner(), ) require.NoError(t, err) - require.EqualValues(t, map[string]string{ - "process.owner": fakeOwner, - }, toMap(res)) + require.EqualValues( + t, map[string]string{ + "process.owner": fakeOwner, + }, toMap(res), + ) } func TestWithProcessRuntimeName(t *testing.T) { mockProcessAttributesProvidersWithErrors() ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithProcessRuntimeName(), ) require.NoError(t, err) - require.EqualValues(t, map[string]string{ - "process.runtime.name": fakeRuntimeName, - }, toMap(res)) + require.EqualValues( + t, map[string]string{ + "process.runtime.name": fakeRuntimeName, + }, toMap(res), + ) } func TestWithProcessRuntimeVersion(t *testing.T) { mockProcessAttributesProvidersWithErrors() ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithProcessRuntimeVersion(), ) require.NoError(t, err) - require.EqualValues(t, map[string]string{ - "process.runtime.version": fakeRuntimeVersion, - }, toMap(res)) + require.EqualValues( + t, map[string]string{ + "process.runtime.version": fakeRuntimeVersion, + }, toMap(res), + ) } func TestWithProcessRuntimeDescription(t *testing.T) { mockProcessAttributesProvidersWithErrors() ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithProcessRuntimeDescription(), ) require.NoError(t, err) - require.EqualValues(t, map[string]string{ - "process.runtime.description": fakeRuntimeDescription, - }, toMap(res)) + require.EqualValues( + t, map[string]string{ + "process.runtime.description": fakeRuntimeDescription, + }, toMap(res), + ) } func TestWithProcess(t *testing.T) { mockProcessAttributesProviders() ctx := context.Background() - res, err := resource.New(ctx, + res, err := resource.New( + ctx, resource.WithProcess(), ) require.NoError(t, err) - require.EqualValues(t, map[string]string{ - "process.pid": fmt.Sprint(fakePID), - "process.executable.name": fakeExecutableName, - "process.executable.path": fakeExecutablePath, - "process.command_args": fmt.Sprint(fakeCommandArgs), - "process.owner": fakeOwner, - "process.runtime.name": fakeRuntimeName, - "process.runtime.version": fakeRuntimeVersion, - "process.runtime.description": fakeRuntimeDescription, - }, toMap(res)) + require.EqualValues( + t, map[string]string{ + "process.pid": fmt.Sprint(fakePID), + "process.executable.name": fakeExecutableName, + "process.executable.path": fakeExecutablePath, + "process.command_args": fmt.Sprint(fakeCommandArgs), + "process.owner": fakeOwner, + "process.runtime.name": fakeRuntimeName, + "process.runtime.version": fakeRuntimeVersion, + "process.runtime.description": fakeRuntimeDescription, + }, toMap(res), + ) } func toMap(res *resource.Resource) map[string]string { @@ -738,18 +799,21 @@ func TestWithContainerID(t *testing.T) { } for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - resource.SetContainerProviders(tc.containerIDProvider) - - res, err := resource.New(context.Background(), - resource.WithContainerID(), - ) - - if tc.expectedErr { - assert.Error(t, err) - } - assert.Equal(t, tc.expectedResource, toMap(res)) - }) + t.Run( + tc.name, func(t *testing.T) { + resource.SetContainerProviders(tc.containerIDProvider) + + res, err := resource.New( + context.Background(), + resource.WithContainerID(), + ) + + if tc.expectedErr { + assert.Error(t, err) + } + assert.Equal(t, tc.expectedResource, toMap(res)) + }, + ) } } @@ -757,18 +821,23 @@ func TestWithContainer(t *testing.T) { t.Cleanup(restoreAttributesProviders) fakeContainerID := "fake-container-id" - resource.SetContainerProviders(func() (string, error) { - return fakeContainerID, nil - }) + resource.SetContainerProviders( + func() (string, error) { + return fakeContainerID, nil + }, + ) - res, err := resource.New(context.Background(), + res, err := resource.New( + context.Background(), resource.WithContainer(), ) assert.NoError(t, err) - assert.Equal(t, map[string]string{ - string(semconv.ContainerIDKey): fakeContainerID, - }, toMap(res)) + assert.Equal( + t, map[string]string{ + string(semconv.ContainerIDKey): fakeContainerID, + }, toMap(res), + ) } func TestResourceConcurrentSafe(t *testing.T) { From b20e4788bd54729a6ed0b5bac410bc396500193e Mon Sep 17 00:00:00 2001 From: Tigran Najaryan Date: Wed, 14 Feb 2024 14:46:20 -0500 Subject: [PATCH 05/11] Replace proto with local version to build --- example/otel-collector/go.mod | 54 ++++++++++++++++-- example/otel-collector/go.sum | 24 ++++---- exporters/otlp/otlptrace/go.sum | 2 - exporters/otlp/otlptrace/otlptracegrpc/go.mod | 56 +++++++++++++++++-- exporters/otlp/otlptrace/otlptracegrpc/go.sum | 24 ++++---- exporters/otlp/otlptrace/otlptracehttp/go.mod | 56 +++++++++++++++++-- exporters/otlp/otlptrace/otlptracehttp/go.sum | 24 ++++---- 7 files changed, 184 insertions(+), 56 deletions(-) diff --git a/example/otel-collector/go.mod b/example/otel-collector/go.mod index e3bb6ec5f33..f1a5f497142 100644 --- a/example/otel-collector/go.mod +++ b/example/otel-collector/go.mod @@ -12,7 +12,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.0-rc.1 go.opentelemetry.io/otel/sdk v1.23.0-rc.1 go.opentelemetry.io/otel/trace v1.23.0-rc.1 - google.golang.org/grpc v1.60.1 + google.golang.org/grpc v1.61.0 ) require ( @@ -20,15 +20,15 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.0-rc.1 // indirect go.opentelemetry.io/otel/metric v1.23.0-rc.1 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect - golang.org/x/net v0.19.0 // indirect + golang.org/x/net v0.20.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect google.golang.org/protobuf v1.32.0 // indirect ) @@ -39,3 +39,47 @@ replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otl replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc replace go.opentelemetry.io/otel/metric => ../../metric + +replace go.opentelemetry.io/proto/otlp => ../../../opentelemetry-proto-go/otlp/ + +replace go.opentelemetry.io/otel/bridge/opencensus => ../../bridge/opencensus + +replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencensus/test + +replace go.opentelemetry.io/otel/bridge/opentracing => ../../bridge/opentracing + +replace go.opentelemetry.io/otel/bridge/opentracing/test => ../../bridge/opentracing/test + +replace go.opentelemetry.io/otel/example/dice => ../dice + +replace go.opentelemetry.io/otel/example/namedtracer => ../namedtracer + +replace go.opentelemetry.io/otel/example/opencensus => ../opencensus + +replace go.opentelemetry.io/otel/example/otel-collector => ./ + +replace go.opentelemetry.io/otel/example/passthrough => ../passthrough + +replace go.opentelemetry.io/otel/example/prometheus => ../prometheus + +replace go.opentelemetry.io/otel/example/zipkin => ../zipkin + +replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc => ../../exporters/otlp/otlpmetric/otlpmetricgrpc + +replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp => ../../exporters/otlp/otlpmetric/otlpmetrichttp + +replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp => ../../exporters/otlp/otlptrace/otlptracehttp + +replace go.opentelemetry.io/otel/exporters/prometheus => ../../exporters/prometheus + +replace go.opentelemetry.io/otel/exporters/stdout/stdoutmetric => ../../exporters/stdout/stdoutmetric + +replace go.opentelemetry.io/otel/exporters/stdout/stdouttrace => ../../exporters/stdout/stdouttrace + +replace go.opentelemetry.io/otel/exporters/zipkin => ../../exporters/zipkin + +replace go.opentelemetry.io/otel/internal/tools => ../../internal/tools + +replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric diff --git a/example/otel-collector/go.sum b/example/otel-collector/go.sum index 07ef3c0add3..5d4356be474 100644 --- a/example/otel-collector/go.sum +++ b/example/otel-collector/go.sum @@ -11,27 +11,25 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= diff --git a/exporters/otlp/otlptrace/go.sum b/exporters/otlp/otlptrace/go.sum index 6d12cac9adc..d4b11e72106 100644 --- a/exporters/otlp/otlptrace/go.sum +++ b/exporters/otlp/otlptrace/go.sum @@ -23,8 +23,6 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= diff --git a/exporters/otlp/otlptrace/otlptracegrpc/go.mod b/exporters/otlp/otlptrace/otlptracegrpc/go.mod index b0dcf22e888..2267e267e1d 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/go.mod +++ b/exporters/otlp/otlptrace/otlptracegrpc/go.mod @@ -11,8 +11,8 @@ require ( go.opentelemetry.io/otel/trace v1.23.0-rc.1 go.opentelemetry.io/proto/otlp v1.1.0 go.uber.org/goleak v1.3.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 - google.golang.org/grpc v1.60.1 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe + google.golang.org/grpc v1.61.0 google.golang.org/protobuf v1.32.0 ) @@ -21,14 +21,14 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.23.0-rc.1 // indirect - golang.org/x/net v0.19.0 // indirect + golang.org/x/net v0.20.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -41,3 +41,49 @@ replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../ replace go.opentelemetry.io/otel/trace => ../../../../trace replace go.opentelemetry.io/otel/metric => ../../../../metric + +replace go.opentelemetry.io/proto/otlp => ../../../../../opentelemetry-proto-go/otlp/ + +replace go.opentelemetry.io/otel/bridge/opencensus => ../../../../bridge/opencensus + +replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../../../bridge/opencensus/test + +replace go.opentelemetry.io/otel/bridge/opentracing => ../../../../bridge/opentracing + +replace go.opentelemetry.io/otel/bridge/opentracing/test => ../../../../bridge/opentracing/test + +replace go.opentelemetry.io/otel/example/dice => ../../../../example/dice + +replace go.opentelemetry.io/otel/example/namedtracer => ../../../../example/namedtracer + +replace go.opentelemetry.io/otel/example/opencensus => ../../../../example/opencensus + +replace go.opentelemetry.io/otel/example/otel-collector => ../../../../example/otel-collector + +replace go.opentelemetry.io/otel/example/passthrough => ../../../../example/passthrough + +replace go.opentelemetry.io/otel/example/prometheus => ../../../../example/prometheus + +replace go.opentelemetry.io/otel/example/zipkin => ../../../../example/zipkin + +replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc => ../../otlpmetric/otlpmetricgrpc + +replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp => ../../otlpmetric/otlpmetrichttp + +replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ./ + +replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp => ../otlptracehttp + +replace go.opentelemetry.io/otel/exporters/prometheus => ../../../prometheus + +replace go.opentelemetry.io/otel/exporters/stdout/stdoutmetric => ../../../stdout/stdoutmetric + +replace go.opentelemetry.io/otel/exporters/stdout/stdouttrace => ../../../stdout/stdouttrace + +replace go.opentelemetry.io/otel/exporters/zipkin => ../../../zipkin + +replace go.opentelemetry.io/otel/internal/tools => ../../../../internal/tools + +replace go.opentelemetry.io/otel/schema => ../../../../schema + +replace go.opentelemetry.io/otel/sdk/metric => ../../../../sdk/metric diff --git a/exporters/otlp/otlptrace/otlptracegrpc/go.sum b/exporters/otlp/otlptrace/otlptracegrpc/go.sum index 471ffacf102..bd42f420560 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/go.sum +++ b/exporters/otlp/otlptrace/otlptracegrpc/go.sum @@ -13,8 +13,8 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -23,24 +23,22 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= diff --git a/exporters/otlp/otlptrace/otlptracehttp/go.mod b/exporters/otlp/otlptrace/otlptracehttp/go.mod index ae9a1ed2bc2..f927bf773de 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/go.mod +++ b/exporters/otlp/otlptrace/otlptracehttp/go.mod @@ -10,7 +10,7 @@ require ( go.opentelemetry.io/otel/sdk v1.23.0-rc.1 go.opentelemetry.io/otel/trace v1.23.0-rc.1 go.opentelemetry.io/proto/otlp v1.1.0 - google.golang.org/grpc v1.60.1 + google.golang.org/grpc v1.61.0 google.golang.org/protobuf v1.32.0 ) @@ -19,15 +19,15 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.23.0-rc.1 // indirect - golang.org/x/net v0.19.0 // indirect + golang.org/x/net v0.20.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -40,3 +40,49 @@ replace go.opentelemetry.io/otel/sdk => ../../../../sdk replace go.opentelemetry.io/otel/trace => ../../../../trace replace go.opentelemetry.io/otel/metric => ../../../../metric + +replace go.opentelemetry.io/proto/otlp => ../../../../../opentelemetry-proto-go/otlp/ + +replace go.opentelemetry.io/otel/bridge/opencensus => ../../../../bridge/opencensus + +replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../../../bridge/opencensus/test + +replace go.opentelemetry.io/otel/bridge/opentracing => ../../../../bridge/opentracing + +replace go.opentelemetry.io/otel/bridge/opentracing/test => ../../../../bridge/opentracing/test + +replace go.opentelemetry.io/otel/example/dice => ../../../../example/dice + +replace go.opentelemetry.io/otel/example/namedtracer => ../../../../example/namedtracer + +replace go.opentelemetry.io/otel/example/opencensus => ../../../../example/opencensus + +replace go.opentelemetry.io/otel/example/otel-collector => ../../../../example/otel-collector + +replace go.opentelemetry.io/otel/example/passthrough => ../../../../example/passthrough + +replace go.opentelemetry.io/otel/example/prometheus => ../../../../example/prometheus + +replace go.opentelemetry.io/otel/example/zipkin => ../../../../example/zipkin + +replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc => ../../otlpmetric/otlpmetricgrpc + +replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp => ../../otlpmetric/otlpmetrichttp + +replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../otlptracegrpc + +replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp => ./ + +replace go.opentelemetry.io/otel/exporters/prometheus => ../../../prometheus + +replace go.opentelemetry.io/otel/exporters/stdout/stdoutmetric => ../../../stdout/stdoutmetric + +replace go.opentelemetry.io/otel/exporters/stdout/stdouttrace => ../../../stdout/stdouttrace + +replace go.opentelemetry.io/otel/exporters/zipkin => ../../../zipkin + +replace go.opentelemetry.io/otel/internal/tools => ../../../../internal/tools + +replace go.opentelemetry.io/otel/schema => ../../../../schema + +replace go.opentelemetry.io/otel/sdk/metric => ../../../../sdk/metric diff --git a/exporters/otlp/otlptrace/otlptracehttp/go.sum b/exporters/otlp/otlptrace/otlptracehttp/go.sum index f1773891403..f6f8cd8e47e 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/go.sum +++ b/exporters/otlp/otlptrace/otlptracehttp/go.sum @@ -13,8 +13,8 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -23,22 +23,20 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= From 552a023cb370242311378b72812efcfdeaa46d73 Mon Sep 17 00:00:00 2001 From: Tigran Najaryan Date: Wed, 14 Feb 2024 15:40:49 -0500 Subject: [PATCH 06/11] Pretty print dice example output --- example/dice/otel.go | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/example/dice/otel.go b/example/dice/otel.go index 0b30735007a..8185c1412f8 100644 --- a/example/dice/otel.go +++ b/example/dice/otel.go @@ -31,7 +31,9 @@ import ( // setupOTelSDK bootstraps the OpenTelemetry pipeline. // If it does not return an error, make sure to call shutdown for proper cleanup. -func setupOTelSDK(ctx context.Context, serviceName, serviceVersion string) (shutdown func(context.Context) error, err error) { +func setupOTelSDK(ctx context.Context, serviceName, serviceVersion string) ( + shutdown func(context.Context) error, err error, +) { var shutdownFuncs []func(context.Context) error // shutdown calls cleanup functions registered via shutdownFuncs. @@ -84,11 +86,14 @@ func setupOTelSDK(ctx context.Context, serviceName, serviceVersion string) (shut } func newResource(serviceName, serviceVersion string) (*resource.Resource, error) { - return resource.Merge(resource.Default(), - resource.NewWithAttributes(semconv.SchemaURL, + return resource.Merge( + resource.Default(), + resource.NewWithAttributes( + semconv.SchemaURL, semconv.ServiceName(serviceName), semconv.ServiceVersion(serviceVersion), - )) + ), + ) } func newPropagator() propagation.TextMapPropagator { @@ -100,31 +105,38 @@ func newPropagator() propagation.TextMapPropagator { func newTraceProvider(res *resource.Resource) (*trace.TracerProvider, error) { traceExporter, err := stdouttrace.New( - stdouttrace.WithPrettyPrint()) + stdouttrace.WithPrettyPrint(), + ) if err != nil { return nil, err } traceProvider := trace.NewTracerProvider( - trace.WithBatcher(traceExporter, + trace.WithBatcher( + traceExporter, // Default is 5s. Set to 1s for demonstrative purposes. - trace.WithBatchTimeout(time.Second)), + trace.WithBatchTimeout(time.Second), + ), trace.WithResource(res), ) return traceProvider, nil } func newMeterProvider(res *resource.Resource) (*metric.MeterProvider, error) { - metricExporter, err := stdoutmetric.New() + metricExporter, err := stdoutmetric.New(stdoutmetric.WithPrettyPrint()) if err != nil { return nil, err } meterProvider := metric.NewMeterProvider( metric.WithResource(res), - metric.WithReader(metric.NewPeriodicReader(metricExporter, - // Default is 1m. Set to 3s for demonstrative purposes. - metric.WithInterval(3*time.Second))), + metric.WithReader( + metric.NewPeriodicReader( + metricExporter, + // Default is 1m. Set to 3s for demonstrative purposes. + metric.WithInterval(3*time.Second), + ), + ), ) return meterProvider, nil } From 0b099b252d12825420fd07f2e6aaee012b645cb2 Mon Sep 17 00:00:00 2001 From: Tigran Najaryan Date: Wed, 14 Feb 2024 17:14:24 -0500 Subject: [PATCH 07/11] Define EntityData --- .../internal/tracetransform/resource.go | 11 +- sdk/resource/builtin.go | 10 +- sdk/resource/internal/entity.go | 62 +++++++++ sdk/resource/resource.go | 122 ++++++------------ 4 files changed, 113 insertions(+), 92 deletions(-) create mode 100644 sdk/resource/internal/entity.go diff --git a/exporters/otlp/otlptrace/internal/tracetransform/resource.go b/exporters/otlp/otlptrace/internal/tracetransform/resource.go index fa3cd5720b8..21c15de7174 100644 --- a/exporters/otlp/otlptrace/internal/tracetransform/resource.go +++ b/exporters/otlp/otlptrace/internal/tracetransform/resource.go @@ -26,13 +26,12 @@ func Resource(r *resource.Resource) *resourcepb.Resource { return nil } - entity := r.Entity() - entityId := Iterator(entity.IdIter()) + attrs := Iterator(r.Iter()) + entityId := Iterator(r.EntityId().Iter()) return &resourcepb.Resource{ - Attributes: ResourceAttributes(r), - DroppedAttributesCount: 0, - EntityType: entity.Type(), - EntityId: entityId, + Attributes: attrs, + EntityType: r.EntityType(), + EntityId: entityId, } } diff --git a/sdk/resource/builtin.go b/sdk/resource/builtin.go index 0387c362cc1..94f93abcb43 100644 --- a/sdk/resource/builtin.go +++ b/sdk/resource/builtin.go @@ -22,6 +22,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk" + "go.opentelemetry.io/otel/sdk/resource/internal" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" ) @@ -98,8 +99,13 @@ func (sd stringDetector) Detect(ctx context.Context) (*Resource, error) { if !a.Valid() { return nil, fmt.Errorf("invalid attribute: %q -> %q", a.Key, a.Value.Emit()) } - attrs := []attribute.KeyValue{sd.K.String(value)} - return NewWithEntity(sd.schemaURL, sd.entityType, attrs, attrs), nil + id := attribute.NewSet(sd.K.String(value)) + entity := internal.EntityData{ + Type: sd.entityType, + Id: id, + Attrs: id, + } + return NewWithEntity(sd.schemaURL, &entity), nil } // Detect implements Detector. diff --git a/sdk/resource/internal/entity.go b/sdk/resource/internal/entity.go new file mode 100644 index 00000000000..ec8d29041b5 --- /dev/null +++ b/sdk/resource/internal/entity.go @@ -0,0 +1,62 @@ +package internal + +import "go.opentelemetry.io/otel/attribute" + +type EntityData struct { + // Defines the producing entity type of this resource, e.g "service", "k8s.pod", etc. + // Empty for legacy Resources that are not entity-aware. + Type string + // Set of attributes that identify the entity. + // Note that a copy of identifying attributes will be also recorded in the Attrs field. + Id attribute.Set + + Attrs attribute.Set +} + +func MergeEntities(a, b *EntityData) *EntityData { + // Note: 'b' attributes will overwrite 'a' with last-value-wins in attribute.Key() + // Meaning this is equivalent to: append(a.Attributes(), b.Attributes()...) + mergedAttrs := mergeAttrs(&b.Attrs, &a.Attrs) + + var mergedType string + var mergedId attribute.Set + + if a.Type == b.Type { + mergedType = a.Type + mergedId = mergeAttrs(&b.Id, &a.Id) + } else { + if a.Type == "" { + mergedType = b.Type + mergedId = b.Id + } else if b.Type == "" { + mergedType = a.Type + mergedId = a.Id + } else { + // Different non-empty entities. + mergedId = a.Id + // TODO: merge the id of the updating Entity into the non-identifying + // attributes of the old Resource, attributes from the updating Entity + // take precedence. + panic("not implemented") + } + } + + return &EntityData{ + Type: mergedType, + Id: mergedId, + Attrs: mergedAttrs, + } +} + +func mergeAttrs(a, b *attribute.Set) attribute.Set { + if a.Len()+b.Len() == 0 { + return *attribute.EmptySet() + } + + mi := attribute.NewMergeIterator(a, b) + combine := make([]attribute.KeyValue, 0, a.Len()+b.Len()) + for mi.Next() { + combine = append(combine, mi.Attribute()) + } + return attribute.NewSet(combine...) +} diff --git a/sdk/resource/resource.go b/sdk/resource/resource.go index fad06084830..c3e3e8ac209 100644 --- a/sdk/resource/resource.go +++ b/sdk/resource/resource.go @@ -22,25 +22,9 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/resource/internal" ) -type Entity struct { - // Defines the producing entity type of this resource, e.g "service", "k8s.pod", etc. - // Empty for legacy Resources that are not entity-aware. - typ string - // Set of attributes that identify the producing entity. - // Note that the identifying attributes may be also recorded in the "attributes" field. - id attribute.Set -} - -func (e *Entity) Type() string { - return e.typ -} - -func (e *Entity) IdIter() attribute.Iterator { - return e.id.Iter() -} - // Resource describes an entity about which identifying information // and metadata is exposed. Resource is an immutable object, // equivalent to a map from key to unique value. @@ -49,10 +33,10 @@ func (e *Entity) IdIter() attribute.Iterator { // (`*resource.Resource`). The `nil` value is equivalent to an empty // Resource. type Resource struct { - attrs attribute.Set schemaURL string - entity Entity + // Producing entity. + entity internal.EntityData } var ( @@ -77,7 +61,7 @@ func New(ctx context.Context, opts ...Option) (*Resource, error) { r := &Resource{ schemaURL: cfg.schemaURL, - entity: Entity{typ: cfg.entityType, id: entityId}, + entity: internal.EntityData{Type: cfg.entityType, Id: entityId}, } return r, detect(ctx, r, cfg.detectors) } @@ -97,21 +81,22 @@ func NewWithAttributes(schemaURL string, attrs ...attribute.KeyValue) *Resource // contains any invalid items those items will be dropped. The attrs and entityId are assumed to be // in a schema identified by schemaURL. func NewWithEntity( - schemaURL string, entityType string, entityId []attribute.KeyValue, attrs []attribute.KeyValue, + schemaURL string, entity *internal.EntityData, ) *Resource { - resource := NewSchemaless(attrs...) + resource := NewSchemaless(entity.Attrs.ToSlice()...) resource.schemaURL = schemaURL - resource.entity.typ = entityType + resource.entity = *entity + //resource.entity.Type = entityType // Ensure attributes comply with the specification: // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/common/README.md#attribute - id, _ := attribute.NewSetWithFiltered( - entityId, func(kv attribute.KeyValue) bool { - return kv.Valid() - }, - ) - - resource.entity.id = id + //id, _ := attribute.NewSetWithFiltered( + // entityId, func(kv attribute.KeyValue) bool { + // return kv.Valid() + // }, + //) + // + //resource.entity.Id = id return resource } @@ -137,7 +122,7 @@ func NewSchemaless(attrs ...attribute.KeyValue) *Resource { return &Resource{} } - return &Resource{attrs: s, entity: Entity{id: attribute.NewSet()}} //nolint + return &Resource{entity: internal.EntityData{Id: attribute.NewSet(), Attrs: s}} //nolint } // String implements the Stringer interface and provides a @@ -149,7 +134,7 @@ func (r *Resource) String() string { if r == nil { return "" } - return r.attrs.Encoded(attribute.DefaultEncoder()) + return r.entity.Attrs.Encoded(attribute.DefaultEncoder()) } // MarshalLog is the marshaling function used by the logging system to represent this Resource. @@ -158,7 +143,7 @@ func (r *Resource) MarshalLog() interface{} { Attributes attribute.Set SchemaURL string }{ - Attributes: r.attrs, + Attributes: r.entity.Attrs, SchemaURL: r.schemaURL, } } @@ -169,7 +154,7 @@ func (r *Resource) Attributes() []attribute.KeyValue { if r == nil { r = Empty() } - return r.attrs.ToSlice() + return r.entity.Attrs.ToSlice() } // SchemaURL returns the schema URL associated with Resource r. @@ -180,11 +165,18 @@ func (r *Resource) SchemaURL() string { return r.schemaURL } -func (r *Resource) Entity() *Entity { +func (r *Resource) EntityId() *attribute.Set { if r == nil { - return &Entity{} + return attribute.EmptySet() } - return &r.entity + return &r.entity.Id +} + +func (r *Resource) EntityType() string { + if r == nil { + return "" + } + return r.entity.Type } // Iter returns an iterator of the Resource attributes. @@ -193,7 +185,7 @@ func (r *Resource) Iter() attribute.Iterator { if r == nil { r = Empty() } - return r.attrs.Iter() + return r.entity.Attrs.Iter() } // Equal returns true when a Resource is equivalent to this Resource. @@ -241,50 +233,12 @@ func Merge(a, b *Resource) (*Resource, error) { return Empty(), errMergeConflictSchemaURL } - // Note: 'b' attributes will overwrite 'a' with last-value-wins in attribute.Key() - // Meaning this is equivalent to: append(a.Attributes(), b.Attributes()...) - combineAttrs := mergeAttrs(b.Set(), a.Set()) - - var combineEntityId []attribute.KeyValue - - var entityType string - if a.entity.typ == b.entity.typ { - entityType = a.entity.typ - combineEntityId = mergeAttrs(&b.entity.id, &a.entity.id) - } else { - if a.entity.typ == "" { - entityType = b.entity.typ - combineEntityId = b.entity.id.ToSlice() - } else if b.entity.typ == "" { - entityType = a.entity.typ - combineEntityId = a.entity.id.ToSlice() - } else { - // Different non-empty entities. - combineEntityId = a.entity.id.ToSlice() - // TODO: merge the id of the updating Entity into the non-identifying - // attributes of the old Resource, attributes from the updating Entity - // take precedence. - panic("not implemented") - } - } + mergedEntity := internal.MergeEntities(&a.entity, &b.entity) + merged := NewWithEntity(schemaURL, mergedEntity) - merged := NewWithEntity(schemaURL, entityType, combineEntityId, combineAttrs) return merged, nil } -func mergeAttrs(a, b *attribute.Set) []attribute.KeyValue { - if a.Len()+b.Len() == 0 { - return nil - } - - mi := attribute.NewMergeIterator(a, b) - combine := make([]attribute.KeyValue, 0, a.Len()+b.Len()) - for mi.Next() { - combine = append(combine, mi.Attribute()) - } - return combine -} - // Empty returns an instance of Resource with no attributes. It is // equivalent to a `nil` Resource. func Empty() *Resource { @@ -338,7 +292,7 @@ func (r *Resource) Set() *attribute.Set { if r == nil { r = Empty() } - return &r.attrs + return &r.entity.Attrs } // MarshalJSON encodes the resource attributes as a JSON list of { "Key": @@ -356,14 +310,14 @@ func (r *Resource) MarshalJSON() ([]byte, error) { Id any } }{ - Attributes: r.attrs.MarshalableToJSON(), + Attributes: r.entity.Attrs.MarshalableToJSON(), SchemaURL: r.schemaURL, Entity: struct { Type string Id any }{ - Type: r.entity.Type(), - Id: r.entity.id.MarshalableToJSON(), + Type: r.entity.Type, + Id: r.entity.Id.MarshalableToJSON(), }, } @@ -375,7 +329,7 @@ func (r *Resource) Len() int { if r == nil { return 0 } - return r.attrs.Len() + return r.entity.Attrs.Len() } // Encoded returns an encoded representation of the resource. @@ -383,5 +337,5 @@ func (r *Resource) Encoded(enc attribute.Encoder) string { if r == nil { return "" } - return r.attrs.Encoded(enc) + return r.entity.Attrs.Encoded(enc) } From 89e681815402c0da1bddb11d7f1ecf49bcdbf0f5 Mon Sep 17 00:00:00 2001 From: Tigran Najaryan Date: Wed, 14 Feb 2024 18:03:10 -0500 Subject: [PATCH 08/11] Add EntityEmitterProvider --- entity.go | 47 +++++++ entity/config.go | 95 ++++++++++++++ entity/embedded/embedded.go | 56 +++++++++ entity/entity.go | 99 +++++++++++++++ internal/global/entity.go | 152 +++++++++++++++++++++++ internal/global/state.go | 86 ++++++++++--- sdk/resource/internal/active_entities.go | 17 +++ sdk/resource/internal/entity.go | 21 ++++ sdk/resource/resource.go | 4 + sdk/trace/provider.go | 16 +-- 10 files changed, 562 insertions(+), 31 deletions(-) create mode 100644 entity.go create mode 100644 entity/config.go create mode 100644 entity/embedded/embedded.go create mode 100644 entity/entity.go create mode 100644 internal/global/entity.go create mode 100644 sdk/resource/internal/active_entities.go diff --git a/entity.go b/entity.go new file mode 100644 index 00000000000..140eaad7359 --- /dev/null +++ b/entity.go @@ -0,0 +1,47 @@ +// 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 otel // import "go.opentelemetry.io/otel" + +import ( + "go.opentelemetry.io/otel/entity" + "go.opentelemetry.io/otel/internal/global" +) + +// EntityEmitter creates a named entityEmitter that implements EntityEmitter interface. +// If the name is an empty string then provider uses default name. +// +// This is short for GetEntityEmitterProvider().EntityEmitter(name, opts...) +func EntityEmitter(name string, opts ...entity.EntityEmitterOption) entity.EntityEmitter { + return GetEntityEmitterProvider().EntityEmitter(name, opts...) +} + +// GetEntityEmitterProvider returns the registered global entity provider. +// If none is registered then an instance of NoopEntityEmitterProvider is returned. +// +// Use the entity provider to create a named entityEmitter. E.g. +// +// entityEmitter := otel.GetEntityEmitterProvider().EntityEmitter("example.com/foo") +// +// or +// +// entityEmitter := otel.EntityEmitter("example.com/foo") +func GetEntityEmitterProvider() entity.EntityEmitterProvider { + return global.EntityEmitterProvider() +} + +// SetEntityEmitterProvider registers `tp` as the global entity provider. +func SetEntityEmitterProvider(tp entity.EntityEmitterProvider) { + global.SetEntityEmitterProvider(tp) +} diff --git a/entity/config.go b/entity/config.go new file mode 100644 index 00000000000..f99e5a3ef63 --- /dev/null +++ b/entity/config.go @@ -0,0 +1,95 @@ +// 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 entity // import "go.opentelemetry.io/otel/entity" + +import ( + "go.opentelemetry.io/otel/attribute" +) + +// EntityEmitterConfig is a group of options for a EntityEmitter. +type EntityEmitterConfig struct { + instrumentationVersion string + // Schema URL of the telemetry emitted by the EntityEmitter. + schemaURL string + attrs attribute.Set +} + +// InstrumentationVersion returns the version of the library providing instrumentation. +func (t *EntityEmitterConfig) InstrumentationVersion() string { + return t.instrumentationVersion +} + +// InstrumentationAttributes returns the attributes associated with the library +// providing instrumentation. +func (t *EntityEmitterConfig) InstrumentationAttributes() attribute.Set { + return t.attrs +} + +// SchemaURL returns the Schema URL of the telemetry emitted by the EntityEmitter. +func (t *EntityEmitterConfig) SchemaURL() string { + return t.schemaURL +} + +// NewEntityEmitterConfig applies all the options to a returned EntityEmitterConfig. +func NewEntityEmitterConfig(options ...EntityEmitterOption) EntityEmitterConfig { + var config EntityEmitterConfig + for _, option := range options { + config = option.apply(config) + } + return config +} + +// EntityEmitterOption applies an option to a EntityEmitterConfig. +type EntityEmitterOption interface { + apply(EntityEmitterConfig) EntityEmitterConfig +} + +type entityEmitterOptionFunc func(EntityEmitterConfig) EntityEmitterConfig + +func (fn entityEmitterOptionFunc) apply(cfg EntityEmitterConfig) EntityEmitterConfig { + return fn(cfg) +} + +// WithInstrumentationVersion sets the instrumentation version. +func WithInstrumentationVersion(version string) EntityEmitterOption { + return entityEmitterOptionFunc( + func(cfg EntityEmitterConfig) EntityEmitterConfig { + cfg.instrumentationVersion = version + return cfg + }, + ) +} + +// WithInstrumentationAttributes sets the instrumentation attributes. +// +// The passed attributes will be de-duplicated. +func WithInstrumentationAttributes(attr ...attribute.KeyValue) EntityEmitterOption { + return entityEmitterOptionFunc( + func(config EntityEmitterConfig) EntityEmitterConfig { + config.attrs = attribute.NewSet(attr...) + return config + }, + ) +} + +// WithSchemaURL sets the schema URL for the EntityEmitter. +func WithSchemaURL(schemaURL string) EntityEmitterOption { + return entityEmitterOptionFunc( + func(cfg EntityEmitterConfig) EntityEmitterConfig { + cfg.schemaURL = schemaURL + return cfg + }, + ) +} diff --git a/entity/embedded/embedded.go b/entity/embedded/embedded.go new file mode 100644 index 00000000000..32c6886d3f6 --- /dev/null +++ b/entity/embedded/embedded.go @@ -0,0 +1,56 @@ +// 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 embedded provides interfaces embedded within the [OpenTelemetry +// entity API]. +// +// Implementers of the [OpenTelemetry entity API] can embed the relevant type +// from this package into their implementation directly. Doing so will result +// in a compilation error for users when the [OpenTelemetry entity API] is +// extended (which is something that can happen without a major version bump of +// the API package). +// +// [OpenTelemetry entity API]: https://pkg.go.dev/go.opentelemetry.io/otel/entity +package embedded // import "go.opentelemetry.io/otel/entity/embedded" + +// EntityEmitterProvider is embedded in +// [go.opentelemetry.io/otel/entity.EntityEmitterProvider]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/entity.EntityEmitterProvider] if you want users to +// experience a compilation error, signaling they need to update to your latest +// implementation, when the [go.opentelemetry.io/otel/entity.EntityEmitterProvider] +// interface is extended (which is something that can happen without a major +// version bump of the API package). +type EntityEmitterProvider interface{ entityEmitterProvider() } + +// EntityEmitter is embedded in [go.opentelemetry.io/otel/entity.EntityEmitter]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/entity.EntityEmitter] if you want users to experience a +// compilation error, signaling they need to update to your latest +// implementation, when the [go.opentelemetry.io/otel/entity.EntityEmitter] interface +// is extended (which is something that can happen without a major version bump +// of the API package). +type EntityEmitter interface{ entityEmitter() } + +// Entity is embedded in [go.opentelemetry.io/otel/entity.Entity]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/entity.Entity] if you want users to experience a +// compilation error, signaling they need to update to your latest +// implementation, when the [go.opentelemetry.io/otel/entity.Entity] interface is +// extended (which is something that can happen without a major version bump of +// the API package). +type Entity interface{ entity() } diff --git a/entity/entity.go b/entity/entity.go new file mode 100644 index 00000000000..fb790e0cf8a --- /dev/null +++ b/entity/entity.go @@ -0,0 +1,99 @@ +// 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 entity // import "go.opentelemetry.io/otel/entity" + +import ( + "go.opentelemetry.io/otel/entity/embedded" +) + +// EntityEmitter is the creator of Spans. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type EntityEmitter interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.EntityEmitter + + // Start creates a span and a context.Context containing the newly-created span. + // + // If the context.Context provided in `ctx` contains a Span then the newly-created + // Span will be a child of that span, otherwise it will be a root span. This behavior + // can be overridden by providing `WithNewRoot()` as a SpanOption, causing the + // newly-created Span to be a root span even if `ctx` contains a Span. + // + // When creating a Span it is recommended to provide all known span attributes using + // the `WithAttributes()` SpanOption as samplers will only have access to the + // attributes provided when a Span is created. + // + // Any Span that is created MUST also be ended. This is the responsibility of the user. + // Implementations of this API may leak memory or other resources if Spans are not ended. + //Start(ctx context.Context, spanName string, opts ...SpanStartOption) (context.Context, Span) +} + +// EntityEmitterProvider provides EntityEmitters that are used by instrumentation code to +// entity computational workflows. +// +// A EntityEmitterProvider is the collection destination of all Spans from EntityEmitters 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 EntityEmitters to instrument code. +// +// Commonly, instrumentation code will accept a EntityEmitterProvider implementation +// at runtime from its users or it can simply use the globally registered one +// (see https://pkg.go.dev/go.opentelemetry.io/otel#GetEntityEmitterProvider). +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type EntityEmitterProvider interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.EntityEmitterProvider + + // EntityEmitter returns a unique EntityEmitter scoped to be used by instrumentation code + // to entity 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 entitys 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 EntityEmitters that handle the different domains of workflow + // data. + // + // If the same name and options are passed multiple times, the same EntityEmitter + // will be returned (it is up to the implementation if this will be the + // same underlying instance of that EntityEmitter or not). It is not necessary to + // call this multiple times with the same name and options to get an + // up-to-date EntityEmitter. All implementations will ensure any EntityEmitterProvider + // configuration changes are propagated to all provided EntityEmitters. + // + // If name is empty, then an implementation defined default name will be + // used instead. + // + // This method is safe to call concurrently. + EntityEmitter(name string, options ...EntityEmitterOption) EntityEmitter +} diff --git a/internal/global/entity.go b/internal/global/entity.go new file mode 100644 index 00000000000..e3150804b9f --- /dev/null +++ b/internal/global/entity.go @@ -0,0 +1,152 @@ +// 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 global // import "go.opentelemetry.io/otel/internal/global" + +/* +This file contains the forwarding implementation of the EntityEmitterProvider used as +the default global instance. Prior to initialization of an SDK, EntityEmitters +returned by the global EntityEmitterProvider will provide no-op functionality. This +means that all Span created prior to initialization are no-op Spans. + +Once an SDK has been initialized, all provided no-op EntityEmitters are swapped for +EntityEmitters provided by the SDK defined EntityEmitterProvider. However, any Span started +prior to this initialization does not change its behavior. Meaning, the Span +remains a no-op Span. + +The implementation to track and swap EntityEmitters locks all new EntityEmitter creation +until the swap is complete. This assumes that this operation is not +performance-critical. If that assumption is incorrect, be sure to configure an +SDK prior to any EntityEmitter creation. +*/ + +import ( + "sync" + "sync/atomic" + + "go.opentelemetry.io/otel/entity" + "go.opentelemetry.io/otel/entity/embedded" +) + +// entityEmitterProvider is a placeholder for a configured SDK EntityEmitterProvider. +// +// All EntityEmitterProvider functionality is forwarded to a delegate once +// configured. +type entityEmitterProvider struct { + embedded.EntityEmitterProvider + + mtx sync.Mutex + entityEmitters map[il]*entityEmitter + delegate entity.EntityEmitterProvider +} + +// Compile-time guarantee that entityEmitterProvider implements the EntityEmitterProvider +// interface. +var _ entity.EntityEmitterProvider = &entityEmitterProvider{} + +// setDelegate configures p to delegate all EntityEmitterProvider functionality to +// provider. +// +// All EntityEmitters provided prior to this function call are switched out to be +// EntityEmitters provided by provider. +// +// It is guaranteed by the caller that this happens only once. +func (p *entityEmitterProvider) setDelegate(provider entity.EntityEmitterProvider) { + p.mtx.Lock() + defer p.mtx.Unlock() + + p.delegate = provider + + if len(p.entityEmitters) == 0 { + return + } + + for _, t := range p.entityEmitters { + t.setDelegate(provider) + } + + p.entityEmitters = nil +} + +// EntityEmitter implements EntityEmitterProvider. +func (p *entityEmitterProvider) EntityEmitter(name string, opts ...entity.EntityEmitterOption) entity.EntityEmitter { + p.mtx.Lock() + defer p.mtx.Unlock() + + if p.delegate != nil { + return p.delegate.EntityEmitter(name, opts...) + } + + // At this moment it is guaranteed that no sdk is installed, save the entityEmitter in the entityEmitters map. + + c := entity.NewEntityEmitterConfig(opts...) + key := il{ + name: name, + version: c.InstrumentationVersion(), + } + + if p.entityEmitters == nil { + p.entityEmitters = make(map[il]*entityEmitter) + } + + if val, ok := p.entityEmitters[key]; ok { + return val + } + + t := &entityEmitter{name: name, opts: opts, provider: p} + p.entityEmitters[key] = t + return t +} + +// entityEmitter is a placeholder for a entity.EntityEmitter. +// +// All EntityEmitter functionality is forwarded to a delegate once configured. +// Otherwise, all functionality is forwarded to a NoopEntityEmitter. +type entityEmitter struct { + embedded.EntityEmitter + + name string + opts []entity.EntityEmitterOption + provider *entityEmitterProvider + + delegate atomic.Value +} + +// Compile-time guarantee that entityEmitter implements the entity.EntityEmitter interface. +var _ entity.EntityEmitter = &entityEmitter{} + +// setDelegate configures t to delegate all EntityEmitter functionality to EntityEmitters +// created by provider. +// +// All subsequent calls to the EntityEmitter methods will be passed to the delegate. +// +// It is guaranteed by the caller that this happens only once. +func (t *entityEmitter) setDelegate(provider entity.EntityEmitterProvider) { + t.delegate.Store(provider.EntityEmitter(t.name, t.opts...)) +} + +//// Start implements entity.EntityEmitter by forwarding the call to t.delegate if +//// set, otherwise it forwards the call to a NoopEntityEmitter. +//func (t *entityEmitter) Start(ctx context.Context, name string, opts ...entity.SpanStartOption) ( +// context.Context, entity.Span, +//) { +// delegate := t.delegate.Load() +// if delegate != nil { +// return delegate.(entity.EntityEmitter).Start(ctx, name, opts...) +// } +// +// s := nonRecordingSpan{sc: entity.SpanContextFromContext(ctx), entityEmitter: t} +// ctx = entity.ContextWithSpan(ctx, s) +// return ctx, s +//} diff --git a/internal/global/state.go b/internal/global/state.go index 7985005bcb6..5e5a496d9f1 100644 --- a/internal/global/state.go +++ b/internal/global/state.go @@ -19,6 +19,7 @@ import ( "sync" "sync/atomic" + "go.opentelemetry.io/otel/entity" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" @@ -36,16 +37,22 @@ type ( meterProviderHolder struct { mp metric.MeterProvider } + + entityEmitterProviderHolder struct { + tp entity.EntityEmitterProvider + } ) var ( - globalTracer = defaultTracerValue() - globalPropagators = defaultPropagatorsValue() - globalMeterProvider = defaultMeterProvider() + globalTracer = defaultTracerValue() + globalPropagators = defaultPropagatorsValue() + globalMeterProvider = defaultMeterProvider() + globalEntityEmitterProvider = defaultEntityEmitterProvider() delegateTraceOnce sync.Once delegateTextMapPropagatorOnce sync.Once delegateMeterOnce sync.Once + delegateEntityProviderOnce sync.Once ) // TracerProvider is the internal implementation for global.TracerProvider. @@ -69,11 +76,13 @@ func SetTracerProvider(tp trace.TracerProvider) { } } - delegateTraceOnce.Do(func() { - if def, ok := current.(*tracerProvider); ok { - def.setDelegate(tp) - } - }) + delegateTraceOnce.Do( + func() { + if def, ok := current.(*tracerProvider); ok { + def.setDelegate(tp) + } + }, + ) globalTracer.Store(tracerProviderHolder{tp: tp}) } @@ -100,11 +109,13 @@ func SetTextMapPropagator(p propagation.TextMapPropagator) { // For the textMapPropagator already returned by TextMapPropagator // delegate to p. - delegateTextMapPropagatorOnce.Do(func() { - if def, ok := current.(*textMapPropagator); ok { - def.SetDelegate(p) - } - }) + delegateTextMapPropagatorOnce.Do( + func() { + if def, ok := current.(*textMapPropagator); ok { + def.SetDelegate(p) + } + }, + ) // Return p when subsequent calls to TextMapPropagator are made. globalPropagators.Store(propagatorsHolder{tm: p}) } @@ -129,11 +140,13 @@ func SetMeterProvider(mp metric.MeterProvider) { } } - delegateMeterOnce.Do(func() { - if def, ok := current.(*meterProvider); ok { - def.setDelegate(mp) - } - }) + delegateMeterOnce.Do( + func() { + if def, ok := current.(*meterProvider); ok { + def.setDelegate(mp) + } + }, + ) globalMeterProvider.Store(meterProviderHolder{mp: mp}) } @@ -154,3 +167,40 @@ func defaultMeterProvider() *atomic.Value { v.Store(meterProviderHolder{mp: &meterProvider{}}) return v } + +func defaultEntityEmitterProvider() *atomic.Value { + v := &atomic.Value{} + v.Store(entityEmitterProviderHolder{tp: &entityEmitterProvider{}}) + return v +} + +// EntityEmitterProvider is the internal implementation for global.EntityEmitterProvider. +func EntityEmitterProvider() entity.EntityEmitterProvider { + return globalEntityEmitterProvider.Load().(entityEmitterProviderHolder).tp +} + +// SetEntityEmitterProvider is the internal implementation for global.SetEntityEmitterProvider. +func SetEntityEmitterProvider(tp entity.EntityEmitterProvider) { + current := EntityEmitterProvider() + + if _, cOk := current.(*entityEmitterProvider); cOk { + if _, tpOk := tp.(*entityEmitterProvider); tpOk && current == tp { + // Do not assign the default delegating EntityEmitterProvider to delegate + // to itself. + Error( + errors.New("no delegate configured in entityEmitter provider"), + "Setting entityEmitter provider to it's current value. No delegate will be configured", + ) + return + } + } + + delegateEntityProviderOnce.Do( + func() { + if def, ok := current.(*entityEmitterProvider); ok { + def.setDelegate(tp) + } + }, + ) + globalEntityEmitterProvider.Store(entityEmitterProviderHolder{tp: tp}) +} diff --git a/sdk/resource/internal/active_entities.go b/sdk/resource/internal/active_entities.go new file mode 100644 index 00000000000..815dc4619c4 --- /dev/null +++ b/sdk/resource/internal/active_entities.go @@ -0,0 +1,17 @@ +package internal + +import "go.opentelemetry.io/otel/attribute" + +// This is a quick implementation of active entities for prototyping purposes. +// A proper implementation will use providers, exporters, etc. just like all other +// signals use. + +var activeEntities map[attribute.Distinct]Entity = map[attribute.Distinct]Entity{} + +func init() { + go exportActive() +} + +func exportActive() { + +} diff --git a/sdk/resource/internal/entity.go b/sdk/resource/internal/entity.go index ec8d29041b5..0f34735238e 100644 --- a/sdk/resource/internal/entity.go +++ b/sdk/resource/internal/entity.go @@ -6,13 +6,18 @@ type EntityData struct { // Defines the producing entity type of this resource, e.g "service", "k8s.pod", etc. // Empty for legacy Resources that are not entity-aware. Type string + // Set of attributes that identify the entity. // Note that a copy of identifying attributes will be also recorded in the Attrs field. Id attribute.Set + // Non-identifying attributes of the Entity. When EntityData is stored in a Resource + // this field also represents the Resource attributes. Attrs attribute.Set } +// MergeEntities merges a and b, with values in b overwriting values in a. +// Inputs are not modified, the result of merging is returned as a new struct. func MergeEntities(a, b *EntityData) *EntityData { // Note: 'b' attributes will overwrite 'a' with last-value-wins in attribute.Key() // Meaning this is equivalent to: append(a.Attributes(), b.Attributes()...) @@ -60,3 +65,19 @@ func mergeAttrs(a, b *attribute.Set) attribute.Set { } return attribute.NewSet(combine...) } + +type Entity struct { + data EntityData +} + +func PublishEntity(d *EntityData) *Entity { + return nil +} + +func (e *Entity) Update(attrs attribute.Set) { + +} + +func (e *Entity) Delete() { + +} diff --git a/sdk/resource/resource.go b/sdk/resource/resource.go index c3e3e8ac209..1effae169ee 100644 --- a/sdk/resource/resource.go +++ b/sdk/resource/resource.go @@ -339,3 +339,7 @@ func (r *Resource) Encoded(enc attribute.Encoder) string { } return r.entity.Attrs.Encoded(enc) } + +func (r *Resource) PublishEntity() { + +} diff --git a/sdk/trace/provider.go b/sdk/trace/provider.go index 108d50e65c0..ac94d03d405 100644 --- a/sdk/trace/provider.go +++ b/sdk/trace/provider.go @@ -124,6 +124,9 @@ func NewTracerProvider(opts ...TracerProviderOption) *TracerProvider { } global.Info("TracerProvider created", "config", o) + // Begin publishing Resource's producing entity as an entity signal. + tp.resource.PublishEntity() + spss := make(spanProcessorStates, 0, len(o.processors)) for _, sp := range o.processors { spss = append(spss, newSpanProcessorState(sp)) @@ -377,19 +380,6 @@ func WithResource(r *resource.Resource) TracerProviderOption { ) } -func WithEntity(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 - }, - ) -} - // WithIDGenerator returns a TracerProviderOption that will configure the // IDGenerator g as a TracerProvider's IDGenerator. The configured IDGenerator // is used by the Tracers the TracerProvider creates to generate new Span and From ebf40545e7be544973c0bbd137f80b9252eeecbd Mon Sep 17 00:00:00 2001 From: Tigran Najaryan Date: Wed, 14 Feb 2024 22:15:14 -0500 Subject: [PATCH 09/11] Begin adding entity exporter --- entity/entity.go | 18 + exporters/otlp/otlpentity/clients.go | 54 ++ exporters/otlp/otlpentity/exporter.go | 120 +++ exporters/otlp/otlpentity/go.mod | 28 + exporters/otlp/otlpentity/go.sum | 14 + .../internal/entitytransform/attribute.go | 159 ++++ .../internal/entitytransform/entity.go | 84 ++ .../entitytransform/instrumentation.go | 31 + exporters/otlp/otlpentity/version.go | 20 + sdk/resource/entity.go | 811 ++++++++++++++++++ sdk/resource/entity_exporter.go | 24 + 11 files changed, 1363 insertions(+) create mode 100644 exporters/otlp/otlpentity/clients.go create mode 100644 exporters/otlp/otlpentity/exporter.go create mode 100644 exporters/otlp/otlpentity/go.mod create mode 100644 exporters/otlp/otlpentity/go.sum create mode 100644 exporters/otlp/otlpentity/internal/entitytransform/attribute.go create mode 100644 exporters/otlp/otlpentity/internal/entitytransform/entity.go create mode 100644 exporters/otlp/otlpentity/internal/entitytransform/instrumentation.go create mode 100644 exporters/otlp/otlpentity/version.go create mode 100644 sdk/resource/entity.go create mode 100644 sdk/resource/entity_exporter.go diff --git a/entity/entity.go b/entity/entity.go index fb790e0cf8a..9280f216860 100644 --- a/entity/entity.go +++ b/entity/entity.go @@ -15,9 +15,27 @@ package entity // import "go.opentelemetry.io/otel/entity" import ( + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/entity/embedded" ) +type Entity interface { + embedded.Entity + + // IsRecording returns the recording state of the Span. It will return + // true if the Span is active and events can be recorded. + IsRecording() bool + + // SetAttributes sets kv as attributes of the Span. If a key from kv + // already exists for an attribute of the Span it will be overwritten with + // the value contained in kv. + SetAttributes(kv ...attribute.KeyValue) + + // TracerProvider returns a TracerProvider that can be used to generate + // additional Spans on the same telemetry pipeline as the current Span. + EntityEmitterProvider() EntityEmitterProvider +} + // EntityEmitter is the creator of Spans. // // Warning: Methods may be added to this interface in minor releases. See diff --git a/exporters/otlp/otlpentity/clients.go b/exporters/otlp/otlpentity/clients.go new file mode 100644 index 00000000000..bb582d1bf29 --- /dev/null +++ b/exporters/otlp/otlpentity/clients.go @@ -0,0 +1,54 @@ +// 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 otlpentity // import "go.opentelemetry.io/otel/exporters/otlp/otlpentity" + +import ( + "context" + + entitiespb "go.opentelemetry.io/proto/otlp/entities/v1" +) + +// Client manages connections to the collector, handles the +// transformation of data into wire format, and the transmission of that +// data to the collector. +type Client interface { + // DO NOT CHANGE: any modification will not be backwards compatible and + // must never be done outside of a new major release. + + // Start should establish connection(s) to endpoint(s). It is + // called just once by the exporter, so the implementation + // does not need to worry about idempotence and locking. + Start(ctx context.Context) error + // DO NOT CHANGE: any modification will not be backwards compatible and + // must never be done outside of a new major release. + + // Stop should close the connections. The function is called + // only once by the exporter, so the implementation does not + // need to worry about idempotence, but it may be called + // concurrently with UploadEntitys, so proper + // locking is required. The function serves as a + // synchronization point - after the function returns, the + // process of closing connections is assumed to be finished. + Stop(ctx context.Context) error + // DO NOT CHANGE: any modification will not be backwards compatible and + // must never be done outside of a new major release. + + // UploadEntitys should transform the passed entitys to the wire + // format and send it to the collector. May be called + // concurrently. + UploadEntities(ctx context.Context, entities []*entitiespb.ScopeEntities) error + // DO NOT CHANGE: any modification will not be backwards compatible and + // must never be done outside of a new major release. +} diff --git a/exporters/otlp/otlpentity/exporter.go b/exporters/otlp/otlpentity/exporter.go new file mode 100644 index 00000000000..f4c7a6a31a1 --- /dev/null +++ b/exporters/otlp/otlpentity/exporter.go @@ -0,0 +1,120 @@ +// 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 otlpentity // import "go.opentelemetry.io/otel/exporters/otlp/otlpentity" + +import ( + "context" + "errors" + "fmt" + "sync" + + "go.opentelemetry.io/otel/exporters/otlp/otlpentity/internal/entitytransform" + "go.opentelemetry.io/otel/sdk/resource" +) + +var errAlreadyStarted = errors.New("already started") + +// Exporter exports entity data in the OTLP wire format. +type Exporter struct { + client Client + + mu sync.RWMutex + started bool + + startOnce sync.Once + stopOnce sync.Once +} + +// ExportSpans exports a batch of spans. +func (e *Exporter) ExportEntities(ctx context.Context, entities []resource.ReadOnlyEntity) error { + protoSpans := entitytransform.Entities(entities) + if len(protoSpans) == 0 { + return nil + } + + err := e.client.UploadEntities(ctx, protoSpans) + if err != nil { + return fmt.Errorf("entitys export: %w", err) + } + return nil +} + +// Start establishes a connection to the receiving endpoint. +func (e *Exporter) Start(ctx context.Context) error { + err := errAlreadyStarted + e.startOnce.Do( + func() { + e.mu.Lock() + e.started = true + e.mu.Unlock() + err = e.client.Start(ctx) + }, + ) + + return err +} + +// Shutdown flushes all exports and closes all connections to the receiving endpoint. +func (e *Exporter) Shutdown(ctx context.Context) error { + e.mu.RLock() + started := e.started + e.mu.RUnlock() + + if !started { + return nil + } + + var err error + + e.stopOnce.Do( + func() { + err = e.client.Stop(ctx) + e.mu.Lock() + e.started = false + e.mu.Unlock() + }, + ) + + return err +} + +var _ resource.EntityExporter = (*Exporter)(nil) + +// New constructs a new Exporter and starts it. +func New(ctx context.Context, client Client) (*Exporter, error) { + exp := NewUnstarted(client) + if err := exp.Start(ctx); err != nil { + return nil, err + } + return exp, nil +} + +// NewUnstarted constructs a new Exporter and does not start it. +func NewUnstarted(client Client) *Exporter { + return &Exporter{ + client: client, + } +} + +// MarshalLog is the marshaling function used by the logging system to represent this Exporter. +func (e *Exporter) MarshalLog() interface{} { + return struct { + Type string + Client Client + }{ + Type: "otlpentity", + Client: e.client, + } +} diff --git a/exporters/otlp/otlpentity/go.mod b/exporters/otlp/otlpentity/go.mod new file mode 100644 index 00000000000..c7e1f888e4c --- /dev/null +++ b/exporters/otlp/otlpentity/go.mod @@ -0,0 +1,28 @@ +module go.opentelemetry.io/otel/exporters/otlp/otlpentity + +go 1.20 + +require ( + go.opentelemetry.io/otel v1.23.1 + go.opentelemetry.io/otel/sdk v1.23.1 + go.opentelemetry.io/proto/otlp v1.1.0 +) + +require ( + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + go.opentelemetry.io/otel/metric v1.23.1 // indirect + go.opentelemetry.io/otel/trace v1.23.0-rc.1 // indirect + golang.org/x/sys v0.16.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect +) + +replace go.opentelemetry.io/otel => ../../.. + +replace go.opentelemetry.io/otel/sdk => ../../../sdk + +replace go.opentelemetry.io/otel/trace => ../../../trace + +replace go.opentelemetry.io/otel/metric => ../../../metric + +replace go.opentelemetry.io/proto/otlp => ../../../../opentelemetry-proto-go/otlp/ diff --git a/exporters/otlp/otlpentity/go.sum b/exporters/otlp/otlpentity/go.sum new file mode 100644 index 00000000000..a33dfad4ef1 --- /dev/null +++ b/exporters/otlp/otlpentity/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/exporters/otlp/otlpentity/internal/entitytransform/attribute.go b/exporters/otlp/otlpentity/internal/entitytransform/attribute.go new file mode 100644 index 00000000000..2fad75cd343 --- /dev/null +++ b/exporters/otlp/otlpentity/internal/entitytransform/attribute.go @@ -0,0 +1,159 @@ +// 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 entitytransform // import "go.opentelemetry.io/otel/exporters/otlp/otlpentity/internal/entitytransform" + +import ( + commonpb "go.opentelemetry.io/proto/otlp/common/v1" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/resource" +) + +// KeyValues transforms a slice of attribute KeyValues into OTLP key-values. +func KeyValues(attrs []attribute.KeyValue) []*commonpb.KeyValue { + if len(attrs) == 0 { + return nil + } + + out := make([]*commonpb.KeyValue, 0, len(attrs)) + for _, kv := range attrs { + out = append(out, KeyValue(kv)) + } + return out +} + +// Iterator transforms an attribute iterator into OTLP key-values. +func Iterator(iter attribute.Iterator) []*commonpb.KeyValue { + l := iter.Len() + if l == 0 { + return nil + } + + out := make([]*commonpb.KeyValue, 0, l) + for iter.Next() { + out = append(out, KeyValue(iter.Attribute())) + } + return out +} + +// ResourceAttributes transforms a Resource OTLP key-values. +func ResourceAttributes(res *resource.Resource) []*commonpb.KeyValue { + return Iterator(res.Iter()) +} + +// KeyValue transforms an attribute KeyValue into an OTLP key-value. +func KeyValue(kv attribute.KeyValue) *commonpb.KeyValue { + return &commonpb.KeyValue{Key: string(kv.Key), Value: Value(kv.Value)} +} + +// Value transforms an attribute Value into an OTLP AnyValue. +func Value(v attribute.Value) *commonpb.AnyValue { + av := new(commonpb.AnyValue) + switch v.Type() { + case attribute.BOOL: + av.Value = &commonpb.AnyValue_BoolValue{ + BoolValue: v.AsBool(), + } + case attribute.BOOLSLICE: + av.Value = &commonpb.AnyValue_ArrayValue{ + ArrayValue: &commonpb.ArrayValue{ + Values: boolSliceValues(v.AsBoolSlice()), + }, + } + case attribute.INT64: + av.Value = &commonpb.AnyValue_IntValue{ + IntValue: v.AsInt64(), + } + case attribute.INT64SLICE: + av.Value = &commonpb.AnyValue_ArrayValue{ + ArrayValue: &commonpb.ArrayValue{ + Values: int64SliceValues(v.AsInt64Slice()), + }, + } + case attribute.FLOAT64: + av.Value = &commonpb.AnyValue_DoubleValue{ + DoubleValue: v.AsFloat64(), + } + case attribute.FLOAT64SLICE: + av.Value = &commonpb.AnyValue_ArrayValue{ + ArrayValue: &commonpb.ArrayValue{ + Values: float64SliceValues(v.AsFloat64Slice()), + }, + } + case attribute.STRING: + av.Value = &commonpb.AnyValue_StringValue{ + StringValue: v.AsString(), + } + case attribute.STRINGSLICE: + av.Value = &commonpb.AnyValue_ArrayValue{ + ArrayValue: &commonpb.ArrayValue{ + Values: stringSliceValues(v.AsStringSlice()), + }, + } + default: + av.Value = &commonpb.AnyValue_StringValue{ + StringValue: "INVALID", + } + } + return av +} + +func boolSliceValues(vals []bool) []*commonpb.AnyValue { + converted := make([]*commonpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &commonpb.AnyValue{ + Value: &commonpb.AnyValue_BoolValue{ + BoolValue: v, + }, + } + } + return converted +} + +func int64SliceValues(vals []int64) []*commonpb.AnyValue { + converted := make([]*commonpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &commonpb.AnyValue{ + Value: &commonpb.AnyValue_IntValue{ + IntValue: v, + }, + } + } + return converted +} + +func float64SliceValues(vals []float64) []*commonpb.AnyValue { + converted := make([]*commonpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &commonpb.AnyValue{ + Value: &commonpb.AnyValue_DoubleValue{ + DoubleValue: v, + }, + } + } + return converted +} + +func stringSliceValues(vals []string) []*commonpb.AnyValue { + converted := make([]*commonpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &commonpb.AnyValue{ + Value: &commonpb.AnyValue_StringValue{ + StringValue: v, + }, + } + } + return converted +} diff --git a/exporters/otlp/otlpentity/internal/entitytransform/entity.go b/exporters/otlp/otlpentity/internal/entitytransform/entity.go new file mode 100644 index 00000000000..6e3f8529832 --- /dev/null +++ b/exporters/otlp/otlpentity/internal/entitytransform/entity.go @@ -0,0 +1,84 @@ +// 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 entitytransform // import "go.opentelemetry.io/otel/exporters/otlp/otlpentity/internal/entitytransform" + +import ( + enititespb "go.opentelemetry.io/proto/otlp/entities/v1" + + resourcesdk "go.opentelemetry.io/otel/sdk/resource" + + "go.opentelemetry.io/otel/sdk/instrumentation" +) + +// Entities transforms a slice of OpenTelemetry spans into a slice of OTLP +// ResourceSpans. +func Entities(roEntities []resourcesdk.ReadOnlyEntity) (r []*enititespb.ScopeEntities) { + if len(roEntities) == 0 { + return nil + } + + type key struct { + is instrumentation.Scope + } + ssm := make(map[key]*enititespb.ScopeEntities) + + for _, roEntity := range roEntities { + if roEntity == nil { + continue + } + + k := key{ + is: roEntity.InstrumentationScope(), + } + events, iOk := ssm[k] + if !iOk { + // Either the resource or instrumentation scope were unknown. + events = &enititespb.ScopeEntities{ + Scope: InstrumentationScope(roEntity.InstrumentationScope()), + EntityEvents: []*enititespb.EntityEvent{}, + SchemaUrl: roEntity.InstrumentationScope().SchemaURL, + } + } + events.EntityEvents = append(events.EntityEvents, entityEvent(roEntity)) + ssm[k] = events + } + + for _, v := range ssm { + r = append(r, v) + } + + return r +} + +// entityEvent transforms a Span into an OTLP entityEvent. +func entityEvent(sd resourcesdk.ReadOnlyEntity) *enititespb.EntityEvent { + if sd == nil { + return nil + } + + s := &enititespb.EntityEvent{ + TimeUnixNano: uint64(sd.StartTime().UnixNano()), + EntityType: sd.Type(), + Id: KeyValues(sd.Id()), + Data: &enititespb.EntityEvent_EntityState{ + EntityState: &enititespb.EntityState{ + Attributes: KeyValues(sd.Attributes()), + DroppedAttributesCount: uint32(sd.DroppedAttributes()), + }, + }, + } + + return s +} diff --git a/exporters/otlp/otlpentity/internal/entitytransform/instrumentation.go b/exporters/otlp/otlpentity/internal/entitytransform/instrumentation.go new file mode 100644 index 00000000000..6c061650c65 --- /dev/null +++ b/exporters/otlp/otlpentity/internal/entitytransform/instrumentation.go @@ -0,0 +1,31 @@ +// 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 entitytransform // import "go.opentelemetry.io/otel/exporters/otlp/otlpentity/internal/entitytransform" + +import ( + commonpb "go.opentelemetry.io/proto/otlp/common/v1" + + "go.opentelemetry.io/otel/sdk/instrumentation" +) + +func InstrumentationScope(il instrumentation.Scope) *commonpb.InstrumentationScope { + if il == (instrumentation.Scope{}) { + return nil + } + return &commonpb.InstrumentationScope{ + Name: il.Name, + Version: il.Version, + } +} diff --git a/exporters/otlp/otlpentity/version.go b/exporters/otlp/otlpentity/version.go new file mode 100644 index 00000000000..a3a6cec568b --- /dev/null +++ b/exporters/otlp/otlpentity/version.go @@ -0,0 +1,20 @@ +// 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 otlpentity // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + +// Version is the current release version of the OpenTelemetry OTLP trace exporter in use. +func Version() string { + return "1.23.0-rc.1" +} diff --git a/sdk/resource/entity.go b/sdk/resource/entity.go new file mode 100644 index 00000000000..53d5b92229b --- /dev/null +++ b/sdk/resource/entity.go @@ -0,0 +1,811 @@ +// 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 resource // import "go.opentelemetry.io/otel/sdk/trace" + +import ( + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/entity" + "go.opentelemetry.io/otel/sdk/instrumentation" +) + +// ReadOnlyEntity allows reading information from the data structure underlying a +// trace.Span. It is used in places where reading information from a span is +// necessary but changing the span isn't necessary or allowed. +// +// Warning: methods may be added to this interface in minor releases. +type ReadOnlyEntity interface { + // StartTime returns the time the span started recording. + StartTime() time.Time + Type() string + Id() []attribute.KeyValue + // Attributes returns the defining attributes of the span. + // The order of the returned attributes is not guaranteed to be stable across invocations. + Attributes() []attribute.KeyValue + // InstrumentationScope returns information about the instrumentation + // scope that created the span. + InstrumentationScope() instrumentation.Scope + // InstrumentationLibrary returns information about the instrumentation + // library that created the span. + // Deprecated: please use InstrumentationScope instead. + InstrumentationLibrary() instrumentation.Library + // DroppedAttributes returns the number of attributes dropped by the span + // due to limits being reached. + DroppedAttributes() int + + // A private method to prevent users implementing the + // interface and so future additions to it will not + // violate compatibility. + private() +} + +type ReadWriteEntity interface { + entity.Entity + ReadOnlyEntity +} + +/* +// recordingSpan is an implementation of the OpenTelemetry Span API +// representing the individual component of a trace that is sampled. +type recordingSpan struct { + embedded.Span + + // mu protects the contents of this span. + mu sync.Mutex + + // parent holds the parent span of this span as a trace.SpanContext. + parent trace.SpanContext + + // spanKind represents the kind of this span as a trace.SpanKind. + spanKind trace.SpanKind + + // name is the name of this span. + name string + + // startTime is the time at which this span was started. + startTime time.Time + + // endTime is the time at which this span was ended. It contains the zero + // value of time.Time until the span is ended. + endTime time.Time + + // status is the status of this span. + status Status + + // childSpanCount holds the number of child spans created for this span. + childSpanCount int + + // spanContext holds the SpanContext of this span. + spanContext trace.SpanContext + + // attributes is a collection of user provided key/values. The collection + // is constrained by a configurable maximum held by the parent + // TracerProvider. When additional attributes are added after this maximum + // is reached these attributes the user is attempting to add are dropped. + // This dropped number of attributes is tracked and reported in the + // ReadOnlyEntity exported when the span ends. + attributes []attribute.KeyValue + droppedAttributes int + + // events are stored in FIFO queue capped by configured limit. + events evictedQueue + + // links are stored in FIFO queue capped by configured limit. + links evictedQueue + + // executionTracerTaskEnd ends the execution tracer span. + executionTracerTaskEnd func() + + // tracer is the SDK tracer that created this span. + tracer *tracer +} + +var ( + _ ReadWriteEntity = (*recordingSpan)(nil) + _ runtimeTracer = (*recordingSpan)(nil) +) + +// SpanContext returns the SpanContext of this span. +func (s *recordingSpan) SpanContext() trace.SpanContext { + if s == nil { + return trace.SpanContext{} + } + return s.spanContext +} + +// IsRecording returns if this span is being recorded. If this span has ended +// this will return false. +func (s *recordingSpan) IsRecording() bool { + if s == nil { + return false + } + s.mu.Lock() + defer s.mu.Unlock() + + return s.endTime.IsZero() +} + +// SetStatus sets the status of the Span in the form of a code and a +// description, overriding previous values set. The description is only +// included in the set status when the code is for an error. If this span is +// not being recorded than this method does nothing. +func (s *recordingSpan) SetStatus(code codes.Code, description string) { + if !s.IsRecording() { + return + } + s.mu.Lock() + defer s.mu.Unlock() + if s.status.Code > code { + return + } + + status := Status{Code: code} + if code == codes.Error { + status.Description = description + } + + s.status = status +} + +// ensureAttributesCapacity inlines functionality from slices.Grow +// so that we can avoid needing to import golang.org/x/exp for go1.20. +// Once support for go1.20 is dropped, we can use slices.Grow available since go1.21 instead. +// Tracking issue: https://github.com/open-telemetry/opentelemetry-go/issues/4819. +func (s *recordingSpan) ensureAttributesCapacity(minCapacity int) { + if n := minCapacity - cap(s.attributes); n > 0 { + s.attributes = append(s.attributes[:cap(s.attributes)], make([]attribute.KeyValue, n)...)[:len(s.attributes)] + } +} + +// SetAttributes sets attributes of this span. +// +// If a key from attributes already exists the value associated with that key +// will be overwritten with the value contained in attributes. +// +// If this span is not being recorded than this method does nothing. +// +// If adding attributes to the span would exceed the maximum amount of +// attributes the span is configured to have, the last added attributes will +// be dropped. +func (s *recordingSpan) SetAttributes(attributes ...attribute.KeyValue) { + if !s.IsRecording() { + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + limit := s.tracer.provider.spanLimits.AttributeCountLimit + if limit == 0 { + // No attributes allowed. + s.droppedAttributes += len(attributes) + return + } + + // If adding these attributes could exceed the capacity of s perform a + // de-duplication and truncation while adding to avoid over allocation. + if limit > 0 && len(s.attributes)+len(attributes) > limit { + s.addOverCapAttrs(limit, attributes) + return + } + + // Otherwise, add without deduplication. When attributes are read they + // will be deduplicated, optimizing the operation. + s.ensureAttributesCapacity(len(s.attributes) + len(attributes)) + for _, a := range attributes { + if !a.Valid() { + // Drop all invalid attributes. + s.droppedAttributes++ + continue + } + a = truncateAttr(s.tracer.provider.spanLimits.AttributeValueLengthLimit, a) + s.attributes = append(s.attributes, a) + } +} + +// addOverCapAttrs adds the attributes attrs to the span s while +// de-duplicating the attributes of s and attrs and dropping attributes that +// exceed the limit. +// +// This method assumes s.mu.Lock is held by the caller. +// +// This method should only be called when there is a possibility that adding +// attrs to s will exceed the limit. Otherwise, attrs should be added to s +// without checking for duplicates and all retrieval methods of the attributes +// for s will de-duplicate as needed. +// +// This method assumes limit is a value > 0. The argument should be validated +// by the caller. +func (s *recordingSpan) addOverCapAttrs(limit int, attrs []attribute.KeyValue) { + // In order to not allocate more capacity to s.attributes than needed, + // prune and truncate this addition of attributes while adding. + + // Do not set a capacity when creating this map. Benchmark testing has + // showed this to only add unused memory allocations in general use. + exists := make(map[attribute.Key]int) + s.dedupeAttrsFromRecord(&exists) + + // Now that s.attributes is deduplicated, adding unique attributes up to + // the capacity of s will not over allocate s.attributes. + if sum := len(attrs) + len(s.attributes); sum < limit { + // After support for go1.20 is dropped, simplify if-else to min(sum, limit). + s.ensureAttributesCapacity(sum) + } else { + s.ensureAttributesCapacity(limit) + } + for _, a := range attrs { + if !a.Valid() { + // Drop all invalid attributes. + s.droppedAttributes++ + continue + } + + if idx, ok := exists[a.Key]; ok { + // Perform all updates before dropping, even when at capacity. + s.attributes[idx] = a + continue + } + + if len(s.attributes) >= limit { + // Do not just drop all of the remaining attributes, make sure + // updates are checked and performed. + s.droppedAttributes++ + } else { + a = truncateAttr(s.tracer.provider.spanLimits.AttributeValueLengthLimit, a) + s.attributes = append(s.attributes, a) + exists[a.Key] = len(s.attributes) - 1 + } + } +} + +// truncateAttr returns a truncated version of attr. Only string and string +// slice attribute values are truncated. String values are truncated to at +// most a length of limit. Each string slice value is truncated in this fashion +// (the slice length itself is unaffected). +// +// No truncation is performed for a negative limit. +func truncateAttr(limit int, attr attribute.KeyValue) attribute.KeyValue { + if limit < 0 { + return attr + } + switch attr.Value.Type() { + case attribute.STRING: + if v := attr.Value.AsString(); len(v) > limit { + return attr.Key.String(safeTruncate(v, limit)) + } + case attribute.STRINGSLICE: + v := attr.Value.AsStringSlice() + for i := range v { + if len(v[i]) > limit { + v[i] = safeTruncate(v[i], limit) + } + } + return attr.Key.StringSlice(v) + } + return attr +} + +// safeTruncate truncates the string and guarantees valid UTF-8 is returned. +func safeTruncate(input string, limit int) string { + if trunc, ok := safeTruncateValidUTF8(input, limit); ok { + return trunc + } + trunc, _ := safeTruncateValidUTF8(strings.ToValidUTF8(input, ""), limit) + return trunc +} + +// safeTruncateValidUTF8 returns a copy of the input string safely truncated to +// limit. The truncation is ensured to occur at the bounds of complete UTF-8 +// characters. If invalid encoding of UTF-8 is encountered, input is returned +// with false, otherwise, the truncated input will be returned with true. +func safeTruncateValidUTF8(input string, limit int) (string, bool) { + for cnt := 0; cnt <= limit; { + r, size := utf8.DecodeRuneInString(input[cnt:]) + if r == utf8.RuneError { + return input, false + } + + if cnt+size > limit { + return input[:cnt], true + } + cnt += size + } + return input, true +} + +// End ends the span. This method does nothing if the span is already ended or +// is not being recorded. +// +// The only SpanOption currently supported is WithTimestamp which will set the +// end time for a Span's life-cycle. +// +// If this method is called while panicking an error event is added to the +// Span before ending it and the panic is continued. +func (s *recordingSpan) End(options ...trace.SpanEndOption) { + // Do not start by checking if the span is being recorded which requires + // acquiring a lock. Make a minimal check that the span is not nil. + if s == nil { + return + } + + // Store the end time as soon as possible to avoid artificially increasing + // the span's duration in case some operation below takes a while. + et := internal.MonotonicEndTime(s.startTime) + + // Do relative expensive check now that we have an end time and see if we + // need to do any more processing. + if !s.IsRecording() { + return + } + + config := trace.NewSpanEndConfig(options...) + if recovered := recover(); recovered != nil { + // Record but don't stop the panic. + defer panic(recovered) + opts := []trace.EventOption{ + trace.WithAttributes( + semconv.ExceptionType(typeStr(recovered)), + semconv.ExceptionMessage(fmt.Sprint(recovered)), + ), + } + + if config.StackTrace() { + opts = append( + opts, trace.WithAttributes( + semconv.ExceptionStacktrace(recordStackTrace()), + ), + ) + } + + s.addEvent(semconv.ExceptionEventName, opts...) + } + + if s.executionTracerTaskEnd != nil { + s.executionTracerTaskEnd() + } + + s.mu.Lock() + // Setting endTime to non-zero marks the span as ended and not recording. + if config.Timestamp().IsZero() { + s.endTime = et + } else { + s.endTime = config.Timestamp() + } + s.mu.Unlock() + + sps := s.tracer.provider.getSpanProcessors() + if len(sps) == 0 { + return + } + snap := s.snapshot() + for _, sp := range sps { + sp.sp.OnEnd(snap) + } +} + +// RecordError will record err as a span event for this span. An additional call to +// SetStatus is required if the Status of the Span should be set to Error, this method +// does not change the Span status. If this span is not being recorded or err is nil +// than this method does nothing. +func (s *recordingSpan) RecordError(err error, opts ...trace.EventOption) { + if s == nil || err == nil || !s.IsRecording() { + return + } + + opts = append( + opts, trace.WithAttributes( + semconv.ExceptionType(typeStr(err)), + semconv.ExceptionMessage(err.Error()), + ), + ) + + c := trace.NewEventConfig(opts...) + if c.StackTrace() { + opts = append( + opts, trace.WithAttributes( + semconv.ExceptionStacktrace(recordStackTrace()), + ), + ) + } + + s.addEvent(semconv.ExceptionEventName, opts...) +} + +func typeStr(i interface{}) string { + t := reflect.TypeOf(i) + if t.PkgPath() == "" && t.Name() == "" { + // Likely a builtin type. + return t.String() + } + return fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) +} + +func recordStackTrace() string { + stackTrace := make([]byte, 2048) + n := runtime.Stack(stackTrace, false) + + return string(stackTrace[0:n]) +} + +// AddEvent adds an event with the provided name and options. If this span is +// not being recorded than this method does nothing. +func (s *recordingSpan) AddEvent(name string, o ...trace.EventOption) { + if !s.IsRecording() { + return + } + s.addEvent(name, o...) +} + +func (s *recordingSpan) addEvent(name string, o ...trace.EventOption) { + c := trace.NewEventConfig(o...) + e := Event{Name: name, Attributes: c.Attributes(), Time: c.Timestamp()} + + // Discard attributes over limit. + limit := s.tracer.provider.spanLimits.AttributePerEventCountLimit + if limit == 0 { + // Drop all attributes. + e.DroppedAttributeCount = len(e.Attributes) + e.Attributes = nil + } else if limit > 0 && len(e.Attributes) > limit { + // Drop over capacity. + e.DroppedAttributeCount = len(e.Attributes) - limit + e.Attributes = e.Attributes[:limit] + } + + s.mu.Lock() + s.events.add(e) + s.mu.Unlock() +} + +// SetName sets the name of this span. If this span is not being recorded than +// this method does nothing. +func (s *recordingSpan) SetName(name string) { + if !s.IsRecording() { + return + } + + s.mu.Lock() + defer s.mu.Unlock() + s.name = name +} + +// Name returns the name of this span. +func (s *recordingSpan) Name() string { + s.mu.Lock() + defer s.mu.Unlock() + return s.name +} + +// Name returns the SpanContext of this span's parent span. +func (s *recordingSpan) Parent() trace.SpanContext { + s.mu.Lock() + defer s.mu.Unlock() + return s.parent +} + +// SpanKind returns the SpanKind of this span. +func (s *recordingSpan) SpanKind() trace.SpanKind { + s.mu.Lock() + defer s.mu.Unlock() + return s.spanKind +} + +// StartTime returns the time this span started. +func (s *recordingSpan) StartTime() time.Time { + s.mu.Lock() + defer s.mu.Unlock() + return s.startTime +} + +// EndTime returns the time this span ended. For spans that have not yet +// ended, the returned value will be the zero value of time.Time. +func (s *recordingSpan) EndTime() time.Time { + s.mu.Lock() + defer s.mu.Unlock() + return s.endTime +} + +// Attributes returns the attributes of this span. +// +// The order of the returned attributes is not guaranteed to be stable. +func (s *recordingSpan) Attributes() []attribute.KeyValue { + s.mu.Lock() + defer s.mu.Unlock() + s.dedupeAttrs() + return s.attributes +} + +// dedupeAttrs deduplicates the attributes of s to fit capacity. +// +// This method assumes s.mu.Lock is held by the caller. +func (s *recordingSpan) dedupeAttrs() { + // Do not set a capacity when creating this map. Benchmark testing has + // showed this to only add unused memory allocations in general use. + exists := make(map[attribute.Key]int) + s.dedupeAttrsFromRecord(&exists) +} + +// dedupeAttrsFromRecord deduplicates the attributes of s to fit capacity +// using record as the record of unique attribute keys to their index. +// +// This method assumes s.mu.Lock is held by the caller. +func (s *recordingSpan) dedupeAttrsFromRecord(record *map[attribute.Key]int) { + // Use the fact that slices share the same backing array. + unique := s.attributes[:0] + for _, a := range s.attributes { + if idx, ok := (*record)[a.Key]; ok { + unique[idx] = a + } else { + unique = append(unique, a) + (*record)[a.Key] = len(unique) - 1 + } + } + // s.attributes have element types of attribute.KeyValue. These types are + // not pointers and they themselves do not contain pointer fields, + // therefore the duplicate values do not need to be zeroed for them to be + // garbage collected. + s.attributes = unique +} + +// Links returns the links of this span. +func (s *recordingSpan) Links() []Link { + s.mu.Lock() + defer s.mu.Unlock() + if len(s.links.queue) == 0 { + return []Link{} + } + return s.interfaceArrayToLinksArray() +} + +// Events returns the events of this span. +func (s *recordingSpan) Events() []Event { + s.mu.Lock() + defer s.mu.Unlock() + if len(s.events.queue) == 0 { + return []Event{} + } + return s.interfaceArrayToEventArray() +} + +// Status returns the status of this span. +func (s *recordingSpan) Status() Status { + s.mu.Lock() + defer s.mu.Unlock() + return s.status +} + +// InstrumentationScope returns the instrumentation.Scope associated with +// the Tracer that created this span. +func (s *recordingSpan) InstrumentationScope() instrumentation.Scope { + s.mu.Lock() + defer s.mu.Unlock() + return s.tracer.instrumentationScope +} + +// InstrumentationLibrary returns the instrumentation.Library associated with +// the Tracer that created this span. +func (s *recordingSpan) InstrumentationLibrary() instrumentation.Library { + s.mu.Lock() + defer s.mu.Unlock() + return s.tracer.instrumentationScope +} + +// Resource returns the Resource associated with the Tracer that created this +// span. +func (s *recordingSpan) Resource() *resource.Resource { + s.mu.Lock() + defer s.mu.Unlock() + return s.tracer.provider.resource +} + +func (s *recordingSpan) addLink(link trace.Link) { + if !s.IsRecording() || !link.SpanContext.IsValid() { + return + } + + l := Link{SpanContext: link.SpanContext, Attributes: link.Attributes} + + // Discard attributes over limit. + limit := s.tracer.provider.spanLimits.AttributePerLinkCountLimit + if limit == 0 { + // Drop all attributes. + l.DroppedAttributeCount = len(l.Attributes) + l.Attributes = nil + } else if limit > 0 && len(l.Attributes) > limit { + l.DroppedAttributeCount = len(l.Attributes) - limit + l.Attributes = l.Attributes[:limit] + } + + s.mu.Lock() + s.links.add(l) + s.mu.Unlock() +} + +// DroppedAttributes returns the number of attributes dropped by the span +// due to limits being reached. +func (s *recordingSpan) DroppedAttributes() int { + s.mu.Lock() + defer s.mu.Unlock() + return s.droppedAttributes +} + +// DroppedLinks returns the number of links dropped by the span due to limits +// being reached. +func (s *recordingSpan) DroppedLinks() int { + s.mu.Lock() + defer s.mu.Unlock() + return s.links.droppedCount +} + +// DroppedEvents returns the number of events dropped by the span due to +// limits being reached. +func (s *recordingSpan) DroppedEvents() int { + s.mu.Lock() + defer s.mu.Unlock() + return s.events.droppedCount +} + +// ChildSpanCount returns the count of spans that consider the span a +// direct parent. +func (s *recordingSpan) ChildSpanCount() int { + s.mu.Lock() + defer s.mu.Unlock() + return s.childSpanCount +} + +// TracerProvider returns a trace.TracerProvider that can be used to generate +// additional Spans on the same telemetry pipeline as the current Span. +func (s *recordingSpan) TracerProvider() trace.TracerProvider { + return s.tracer.provider +} + +// snapshot creates a read-only copy of the current state of the span. +func (s *recordingSpan) snapshot() ReadOnlySpan { + var sd snapshot + s.mu.Lock() + defer s.mu.Unlock() + + sd.endTime = s.endTime + sd.instrumentationScope = s.tracer.instrumentationScope + sd.name = s.name + sd.parent = s.parent + sd.resource = s.tracer.provider.resource + sd.spanContext = s.spanContext + sd.spanKind = s.spanKind + sd.startTime = s.startTime + sd.status = s.status + sd.childSpanCount = s.childSpanCount + + if len(s.attributes) > 0 { + s.dedupeAttrs() + sd.attributes = s.attributes + } + sd.droppedAttributeCount = s.droppedAttributes + if len(s.events.queue) > 0 { + sd.events = s.interfaceArrayToEventArray() + sd.droppedEventCount = s.events.droppedCount + } + if len(s.links.queue) > 0 { + sd.links = s.interfaceArrayToLinksArray() + sd.droppedLinkCount = s.links.droppedCount + } + return &sd +} + +func (s *recordingSpan) interfaceArrayToLinksArray() []Link { + linkArr := make([]Link, 0) + for _, value := range s.links.queue { + linkArr = append(linkArr, value.(Link)) + } + return linkArr +} + +func (s *recordingSpan) interfaceArrayToEventArray() []Event { + eventArr := make([]Event, 0) + for _, value := range s.events.queue { + eventArr = append(eventArr, value.(Event)) + } + return eventArr +} + +func (s *recordingSpan) addChild() { + if !s.IsRecording() { + return + } + s.mu.Lock() + s.childSpanCount++ + s.mu.Unlock() +} + +func (*recordingSpan) private() {} + +// runtimeTrace starts a "runtime/trace".Task for the span and returns a +// context containing the task. +func (s *recordingSpan) runtimeTrace(ctx context.Context) context.Context { + if !rt.IsEnabled() { + // Avoid additional overhead if runtime/trace is not enabled. + return ctx + } + nctx, task := rt.NewTask(ctx, s.name) + + s.mu.Lock() + s.executionTracerTaskEnd = task.End + s.mu.Unlock() + + return nctx +} + +// nonRecordingSpan is a minimal implementation of the OpenTelemetry Span API +// that wraps a SpanContext. It performs no operations other than to return +// the wrapped SpanContext or TracerProvider that created it. +type nonRecordingSpan struct { + embedded.Span + + // tracer is the SDK tracer that created this span. + tracer *tracer + sc trace.SpanContext +} + +var _ trace.Span = nonRecordingSpan{} + +// SpanContext returns the wrapped SpanContext. +func (s nonRecordingSpan) SpanContext() trace.SpanContext { return s.sc } + +// IsRecording always returns false. +func (nonRecordingSpan) IsRecording() bool { return false } + +// SetStatus does nothing. +func (nonRecordingSpan) SetStatus(codes.Code, string) {} + +// SetError does nothing. +func (nonRecordingSpan) SetError(bool) {} + +// SetAttributes does nothing. +func (nonRecordingSpan) SetAttributes(...attribute.KeyValue) {} + +// End does nothing. +func (nonRecordingSpan) End(...trace.SpanEndOption) {} + +// RecordError does nothing. +func (nonRecordingSpan) RecordError(error, ...trace.EventOption) {} + +// AddEvent does nothing. +func (nonRecordingSpan) AddEvent(string, ...trace.EventOption) {} + +// SetName does nothing. +func (nonRecordingSpan) SetName(string) {} + +// TracerProvider returns the trace.TracerProvider that provided the Tracer +// that created this span. +func (s nonRecordingSpan) TracerProvider() trace.TracerProvider { return s.tracer.provider } + +func isRecording(s SamplingResult) bool { + return s.Decision == RecordOnly || s.Decision == RecordAndSample +} + +func isSampled(s SamplingResult) bool { + return s.Decision == RecordAndSample +} + +// Status is the classified state of a Span. +type Status struct { + // Code is an identifier of a Spans state classification. + Code codes.Code + // Description is a user hint about why that status was set. It is only + // applicable when Code is Error. + Description string +} +*/ diff --git a/sdk/resource/entity_exporter.go b/sdk/resource/entity_exporter.go new file mode 100644 index 00000000000..066d9bbf25b --- /dev/null +++ b/sdk/resource/entity_exporter.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 resource // import "go.opentelemetry.io/otel/sdk/entity" + +import ( + "context" +) + +type EntityExporter interface { + ExportEntities(ctx context.Context, spans []ReadOnlyEntity) error + Shutdown(ctx context.Context) error +} From 1511afe2bdcc186f60aa48d1c5e0eb819d98ed51 Mon Sep 17 00:00:00 2001 From: Tigran Najaryan Date: Wed, 21 Feb 2024 14:17:45 -0500 Subject: [PATCH 10/11] Revert "Begin adding entity exporter" This reverts commit ebf40545e7be544973c0bbd137f80b9252eeecbd. --- entity/entity.go | 18 - exporters/otlp/otlpentity/clients.go | 54 -- exporters/otlp/otlpentity/exporter.go | 120 --- exporters/otlp/otlpentity/go.mod | 28 - exporters/otlp/otlpentity/go.sum | 14 - .../internal/entitytransform/attribute.go | 159 ---- .../internal/entitytransform/entity.go | 84 -- .../entitytransform/instrumentation.go | 31 - exporters/otlp/otlpentity/version.go | 20 - sdk/resource/entity.go | 811 ------------------ sdk/resource/entity_exporter.go | 24 - 11 files changed, 1363 deletions(-) delete mode 100644 exporters/otlp/otlpentity/clients.go delete mode 100644 exporters/otlp/otlpentity/exporter.go delete mode 100644 exporters/otlp/otlpentity/go.mod delete mode 100644 exporters/otlp/otlpentity/go.sum delete mode 100644 exporters/otlp/otlpentity/internal/entitytransform/attribute.go delete mode 100644 exporters/otlp/otlpentity/internal/entitytransform/entity.go delete mode 100644 exporters/otlp/otlpentity/internal/entitytransform/instrumentation.go delete mode 100644 exporters/otlp/otlpentity/version.go delete mode 100644 sdk/resource/entity.go delete mode 100644 sdk/resource/entity_exporter.go diff --git a/entity/entity.go b/entity/entity.go index 9280f216860..fb790e0cf8a 100644 --- a/entity/entity.go +++ b/entity/entity.go @@ -15,27 +15,9 @@ package entity // import "go.opentelemetry.io/otel/entity" import ( - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/entity/embedded" ) -type Entity interface { - embedded.Entity - - // IsRecording returns the recording state of the Span. It will return - // true if the Span is active and events can be recorded. - IsRecording() bool - - // SetAttributes sets kv as attributes of the Span. If a key from kv - // already exists for an attribute of the Span it will be overwritten with - // the value contained in kv. - SetAttributes(kv ...attribute.KeyValue) - - // TracerProvider returns a TracerProvider that can be used to generate - // additional Spans on the same telemetry pipeline as the current Span. - EntityEmitterProvider() EntityEmitterProvider -} - // EntityEmitter is the creator of Spans. // // Warning: Methods may be added to this interface in minor releases. See diff --git a/exporters/otlp/otlpentity/clients.go b/exporters/otlp/otlpentity/clients.go deleted file mode 100644 index bb582d1bf29..00000000000 --- a/exporters/otlp/otlpentity/clients.go +++ /dev/null @@ -1,54 +0,0 @@ -// 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 otlpentity // import "go.opentelemetry.io/otel/exporters/otlp/otlpentity" - -import ( - "context" - - entitiespb "go.opentelemetry.io/proto/otlp/entities/v1" -) - -// Client manages connections to the collector, handles the -// transformation of data into wire format, and the transmission of that -// data to the collector. -type Client interface { - // DO NOT CHANGE: any modification will not be backwards compatible and - // must never be done outside of a new major release. - - // Start should establish connection(s) to endpoint(s). It is - // called just once by the exporter, so the implementation - // does not need to worry about idempotence and locking. - Start(ctx context.Context) error - // DO NOT CHANGE: any modification will not be backwards compatible and - // must never be done outside of a new major release. - - // Stop should close the connections. The function is called - // only once by the exporter, so the implementation does not - // need to worry about idempotence, but it may be called - // concurrently with UploadEntitys, so proper - // locking is required. The function serves as a - // synchronization point - after the function returns, the - // process of closing connections is assumed to be finished. - Stop(ctx context.Context) error - // DO NOT CHANGE: any modification will not be backwards compatible and - // must never be done outside of a new major release. - - // UploadEntitys should transform the passed entitys to the wire - // format and send it to the collector. May be called - // concurrently. - UploadEntities(ctx context.Context, entities []*entitiespb.ScopeEntities) error - // DO NOT CHANGE: any modification will not be backwards compatible and - // must never be done outside of a new major release. -} diff --git a/exporters/otlp/otlpentity/exporter.go b/exporters/otlp/otlpentity/exporter.go deleted file mode 100644 index f4c7a6a31a1..00000000000 --- a/exporters/otlp/otlpentity/exporter.go +++ /dev/null @@ -1,120 +0,0 @@ -// 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 otlpentity // import "go.opentelemetry.io/otel/exporters/otlp/otlpentity" - -import ( - "context" - "errors" - "fmt" - "sync" - - "go.opentelemetry.io/otel/exporters/otlp/otlpentity/internal/entitytransform" - "go.opentelemetry.io/otel/sdk/resource" -) - -var errAlreadyStarted = errors.New("already started") - -// Exporter exports entity data in the OTLP wire format. -type Exporter struct { - client Client - - mu sync.RWMutex - started bool - - startOnce sync.Once - stopOnce sync.Once -} - -// ExportSpans exports a batch of spans. -func (e *Exporter) ExportEntities(ctx context.Context, entities []resource.ReadOnlyEntity) error { - protoSpans := entitytransform.Entities(entities) - if len(protoSpans) == 0 { - return nil - } - - err := e.client.UploadEntities(ctx, protoSpans) - if err != nil { - return fmt.Errorf("entitys export: %w", err) - } - return nil -} - -// Start establishes a connection to the receiving endpoint. -func (e *Exporter) Start(ctx context.Context) error { - err := errAlreadyStarted - e.startOnce.Do( - func() { - e.mu.Lock() - e.started = true - e.mu.Unlock() - err = e.client.Start(ctx) - }, - ) - - return err -} - -// Shutdown flushes all exports and closes all connections to the receiving endpoint. -func (e *Exporter) Shutdown(ctx context.Context) error { - e.mu.RLock() - started := e.started - e.mu.RUnlock() - - if !started { - return nil - } - - var err error - - e.stopOnce.Do( - func() { - err = e.client.Stop(ctx) - e.mu.Lock() - e.started = false - e.mu.Unlock() - }, - ) - - return err -} - -var _ resource.EntityExporter = (*Exporter)(nil) - -// New constructs a new Exporter and starts it. -func New(ctx context.Context, client Client) (*Exporter, error) { - exp := NewUnstarted(client) - if err := exp.Start(ctx); err != nil { - return nil, err - } - return exp, nil -} - -// NewUnstarted constructs a new Exporter and does not start it. -func NewUnstarted(client Client) *Exporter { - return &Exporter{ - client: client, - } -} - -// MarshalLog is the marshaling function used by the logging system to represent this Exporter. -func (e *Exporter) MarshalLog() interface{} { - return struct { - Type string - Client Client - }{ - Type: "otlpentity", - Client: e.client, - } -} diff --git a/exporters/otlp/otlpentity/go.mod b/exporters/otlp/otlpentity/go.mod deleted file mode 100644 index c7e1f888e4c..00000000000 --- a/exporters/otlp/otlpentity/go.mod +++ /dev/null @@ -1,28 +0,0 @@ -module go.opentelemetry.io/otel/exporters/otlp/otlpentity - -go 1.20 - -require ( - go.opentelemetry.io/otel v1.23.1 - go.opentelemetry.io/otel/sdk v1.23.1 - go.opentelemetry.io/proto/otlp v1.1.0 -) - -require ( - github.com/go-logr/logr v1.4.1 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - go.opentelemetry.io/otel/metric v1.23.1 // indirect - go.opentelemetry.io/otel/trace v1.23.0-rc.1 // indirect - golang.org/x/sys v0.16.0 // indirect - google.golang.org/protobuf v1.32.0 // indirect -) - -replace go.opentelemetry.io/otel => ../../.. - -replace go.opentelemetry.io/otel/sdk => ../../../sdk - -replace go.opentelemetry.io/otel/trace => ../../../trace - -replace go.opentelemetry.io/otel/metric => ../../../metric - -replace go.opentelemetry.io/proto/otlp => ../../../../opentelemetry-proto-go/otlp/ diff --git a/exporters/otlp/otlpentity/go.sum b/exporters/otlp/otlpentity/go.sum deleted file mode 100644 index a33dfad4ef1..00000000000 --- a/exporters/otlp/otlpentity/go.sum +++ /dev/null @@ -1,14 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/exporters/otlp/otlpentity/internal/entitytransform/attribute.go b/exporters/otlp/otlpentity/internal/entitytransform/attribute.go deleted file mode 100644 index 2fad75cd343..00000000000 --- a/exporters/otlp/otlpentity/internal/entitytransform/attribute.go +++ /dev/null @@ -1,159 +0,0 @@ -// 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 entitytransform // import "go.opentelemetry.io/otel/exporters/otlp/otlpentity/internal/entitytransform" - -import ( - commonpb "go.opentelemetry.io/proto/otlp/common/v1" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/resource" -) - -// KeyValues transforms a slice of attribute KeyValues into OTLP key-values. -func KeyValues(attrs []attribute.KeyValue) []*commonpb.KeyValue { - if len(attrs) == 0 { - return nil - } - - out := make([]*commonpb.KeyValue, 0, len(attrs)) - for _, kv := range attrs { - out = append(out, KeyValue(kv)) - } - return out -} - -// Iterator transforms an attribute iterator into OTLP key-values. -func Iterator(iter attribute.Iterator) []*commonpb.KeyValue { - l := iter.Len() - if l == 0 { - return nil - } - - out := make([]*commonpb.KeyValue, 0, l) - for iter.Next() { - out = append(out, KeyValue(iter.Attribute())) - } - return out -} - -// ResourceAttributes transforms a Resource OTLP key-values. -func ResourceAttributes(res *resource.Resource) []*commonpb.KeyValue { - return Iterator(res.Iter()) -} - -// KeyValue transforms an attribute KeyValue into an OTLP key-value. -func KeyValue(kv attribute.KeyValue) *commonpb.KeyValue { - return &commonpb.KeyValue{Key: string(kv.Key), Value: Value(kv.Value)} -} - -// Value transforms an attribute Value into an OTLP AnyValue. -func Value(v attribute.Value) *commonpb.AnyValue { - av := new(commonpb.AnyValue) - switch v.Type() { - case attribute.BOOL: - av.Value = &commonpb.AnyValue_BoolValue{ - BoolValue: v.AsBool(), - } - case attribute.BOOLSLICE: - av.Value = &commonpb.AnyValue_ArrayValue{ - ArrayValue: &commonpb.ArrayValue{ - Values: boolSliceValues(v.AsBoolSlice()), - }, - } - case attribute.INT64: - av.Value = &commonpb.AnyValue_IntValue{ - IntValue: v.AsInt64(), - } - case attribute.INT64SLICE: - av.Value = &commonpb.AnyValue_ArrayValue{ - ArrayValue: &commonpb.ArrayValue{ - Values: int64SliceValues(v.AsInt64Slice()), - }, - } - case attribute.FLOAT64: - av.Value = &commonpb.AnyValue_DoubleValue{ - DoubleValue: v.AsFloat64(), - } - case attribute.FLOAT64SLICE: - av.Value = &commonpb.AnyValue_ArrayValue{ - ArrayValue: &commonpb.ArrayValue{ - Values: float64SliceValues(v.AsFloat64Slice()), - }, - } - case attribute.STRING: - av.Value = &commonpb.AnyValue_StringValue{ - StringValue: v.AsString(), - } - case attribute.STRINGSLICE: - av.Value = &commonpb.AnyValue_ArrayValue{ - ArrayValue: &commonpb.ArrayValue{ - Values: stringSliceValues(v.AsStringSlice()), - }, - } - default: - av.Value = &commonpb.AnyValue_StringValue{ - StringValue: "INVALID", - } - } - return av -} - -func boolSliceValues(vals []bool) []*commonpb.AnyValue { - converted := make([]*commonpb.AnyValue, len(vals)) - for i, v := range vals { - converted[i] = &commonpb.AnyValue{ - Value: &commonpb.AnyValue_BoolValue{ - BoolValue: v, - }, - } - } - return converted -} - -func int64SliceValues(vals []int64) []*commonpb.AnyValue { - converted := make([]*commonpb.AnyValue, len(vals)) - for i, v := range vals { - converted[i] = &commonpb.AnyValue{ - Value: &commonpb.AnyValue_IntValue{ - IntValue: v, - }, - } - } - return converted -} - -func float64SliceValues(vals []float64) []*commonpb.AnyValue { - converted := make([]*commonpb.AnyValue, len(vals)) - for i, v := range vals { - converted[i] = &commonpb.AnyValue{ - Value: &commonpb.AnyValue_DoubleValue{ - DoubleValue: v, - }, - } - } - return converted -} - -func stringSliceValues(vals []string) []*commonpb.AnyValue { - converted := make([]*commonpb.AnyValue, len(vals)) - for i, v := range vals { - converted[i] = &commonpb.AnyValue{ - Value: &commonpb.AnyValue_StringValue{ - StringValue: v, - }, - } - } - return converted -} diff --git a/exporters/otlp/otlpentity/internal/entitytransform/entity.go b/exporters/otlp/otlpentity/internal/entitytransform/entity.go deleted file mode 100644 index 6e3f8529832..00000000000 --- a/exporters/otlp/otlpentity/internal/entitytransform/entity.go +++ /dev/null @@ -1,84 +0,0 @@ -// 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 entitytransform // import "go.opentelemetry.io/otel/exporters/otlp/otlpentity/internal/entitytransform" - -import ( - enititespb "go.opentelemetry.io/proto/otlp/entities/v1" - - resourcesdk "go.opentelemetry.io/otel/sdk/resource" - - "go.opentelemetry.io/otel/sdk/instrumentation" -) - -// Entities transforms a slice of OpenTelemetry spans into a slice of OTLP -// ResourceSpans. -func Entities(roEntities []resourcesdk.ReadOnlyEntity) (r []*enititespb.ScopeEntities) { - if len(roEntities) == 0 { - return nil - } - - type key struct { - is instrumentation.Scope - } - ssm := make(map[key]*enititespb.ScopeEntities) - - for _, roEntity := range roEntities { - if roEntity == nil { - continue - } - - k := key{ - is: roEntity.InstrumentationScope(), - } - events, iOk := ssm[k] - if !iOk { - // Either the resource or instrumentation scope were unknown. - events = &enititespb.ScopeEntities{ - Scope: InstrumentationScope(roEntity.InstrumentationScope()), - EntityEvents: []*enititespb.EntityEvent{}, - SchemaUrl: roEntity.InstrumentationScope().SchemaURL, - } - } - events.EntityEvents = append(events.EntityEvents, entityEvent(roEntity)) - ssm[k] = events - } - - for _, v := range ssm { - r = append(r, v) - } - - return r -} - -// entityEvent transforms a Span into an OTLP entityEvent. -func entityEvent(sd resourcesdk.ReadOnlyEntity) *enititespb.EntityEvent { - if sd == nil { - return nil - } - - s := &enititespb.EntityEvent{ - TimeUnixNano: uint64(sd.StartTime().UnixNano()), - EntityType: sd.Type(), - Id: KeyValues(sd.Id()), - Data: &enititespb.EntityEvent_EntityState{ - EntityState: &enititespb.EntityState{ - Attributes: KeyValues(sd.Attributes()), - DroppedAttributesCount: uint32(sd.DroppedAttributes()), - }, - }, - } - - return s -} diff --git a/exporters/otlp/otlpentity/internal/entitytransform/instrumentation.go b/exporters/otlp/otlpentity/internal/entitytransform/instrumentation.go deleted file mode 100644 index 6c061650c65..00000000000 --- a/exporters/otlp/otlpentity/internal/entitytransform/instrumentation.go +++ /dev/null @@ -1,31 +0,0 @@ -// 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 entitytransform // import "go.opentelemetry.io/otel/exporters/otlp/otlpentity/internal/entitytransform" - -import ( - commonpb "go.opentelemetry.io/proto/otlp/common/v1" - - "go.opentelemetry.io/otel/sdk/instrumentation" -) - -func InstrumentationScope(il instrumentation.Scope) *commonpb.InstrumentationScope { - if il == (instrumentation.Scope{}) { - return nil - } - return &commonpb.InstrumentationScope{ - Name: il.Name, - Version: il.Version, - } -} diff --git a/exporters/otlp/otlpentity/version.go b/exporters/otlp/otlpentity/version.go deleted file mode 100644 index a3a6cec568b..00000000000 --- a/exporters/otlp/otlpentity/version.go +++ /dev/null @@ -1,20 +0,0 @@ -// 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 otlpentity // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace" - -// Version is the current release version of the OpenTelemetry OTLP trace exporter in use. -func Version() string { - return "1.23.0-rc.1" -} diff --git a/sdk/resource/entity.go b/sdk/resource/entity.go deleted file mode 100644 index 53d5b92229b..00000000000 --- a/sdk/resource/entity.go +++ /dev/null @@ -1,811 +0,0 @@ -// 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 resource // import "go.opentelemetry.io/otel/sdk/trace" - -import ( - "time" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/entity" - "go.opentelemetry.io/otel/sdk/instrumentation" -) - -// ReadOnlyEntity allows reading information from the data structure underlying a -// trace.Span. It is used in places where reading information from a span is -// necessary but changing the span isn't necessary or allowed. -// -// Warning: methods may be added to this interface in minor releases. -type ReadOnlyEntity interface { - // StartTime returns the time the span started recording. - StartTime() time.Time - Type() string - Id() []attribute.KeyValue - // Attributes returns the defining attributes of the span. - // The order of the returned attributes is not guaranteed to be stable across invocations. - Attributes() []attribute.KeyValue - // InstrumentationScope returns information about the instrumentation - // scope that created the span. - InstrumentationScope() instrumentation.Scope - // InstrumentationLibrary returns information about the instrumentation - // library that created the span. - // Deprecated: please use InstrumentationScope instead. - InstrumentationLibrary() instrumentation.Library - // DroppedAttributes returns the number of attributes dropped by the span - // due to limits being reached. - DroppedAttributes() int - - // A private method to prevent users implementing the - // interface and so future additions to it will not - // violate compatibility. - private() -} - -type ReadWriteEntity interface { - entity.Entity - ReadOnlyEntity -} - -/* -// recordingSpan is an implementation of the OpenTelemetry Span API -// representing the individual component of a trace that is sampled. -type recordingSpan struct { - embedded.Span - - // mu protects the contents of this span. - mu sync.Mutex - - // parent holds the parent span of this span as a trace.SpanContext. - parent trace.SpanContext - - // spanKind represents the kind of this span as a trace.SpanKind. - spanKind trace.SpanKind - - // name is the name of this span. - name string - - // startTime is the time at which this span was started. - startTime time.Time - - // endTime is the time at which this span was ended. It contains the zero - // value of time.Time until the span is ended. - endTime time.Time - - // status is the status of this span. - status Status - - // childSpanCount holds the number of child spans created for this span. - childSpanCount int - - // spanContext holds the SpanContext of this span. - spanContext trace.SpanContext - - // attributes is a collection of user provided key/values. The collection - // is constrained by a configurable maximum held by the parent - // TracerProvider. When additional attributes are added after this maximum - // is reached these attributes the user is attempting to add are dropped. - // This dropped number of attributes is tracked and reported in the - // ReadOnlyEntity exported when the span ends. - attributes []attribute.KeyValue - droppedAttributes int - - // events are stored in FIFO queue capped by configured limit. - events evictedQueue - - // links are stored in FIFO queue capped by configured limit. - links evictedQueue - - // executionTracerTaskEnd ends the execution tracer span. - executionTracerTaskEnd func() - - // tracer is the SDK tracer that created this span. - tracer *tracer -} - -var ( - _ ReadWriteEntity = (*recordingSpan)(nil) - _ runtimeTracer = (*recordingSpan)(nil) -) - -// SpanContext returns the SpanContext of this span. -func (s *recordingSpan) SpanContext() trace.SpanContext { - if s == nil { - return trace.SpanContext{} - } - return s.spanContext -} - -// IsRecording returns if this span is being recorded. If this span has ended -// this will return false. -func (s *recordingSpan) IsRecording() bool { - if s == nil { - return false - } - s.mu.Lock() - defer s.mu.Unlock() - - return s.endTime.IsZero() -} - -// SetStatus sets the status of the Span in the form of a code and a -// description, overriding previous values set. The description is only -// included in the set status when the code is for an error. If this span is -// not being recorded than this method does nothing. -func (s *recordingSpan) SetStatus(code codes.Code, description string) { - if !s.IsRecording() { - return - } - s.mu.Lock() - defer s.mu.Unlock() - if s.status.Code > code { - return - } - - status := Status{Code: code} - if code == codes.Error { - status.Description = description - } - - s.status = status -} - -// ensureAttributesCapacity inlines functionality from slices.Grow -// so that we can avoid needing to import golang.org/x/exp for go1.20. -// Once support for go1.20 is dropped, we can use slices.Grow available since go1.21 instead. -// Tracking issue: https://github.com/open-telemetry/opentelemetry-go/issues/4819. -func (s *recordingSpan) ensureAttributesCapacity(minCapacity int) { - if n := minCapacity - cap(s.attributes); n > 0 { - s.attributes = append(s.attributes[:cap(s.attributes)], make([]attribute.KeyValue, n)...)[:len(s.attributes)] - } -} - -// SetAttributes sets attributes of this span. -// -// If a key from attributes already exists the value associated with that key -// will be overwritten with the value contained in attributes. -// -// If this span is not being recorded than this method does nothing. -// -// If adding attributes to the span would exceed the maximum amount of -// attributes the span is configured to have, the last added attributes will -// be dropped. -func (s *recordingSpan) SetAttributes(attributes ...attribute.KeyValue) { - if !s.IsRecording() { - return - } - - s.mu.Lock() - defer s.mu.Unlock() - - limit := s.tracer.provider.spanLimits.AttributeCountLimit - if limit == 0 { - // No attributes allowed. - s.droppedAttributes += len(attributes) - return - } - - // If adding these attributes could exceed the capacity of s perform a - // de-duplication and truncation while adding to avoid over allocation. - if limit > 0 && len(s.attributes)+len(attributes) > limit { - s.addOverCapAttrs(limit, attributes) - return - } - - // Otherwise, add without deduplication. When attributes are read they - // will be deduplicated, optimizing the operation. - s.ensureAttributesCapacity(len(s.attributes) + len(attributes)) - for _, a := range attributes { - if !a.Valid() { - // Drop all invalid attributes. - s.droppedAttributes++ - continue - } - a = truncateAttr(s.tracer.provider.spanLimits.AttributeValueLengthLimit, a) - s.attributes = append(s.attributes, a) - } -} - -// addOverCapAttrs adds the attributes attrs to the span s while -// de-duplicating the attributes of s and attrs and dropping attributes that -// exceed the limit. -// -// This method assumes s.mu.Lock is held by the caller. -// -// This method should only be called when there is a possibility that adding -// attrs to s will exceed the limit. Otherwise, attrs should be added to s -// without checking for duplicates and all retrieval methods of the attributes -// for s will de-duplicate as needed. -// -// This method assumes limit is a value > 0. The argument should be validated -// by the caller. -func (s *recordingSpan) addOverCapAttrs(limit int, attrs []attribute.KeyValue) { - // In order to not allocate more capacity to s.attributes than needed, - // prune and truncate this addition of attributes while adding. - - // Do not set a capacity when creating this map. Benchmark testing has - // showed this to only add unused memory allocations in general use. - exists := make(map[attribute.Key]int) - s.dedupeAttrsFromRecord(&exists) - - // Now that s.attributes is deduplicated, adding unique attributes up to - // the capacity of s will not over allocate s.attributes. - if sum := len(attrs) + len(s.attributes); sum < limit { - // After support for go1.20 is dropped, simplify if-else to min(sum, limit). - s.ensureAttributesCapacity(sum) - } else { - s.ensureAttributesCapacity(limit) - } - for _, a := range attrs { - if !a.Valid() { - // Drop all invalid attributes. - s.droppedAttributes++ - continue - } - - if idx, ok := exists[a.Key]; ok { - // Perform all updates before dropping, even when at capacity. - s.attributes[idx] = a - continue - } - - if len(s.attributes) >= limit { - // Do not just drop all of the remaining attributes, make sure - // updates are checked and performed. - s.droppedAttributes++ - } else { - a = truncateAttr(s.tracer.provider.spanLimits.AttributeValueLengthLimit, a) - s.attributes = append(s.attributes, a) - exists[a.Key] = len(s.attributes) - 1 - } - } -} - -// truncateAttr returns a truncated version of attr. Only string and string -// slice attribute values are truncated. String values are truncated to at -// most a length of limit. Each string slice value is truncated in this fashion -// (the slice length itself is unaffected). -// -// No truncation is performed for a negative limit. -func truncateAttr(limit int, attr attribute.KeyValue) attribute.KeyValue { - if limit < 0 { - return attr - } - switch attr.Value.Type() { - case attribute.STRING: - if v := attr.Value.AsString(); len(v) > limit { - return attr.Key.String(safeTruncate(v, limit)) - } - case attribute.STRINGSLICE: - v := attr.Value.AsStringSlice() - for i := range v { - if len(v[i]) > limit { - v[i] = safeTruncate(v[i], limit) - } - } - return attr.Key.StringSlice(v) - } - return attr -} - -// safeTruncate truncates the string and guarantees valid UTF-8 is returned. -func safeTruncate(input string, limit int) string { - if trunc, ok := safeTruncateValidUTF8(input, limit); ok { - return trunc - } - trunc, _ := safeTruncateValidUTF8(strings.ToValidUTF8(input, ""), limit) - return trunc -} - -// safeTruncateValidUTF8 returns a copy of the input string safely truncated to -// limit. The truncation is ensured to occur at the bounds of complete UTF-8 -// characters. If invalid encoding of UTF-8 is encountered, input is returned -// with false, otherwise, the truncated input will be returned with true. -func safeTruncateValidUTF8(input string, limit int) (string, bool) { - for cnt := 0; cnt <= limit; { - r, size := utf8.DecodeRuneInString(input[cnt:]) - if r == utf8.RuneError { - return input, false - } - - if cnt+size > limit { - return input[:cnt], true - } - cnt += size - } - return input, true -} - -// End ends the span. This method does nothing if the span is already ended or -// is not being recorded. -// -// The only SpanOption currently supported is WithTimestamp which will set the -// end time for a Span's life-cycle. -// -// If this method is called while panicking an error event is added to the -// Span before ending it and the panic is continued. -func (s *recordingSpan) End(options ...trace.SpanEndOption) { - // Do not start by checking if the span is being recorded which requires - // acquiring a lock. Make a minimal check that the span is not nil. - if s == nil { - return - } - - // Store the end time as soon as possible to avoid artificially increasing - // the span's duration in case some operation below takes a while. - et := internal.MonotonicEndTime(s.startTime) - - // Do relative expensive check now that we have an end time and see if we - // need to do any more processing. - if !s.IsRecording() { - return - } - - config := trace.NewSpanEndConfig(options...) - if recovered := recover(); recovered != nil { - // Record but don't stop the panic. - defer panic(recovered) - opts := []trace.EventOption{ - trace.WithAttributes( - semconv.ExceptionType(typeStr(recovered)), - semconv.ExceptionMessage(fmt.Sprint(recovered)), - ), - } - - if config.StackTrace() { - opts = append( - opts, trace.WithAttributes( - semconv.ExceptionStacktrace(recordStackTrace()), - ), - ) - } - - s.addEvent(semconv.ExceptionEventName, opts...) - } - - if s.executionTracerTaskEnd != nil { - s.executionTracerTaskEnd() - } - - s.mu.Lock() - // Setting endTime to non-zero marks the span as ended and not recording. - if config.Timestamp().IsZero() { - s.endTime = et - } else { - s.endTime = config.Timestamp() - } - s.mu.Unlock() - - sps := s.tracer.provider.getSpanProcessors() - if len(sps) == 0 { - return - } - snap := s.snapshot() - for _, sp := range sps { - sp.sp.OnEnd(snap) - } -} - -// RecordError will record err as a span event for this span. An additional call to -// SetStatus is required if the Status of the Span should be set to Error, this method -// does not change the Span status. If this span is not being recorded or err is nil -// than this method does nothing. -func (s *recordingSpan) RecordError(err error, opts ...trace.EventOption) { - if s == nil || err == nil || !s.IsRecording() { - return - } - - opts = append( - opts, trace.WithAttributes( - semconv.ExceptionType(typeStr(err)), - semconv.ExceptionMessage(err.Error()), - ), - ) - - c := trace.NewEventConfig(opts...) - if c.StackTrace() { - opts = append( - opts, trace.WithAttributes( - semconv.ExceptionStacktrace(recordStackTrace()), - ), - ) - } - - s.addEvent(semconv.ExceptionEventName, opts...) -} - -func typeStr(i interface{}) string { - t := reflect.TypeOf(i) - if t.PkgPath() == "" && t.Name() == "" { - // Likely a builtin type. - return t.String() - } - return fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) -} - -func recordStackTrace() string { - stackTrace := make([]byte, 2048) - n := runtime.Stack(stackTrace, false) - - return string(stackTrace[0:n]) -} - -// AddEvent adds an event with the provided name and options. If this span is -// not being recorded than this method does nothing. -func (s *recordingSpan) AddEvent(name string, o ...trace.EventOption) { - if !s.IsRecording() { - return - } - s.addEvent(name, o...) -} - -func (s *recordingSpan) addEvent(name string, o ...trace.EventOption) { - c := trace.NewEventConfig(o...) - e := Event{Name: name, Attributes: c.Attributes(), Time: c.Timestamp()} - - // Discard attributes over limit. - limit := s.tracer.provider.spanLimits.AttributePerEventCountLimit - if limit == 0 { - // Drop all attributes. - e.DroppedAttributeCount = len(e.Attributes) - e.Attributes = nil - } else if limit > 0 && len(e.Attributes) > limit { - // Drop over capacity. - e.DroppedAttributeCount = len(e.Attributes) - limit - e.Attributes = e.Attributes[:limit] - } - - s.mu.Lock() - s.events.add(e) - s.mu.Unlock() -} - -// SetName sets the name of this span. If this span is not being recorded than -// this method does nothing. -func (s *recordingSpan) SetName(name string) { - if !s.IsRecording() { - return - } - - s.mu.Lock() - defer s.mu.Unlock() - s.name = name -} - -// Name returns the name of this span. -func (s *recordingSpan) Name() string { - s.mu.Lock() - defer s.mu.Unlock() - return s.name -} - -// Name returns the SpanContext of this span's parent span. -func (s *recordingSpan) Parent() trace.SpanContext { - s.mu.Lock() - defer s.mu.Unlock() - return s.parent -} - -// SpanKind returns the SpanKind of this span. -func (s *recordingSpan) SpanKind() trace.SpanKind { - s.mu.Lock() - defer s.mu.Unlock() - return s.spanKind -} - -// StartTime returns the time this span started. -func (s *recordingSpan) StartTime() time.Time { - s.mu.Lock() - defer s.mu.Unlock() - return s.startTime -} - -// EndTime returns the time this span ended. For spans that have not yet -// ended, the returned value will be the zero value of time.Time. -func (s *recordingSpan) EndTime() time.Time { - s.mu.Lock() - defer s.mu.Unlock() - return s.endTime -} - -// Attributes returns the attributes of this span. -// -// The order of the returned attributes is not guaranteed to be stable. -func (s *recordingSpan) Attributes() []attribute.KeyValue { - s.mu.Lock() - defer s.mu.Unlock() - s.dedupeAttrs() - return s.attributes -} - -// dedupeAttrs deduplicates the attributes of s to fit capacity. -// -// This method assumes s.mu.Lock is held by the caller. -func (s *recordingSpan) dedupeAttrs() { - // Do not set a capacity when creating this map. Benchmark testing has - // showed this to only add unused memory allocations in general use. - exists := make(map[attribute.Key]int) - s.dedupeAttrsFromRecord(&exists) -} - -// dedupeAttrsFromRecord deduplicates the attributes of s to fit capacity -// using record as the record of unique attribute keys to their index. -// -// This method assumes s.mu.Lock is held by the caller. -func (s *recordingSpan) dedupeAttrsFromRecord(record *map[attribute.Key]int) { - // Use the fact that slices share the same backing array. - unique := s.attributes[:0] - for _, a := range s.attributes { - if idx, ok := (*record)[a.Key]; ok { - unique[idx] = a - } else { - unique = append(unique, a) - (*record)[a.Key] = len(unique) - 1 - } - } - // s.attributes have element types of attribute.KeyValue. These types are - // not pointers and they themselves do not contain pointer fields, - // therefore the duplicate values do not need to be zeroed for them to be - // garbage collected. - s.attributes = unique -} - -// Links returns the links of this span. -func (s *recordingSpan) Links() []Link { - s.mu.Lock() - defer s.mu.Unlock() - if len(s.links.queue) == 0 { - return []Link{} - } - return s.interfaceArrayToLinksArray() -} - -// Events returns the events of this span. -func (s *recordingSpan) Events() []Event { - s.mu.Lock() - defer s.mu.Unlock() - if len(s.events.queue) == 0 { - return []Event{} - } - return s.interfaceArrayToEventArray() -} - -// Status returns the status of this span. -func (s *recordingSpan) Status() Status { - s.mu.Lock() - defer s.mu.Unlock() - return s.status -} - -// InstrumentationScope returns the instrumentation.Scope associated with -// the Tracer that created this span. -func (s *recordingSpan) InstrumentationScope() instrumentation.Scope { - s.mu.Lock() - defer s.mu.Unlock() - return s.tracer.instrumentationScope -} - -// InstrumentationLibrary returns the instrumentation.Library associated with -// the Tracer that created this span. -func (s *recordingSpan) InstrumentationLibrary() instrumentation.Library { - s.mu.Lock() - defer s.mu.Unlock() - return s.tracer.instrumentationScope -} - -// Resource returns the Resource associated with the Tracer that created this -// span. -func (s *recordingSpan) Resource() *resource.Resource { - s.mu.Lock() - defer s.mu.Unlock() - return s.tracer.provider.resource -} - -func (s *recordingSpan) addLink(link trace.Link) { - if !s.IsRecording() || !link.SpanContext.IsValid() { - return - } - - l := Link{SpanContext: link.SpanContext, Attributes: link.Attributes} - - // Discard attributes over limit. - limit := s.tracer.provider.spanLimits.AttributePerLinkCountLimit - if limit == 0 { - // Drop all attributes. - l.DroppedAttributeCount = len(l.Attributes) - l.Attributes = nil - } else if limit > 0 && len(l.Attributes) > limit { - l.DroppedAttributeCount = len(l.Attributes) - limit - l.Attributes = l.Attributes[:limit] - } - - s.mu.Lock() - s.links.add(l) - s.mu.Unlock() -} - -// DroppedAttributes returns the number of attributes dropped by the span -// due to limits being reached. -func (s *recordingSpan) DroppedAttributes() int { - s.mu.Lock() - defer s.mu.Unlock() - return s.droppedAttributes -} - -// DroppedLinks returns the number of links dropped by the span due to limits -// being reached. -func (s *recordingSpan) DroppedLinks() int { - s.mu.Lock() - defer s.mu.Unlock() - return s.links.droppedCount -} - -// DroppedEvents returns the number of events dropped by the span due to -// limits being reached. -func (s *recordingSpan) DroppedEvents() int { - s.mu.Lock() - defer s.mu.Unlock() - return s.events.droppedCount -} - -// ChildSpanCount returns the count of spans that consider the span a -// direct parent. -func (s *recordingSpan) ChildSpanCount() int { - s.mu.Lock() - defer s.mu.Unlock() - return s.childSpanCount -} - -// TracerProvider returns a trace.TracerProvider that can be used to generate -// additional Spans on the same telemetry pipeline as the current Span. -func (s *recordingSpan) TracerProvider() trace.TracerProvider { - return s.tracer.provider -} - -// snapshot creates a read-only copy of the current state of the span. -func (s *recordingSpan) snapshot() ReadOnlySpan { - var sd snapshot - s.mu.Lock() - defer s.mu.Unlock() - - sd.endTime = s.endTime - sd.instrumentationScope = s.tracer.instrumentationScope - sd.name = s.name - sd.parent = s.parent - sd.resource = s.tracer.provider.resource - sd.spanContext = s.spanContext - sd.spanKind = s.spanKind - sd.startTime = s.startTime - sd.status = s.status - sd.childSpanCount = s.childSpanCount - - if len(s.attributes) > 0 { - s.dedupeAttrs() - sd.attributes = s.attributes - } - sd.droppedAttributeCount = s.droppedAttributes - if len(s.events.queue) > 0 { - sd.events = s.interfaceArrayToEventArray() - sd.droppedEventCount = s.events.droppedCount - } - if len(s.links.queue) > 0 { - sd.links = s.interfaceArrayToLinksArray() - sd.droppedLinkCount = s.links.droppedCount - } - return &sd -} - -func (s *recordingSpan) interfaceArrayToLinksArray() []Link { - linkArr := make([]Link, 0) - for _, value := range s.links.queue { - linkArr = append(linkArr, value.(Link)) - } - return linkArr -} - -func (s *recordingSpan) interfaceArrayToEventArray() []Event { - eventArr := make([]Event, 0) - for _, value := range s.events.queue { - eventArr = append(eventArr, value.(Event)) - } - return eventArr -} - -func (s *recordingSpan) addChild() { - if !s.IsRecording() { - return - } - s.mu.Lock() - s.childSpanCount++ - s.mu.Unlock() -} - -func (*recordingSpan) private() {} - -// runtimeTrace starts a "runtime/trace".Task for the span and returns a -// context containing the task. -func (s *recordingSpan) runtimeTrace(ctx context.Context) context.Context { - if !rt.IsEnabled() { - // Avoid additional overhead if runtime/trace is not enabled. - return ctx - } - nctx, task := rt.NewTask(ctx, s.name) - - s.mu.Lock() - s.executionTracerTaskEnd = task.End - s.mu.Unlock() - - return nctx -} - -// nonRecordingSpan is a minimal implementation of the OpenTelemetry Span API -// that wraps a SpanContext. It performs no operations other than to return -// the wrapped SpanContext or TracerProvider that created it. -type nonRecordingSpan struct { - embedded.Span - - // tracer is the SDK tracer that created this span. - tracer *tracer - sc trace.SpanContext -} - -var _ trace.Span = nonRecordingSpan{} - -// SpanContext returns the wrapped SpanContext. -func (s nonRecordingSpan) SpanContext() trace.SpanContext { return s.sc } - -// IsRecording always returns false. -func (nonRecordingSpan) IsRecording() bool { return false } - -// SetStatus does nothing. -func (nonRecordingSpan) SetStatus(codes.Code, string) {} - -// SetError does nothing. -func (nonRecordingSpan) SetError(bool) {} - -// SetAttributes does nothing. -func (nonRecordingSpan) SetAttributes(...attribute.KeyValue) {} - -// End does nothing. -func (nonRecordingSpan) End(...trace.SpanEndOption) {} - -// RecordError does nothing. -func (nonRecordingSpan) RecordError(error, ...trace.EventOption) {} - -// AddEvent does nothing. -func (nonRecordingSpan) AddEvent(string, ...trace.EventOption) {} - -// SetName does nothing. -func (nonRecordingSpan) SetName(string) {} - -// TracerProvider returns the trace.TracerProvider that provided the Tracer -// that created this span. -func (s nonRecordingSpan) TracerProvider() trace.TracerProvider { return s.tracer.provider } - -func isRecording(s SamplingResult) bool { - return s.Decision == RecordOnly || s.Decision == RecordAndSample -} - -func isSampled(s SamplingResult) bool { - return s.Decision == RecordAndSample -} - -// Status is the classified state of a Span. -type Status struct { - // Code is an identifier of a Spans state classification. - Code codes.Code - // Description is a user hint about why that status was set. It is only - // applicable when Code is Error. - Description string -} -*/ diff --git a/sdk/resource/entity_exporter.go b/sdk/resource/entity_exporter.go deleted file mode 100644 index 066d9bbf25b..00000000000 --- a/sdk/resource/entity_exporter.go +++ /dev/null @@ -1,24 +0,0 @@ -// 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 resource // import "go.opentelemetry.io/otel/sdk/entity" - -import ( - "context" -) - -type EntityExporter interface { - ExportEntities(ctx context.Context, spans []ReadOnlyEntity) error - Shutdown(ctx context.Context) error -} From 781faa58d44de6ea40965c3afccb3e70971b26f0 Mon Sep 17 00:00:00 2001 From: Tigran Najaryan Date: Wed, 21 Feb 2024 14:17:45 -0500 Subject: [PATCH 11/11] Revert "Add EntityEmitterProvider" This reverts commit 89e681815402c0da1bddb11d7f1ecf49bcdbf0f5. --- entity.go | 47 ------- entity/config.go | 95 -------------- entity/embedded/embedded.go | 56 --------- entity/entity.go | 99 --------------- internal/global/entity.go | 152 ----------------------- internal/global/state.go | 86 +++---------- sdk/resource/internal/active_entities.go | 17 --- sdk/resource/internal/entity.go | 21 ---- sdk/resource/resource.go | 4 - sdk/trace/provider.go | 16 ++- 10 files changed, 31 insertions(+), 562 deletions(-) delete mode 100644 entity.go delete mode 100644 entity/config.go delete mode 100644 entity/embedded/embedded.go delete mode 100644 entity/entity.go delete mode 100644 internal/global/entity.go delete mode 100644 sdk/resource/internal/active_entities.go diff --git a/entity.go b/entity.go deleted file mode 100644 index 140eaad7359..00000000000 --- a/entity.go +++ /dev/null @@ -1,47 +0,0 @@ -// 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 otel // import "go.opentelemetry.io/otel" - -import ( - "go.opentelemetry.io/otel/entity" - "go.opentelemetry.io/otel/internal/global" -) - -// EntityEmitter creates a named entityEmitter that implements EntityEmitter interface. -// If the name is an empty string then provider uses default name. -// -// This is short for GetEntityEmitterProvider().EntityEmitter(name, opts...) -func EntityEmitter(name string, opts ...entity.EntityEmitterOption) entity.EntityEmitter { - return GetEntityEmitterProvider().EntityEmitter(name, opts...) -} - -// GetEntityEmitterProvider returns the registered global entity provider. -// If none is registered then an instance of NoopEntityEmitterProvider is returned. -// -// Use the entity provider to create a named entityEmitter. E.g. -// -// entityEmitter := otel.GetEntityEmitterProvider().EntityEmitter("example.com/foo") -// -// or -// -// entityEmitter := otel.EntityEmitter("example.com/foo") -func GetEntityEmitterProvider() entity.EntityEmitterProvider { - return global.EntityEmitterProvider() -} - -// SetEntityEmitterProvider registers `tp` as the global entity provider. -func SetEntityEmitterProvider(tp entity.EntityEmitterProvider) { - global.SetEntityEmitterProvider(tp) -} diff --git a/entity/config.go b/entity/config.go deleted file mode 100644 index f99e5a3ef63..00000000000 --- a/entity/config.go +++ /dev/null @@ -1,95 +0,0 @@ -// 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 entity // import "go.opentelemetry.io/otel/entity" - -import ( - "go.opentelemetry.io/otel/attribute" -) - -// EntityEmitterConfig is a group of options for a EntityEmitter. -type EntityEmitterConfig struct { - instrumentationVersion string - // Schema URL of the telemetry emitted by the EntityEmitter. - schemaURL string - attrs attribute.Set -} - -// InstrumentationVersion returns the version of the library providing instrumentation. -func (t *EntityEmitterConfig) InstrumentationVersion() string { - return t.instrumentationVersion -} - -// InstrumentationAttributes returns the attributes associated with the library -// providing instrumentation. -func (t *EntityEmitterConfig) InstrumentationAttributes() attribute.Set { - return t.attrs -} - -// SchemaURL returns the Schema URL of the telemetry emitted by the EntityEmitter. -func (t *EntityEmitterConfig) SchemaURL() string { - return t.schemaURL -} - -// NewEntityEmitterConfig applies all the options to a returned EntityEmitterConfig. -func NewEntityEmitterConfig(options ...EntityEmitterOption) EntityEmitterConfig { - var config EntityEmitterConfig - for _, option := range options { - config = option.apply(config) - } - return config -} - -// EntityEmitterOption applies an option to a EntityEmitterConfig. -type EntityEmitterOption interface { - apply(EntityEmitterConfig) EntityEmitterConfig -} - -type entityEmitterOptionFunc func(EntityEmitterConfig) EntityEmitterConfig - -func (fn entityEmitterOptionFunc) apply(cfg EntityEmitterConfig) EntityEmitterConfig { - return fn(cfg) -} - -// WithInstrumentationVersion sets the instrumentation version. -func WithInstrumentationVersion(version string) EntityEmitterOption { - return entityEmitterOptionFunc( - func(cfg EntityEmitterConfig) EntityEmitterConfig { - cfg.instrumentationVersion = version - return cfg - }, - ) -} - -// WithInstrumentationAttributes sets the instrumentation attributes. -// -// The passed attributes will be de-duplicated. -func WithInstrumentationAttributes(attr ...attribute.KeyValue) EntityEmitterOption { - return entityEmitterOptionFunc( - func(config EntityEmitterConfig) EntityEmitterConfig { - config.attrs = attribute.NewSet(attr...) - return config - }, - ) -} - -// WithSchemaURL sets the schema URL for the EntityEmitter. -func WithSchemaURL(schemaURL string) EntityEmitterOption { - return entityEmitterOptionFunc( - func(cfg EntityEmitterConfig) EntityEmitterConfig { - cfg.schemaURL = schemaURL - return cfg - }, - ) -} diff --git a/entity/embedded/embedded.go b/entity/embedded/embedded.go deleted file mode 100644 index 32c6886d3f6..00000000000 --- a/entity/embedded/embedded.go +++ /dev/null @@ -1,56 +0,0 @@ -// 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 embedded provides interfaces embedded within the [OpenTelemetry -// entity API]. -// -// Implementers of the [OpenTelemetry entity API] can embed the relevant type -// from this package into their implementation directly. Doing so will result -// in a compilation error for users when the [OpenTelemetry entity API] is -// extended (which is something that can happen without a major version bump of -// the API package). -// -// [OpenTelemetry entity API]: https://pkg.go.dev/go.opentelemetry.io/otel/entity -package embedded // import "go.opentelemetry.io/otel/entity/embedded" - -// EntityEmitterProvider is embedded in -// [go.opentelemetry.io/otel/entity.EntityEmitterProvider]. -// -// Embed this interface in your implementation of the -// [go.opentelemetry.io/otel/entity.EntityEmitterProvider] if you want users to -// experience a compilation error, signaling they need to update to your latest -// implementation, when the [go.opentelemetry.io/otel/entity.EntityEmitterProvider] -// interface is extended (which is something that can happen without a major -// version bump of the API package). -type EntityEmitterProvider interface{ entityEmitterProvider() } - -// EntityEmitter is embedded in [go.opentelemetry.io/otel/entity.EntityEmitter]. -// -// Embed this interface in your implementation of the -// [go.opentelemetry.io/otel/entity.EntityEmitter] if you want users to experience a -// compilation error, signaling they need to update to your latest -// implementation, when the [go.opentelemetry.io/otel/entity.EntityEmitter] interface -// is extended (which is something that can happen without a major version bump -// of the API package). -type EntityEmitter interface{ entityEmitter() } - -// Entity is embedded in [go.opentelemetry.io/otel/entity.Entity]. -// -// Embed this interface in your implementation of the -// [go.opentelemetry.io/otel/entity.Entity] if you want users to experience a -// compilation error, signaling they need to update to your latest -// implementation, when the [go.opentelemetry.io/otel/entity.Entity] interface is -// extended (which is something that can happen without a major version bump of -// the API package). -type Entity interface{ entity() } diff --git a/entity/entity.go b/entity/entity.go deleted file mode 100644 index fb790e0cf8a..00000000000 --- a/entity/entity.go +++ /dev/null @@ -1,99 +0,0 @@ -// 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 entity // import "go.opentelemetry.io/otel/entity" - -import ( - "go.opentelemetry.io/otel/entity/embedded" -) - -// EntityEmitter is the creator of Spans. -// -// Warning: Methods may be added to this interface in minor releases. See -// package documentation on API implementation for information on how to set -// default behavior for unimplemented methods. -type EntityEmitter interface { - // Users of the interface can ignore this. This embedded type is only used - // by implementations of this interface. See the "API Implementations" - // section of the package documentation for more information. - embedded.EntityEmitter - - // Start creates a span and a context.Context containing the newly-created span. - // - // If the context.Context provided in `ctx` contains a Span then the newly-created - // Span will be a child of that span, otherwise it will be a root span. This behavior - // can be overridden by providing `WithNewRoot()` as a SpanOption, causing the - // newly-created Span to be a root span even if `ctx` contains a Span. - // - // When creating a Span it is recommended to provide all known span attributes using - // the `WithAttributes()` SpanOption as samplers will only have access to the - // attributes provided when a Span is created. - // - // Any Span that is created MUST also be ended. This is the responsibility of the user. - // Implementations of this API may leak memory or other resources if Spans are not ended. - //Start(ctx context.Context, spanName string, opts ...SpanStartOption) (context.Context, Span) -} - -// EntityEmitterProvider provides EntityEmitters that are used by instrumentation code to -// entity computational workflows. -// -// A EntityEmitterProvider is the collection destination of all Spans from EntityEmitters 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 EntityEmitters to instrument code. -// -// Commonly, instrumentation code will accept a EntityEmitterProvider implementation -// at runtime from its users or it can simply use the globally registered one -// (see https://pkg.go.dev/go.opentelemetry.io/otel#GetEntityEmitterProvider). -// -// Warning: Methods may be added to this interface in minor releases. See -// package documentation on API implementation for information on how to set -// default behavior for unimplemented methods. -type EntityEmitterProvider interface { - // Users of the interface can ignore this. This embedded type is only used - // by implementations of this interface. See the "API Implementations" - // section of the package documentation for more information. - embedded.EntityEmitterProvider - - // EntityEmitter returns a unique EntityEmitter scoped to be used by instrumentation code - // to entity 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 entitys 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 EntityEmitters that handle the different domains of workflow - // data. - // - // If the same name and options are passed multiple times, the same EntityEmitter - // will be returned (it is up to the implementation if this will be the - // same underlying instance of that EntityEmitter or not). It is not necessary to - // call this multiple times with the same name and options to get an - // up-to-date EntityEmitter. All implementations will ensure any EntityEmitterProvider - // configuration changes are propagated to all provided EntityEmitters. - // - // If name is empty, then an implementation defined default name will be - // used instead. - // - // This method is safe to call concurrently. - EntityEmitter(name string, options ...EntityEmitterOption) EntityEmitter -} diff --git a/internal/global/entity.go b/internal/global/entity.go deleted file mode 100644 index e3150804b9f..00000000000 --- a/internal/global/entity.go +++ /dev/null @@ -1,152 +0,0 @@ -// 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 global // import "go.opentelemetry.io/otel/internal/global" - -/* -This file contains the forwarding implementation of the EntityEmitterProvider used as -the default global instance. Prior to initialization of an SDK, EntityEmitters -returned by the global EntityEmitterProvider will provide no-op functionality. This -means that all Span created prior to initialization are no-op Spans. - -Once an SDK has been initialized, all provided no-op EntityEmitters are swapped for -EntityEmitters provided by the SDK defined EntityEmitterProvider. However, any Span started -prior to this initialization does not change its behavior. Meaning, the Span -remains a no-op Span. - -The implementation to track and swap EntityEmitters locks all new EntityEmitter creation -until the swap is complete. This assumes that this operation is not -performance-critical. If that assumption is incorrect, be sure to configure an -SDK prior to any EntityEmitter creation. -*/ - -import ( - "sync" - "sync/atomic" - - "go.opentelemetry.io/otel/entity" - "go.opentelemetry.io/otel/entity/embedded" -) - -// entityEmitterProvider is a placeholder for a configured SDK EntityEmitterProvider. -// -// All EntityEmitterProvider functionality is forwarded to a delegate once -// configured. -type entityEmitterProvider struct { - embedded.EntityEmitterProvider - - mtx sync.Mutex - entityEmitters map[il]*entityEmitter - delegate entity.EntityEmitterProvider -} - -// Compile-time guarantee that entityEmitterProvider implements the EntityEmitterProvider -// interface. -var _ entity.EntityEmitterProvider = &entityEmitterProvider{} - -// setDelegate configures p to delegate all EntityEmitterProvider functionality to -// provider. -// -// All EntityEmitters provided prior to this function call are switched out to be -// EntityEmitters provided by provider. -// -// It is guaranteed by the caller that this happens only once. -func (p *entityEmitterProvider) setDelegate(provider entity.EntityEmitterProvider) { - p.mtx.Lock() - defer p.mtx.Unlock() - - p.delegate = provider - - if len(p.entityEmitters) == 0 { - return - } - - for _, t := range p.entityEmitters { - t.setDelegate(provider) - } - - p.entityEmitters = nil -} - -// EntityEmitter implements EntityEmitterProvider. -func (p *entityEmitterProvider) EntityEmitter(name string, opts ...entity.EntityEmitterOption) entity.EntityEmitter { - p.mtx.Lock() - defer p.mtx.Unlock() - - if p.delegate != nil { - return p.delegate.EntityEmitter(name, opts...) - } - - // At this moment it is guaranteed that no sdk is installed, save the entityEmitter in the entityEmitters map. - - c := entity.NewEntityEmitterConfig(opts...) - key := il{ - name: name, - version: c.InstrumentationVersion(), - } - - if p.entityEmitters == nil { - p.entityEmitters = make(map[il]*entityEmitter) - } - - if val, ok := p.entityEmitters[key]; ok { - return val - } - - t := &entityEmitter{name: name, opts: opts, provider: p} - p.entityEmitters[key] = t - return t -} - -// entityEmitter is a placeholder for a entity.EntityEmitter. -// -// All EntityEmitter functionality is forwarded to a delegate once configured. -// Otherwise, all functionality is forwarded to a NoopEntityEmitter. -type entityEmitter struct { - embedded.EntityEmitter - - name string - opts []entity.EntityEmitterOption - provider *entityEmitterProvider - - delegate atomic.Value -} - -// Compile-time guarantee that entityEmitter implements the entity.EntityEmitter interface. -var _ entity.EntityEmitter = &entityEmitter{} - -// setDelegate configures t to delegate all EntityEmitter functionality to EntityEmitters -// created by provider. -// -// All subsequent calls to the EntityEmitter methods will be passed to the delegate. -// -// It is guaranteed by the caller that this happens only once. -func (t *entityEmitter) setDelegate(provider entity.EntityEmitterProvider) { - t.delegate.Store(provider.EntityEmitter(t.name, t.opts...)) -} - -//// Start implements entity.EntityEmitter by forwarding the call to t.delegate if -//// set, otherwise it forwards the call to a NoopEntityEmitter. -//func (t *entityEmitter) Start(ctx context.Context, name string, opts ...entity.SpanStartOption) ( -// context.Context, entity.Span, -//) { -// delegate := t.delegate.Load() -// if delegate != nil { -// return delegate.(entity.EntityEmitter).Start(ctx, name, opts...) -// } -// -// s := nonRecordingSpan{sc: entity.SpanContextFromContext(ctx), entityEmitter: t} -// ctx = entity.ContextWithSpan(ctx, s) -// return ctx, s -//} diff --git a/internal/global/state.go b/internal/global/state.go index 5e5a496d9f1..7985005bcb6 100644 --- a/internal/global/state.go +++ b/internal/global/state.go @@ -19,7 +19,6 @@ import ( "sync" "sync/atomic" - "go.opentelemetry.io/otel/entity" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" @@ -37,22 +36,16 @@ type ( meterProviderHolder struct { mp metric.MeterProvider } - - entityEmitterProviderHolder struct { - tp entity.EntityEmitterProvider - } ) var ( - globalTracer = defaultTracerValue() - globalPropagators = defaultPropagatorsValue() - globalMeterProvider = defaultMeterProvider() - globalEntityEmitterProvider = defaultEntityEmitterProvider() + globalTracer = defaultTracerValue() + globalPropagators = defaultPropagatorsValue() + globalMeterProvider = defaultMeterProvider() delegateTraceOnce sync.Once delegateTextMapPropagatorOnce sync.Once delegateMeterOnce sync.Once - delegateEntityProviderOnce sync.Once ) // TracerProvider is the internal implementation for global.TracerProvider. @@ -76,13 +69,11 @@ func SetTracerProvider(tp trace.TracerProvider) { } } - delegateTraceOnce.Do( - func() { - if def, ok := current.(*tracerProvider); ok { - def.setDelegate(tp) - } - }, - ) + delegateTraceOnce.Do(func() { + if def, ok := current.(*tracerProvider); ok { + def.setDelegate(tp) + } + }) globalTracer.Store(tracerProviderHolder{tp: tp}) } @@ -109,13 +100,11 @@ func SetTextMapPropagator(p propagation.TextMapPropagator) { // For the textMapPropagator already returned by TextMapPropagator // delegate to p. - delegateTextMapPropagatorOnce.Do( - func() { - if def, ok := current.(*textMapPropagator); ok { - def.SetDelegate(p) - } - }, - ) + delegateTextMapPropagatorOnce.Do(func() { + if def, ok := current.(*textMapPropagator); ok { + def.SetDelegate(p) + } + }) // Return p when subsequent calls to TextMapPropagator are made. globalPropagators.Store(propagatorsHolder{tm: p}) } @@ -140,13 +129,11 @@ func SetMeterProvider(mp metric.MeterProvider) { } } - delegateMeterOnce.Do( - func() { - if def, ok := current.(*meterProvider); ok { - def.setDelegate(mp) - } - }, - ) + delegateMeterOnce.Do(func() { + if def, ok := current.(*meterProvider); ok { + def.setDelegate(mp) + } + }) globalMeterProvider.Store(meterProviderHolder{mp: mp}) } @@ -167,40 +154,3 @@ func defaultMeterProvider() *atomic.Value { v.Store(meterProviderHolder{mp: &meterProvider{}}) return v } - -func defaultEntityEmitterProvider() *atomic.Value { - v := &atomic.Value{} - v.Store(entityEmitterProviderHolder{tp: &entityEmitterProvider{}}) - return v -} - -// EntityEmitterProvider is the internal implementation for global.EntityEmitterProvider. -func EntityEmitterProvider() entity.EntityEmitterProvider { - return globalEntityEmitterProvider.Load().(entityEmitterProviderHolder).tp -} - -// SetEntityEmitterProvider is the internal implementation for global.SetEntityEmitterProvider. -func SetEntityEmitterProvider(tp entity.EntityEmitterProvider) { - current := EntityEmitterProvider() - - if _, cOk := current.(*entityEmitterProvider); cOk { - if _, tpOk := tp.(*entityEmitterProvider); tpOk && current == tp { - // Do not assign the default delegating EntityEmitterProvider to delegate - // to itself. - Error( - errors.New("no delegate configured in entityEmitter provider"), - "Setting entityEmitter provider to it's current value. No delegate will be configured", - ) - return - } - } - - delegateEntityProviderOnce.Do( - func() { - if def, ok := current.(*entityEmitterProvider); ok { - def.setDelegate(tp) - } - }, - ) - globalEntityEmitterProvider.Store(entityEmitterProviderHolder{tp: tp}) -} diff --git a/sdk/resource/internal/active_entities.go b/sdk/resource/internal/active_entities.go deleted file mode 100644 index 815dc4619c4..00000000000 --- a/sdk/resource/internal/active_entities.go +++ /dev/null @@ -1,17 +0,0 @@ -package internal - -import "go.opentelemetry.io/otel/attribute" - -// This is a quick implementation of active entities for prototyping purposes. -// A proper implementation will use providers, exporters, etc. just like all other -// signals use. - -var activeEntities map[attribute.Distinct]Entity = map[attribute.Distinct]Entity{} - -func init() { - go exportActive() -} - -func exportActive() { - -} diff --git a/sdk/resource/internal/entity.go b/sdk/resource/internal/entity.go index 0f34735238e..ec8d29041b5 100644 --- a/sdk/resource/internal/entity.go +++ b/sdk/resource/internal/entity.go @@ -6,18 +6,13 @@ type EntityData struct { // Defines the producing entity type of this resource, e.g "service", "k8s.pod", etc. // Empty for legacy Resources that are not entity-aware. Type string - // Set of attributes that identify the entity. // Note that a copy of identifying attributes will be also recorded in the Attrs field. Id attribute.Set - // Non-identifying attributes of the Entity. When EntityData is stored in a Resource - // this field also represents the Resource attributes. Attrs attribute.Set } -// MergeEntities merges a and b, with values in b overwriting values in a. -// Inputs are not modified, the result of merging is returned as a new struct. func MergeEntities(a, b *EntityData) *EntityData { // Note: 'b' attributes will overwrite 'a' with last-value-wins in attribute.Key() // Meaning this is equivalent to: append(a.Attributes(), b.Attributes()...) @@ -65,19 +60,3 @@ func mergeAttrs(a, b *attribute.Set) attribute.Set { } return attribute.NewSet(combine...) } - -type Entity struct { - data EntityData -} - -func PublishEntity(d *EntityData) *Entity { - return nil -} - -func (e *Entity) Update(attrs attribute.Set) { - -} - -func (e *Entity) Delete() { - -} diff --git a/sdk/resource/resource.go b/sdk/resource/resource.go index 1effae169ee..c3e3e8ac209 100644 --- a/sdk/resource/resource.go +++ b/sdk/resource/resource.go @@ -339,7 +339,3 @@ func (r *Resource) Encoded(enc attribute.Encoder) string { } return r.entity.Attrs.Encoded(enc) } - -func (r *Resource) PublishEntity() { - -} diff --git a/sdk/trace/provider.go b/sdk/trace/provider.go index ac94d03d405..108d50e65c0 100644 --- a/sdk/trace/provider.go +++ b/sdk/trace/provider.go @@ -124,9 +124,6 @@ func NewTracerProvider(opts ...TracerProviderOption) *TracerProvider { } global.Info("TracerProvider created", "config", o) - // Begin publishing Resource's producing entity as an entity signal. - tp.resource.PublishEntity() - spss := make(spanProcessorStates, 0, len(o.processors)) for _, sp := range o.processors { spss = append(spss, newSpanProcessorState(sp)) @@ -380,6 +377,19 @@ func WithResource(r *resource.Resource) TracerProviderOption { ) } +func WithEntity(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 + }, + ) +} + // WithIDGenerator returns a TracerProviderOption that will configure the // IDGenerator g as a TracerProvider's IDGenerator. The configured IDGenerator // is used by the Tracers the TracerProvider creates to generate new Span and