2020-03-24 07:41:10 +02:00
// Copyright The OpenTelemetry Authors
2024-02-29 08:05:28 +02:00
// SPDX-License-Identifier: Apache-2.0
2019-08-02 22:52:55 +02:00
2020-11-04 19:10:58 +02:00
package trace // import "go.opentelemetry.io/otel/sdk/trace"
2019-08-02 22:52:55 +02:00
import (
2020-12-10 03:30:32 +02:00
"context"
2020-02-28 23:44:53 +02:00
"fmt"
"reflect"
2021-08-13 23:44:18 +02:00
"runtime"
2021-09-02 23:30:12 +02:00
rt "runtime/trace"
2024-02-25 20:48:32 +02:00
"slices"
2022-09-12 16:54:58 +02:00
"strings"
2019-08-02 22:52:55 +02:00
"sync"
"time"
2022-09-12 16:54:58 +02:00
"unicode/utf8"
2019-08-02 22:52:55 +02:00
2021-02-18 19:59:37 +02:00
"go.opentelemetry.io/otel/attribute"
2020-08-10 18:17:09 +02:00
"go.opentelemetry.io/otel/codes"
2024-05-30 20:40:08 +02:00
"go.opentelemetry.io/otel/internal/global"
2020-12-11 07:15:44 +02:00
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/resource"
2024-06-06 18:36:59 +02:00
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
2021-11-13 18:35:04 +02:00
"go.opentelemetry.io/otel/trace"
2023-10-19 19:16:24 +02:00
"go.opentelemetry.io/otel/trace/embedded"
2019-08-02 22:52:55 +02:00
)
2020-12-11 07:15:44 +02:00
// ReadOnlySpan 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.
2021-06-18 16:56:11 +02:00
//
// Warning: methods may be added to this interface in minor releases.
2020-12-11 07:15:44 +02:00
type ReadOnlySpan interface {
2021-05-05 01:45:13 +02:00
// Name returns the name of the span.
2020-12-11 07:15:44 +02:00
Name ( ) string
2021-05-05 01:45:13 +02:00
// SpanContext returns the unique SpanContext that identifies the span.
2020-12-11 07:15:44 +02:00
SpanContext ( ) trace . SpanContext
2021-05-05 01:45:13 +02:00
// Parent returns the unique SpanContext that identifies the parent of the
// span if one exists. If the span has no parent the returned SpanContext
// will be invalid.
2020-12-11 07:15:44 +02:00
Parent ( ) trace . SpanContext
2021-05-05 01:45:13 +02:00
// SpanKind returns the role the span plays in a Trace.
2020-12-11 07:15:44 +02:00
SpanKind ( ) trace . SpanKind
2021-05-05 01:45:13 +02:00
// StartTime returns the time the span started recording.
2020-12-11 07:15:44 +02:00
StartTime ( ) time . Time
2021-05-05 01:45:13 +02:00
// EndTime returns the time the span stopped recording. It will be zero if
// the span has not ended.
2020-12-11 07:15:44 +02:00
EndTime ( ) time . Time
2021-05-05 01:45:13 +02:00
// Attributes returns the defining attributes of the span.
2022-02-07 22:58:05 +02:00
// The order of the returned attributes is not guaranteed to be stable across invocations.
2021-02-18 19:59:37 +02:00
Attributes ( ) [ ] attribute . KeyValue
2021-05-05 01:45:13 +02:00
// Links returns all the links the span has to other spans.
2021-07-26 17:06:41 +02:00
Links ( ) [ ] Link
2021-05-05 01:45:13 +02:00
// Events returns all the events that occurred within in the spans
// lifetime.
2021-04-29 19:29:48 +02:00
Events ( ) [ ] Event
2021-05-05 01:45:13 +02:00
// Status returns the spans status.
2021-05-03 21:00:54 +02:00
Status ( ) Status
2022-07-06 20:55:46 +02:00
// InstrumentationScope returns information about the instrumentation
// scope that created the span.
InstrumentationScope ( ) instrumentation . Scope
2021-05-05 01:45:13 +02:00
// InstrumentationLibrary returns information about the instrumentation
// library that created the span.
2022-07-06 20:55:46 +02:00
// Deprecated: please use InstrumentationScope instead.
2024-07-24 09:33:07 +02:00
InstrumentationLibrary ( ) instrumentation . Library //nolint:staticcheck // This method needs to be define for backwards compatibility
2021-05-05 01:45:13 +02:00
// Resource returns information about the entity that produced the span.
2020-12-11 07:15:44 +02:00
Resource ( ) * resource . Resource
2021-05-05 01:45:13 +02:00
// DroppedAttributes returns the number of attributes dropped by the span
// due to limits being reached.
DroppedAttributes ( ) int
// DroppedLinks returns the number of links dropped by the span due to
// limits being reached.
DroppedLinks ( ) int
// DroppedEvents returns the number of events dropped by the span due to
// limits being reached.
DroppedEvents ( ) int
// ChildSpanCount returns the count of spans that consider the span a
// direct parent.
ChildSpanCount ( ) int
2021-02-24 20:03:35 +02:00
// A private method to prevent users implementing the
// interface and so future additions to it will not
// violate compatibility.
private ( )
2020-12-11 07:15:44 +02:00
}
// ReadWriteSpan exposes the same methods as trace.Span and in addition allows
// reading information from the underlying data structure.
// This interface exposes the union of the methods of trace.Span (which is a
// "write-only" span) and ReadOnlySpan. New methods for writing or reading span
// information should be added under trace.Span or ReadOnlySpan, respectively.
2021-06-18 16:56:11 +02:00
//
// Warning: methods may be added to this interface in minor releases.
2020-12-11 07:15:44 +02:00
type ReadWriteSpan interface {
trace . Span
ReadOnlySpan
}
2021-09-02 23:30:12 +02:00
// recordingSpan is an implementation of the OpenTelemetry Span API
// representing the individual component of a trace that is sampled.
type recordingSpan struct {
2023-10-19 19:16:24 +02:00
embedded . Span
2020-11-09 20:40:02 +02:00
// mu protects the contents of this span.
mu sync . Mutex
2020-12-11 07:15:44 +02:00
// 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
2021-05-03 21:00:54 +02:00
// status is the status of this span.
status Status
2020-12-11 07:15:44 +02:00
// childSpanCount holds the number of child spans created for this span.
childSpanCount int
// spanContext holds the SpanContext of this span.
2020-11-07 00:13:31 +02:00
spanContext trace . SpanContext
2019-08-02 22:52:55 +02:00
2022-02-07 22:58:05 +02:00
// 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
// ReadOnlySpan exported when the span ends.
attributes [ ] attribute . KeyValue
droppedAttributes int
2024-05-30 20:40:08 +02:00
logDropAttrsOnce sync . Once
2019-08-02 22:52:55 +02:00
2021-05-05 01:45:13 +02:00
// events are stored in FIFO queue capped by configured limit.
2024-05-30 20:40:08 +02:00
events evictedQueue [ Event ]
2019-08-02 22:52:55 +02:00
// links are stored in FIFO queue capped by configured limit.
2024-05-30 20:40:08 +02:00
links evictedQueue [ Link ]
2019-08-02 22:52:55 +02:00
2020-11-09 20:40:02 +02:00
// executionTracerTaskEnd ends the execution tracer span.
executionTracerTaskEnd func ( )
// tracer is the SDK tracer that created this span.
tracer * tracer
2019-08-02 22:52:55 +02:00
}
2023-10-16 19:02:21 +02:00
var (
_ ReadWriteSpan = ( * recordingSpan ) ( nil )
_ runtimeTracer = ( * recordingSpan ) ( nil )
)
2019-08-02 22:52:55 +02:00
2021-02-17 16:41:18 +02:00
// SpanContext returns the SpanContext of this span.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) SpanContext ( ) trace . SpanContext {
2019-08-02 22:52:55 +02:00
if s == nil {
2020-11-07 00:13:31 +02:00
return trace . SpanContext { }
2019-08-02 22:52:55 +02:00
}
return s . spanContext
}
2021-02-17 16:41:18 +02:00
// IsRecording returns if this span is being recorded. If this span has ended
// this will return false.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) IsRecording ( ) bool {
2019-08-02 22:52:55 +02:00
if s == nil {
return false
}
2020-12-11 07:15:44 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
2021-03-30 22:26:42 +02:00
2024-10-07 09:30:29 +02:00
return s . isRecording ( )
}
// isRecording returns if this span is being recorded. If this span has ended
// this will return false.
2024-10-09 09:51:17 +02:00
//
// This method assumes s.mu.Lock is held by the caller.
2024-10-07 09:30:29 +02:00
func ( s * recordingSpan ) isRecording ( ) bool {
if s == nil {
return false
}
2021-09-02 23:30:12 +02:00
return s . endTime . IsZero ( )
2019-08-02 22:52:55 +02:00
}
2021-05-03 21:00:54 +02:00
// 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.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) SetStatus ( code codes . Code , description string ) {
2024-10-07 09:30:29 +02:00
if s == nil {
2019-08-02 22:52:55 +02:00
return
}
2024-10-07 09:30:29 +02:00
2022-09-23 20:39:53 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
2024-10-07 09:30:29 +02:00
if ! s . isRecording ( ) {
return
}
2022-09-23 20:39:53 +02:00
if s . status . Code > code {
return
}
2021-05-03 21:00:54 +02:00
status := Status { Code : code }
2021-03-08 19:40:38 +02:00
if code == codes . Error {
2021-05-03 21:00:54 +02:00
status . Description = description
2021-03-08 19:40:38 +02:00
}
2021-05-03 21:00:54 +02:00
s . status = status
2019-08-02 22:52:55 +02:00
}
2021-02-17 16:41:18 +02:00
// 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.
2022-02-07 22:58:05 +02:00
//
// 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.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) SetAttributes ( attributes ... attribute . KeyValue ) {
2024-10-07 09:30:29 +02:00
if s == nil || len ( attributes ) == 0 {
2019-08-02 22:52:55 +02:00
return
}
2022-02-07 22:58:05 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
2024-10-07 09:30:29 +02:00
if ! s . isRecording ( ) {
return
}
2022-02-07 22:58:05 +02:00
2022-03-03 17:56:07 +02:00
limit := s . tracer . provider . spanLimits . AttributeCountLimit
if limit == 0 {
// No attributes allowed.
2024-05-30 20:40:08 +02:00
s . addDroppedAttr ( len ( attributes ) )
2022-03-03 17:56:07 +02:00
return
}
2022-02-07 22:58:05 +02:00
// If adding these attributes could exceed the capacity of s perform a
// de-duplication and truncation while adding to avoid over allocation.
2022-03-03 17:56:07 +02:00
if limit > 0 && len ( s . attributes ) + len ( attributes ) > limit {
s . addOverCapAttrs ( limit , attributes )
2022-02-07 22:58:05 +02:00
return
}
// Otherwise, add without deduplication. When attributes are read they
// will be deduplicated, optimizing the operation.
2024-10-07 09:30:29 +02:00
s . attributes = slices . Grow ( s . attributes , len ( attributes ) )
2022-02-07 22:58:05 +02:00
for _ , a := range attributes {
if ! a . Valid ( ) {
// Drop all invalid attributes.
2024-05-30 20:40:08 +02:00
s . addDroppedAttr ( 1 )
2022-02-07 22:58:05 +02:00
continue
}
2022-03-03 17:56:07 +02:00
a = truncateAttr ( s . tracer . provider . spanLimits . AttributeValueLengthLimit , a )
2022-02-07 22:58:05 +02:00
s . attributes = append ( s . attributes , a )
}
}
2024-05-30 20:40:08 +02:00
// Declared as a var so tests can override.
var logDropAttrs = func ( ) {
global . Warn ( "limit reached: dropping trace Span attributes" )
}
// addDroppedAttr adds incr to the count of dropped attributes.
//
// The first, and only the first, time this method is called a warning will be
// logged.
//
// This method assumes s.mu.Lock is held by the caller.
func ( s * recordingSpan ) addDroppedAttr ( incr int ) {
s . droppedAttributes += incr
s . logDropAttrsOnce . Do ( logDropAttrs )
}
2022-02-07 22:58:05 +02:00
// addOverCapAttrs adds the attributes attrs to the span s while
// de-duplicating the attributes of s and attrs and dropping attributes that
2022-03-03 17:56:07 +02:00
// exceed the limit.
2022-02-07 22:58:05 +02:00
//
// This method assumes s.mu.Lock is held by the caller.
//
// This method should only be called when there is a possibility that adding
2022-03-03 17:56:07 +02:00
// 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 ) {
2022-02-07 22:58:05 +02:00
// 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.
2024-10-07 09:30:29 +02:00
exists := make ( map [ attribute . Key ] int , len ( s . attributes ) )
s . dedupeAttrsFromRecord ( exists )
2022-02-07 22:58:05 +02:00
// Now that s.attributes is deduplicated, adding unique attributes up to
// the capacity of s will not over allocate s.attributes.
2024-10-07 09:30:29 +02:00
// max size = limit
maxCap := min ( len ( attrs ) + len ( s . attributes ) , limit )
if cap ( s . attributes ) < maxCap {
s . attributes = slices . Grow ( s . attributes , maxCap - cap ( s . attributes ) )
}
2022-02-07 22:58:05 +02:00
for _ , a := range attrs {
if ! a . Valid ( ) {
// Drop all invalid attributes.
2024-05-30 20:40:08 +02:00
s . addDroppedAttr ( 1 )
2022-02-07 22:58:05 +02:00
continue
}
if idx , ok := exists [ a . Key ] ; ok {
// Perform all updates before dropping, even when at capacity.
2024-10-07 09:30:29 +02:00
a = truncateAttr ( s . tracer . provider . spanLimits . AttributeValueLengthLimit , a )
2022-02-07 22:58:05 +02:00
s . attributes [ idx ] = a
continue
}
2022-03-03 17:56:07 +02:00
if len ( s . attributes ) >= limit {
2022-02-07 22:58:05 +02:00
// Do not just drop all of the remaining attributes, make sure
// updates are checked and performed.
2024-05-30 20:40:08 +02:00
s . addDroppedAttr ( 1 )
2022-02-07 22:58:05 +02:00
} else {
2022-03-03 17:56:07 +02:00
a = truncateAttr ( s . tracer . provider . spanLimits . AttributeValueLengthLimit , a )
2022-02-07 22:58:05 +02:00
s . attributes = append ( s . attributes , a )
exists [ a . Key ] = len ( s . attributes ) - 1
}
}
2019-08-02 22:52:55 +02:00
}
2022-03-03 17:56:07 +02:00
// truncateAttr returns a truncated version of attr. Only string and string
// slice attribute values are truncated. String values are truncated to at
2022-09-12 16:54:58 +02:00
// most a length of limit. Each string slice value is truncated in this fashion
2022-03-03 17:56:07 +02:00
// (the slice length itself is unaffected).
//
2023-04-12 02:28:13 +02:00
// No truncation is performed for a negative limit.
2022-03-03 17:56:07 +02:00
func truncateAttr ( limit int , attr attribute . KeyValue ) attribute . KeyValue {
if limit < 0 {
return attr
}
switch attr . Value . Type ( ) {
case attribute . STRING :
Fix attribute value truncation (#5997)
Fix #5996
### Correctness
From the [OTel
specification](https://github.com/open-telemetry/opentelemetry-specification/blob/88bffeac48aa5eb37ac8044e931af63a0b55fe00/specification/common/README.md#attribute-limits):
> - set an attribute value length limit such that for each attribute
value:
> - if it is a string, if it exceeds that limit (counting any character
in it as 1), SDKs MUST truncate that value, so that its length is at
most equal to the limit...
Our current implementation truncates on number of bytes not characters.
Unit tests are added/updated to validate this fix and prevent
regressions.
### Performance
```
goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/sdk/trace
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ sec/op │ sec/op vs base │
Truncate/Unlimited-8 1.2300n ± 7% 0.8757n ± 3% -28.80% (p=0.000 n=10)
Truncate/Zero-8 2.341n ± 2% 1.550n ± 9% -33.77% (p=0.000 n=10)
Truncate/Short-8 31.6800n ± 3% 0.9960n ± 4% -96.86% (p=0.000 n=10)
Truncate/ASCII-8 8.821n ± 1% 3.567n ± 3% -59.57% (p=0.000 n=10)
Truncate/ValidUTF-8-8 11.960n ± 1% 7.163n ± 1% -40.10% (p=0.000 n=10)
Truncate/InvalidUTF-8-8 56.35n ± 0% 37.34n ± 18% -33.74% (p=0.000 n=10)
Truncate/MixedUTF-8-8 81.83n ± 1% 50.00n ± 1% -38.90% (p=0.000 n=10)
geomean 12.37n 4.865n -60.68%
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ B/op │ B/op vs base │
Truncate/Unlimited-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Zero-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Short-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ASCII-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ValidUTF-8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/InvalidUTF-8-8 16.00 ± 0% 16.00 ± 0% ~ (p=1.000 n=10) ¹
Truncate/MixedUTF-8-8 32.00 ± 0% 32.00 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ allocs/op │ allocs/op vs base │
Truncate/Unlimited-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Zero-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Short-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ASCII-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ValidUTF-8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/InvalidUTF-8-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/MixedUTF-8-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
```
#### Values shorter than limit
This is the default code path. Most attribute values will be shorter
than the default 128 limit that users will not modify.
The current code, `safeTruncate` requires a full iteration of the value
to determine it is valid and under the limit.
The replacement, `truncate`, first checks if the number of bytes in the
value are less than or equal to the limit (which guarantees the number
of characters are less than or equal to the limit) and returns
immediately. This will mean that invalid encoding less than the limit is
not changed, which meets the specification requirements.
#### Values longer than the limit
For values who's number of bytes exceeds the limit, they are iterated
only once with the replacement, `truncate`.
In comparison, the current code, `safeTruncate`, can iterate the string
up to three separate times when the string contains invalid characters.
2024-11-26 10:41:55 +02:00
v := attr . Value . AsString ( )
return attr . Key . String ( truncate ( limit , v ) )
2022-03-03 17:56:07 +02:00
case attribute . STRINGSLICE :
2022-10-13 16:34:02 +02:00
v := attr . Value . AsStringSlice ( )
2022-03-03 17:56:07 +02:00
for i := range v {
Fix attribute value truncation (#5997)
Fix #5996
### Correctness
From the [OTel
specification](https://github.com/open-telemetry/opentelemetry-specification/blob/88bffeac48aa5eb37ac8044e931af63a0b55fe00/specification/common/README.md#attribute-limits):
> - set an attribute value length limit such that for each attribute
value:
> - if it is a string, if it exceeds that limit (counting any character
in it as 1), SDKs MUST truncate that value, so that its length is at
most equal to the limit...
Our current implementation truncates on number of bytes not characters.
Unit tests are added/updated to validate this fix and prevent
regressions.
### Performance
```
goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/sdk/trace
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ sec/op │ sec/op vs base │
Truncate/Unlimited-8 1.2300n ± 7% 0.8757n ± 3% -28.80% (p=0.000 n=10)
Truncate/Zero-8 2.341n ± 2% 1.550n ± 9% -33.77% (p=0.000 n=10)
Truncate/Short-8 31.6800n ± 3% 0.9960n ± 4% -96.86% (p=0.000 n=10)
Truncate/ASCII-8 8.821n ± 1% 3.567n ± 3% -59.57% (p=0.000 n=10)
Truncate/ValidUTF-8-8 11.960n ± 1% 7.163n ± 1% -40.10% (p=0.000 n=10)
Truncate/InvalidUTF-8-8 56.35n ± 0% 37.34n ± 18% -33.74% (p=0.000 n=10)
Truncate/MixedUTF-8-8 81.83n ± 1% 50.00n ± 1% -38.90% (p=0.000 n=10)
geomean 12.37n 4.865n -60.68%
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ B/op │ B/op vs base │
Truncate/Unlimited-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Zero-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Short-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ASCII-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ValidUTF-8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/InvalidUTF-8-8 16.00 ± 0% 16.00 ± 0% ~ (p=1.000 n=10) ¹
Truncate/MixedUTF-8-8 32.00 ± 0% 32.00 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ allocs/op │ allocs/op vs base │
Truncate/Unlimited-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Zero-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Short-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ASCII-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ValidUTF-8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/InvalidUTF-8-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/MixedUTF-8-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
```
#### Values shorter than limit
This is the default code path. Most attribute values will be shorter
than the default 128 limit that users will not modify.
The current code, `safeTruncate` requires a full iteration of the value
to determine it is valid and under the limit.
The replacement, `truncate`, first checks if the number of bytes in the
value are less than or equal to the limit (which guarantees the number
of characters are less than or equal to the limit) and returns
immediately. This will mean that invalid encoding less than the limit is
not changed, which meets the specification requirements.
#### Values longer than the limit
For values who's number of bytes exceeds the limit, they are iterated
only once with the replacement, `truncate`.
In comparison, the current code, `safeTruncate`, can iterate the string
up to three separate times when the string contains invalid characters.
2024-11-26 10:41:55 +02:00
v [ i ] = truncate ( limit , v [ i ] )
2022-03-03 17:56:07 +02:00
}
2022-10-13 16:34:02 +02:00
return attr . Key . StringSlice ( v )
2022-03-03 17:56:07 +02:00
}
return attr
}
Fix attribute value truncation (#5997)
Fix #5996
### Correctness
From the [OTel
specification](https://github.com/open-telemetry/opentelemetry-specification/blob/88bffeac48aa5eb37ac8044e931af63a0b55fe00/specification/common/README.md#attribute-limits):
> - set an attribute value length limit such that for each attribute
value:
> - if it is a string, if it exceeds that limit (counting any character
in it as 1), SDKs MUST truncate that value, so that its length is at
most equal to the limit...
Our current implementation truncates on number of bytes not characters.
Unit tests are added/updated to validate this fix and prevent
regressions.
### Performance
```
goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/sdk/trace
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ sec/op │ sec/op vs base │
Truncate/Unlimited-8 1.2300n ± 7% 0.8757n ± 3% -28.80% (p=0.000 n=10)
Truncate/Zero-8 2.341n ± 2% 1.550n ± 9% -33.77% (p=0.000 n=10)
Truncate/Short-8 31.6800n ± 3% 0.9960n ± 4% -96.86% (p=0.000 n=10)
Truncate/ASCII-8 8.821n ± 1% 3.567n ± 3% -59.57% (p=0.000 n=10)
Truncate/ValidUTF-8-8 11.960n ± 1% 7.163n ± 1% -40.10% (p=0.000 n=10)
Truncate/InvalidUTF-8-8 56.35n ± 0% 37.34n ± 18% -33.74% (p=0.000 n=10)
Truncate/MixedUTF-8-8 81.83n ± 1% 50.00n ± 1% -38.90% (p=0.000 n=10)
geomean 12.37n 4.865n -60.68%
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ B/op │ B/op vs base │
Truncate/Unlimited-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Zero-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Short-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ASCII-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ValidUTF-8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/InvalidUTF-8-8 16.00 ± 0% 16.00 ± 0% ~ (p=1.000 n=10) ¹
Truncate/MixedUTF-8-8 32.00 ± 0% 32.00 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ allocs/op │ allocs/op vs base │
Truncate/Unlimited-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Zero-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Short-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ASCII-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ValidUTF-8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/InvalidUTF-8-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/MixedUTF-8-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
```
#### Values shorter than limit
This is the default code path. Most attribute values will be shorter
than the default 128 limit that users will not modify.
The current code, `safeTruncate` requires a full iteration of the value
to determine it is valid and under the limit.
The replacement, `truncate`, first checks if the number of bytes in the
value are less than or equal to the limit (which guarantees the number
of characters are less than or equal to the limit) and returns
immediately. This will mean that invalid encoding less than the limit is
not changed, which meets the specification requirements.
#### Values longer than the limit
For values who's number of bytes exceeds the limit, they are iterated
only once with the replacement, `truncate`.
In comparison, the current code, `safeTruncate`, can iterate the string
up to three separate times when the string contains invalid characters.
2024-11-26 10:41:55 +02:00
// truncate returns a truncated version of s such that it contains less than
// the limit number of characters. Truncation is applied by returning the limit
// number of valid characters contained in s.
//
// If limit is negative, it returns the original string.
//
// UTF-8 is supported. When truncating, all invalid characters are dropped
// before applying truncation.
//
// If s already contains less than the limit number of bytes, it is returned
// unchanged. No invalid characters are removed.
func truncate ( limit int , s string ) string {
// This prioritize performance in the following order based on the most
// common expected use-cases.
//
// - Short values less than the default limit (128).
// - Strings with valid encodings that exceed the limit.
// - No limit.
// - Strings with invalid encodings that exceed the limit.
if limit < 0 || len ( s ) <= limit {
return s
2022-09-12 16:54:58 +02:00
}
Fix attribute value truncation (#5997)
Fix #5996
### Correctness
From the [OTel
specification](https://github.com/open-telemetry/opentelemetry-specification/blob/88bffeac48aa5eb37ac8044e931af63a0b55fe00/specification/common/README.md#attribute-limits):
> - set an attribute value length limit such that for each attribute
value:
> - if it is a string, if it exceeds that limit (counting any character
in it as 1), SDKs MUST truncate that value, so that its length is at
most equal to the limit...
Our current implementation truncates on number of bytes not characters.
Unit tests are added/updated to validate this fix and prevent
regressions.
### Performance
```
goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/sdk/trace
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ sec/op │ sec/op vs base │
Truncate/Unlimited-8 1.2300n ± 7% 0.8757n ± 3% -28.80% (p=0.000 n=10)
Truncate/Zero-8 2.341n ± 2% 1.550n ± 9% -33.77% (p=0.000 n=10)
Truncate/Short-8 31.6800n ± 3% 0.9960n ± 4% -96.86% (p=0.000 n=10)
Truncate/ASCII-8 8.821n ± 1% 3.567n ± 3% -59.57% (p=0.000 n=10)
Truncate/ValidUTF-8-8 11.960n ± 1% 7.163n ± 1% -40.10% (p=0.000 n=10)
Truncate/InvalidUTF-8-8 56.35n ± 0% 37.34n ± 18% -33.74% (p=0.000 n=10)
Truncate/MixedUTF-8-8 81.83n ± 1% 50.00n ± 1% -38.90% (p=0.000 n=10)
geomean 12.37n 4.865n -60.68%
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ B/op │ B/op vs base │
Truncate/Unlimited-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Zero-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Short-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ASCII-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ValidUTF-8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/InvalidUTF-8-8 16.00 ± 0% 16.00 ± 0% ~ (p=1.000 n=10) ¹
Truncate/MixedUTF-8-8 32.00 ± 0% 32.00 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ allocs/op │ allocs/op vs base │
Truncate/Unlimited-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Zero-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Short-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ASCII-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ValidUTF-8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/InvalidUTF-8-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/MixedUTF-8-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
```
#### Values shorter than limit
This is the default code path. Most attribute values will be shorter
than the default 128 limit that users will not modify.
The current code, `safeTruncate` requires a full iteration of the value
to determine it is valid and under the limit.
The replacement, `truncate`, first checks if the number of bytes in the
value are less than or equal to the limit (which guarantees the number
of characters are less than or equal to the limit) and returns
immediately. This will mean that invalid encoding less than the limit is
not changed, which meets the specification requirements.
#### Values longer than the limit
For values who's number of bytes exceeds the limit, they are iterated
only once with the replacement, `truncate`.
In comparison, the current code, `safeTruncate`, can iterate the string
up to three separate times when the string contains invalid characters.
2024-11-26 10:41:55 +02:00
// Optimistically, assume all valid UTF-8.
var b strings . Builder
count := 0
for i , c := range s {
if c != utf8 . RuneError {
count ++
if count > limit {
return s [ : i ]
}
continue
2022-09-12 16:54:58 +02:00
}
Fix attribute value truncation (#5997)
Fix #5996
### Correctness
From the [OTel
specification](https://github.com/open-telemetry/opentelemetry-specification/blob/88bffeac48aa5eb37ac8044e931af63a0b55fe00/specification/common/README.md#attribute-limits):
> - set an attribute value length limit such that for each attribute
value:
> - if it is a string, if it exceeds that limit (counting any character
in it as 1), SDKs MUST truncate that value, so that its length is at
most equal to the limit...
Our current implementation truncates on number of bytes not characters.
Unit tests are added/updated to validate this fix and prevent
regressions.
### Performance
```
goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/sdk/trace
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ sec/op │ sec/op vs base │
Truncate/Unlimited-8 1.2300n ± 7% 0.8757n ± 3% -28.80% (p=0.000 n=10)
Truncate/Zero-8 2.341n ± 2% 1.550n ± 9% -33.77% (p=0.000 n=10)
Truncate/Short-8 31.6800n ± 3% 0.9960n ± 4% -96.86% (p=0.000 n=10)
Truncate/ASCII-8 8.821n ± 1% 3.567n ± 3% -59.57% (p=0.000 n=10)
Truncate/ValidUTF-8-8 11.960n ± 1% 7.163n ± 1% -40.10% (p=0.000 n=10)
Truncate/InvalidUTF-8-8 56.35n ± 0% 37.34n ± 18% -33.74% (p=0.000 n=10)
Truncate/MixedUTF-8-8 81.83n ± 1% 50.00n ± 1% -38.90% (p=0.000 n=10)
geomean 12.37n 4.865n -60.68%
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ B/op │ B/op vs base │
Truncate/Unlimited-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Zero-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Short-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ASCII-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ValidUTF-8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/InvalidUTF-8-8 16.00 ± 0% 16.00 ± 0% ~ (p=1.000 n=10) ¹
Truncate/MixedUTF-8-8 32.00 ± 0% 32.00 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ allocs/op │ allocs/op vs base │
Truncate/Unlimited-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Zero-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Short-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ASCII-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ValidUTF-8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/InvalidUTF-8-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/MixedUTF-8-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
```
#### Values shorter than limit
This is the default code path. Most attribute values will be shorter
than the default 128 limit that users will not modify.
The current code, `safeTruncate` requires a full iteration of the value
to determine it is valid and under the limit.
The replacement, `truncate`, first checks if the number of bytes in the
value are less than or equal to the limit (which guarantees the number
of characters are less than or equal to the limit) and returns
immediately. This will mean that invalid encoding less than the limit is
not changed, which meets the specification requirements.
#### Values longer than the limit
For values who's number of bytes exceeds the limit, they are iterated
only once with the replacement, `truncate`.
In comparison, the current code, `safeTruncate`, can iterate the string
up to three separate times when the string contains invalid characters.
2024-11-26 10:41:55 +02:00
_ , size := utf8 . DecodeRuneInString ( s [ i : ] )
if size == 1 {
// Invalid encoding.
b . Grow ( len ( s ) - 1 )
_ , _ = b . WriteString ( s [ : i ] )
s = s [ i : ]
break
2022-09-12 16:54:58 +02:00
}
}
Fix attribute value truncation (#5997)
Fix #5996
### Correctness
From the [OTel
specification](https://github.com/open-telemetry/opentelemetry-specification/blob/88bffeac48aa5eb37ac8044e931af63a0b55fe00/specification/common/README.md#attribute-limits):
> - set an attribute value length limit such that for each attribute
value:
> - if it is a string, if it exceeds that limit (counting any character
in it as 1), SDKs MUST truncate that value, so that its length is at
most equal to the limit...
Our current implementation truncates on number of bytes not characters.
Unit tests are added/updated to validate this fix and prevent
regressions.
### Performance
```
goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/sdk/trace
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ sec/op │ sec/op vs base │
Truncate/Unlimited-8 1.2300n ± 7% 0.8757n ± 3% -28.80% (p=0.000 n=10)
Truncate/Zero-8 2.341n ± 2% 1.550n ± 9% -33.77% (p=0.000 n=10)
Truncate/Short-8 31.6800n ± 3% 0.9960n ± 4% -96.86% (p=0.000 n=10)
Truncate/ASCII-8 8.821n ± 1% 3.567n ± 3% -59.57% (p=0.000 n=10)
Truncate/ValidUTF-8-8 11.960n ± 1% 7.163n ± 1% -40.10% (p=0.000 n=10)
Truncate/InvalidUTF-8-8 56.35n ± 0% 37.34n ± 18% -33.74% (p=0.000 n=10)
Truncate/MixedUTF-8-8 81.83n ± 1% 50.00n ± 1% -38.90% (p=0.000 n=10)
geomean 12.37n 4.865n -60.68%
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ B/op │ B/op vs base │
Truncate/Unlimited-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Zero-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Short-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ASCII-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ValidUTF-8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/InvalidUTF-8-8 16.00 ± 0% 16.00 ± 0% ~ (p=1.000 n=10) ¹
Truncate/MixedUTF-8-8 32.00 ± 0% 32.00 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
│ commit-b6264913(old).txt │ commit-54c61ac2(new).txt │
│ allocs/op │ allocs/op vs base │
Truncate/Unlimited-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Zero-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/Short-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ASCII-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/ValidUTF-8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/InvalidUTF-8-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
Truncate/MixedUTF-8-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
```
#### Values shorter than limit
This is the default code path. Most attribute values will be shorter
than the default 128 limit that users will not modify.
The current code, `safeTruncate` requires a full iteration of the value
to determine it is valid and under the limit.
The replacement, `truncate`, first checks if the number of bytes in the
value are less than or equal to the limit (which guarantees the number
of characters are less than or equal to the limit) and returns
immediately. This will mean that invalid encoding less than the limit is
not changed, which meets the specification requirements.
#### Values longer than the limit
For values who's number of bytes exceeds the limit, they are iterated
only once with the replacement, `truncate`.
In comparison, the current code, `safeTruncate`, can iterate the string
up to three separate times when the string contains invalid characters.
2024-11-26 10:41:55 +02:00
// Fast-path, no invalid input.
if b . Cap ( ) == 0 {
return s
}
// Truncate while validating UTF-8.
for i := 0 ; i < len ( s ) && count < limit ; {
c := s [ i ]
if c < utf8 . RuneSelf {
// Optimization for single byte runes (common case).
_ = b . WriteByte ( c )
i ++
count ++
continue
}
_ , size := utf8 . DecodeRuneInString ( s [ i : ] )
if size == 1 {
// We checked for all 1-byte runes above, this is a RuneError.
i ++
continue
}
_ , _ = b . WriteString ( s [ i : i + size ] )
i += size
count ++
}
return b . String ( )
2022-09-12 16:54:58 +02:00
}
2021-02-17 16:41:18 +02:00
// End ends the span. This method does nothing if the span is already ended or
// is not being recorded.
2020-09-03 16:34:36 +02:00
//
2024-11-27 17:53:10 +02:00
// The only SpanEndOption currently supported are [trace.WithTimestamp], and
// [trace.WithStackTrace].
2020-09-03 16:34:36 +02:00
//
// If this method is called while panicking an error event is added to the
// Span before ending it and the panic is continued.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) End ( options ... trace . SpanEndOption ) {
2021-02-17 16:41:18 +02:00
// 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.
2019-08-02 22:52:55 +02:00
if s == nil {
return
}
2020-12-11 07:15:44 +02:00
// Store the end time as soon as possible to avoid artificially increasing
// the span's duration in case some operation below takes a while.
2024-05-31 12:37:38 +02:00
et := monotonicEndTime ( s . startTime )
2020-12-11 07:15:44 +02:00
2024-10-09 09:51:17 +02:00
// Lock the span now that we have an end time and see if we need to do any more processing.
s . mu . Lock ( )
if ! s . isRecording ( ) {
s . mu . Unlock ( )
2021-02-17 16:41:18 +02:00
return
}
2021-08-13 23:44:18 +02:00
config := trace . NewSpanEndConfig ( options ... )
2020-08-08 21:10:36 +02:00
if recovered := recover ( ) ; recovered != nil {
// Record but don't stop the panic.
defer panic ( recovered )
2021-08-13 23:44:18 +02:00
opts := [ ] trace . EventOption {
2020-11-07 00:13:31 +02:00
trace . WithAttributes (
2023-02-07 23:42:47 +02:00
semconv . ExceptionType ( typeStr ( recovered ) ) ,
semconv . ExceptionMessage ( fmt . Sprint ( recovered ) ) ,
2020-10-16 17:09:27 +02:00
) ,
2021-08-13 23:44:18 +02:00
}
if config . StackTrace ( ) {
opts = append ( opts , trace . WithAttributes (
2023-02-07 23:42:47 +02:00
semconv . ExceptionStacktrace ( recordStackTrace ( ) ) ,
2021-08-13 23:44:18 +02:00
) )
}
s . addEvent ( semconv . ExceptionEventName , opts ... )
2020-08-08 21:10:36 +02:00
}
2019-08-02 22:52:55 +02:00
if s . executionTracerTaskEnd != nil {
2024-10-09 09:51:17 +02:00
s . mu . Unlock ( )
2019-08-02 22:52:55 +02:00
s . executionTracerTaskEnd ( )
2024-10-09 09:51:17 +02:00
s . mu . Lock ( )
2019-08-02 22:52:55 +02:00
}
2020-12-11 07:15:44 +02:00
2021-02-17 16:41:18 +02:00
// Setting endTime to non-zero marks the span as ended and not recording.
2021-05-27 16:53:56 +02:00
if config . Timestamp ( ) . IsZero ( ) {
2020-12-11 07:15:44 +02:00
s . endTime = et
} else {
2021-05-27 16:53:56 +02:00
s . endTime = config . Timestamp ( )
2020-12-11 07:15:44 +02:00
}
s . mu . Unlock ( )
2023-04-01 16:57:35 +02:00
sps := s . tracer . provider . getSpanProcessors ( )
2022-10-12 22:44:18 +02:00
if len ( sps ) == 0 {
return
}
snap := s . snapshot ( )
for _ , sp := range sps {
sp . sp . OnEnd ( snap )
2020-12-11 07:15:44 +02:00
}
2019-08-02 22:52:55 +02:00
}
2024-05-31 12:37:38 +02:00
// monotonicEndTime returns the end time at present but offset from start,
// monotonically.
//
// The monotonic clock is used in subtractions hence the duration since start
// added back to start gives end as a monotonic time. See
// https://golang.org/pkg/time/#hdr-Monotonic_Clocks
func monotonicEndTime ( start time . Time ) time . Time {
return start . Add ( time . Since ( start ) )
}
2021-03-08 21:31:01 +02:00
// 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.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) RecordError ( err error , opts ... trace . EventOption ) {
2024-10-09 09:51:17 +02:00
if s == nil || err == nil {
return
}
s . mu . Lock ( )
defer s . mu . Unlock ( )
if ! s . isRecording ( ) {
2020-02-28 23:44:53 +02:00
return
}
2020-11-07 00:13:31 +02:00
opts = append ( opts , trace . WithAttributes (
2023-02-07 23:42:47 +02:00
semconv . ExceptionType ( typeStr ( err ) ) ,
semconv . ExceptionMessage ( err . Error ( ) ) ,
2020-10-16 17:09:27 +02:00
) )
2021-08-13 23:44:18 +02:00
c := trace . NewEventConfig ( opts ... )
if c . StackTrace ( ) {
opts = append ( opts , trace . WithAttributes (
2023-02-07 23:42:47 +02:00
semconv . ExceptionStacktrace ( recordStackTrace ( ) ) ,
2021-08-13 23:44:18 +02:00
) )
}
2021-04-01 22:07:46 +02:00
s . addEvent ( semconv . ExceptionEventName , opts ... )
2020-02-28 23:44:53 +02:00
}
2020-08-08 21:10:36 +02:00
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 ( ) )
}
2021-08-13 23:44:18 +02:00
func recordStackTrace ( ) string {
stackTrace := make ( [ ] byte , 2048 )
n := runtime . Stack ( stackTrace , false )
return string ( stackTrace [ 0 : n ] )
}
2021-02-17 16:41:18 +02:00
// AddEvent adds an event with the provided name and options. If this span is
2024-10-09 09:51:17 +02:00
// not being recorded then this method does nothing.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) AddEvent ( name string , o ... trace . EventOption ) {
2024-10-09 09:51:17 +02:00
if s == nil {
return
}
s . mu . Lock ( )
defer s . mu . Unlock ( )
if ! s . isRecording ( ) {
2019-08-02 22:52:55 +02:00
return
}
2020-10-16 17:09:27 +02:00
s . addEvent ( name , o ... )
2019-08-02 22:52:55 +02:00
}
2024-10-09 09:51:17 +02:00
// addEvent adds an event with the provided name and options.
//
// This method assumes s.mu.Lock is held by the caller.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) addEvent ( name string , o ... trace . EventOption ) {
2020-11-07 00:13:31 +02:00
c := trace . NewEventConfig ( o ... )
2022-03-03 17:56:07 +02:00
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 ]
2021-02-18 21:31:35 +02:00
}
2022-03-03 17:56:07 +02:00
s . events . add ( e )
2019-08-02 22:52:55 +02:00
}
2021-02-17 16:41:18 +02:00
// SetName sets the name of this span. If this span is not being recorded than
// this method does nothing.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) SetName ( name string ) {
2024-10-07 09:30:29 +02:00
if s == nil {
2021-02-17 16:41:18 +02:00
return
}
2019-10-23 00:25:33 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
2024-10-07 09:30:29 +02:00
if ! s . isRecording ( ) {
return
}
2020-12-11 07:15:44 +02:00
s . name = name
2019-08-26 20:53:12 +02:00
}
2021-02-17 16:41:18 +02:00
// Name returns the name of this span.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) Name ( ) string {
2020-12-11 07:15:44 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
return s . name
}
2021-02-17 16:41:18 +02:00
// Name returns the SpanContext of this span's parent span.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) Parent ( ) trace . SpanContext {
2020-12-11 07:15:44 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
return s . parent
}
2021-02-17 16:41:18 +02:00
// SpanKind returns the SpanKind of this span.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) SpanKind ( ) trace . SpanKind {
2020-12-11 07:15:44 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
return s . spanKind
}
2021-02-17 16:41:18 +02:00
// StartTime returns the time this span started.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) StartTime ( ) time . Time {
2020-12-11 07:15:44 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
return s . startTime
}
2021-02-17 16:41:18 +02:00
// 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.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) EndTime ( ) time . Time {
2020-12-11 07:15:44 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
return s . endTime
}
2021-02-17 16:41:18 +02:00
// Attributes returns the attributes of this span.
2022-02-07 22:58:05 +02:00
//
// The order of the returned attributes is not guaranteed to be stable.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) Attributes ( ) [ ] attribute . KeyValue {
2020-12-11 07:15:44 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
2022-02-07 22:58:05 +02:00
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.
2024-10-07 09:30:29 +02:00
exists := make ( map [ attribute . Key ] int , len ( s . attributes ) )
s . dedupeAttrsFromRecord ( exists )
2022-02-07 22:58:05 +02:00
}
// 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.
2024-10-07 09:30:29 +02:00
func ( s * recordingSpan ) dedupeAttrsFromRecord ( record map [ attribute . Key ] int ) {
2022-02-07 22:58:05 +02:00
// Use the fact that slices share the same backing array.
unique := s . attributes [ : 0 ]
for _ , a := range s . attributes {
2024-10-07 09:30:29 +02:00
if idx , ok := record [ a . Key ] ; ok {
2022-02-07 22:58:05 +02:00
unique [ idx ] = a
} else {
unique = append ( unique , a )
2024-10-07 09:30:29 +02:00
record [ a . Key ] = len ( unique ) - 1
2022-02-07 22:58:05 +02:00
}
2020-12-11 07:15:44 +02:00
}
2024-11-08 08:36:35 +02:00
clear ( s . attributes [ len ( unique ) : ] ) // Erase unneeded elements to let GC collect objects.
2022-02-07 22:58:05 +02:00
s . attributes = unique
2020-12-11 07:15:44 +02:00
}
2021-04-23 17:04:59 +02:00
// Links returns the links of this span.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) Links ( ) [ ] Link {
2020-12-11 07:15:44 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
if len ( s . links . queue ) == 0 {
2021-07-26 17:06:41 +02:00
return [ ] Link { }
2020-12-11 07:15:44 +02:00
}
2024-05-30 20:40:08 +02:00
return s . links . copy ( )
2020-12-11 07:15:44 +02:00
}
2021-02-17 16:41:18 +02:00
// Events returns the events of this span.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) Events ( ) [ ] Event {
2020-12-11 07:15:44 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
2021-05-05 01:45:13 +02:00
if len ( s . events . queue ) == 0 {
2021-04-29 19:29:48 +02:00
return [ ] Event { }
2020-12-11 07:15:44 +02:00
}
2024-05-30 20:40:08 +02:00
return s . events . copy ( )
2020-12-11 07:15:44 +02:00
}
2021-05-03 21:00:54 +02:00
// Status returns the status of this span.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) Status ( ) Status {
2020-12-11 07:15:44 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
2021-05-03 21:00:54 +02:00
return s . status
2020-12-11 07:15:44 +02:00
}
2022-07-06 20:55:46 +02:00
// 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
}
2021-02-17 16:41:18 +02:00
// InstrumentationLibrary returns the instrumentation.Library associated with
// the Tracer that created this span.
2024-07-24 09:33:07 +02:00
func ( s * recordingSpan ) InstrumentationLibrary ( ) instrumentation . Library { //nolint:staticcheck // This method needs to be define for backwards compatibility
2020-12-11 07:15:44 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
2022-07-06 20:55:46 +02:00
return s . tracer . instrumentationScope
2020-12-11 07:15:44 +02:00
}
2021-02-17 16:41:18 +02:00
// Resource returns the Resource associated with the Tracer that created this
// span.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) Resource ( ) * resource . Resource {
2020-12-11 07:15:44 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
2022-02-02 01:20:35 +02:00
return s . tracer . provider . resource
2020-12-11 07:15:44 +02:00
}
2024-03-28 09:35:15 +02:00
func ( s * recordingSpan ) AddLink ( link trace . Link ) {
2024-10-09 09:51:17 +02:00
if s == nil {
2024-05-09 22:25:02 +02:00
return
}
if ! link . SpanContext . IsValid ( ) && len ( link . Attributes ) == 0 &&
link . SpanContext . TraceState ( ) . Len ( ) == 0 {
2019-09-21 09:26:20 +02:00
return
}
2021-07-26 17:06:41 +02:00
2024-10-09 09:51:17 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
if ! s . isRecording ( ) {
return
}
2022-03-03 17:56:07 +02:00
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 ]
2021-02-18 21:31:35 +02:00
}
2022-03-03 17:56:07 +02:00
s . links . add ( l )
2019-09-21 09:26:20 +02:00
}
2021-05-05 01:45:13 +02:00
// DroppedAttributes returns the number of attributes dropped by the span
// due to limits being reached.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) DroppedAttributes ( ) int {
2019-08-02 22:52:55 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
2022-02-07 22:58:05 +02:00
return s . droppedAttributes
2021-05-05 01:45:13 +02:00
}
// DroppedLinks returns the number of links dropped by the span due to limits
// being reached.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) DroppedLinks ( ) int {
2021-05-05 01:45:13 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
return s . links . droppedCount
}
2019-11-18 20:51:57 +02:00
2021-05-05 01:45:13 +02:00
// DroppedEvents returns the number of events dropped by the span due to
// limits being reached.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) DroppedEvents ( ) int {
2021-05-05 01:45:13 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
return s . events . droppedCount
}
// ChildSpanCount returns the count of spans that consider the span a
// direct parent.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) ChildSpanCount ( ) int {
2021-05-05 01:45:13 +02:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
return s . childSpanCount
}
2021-06-17 18:05:44 +02:00
// TracerProvider returns a trace.TracerProvider that can be used to generate
// additional Spans on the same telemetry pipeline as the current Span.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) TracerProvider ( ) trace . TracerProvider {
2021-06-17 18:05:44 +02:00
return s . tracer . provider
}
2021-05-05 01:45:13 +02:00
// snapshot creates a read-only copy of the current state of the span.
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) snapshot ( ) ReadOnlySpan {
2021-05-05 01:45:13 +02:00
var sd snapshot
s . mu . Lock ( )
defer s . mu . Unlock ( )
sd . endTime = s . endTime
2022-07-06 20:55:46 +02:00
sd . instrumentationScope = s . tracer . instrumentationScope
2021-05-05 01:45:13 +02:00
sd . name = s . name
sd . parent = s . parent
2022-02-02 01:20:35 +02:00
sd . resource = s . tracer . provider . resource
2021-05-05 01:45:13 +02:00
sd . spanContext = s . spanContext
sd . spanKind = s . spanKind
sd . startTime = s . startTime
sd . status = s . status
sd . childSpanCount = s . childSpanCount
2020-12-11 07:15:44 +02:00
2022-02-07 22:58:05 +02:00
if len ( s . attributes ) > 0 {
s . dedupeAttrs ( )
sd . attributes = s . attributes
2020-12-11 07:15:44 +02:00
}
2022-02-07 22:58:05 +02:00
sd . droppedAttributeCount = s . droppedAttributes
2021-05-05 01:45:13 +02:00
if len ( s . events . queue ) > 0 {
2024-05-30 20:40:08 +02:00
sd . events = s . events . copy ( )
2021-05-05 01:45:13 +02:00
sd . droppedEventCount = s . events . droppedCount
2019-08-02 22:52:55 +02:00
}
2019-09-21 09:26:20 +02:00
if len ( s . links . queue ) > 0 {
2024-05-30 20:40:08 +02:00
sd . links = s . links . copy ( )
2021-05-05 01:45:13 +02:00
sd . droppedLinkCount = s . links . droppedCount
2019-09-21 09:26:20 +02:00
}
2019-08-02 22:52:55 +02:00
return & sd
}
2021-09-02 23:30:12 +02:00
func ( s * recordingSpan ) addChild ( ) {
2024-10-07 09:30:29 +02:00
if s == nil {
2019-08-02 22:52:55 +02:00
return
}
2024-10-07 09:30:29 +02:00
2019-08-02 22:52:55 +02:00
s . mu . Lock ( )
2024-10-07 09:30:29 +02:00
defer s . mu . Unlock ( )
if ! s . isRecording ( ) {
return
}
2020-12-11 07:15:44 +02:00
s . childSpanCount ++
2019-08-02 22:52:55 +02:00
}
2021-09-02 23:30:12 +02:00
func ( * recordingSpan ) private ( ) { }
2021-02-24 20:03:35 +02:00
2021-09-02 23:30:12 +02:00
// 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 )
2019-08-02 22:52:55 +02:00
2021-09-02 23:30:12 +02:00
s . mu . Lock ( )
s . executionTracerTaskEnd = task . End
s . mu . Unlock ( )
2019-08-02 22:52:55 +02:00
2021-09-02 23:30:12 +02:00
return nctx
}
2021-03-25 16:36:39 +02:00
2021-09-02 23:30:12 +02:00
// 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 {
2023-10-19 19:16:24 +02:00
embedded . Span
2021-09-02 23:30:12 +02:00
// tracer is the SDK tracer that created this span.
tracer * tracer
sc trace . SpanContext
}
2020-12-11 07:15:44 +02:00
2021-09-02 23:30:12 +02:00
var _ trace . Span = nonRecordingSpan { }
2021-03-25 16:36:39 +02:00
2021-09-02 23:30:12 +02:00
// SpanContext returns the wrapped SpanContext.
func ( s nonRecordingSpan ) SpanContext ( ) trace . SpanContext { return s . sc }
2019-08-02 22:52:55 +02:00
2021-09-02 23:30:12 +02:00
// IsRecording always returns false.
func ( nonRecordingSpan ) IsRecording ( ) bool { return false }
2019-08-02 22:52:55 +02:00
2021-09-02 23:30:12 +02:00
// SetStatus does nothing.
func ( nonRecordingSpan ) SetStatus ( codes . Code , string ) { }
2020-12-11 07:15:44 +02:00
2021-09-02 23:30:12 +02:00
// SetError does nothing.
func ( nonRecordingSpan ) SetError ( bool ) { }
2019-08-02 22:52:55 +02:00
2021-09-02 23:30:12 +02:00
// SetAttributes does nothing.
func ( nonRecordingSpan ) SetAttributes ( ... attribute . KeyValue ) { }
2020-03-10 17:25:11 +02:00
2021-09-02 23:30:12 +02:00
// 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 ) { }
2024-03-28 09:35:15 +02:00
// AddLink does nothing.
func ( nonRecordingSpan ) AddLink ( trace . Link ) { }
2021-09-02 23:30:12 +02:00
// 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 }
2019-08-26 20:53:12 +02:00
2021-03-09 19:45:09 +02:00
func isRecording ( s SamplingResult ) bool {
return s . Decision == RecordOnly || s . Decision == RecordAndSample
}
func isSampled ( s SamplingResult ) bool {
return s . Decision == RecordAndSample
2019-08-26 20:53:12 +02:00
}
2021-04-07 17:03:43 +02:00
2021-05-03 21:00:54 +02:00
// Status is the classified state of a Span.
type Status struct {
2021-05-05 01:45:13 +02:00
// Code is an identifier of a Spans state classification.
2021-05-03 21:00:54 +02:00
Code codes . Code
2021-06-24 21:41:08 +02:00
// Description is a user hint about why that status was set. It is only
2021-05-03 21:00:54 +02:00
// applicable when Code is Error.
Description string
}