upgrade deps; rewrite smtp session
This commit is contained in:
592
vendor/github.com/getsentry/sentry-go/tracing.go
generated
vendored
592
vendor/github.com/getsentry/sentry-go/tracing.go
generated
vendored
@@ -9,9 +9,15 @@ import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
SentryTraceHeader = "sentry-trace"
|
||||
SentryBaggageHeader = "baggage"
|
||||
)
|
||||
|
||||
// A Span is the building block of a Sentry transaction. Spans build up a tree
|
||||
// structure of timed operations. The span tree makes up a transaction event
|
||||
// that is sent to Sentry when the root span is finished.
|
||||
@@ -21,6 +27,7 @@ type Span struct { //nolint: maligned // prefer readability over optimal memory
|
||||
TraceID TraceID `json:"trace_id"`
|
||||
SpanID SpanID `json:"span_id"`
|
||||
ParentSpanID SpanID `json:"parent_span_id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Op string `json:"op,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Status SpanStatus `json:"status,omitempty"`
|
||||
@@ -28,24 +35,43 @@ type Span struct { //nolint: maligned // prefer readability over optimal memory
|
||||
StartTime time.Time `json:"start_timestamp"`
|
||||
EndTime time.Time `json:"timestamp"`
|
||||
Data map[string]interface{} `json:"data,omitempty"`
|
||||
Sampled Sampled `json:"-"`
|
||||
Source TransactionSource `json:"-"`
|
||||
|
||||
Sampled Sampled `json:"-"`
|
||||
|
||||
// mu protects concurrent writes to map fields
|
||||
mu sync.RWMutex
|
||||
// sample rate the span was sampled with.
|
||||
sampleRate float64
|
||||
// ctx is the context where the span was started. Always non-nil.
|
||||
ctx context.Context
|
||||
|
||||
// Dynamic Sampling context
|
||||
dynamicSamplingContext DynamicSamplingContext
|
||||
// parent refers to the immediate local parent span. A remote parent span is
|
||||
// only referenced by setting ParentSpanID.
|
||||
parent *Span
|
||||
|
||||
// isTransaction is true only for the root span of a local span tree. The
|
||||
// root span is the first span started in a context. Note that a local root
|
||||
// span may have a remote parent belonging to the same trace, therefore
|
||||
// isTransaction depends on ctx and not on parent.
|
||||
isTransaction bool
|
||||
|
||||
// recorder stores all spans in a transaction. Guaranteed to be non-nil.
|
||||
recorder *spanRecorder
|
||||
// span context, can only be set on transactions
|
||||
contexts map[string]Context
|
||||
// collectProfile is a function that collects a profile of the current transaction. May be nil.
|
||||
collectProfile transactionProfiler
|
||||
// a Once instance to make sure that Finish() is only called once.
|
||||
finishOnce sync.Once
|
||||
}
|
||||
|
||||
// TraceParentContext describes the context of a (remote) parent span.
|
||||
//
|
||||
// The context is normally extracted from a received "sentry-trace" header and
|
||||
// used to initialize a new transaction.
|
||||
//
|
||||
// Note: the name might be not the best one. It was taken mostly to stay aligned
|
||||
// with other SDKs, and it alludes to W3C "traceparent" header (https://www.w3.org/TR/trace-context/),
|
||||
// which serves a similar purpose to "sentry-trace". We should eventually consider
|
||||
// making this type internal-only and give it a better name.
|
||||
type TraceParentContext struct {
|
||||
TraceID TraceID
|
||||
ParentSpanID SpanID
|
||||
Sampled Sampled
|
||||
}
|
||||
|
||||
// (*) Note on maligned:
|
||||
@@ -81,14 +107,18 @@ func StartSpan(ctx context.Context, operation string, options ...SpanOption) *Sp
|
||||
// defaults
|
||||
Op: operation,
|
||||
StartTime: time.Now(),
|
||||
Sampled: SampledUndefined,
|
||||
|
||||
ctx: context.WithValue(ctx, spanContextKey{}, &span),
|
||||
parent: parent,
|
||||
isTransaction: !hasParent,
|
||||
ctx: context.WithValue(ctx, spanContextKey{}, &span),
|
||||
parent: parent,
|
||||
}
|
||||
|
||||
if hasParent {
|
||||
span.TraceID = parent.TraceID
|
||||
} else {
|
||||
// Only set the Source if this is a transaction
|
||||
span.Source = SourceCustom
|
||||
|
||||
// Implementation note:
|
||||
//
|
||||
// While math/rand is ~2x faster than crypto/rand (exact
|
||||
@@ -146,38 +176,27 @@ func StartSpan(ctx context.Context, operation string, options ...SpanOption) *Sp
|
||||
}
|
||||
span.recorder.record(&span)
|
||||
|
||||
hub := hubFromContext(ctx)
|
||||
|
||||
// Update scope so that all events include a trace context, allowing
|
||||
// Sentry to correlate errors to transactions/spans.
|
||||
hubFromContext(ctx).Scope().SetContext("trace", span.traceContext())
|
||||
hub.Scope().SetContext("trace", span.traceContext().Map())
|
||||
|
||||
// Start profiling only if it's a sampled root transaction.
|
||||
if span.IsTransaction() && span.Sampled.Bool() {
|
||||
span.sampleTransactionProfile()
|
||||
}
|
||||
|
||||
return &span
|
||||
}
|
||||
|
||||
// Finish sets the span's end time, unless already set. If the span is the root
|
||||
// of a span tree, Finish sends the span tree to Sentry as a transaction.
|
||||
//
|
||||
// The logic is executed at most once per span, so that (incorrectly) calling it twice
|
||||
// never double sends to Sentry.
|
||||
func (s *Span) Finish() {
|
||||
// TODO(tracing): maybe make Finish run at most once, such that
|
||||
// (incorrectly) calling it twice never double sends to Sentry.
|
||||
|
||||
if s.EndTime.IsZero() {
|
||||
s.EndTime = monotonicTimeSince(s.StartTime)
|
||||
}
|
||||
if !s.Sampled.Bool() {
|
||||
return
|
||||
}
|
||||
event := s.toEvent()
|
||||
if event == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(tracing): add breadcrumbs
|
||||
// (see https://github.com/getsentry/sentry-python/blob/f6f3525f8812f609/sentry_sdk/tracing.py#L372)
|
||||
|
||||
hub := hubFromContext(s.ctx)
|
||||
if hub.Scope().Transaction() == "" {
|
||||
Logger.Printf("Missing transaction name for span with op = %q", s.Op)
|
||||
}
|
||||
hub.CaptureEvent(event)
|
||||
s.finishOnce.Do(s.doFinish)
|
||||
}
|
||||
|
||||
// Context returns the context containing the span.
|
||||
@@ -195,12 +214,66 @@ func (s *Span) StartChild(operation string, options ...SpanOption) *Span {
|
||||
// accessing the tags map directly as SetTag takes care of initializing the map
|
||||
// when necessary.
|
||||
func (s *Span) SetTag(name, value string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.Tags == nil {
|
||||
s.Tags = make(map[string]string)
|
||||
}
|
||||
s.Tags[name] = value
|
||||
}
|
||||
|
||||
// SetData sets a data on the span. It is recommended to use SetData instead of
|
||||
// accessing the data map directly as SetData takes care of initializing the map
|
||||
// when necessary.
|
||||
func (s *Span) SetData(name, value string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.Data == nil {
|
||||
s.Data = make(map[string]interface{})
|
||||
}
|
||||
s.Data[name] = value
|
||||
}
|
||||
|
||||
// SetContext sets a context on the span. It is recommended to use SetContext instead of
|
||||
// accessing the contexts map directly as SetContext takes care of initializing the map
|
||||
// when necessary.
|
||||
func (s *Span) SetContext(key string, value Context) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.contexts == nil {
|
||||
s.contexts = make(map[string]Context)
|
||||
}
|
||||
s.contexts[key] = value
|
||||
}
|
||||
|
||||
// IsTransaction checks if the given span is a transaction.
|
||||
func (s *Span) IsTransaction() bool {
|
||||
return s.parent == nil
|
||||
}
|
||||
|
||||
// GetTransaction returns the transaction that contains this span.
|
||||
//
|
||||
// For transaction spans it returns itself. For spans that were created manually
|
||||
// the method returns "nil".
|
||||
func (s *Span) GetTransaction() *Span {
|
||||
spanRecorder := s.spanRecorder()
|
||||
if spanRecorder == nil {
|
||||
// This probably means that the Span was created manually (not via
|
||||
// StartTransaction/StartSpan or StartChild).
|
||||
// Return "nil" to indicate that it's not a normal situation.
|
||||
return nil
|
||||
}
|
||||
recorderRoot := spanRecorder.root()
|
||||
if recorderRoot == nil {
|
||||
// Same as above: manually created Span.
|
||||
return nil
|
||||
}
|
||||
return recorderRoot
|
||||
}
|
||||
|
||||
// TODO(tracing): maybe add shortcuts to get/set transaction name. Right now the
|
||||
// transaction name is in the Scope, as it has existed there historically, prior
|
||||
// to tracing.
|
||||
@@ -210,8 +283,9 @@ func (s *Span) SetTag(name, value string) {
|
||||
// func (s *Span) TransactionName() string
|
||||
// func (s *Span) SetTransactionName(name string)
|
||||
|
||||
// ToSentryTrace returns the trace propagation value used with the sentry-trace
|
||||
// HTTP header.
|
||||
// ToSentryTrace returns the seralized TraceParentContext from a transaction/span.
|
||||
// Use this function to propagate the TraceParentContext to a downstream SDK,
|
||||
// either as the value of the "sentry-trace" HTTP header, or as an html "sentry-trace" meta tag.
|
||||
func (s *Span) ToSentryTrace() string {
|
||||
// TODO(tracing): add instrumentation for outgoing HTTP requests using
|
||||
// ToSentryTrace.
|
||||
@@ -226,25 +300,76 @@ func (s *Span) ToSentryTrace() string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ToBaggage returns the serialized DynamicSamplingContext from a transaction.
|
||||
// Use this function to propagate the DynamicSamplingContext to a downstream SDK,
|
||||
// either as the value of the "baggage" HTTP header, or as an html "baggage" meta tag.
|
||||
func (s *Span) ToBaggage() string {
|
||||
if containingTransaction := s.GetTransaction(); containingTransaction != nil {
|
||||
// In case there is currently no frozen DynamicSamplingContext attached to the transaction,
|
||||
// create one from the properties of the transaction.
|
||||
if !s.dynamicSamplingContext.IsFrozen() {
|
||||
// This will return a frozen DynamicSamplingContext.
|
||||
s.dynamicSamplingContext = DynamicSamplingContextFromTransaction(containingTransaction)
|
||||
}
|
||||
|
||||
return containingTransaction.dynamicSamplingContext.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetDynamicSamplingContext sets the given dynamic sampling context on the
|
||||
// current transaction.
|
||||
func (s *Span) SetDynamicSamplingContext(dsc DynamicSamplingContext) {
|
||||
if s.IsTransaction() {
|
||||
s.dynamicSamplingContext = dsc
|
||||
}
|
||||
}
|
||||
|
||||
// doFinish runs the actual Span.Finish() logic.
|
||||
func (s *Span) doFinish() {
|
||||
if s.EndTime.IsZero() {
|
||||
s.EndTime = monotonicTimeSince(s.StartTime)
|
||||
}
|
||||
|
||||
if !s.Sampled.Bool() {
|
||||
return
|
||||
}
|
||||
event := s.toEvent()
|
||||
if event == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.collectProfile != nil {
|
||||
event.sdkMetaData.transactionProfile = s.collectProfile(s)
|
||||
}
|
||||
|
||||
// TODO(tracing): add breadcrumbs
|
||||
// (see https://github.com/getsentry/sentry-python/blob/f6f3525f8812f609/sentry_sdk/tracing.py#L372)
|
||||
|
||||
hub := hubFromContext(s.ctx)
|
||||
hub.CaptureEvent(event)
|
||||
}
|
||||
|
||||
// sentryTracePattern matches either
|
||||
//
|
||||
// TRACE_ID - SPAN_ID
|
||||
// [[:xdigit:]]{32}-[[:xdigit:]]{16}
|
||||
// TRACE_ID - SPAN_ID
|
||||
// [[:xdigit:]]{32}-[[:xdigit:]]{16}
|
||||
//
|
||||
// or
|
||||
//
|
||||
// TRACE_ID - SPAN_ID - SAMPLED
|
||||
// [[:xdigit:]]{32}-[[:xdigit:]]{16}-[01]
|
||||
// TRACE_ID - SPAN_ID - SAMPLED
|
||||
// [[:xdigit:]]{32}-[[:xdigit:]]{16}-[01]
|
||||
var sentryTracePattern = regexp.MustCompile(`^([[:xdigit:]]{32})-([[:xdigit:]]{16})(?:-([01]))?$`)
|
||||
|
||||
// updateFromSentryTrace parses a sentry-trace HTTP header (as returned by
|
||||
// ToSentryTrace) and updates fields of the span. If the header cannot be
|
||||
// recognized as valid, the span is left unchanged.
|
||||
func (s *Span) updateFromSentryTrace(header []byte) {
|
||||
// recognized as valid, the span is left unchanged. The returned value indicates
|
||||
// whether the span was updated.
|
||||
func (s *Span) updateFromSentryTrace(header []byte) (updated bool) {
|
||||
m := sentryTracePattern.FindSubmatch(header)
|
||||
if m == nil {
|
||||
// no match
|
||||
return
|
||||
return false
|
||||
}
|
||||
_, _ = hex.Decode(s.TraceID[:], m[1])
|
||||
_, _ = hex.Decode(s.ParentSpanID[:], m[2])
|
||||
@@ -256,6 +381,18 @@ func (s *Span) updateFromSentryTrace(header []byte) {
|
||||
s.Sampled = SampledTrue
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Span) updateFromBaggage(header []byte) {
|
||||
if s.IsTransaction() {
|
||||
dsc, err := DynamicSamplingContextFromHeader(header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.dynamicSamplingContext = dsc
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Span) MarshalJSON() ([]byte, error) {
|
||||
@@ -275,46 +412,107 @@ func (s *Span) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Span) clientOptions() *ClientOptions {
|
||||
client := hubFromContext(s.ctx).Client()
|
||||
if client != nil {
|
||||
return &client.options
|
||||
}
|
||||
return &ClientOptions{}
|
||||
}
|
||||
|
||||
func (s *Span) sample() Sampled {
|
||||
// https://develop.sentry.dev/sdk/unified-api/tracing/#sampling
|
||||
// #1 explicit sampling decision via StartSpan options.
|
||||
clientOptions := s.clientOptions()
|
||||
// https://develop.sentry.dev/sdk/performance/#sampling
|
||||
// #1 tracing is not enabled.
|
||||
if !clientOptions.EnableTracing {
|
||||
Logger.Printf("Dropping transaction: EnableTracing is set to %t", clientOptions.EnableTracing)
|
||||
s.sampleRate = 0.0
|
||||
return SampledFalse
|
||||
}
|
||||
|
||||
// #2 explicit sampling decision via StartSpan/StartTransaction options.
|
||||
if s.Sampled != SampledUndefined {
|
||||
Logger.Printf("Using explicit sampling decision from StartSpan/StartTransaction: %v", s.Sampled)
|
||||
switch s.Sampled {
|
||||
case SampledTrue:
|
||||
s.sampleRate = 1.0
|
||||
case SampledFalse:
|
||||
s.sampleRate = 0.0
|
||||
}
|
||||
return s.Sampled
|
||||
}
|
||||
hub := hubFromContext(s.ctx)
|
||||
var clientOptions ClientOptions
|
||||
client := hub.Client()
|
||||
if client != nil {
|
||||
clientOptions = hub.Client().Options()
|
||||
}
|
||||
samplingContext := SamplingContext{Span: s, Parent: s.parent}
|
||||
|
||||
// Variant for non-transaction spans: they inherit the parent decision.
|
||||
// TracesSampler only runs for the root span.
|
||||
// Note: non-transaction should always have a parent, but we check both
|
||||
// conditions anyway -- the first for semantic meaning, the second to
|
||||
// avoid a nil pointer dereference.
|
||||
if !s.isTransaction && s.parent != nil {
|
||||
if !s.IsTransaction() && s.parent != nil {
|
||||
return s.parent.Sampled
|
||||
}
|
||||
// #2 use TracesSampler from ClientOptions.
|
||||
|
||||
// #3 use TracesSampler from ClientOptions.
|
||||
sampler := clientOptions.TracesSampler
|
||||
if sampler != nil {
|
||||
return sampler.Sample(samplingContext)
|
||||
samplingContext := SamplingContext{
|
||||
Span: s,
|
||||
Parent: s.parent,
|
||||
}
|
||||
// #3 inherit parent decision.
|
||||
|
||||
if sampler != nil {
|
||||
tracesSamplerSampleRate := sampler.Sample(samplingContext)
|
||||
s.sampleRate = tracesSamplerSampleRate
|
||||
if tracesSamplerSampleRate < 0.0 || tracesSamplerSampleRate > 1.0 {
|
||||
Logger.Printf("Dropping transaction: Returned TracesSampler rate is out of range [0.0, 1.0]: %f", tracesSamplerSampleRate)
|
||||
return SampledFalse
|
||||
}
|
||||
if tracesSamplerSampleRate == 0 {
|
||||
Logger.Printf("Dropping transaction: Returned TracesSampler rate is: %f", tracesSamplerSampleRate)
|
||||
return SampledFalse
|
||||
}
|
||||
|
||||
if rng.Float64() < tracesSamplerSampleRate {
|
||||
return SampledTrue
|
||||
}
|
||||
Logger.Printf("Dropping transaction: TracesSampler returned rate: %f", tracesSamplerSampleRate)
|
||||
return SampledFalse
|
||||
}
|
||||
// #4 inherit parent decision.
|
||||
if s.parent != nil {
|
||||
Logger.Printf("Using sampling decision from parent: %v", s.parent.Sampled)
|
||||
switch s.parent.Sampled {
|
||||
case SampledTrue:
|
||||
s.sampleRate = 1.0
|
||||
case SampledFalse:
|
||||
s.sampleRate = 0.0
|
||||
}
|
||||
return s.parent.Sampled
|
||||
}
|
||||
// #4 uniform sampling using TracesSampleRate.
|
||||
sampler = UniformTracesSampler(clientOptions.TracesSampleRate)
|
||||
return sampler.Sample(samplingContext)
|
||||
|
||||
// #5 use TracesSampleRate from ClientOptions.
|
||||
sampleRate := clientOptions.TracesSampleRate
|
||||
s.sampleRate = sampleRate
|
||||
if sampleRate < 0.0 || sampleRate > 1.0 {
|
||||
Logger.Printf("Dropping transaction: TracesSamplerRate out of range [0.0, 1.0]: %f", sampleRate)
|
||||
return SampledFalse
|
||||
}
|
||||
if sampleRate == 0.0 {
|
||||
Logger.Printf("Dropping transaction: TracesSampleRate rate is: %f", sampleRate)
|
||||
return SampledFalse
|
||||
}
|
||||
|
||||
if rng.Float64() < sampleRate {
|
||||
return SampledTrue
|
||||
}
|
||||
|
||||
return SampledFalse
|
||||
}
|
||||
|
||||
func (s *Span) toEvent() *Event {
|
||||
if !s.isTransaction {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if !s.IsTransaction() {
|
||||
return nil // only transactions can be transformed into events
|
||||
}
|
||||
hub := hubFromContext(s.ctx)
|
||||
|
||||
children := s.recorder.children()
|
||||
finished := make([]*Span, 0, len(children))
|
||||
@@ -326,17 +524,39 @@ func (s *Span) toEvent() *Event {
|
||||
finished = append(finished, child)
|
||||
}
|
||||
|
||||
// Create and attach a DynamicSamplingContext to the transaction.
|
||||
// If the DynamicSamplingContext is not frozen at this point, we can assume being head of trace.
|
||||
if !s.dynamicSamplingContext.IsFrozen() {
|
||||
s.dynamicSamplingContext = DynamicSamplingContextFromTransaction(s)
|
||||
}
|
||||
|
||||
contexts := map[string]Context{}
|
||||
for k, v := range s.contexts {
|
||||
contexts[k] = cloneContext(v)
|
||||
}
|
||||
contexts["trace"] = s.traceContext().Map()
|
||||
|
||||
// Make sure that the transaction source is valid
|
||||
transactionSource := s.Source
|
||||
if !transactionSource.isValid() {
|
||||
transactionSource = SourceCustom
|
||||
}
|
||||
|
||||
return &Event{
|
||||
Type: transactionType,
|
||||
Transaction: hub.Scope().Transaction(),
|
||||
Contexts: map[string]interface{}{
|
||||
"trace": s.traceContext(),
|
||||
Transaction: s.Name,
|
||||
Contexts: contexts,
|
||||
Tags: s.Tags,
|
||||
Extra: s.Data,
|
||||
Timestamp: s.EndTime,
|
||||
StartTime: s.StartTime,
|
||||
Spans: finished,
|
||||
TransactionInfo: &TransactionInfo{
|
||||
Source: transactionSource,
|
||||
},
|
||||
sdkMetaData: SDKMetaData{
|
||||
dsc: s.dynamicSamplingContext,
|
||||
},
|
||||
Tags: s.Tags,
|
||||
Extra: s.Data,
|
||||
Timestamp: s.EndTime,
|
||||
StartTime: s.StartTime,
|
||||
Spans: finished,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,6 +574,23 @@ func (s *Span) traceContext() *TraceContext {
|
||||
// spanRecorder stores the span tree. Guaranteed to be non-nil.
|
||||
func (s *Span) spanRecorder() *spanRecorder { return s.recorder }
|
||||
|
||||
// ParseTraceParentContext parses a sentry-trace header and builds a TraceParentContext from the
|
||||
// parsed values. If the header was parsed correctly, the second returned argument
|
||||
// ("valid") will be set to true, otherwise (e.g., empty or malformed header) it will
|
||||
// be false.
|
||||
func ParseTraceParentContext(header []byte) (traceParentContext TraceParentContext, valid bool) {
|
||||
s := Span{}
|
||||
updated := s.updateFromSentryTrace(header)
|
||||
if !updated {
|
||||
return TraceParentContext{}, false
|
||||
}
|
||||
return TraceParentContext{
|
||||
TraceID: s.TraceID,
|
||||
ParentSpanID: s.ParentSpanID,
|
||||
Sampled: s.Sampled,
|
||||
}, true
|
||||
}
|
||||
|
||||
// TraceID identifies a trace.
|
||||
type TraceID [16]byte
|
||||
|
||||
@@ -394,6 +631,36 @@ var (
|
||||
zeroSpanID SpanID
|
||||
)
|
||||
|
||||
// Contains information about how the name of the transaction was determined.
|
||||
type TransactionSource string
|
||||
|
||||
const (
|
||||
SourceCustom TransactionSource = "custom"
|
||||
SourceURL TransactionSource = "url"
|
||||
SourceRoute TransactionSource = "route"
|
||||
SourceView TransactionSource = "view"
|
||||
SourceComponent TransactionSource = "component"
|
||||
SourceTask TransactionSource = "task"
|
||||
)
|
||||
|
||||
// A set of all valid transaction sources.
|
||||
var allTransactionSources = map[TransactionSource]struct{}{
|
||||
SourceCustom: {},
|
||||
SourceURL: {},
|
||||
SourceRoute: {},
|
||||
SourceView: {},
|
||||
SourceComponent: {},
|
||||
SourceTask: {},
|
||||
}
|
||||
|
||||
// isValid returns 'true' if the given transaction source is a valid
|
||||
// source as recognized by the envelope protocol:
|
||||
// https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
|
||||
func (ts TransactionSource) isValid() bool {
|
||||
_, found := allTransactionSources[ts]
|
||||
return found
|
||||
}
|
||||
|
||||
// SpanStatus is the status of a span.
|
||||
type SpanStatus uint8
|
||||
|
||||
@@ -499,15 +766,40 @@ func (tc *TraceContext) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (tc TraceContext) Map() map[string]interface{} {
|
||||
m := map[string]interface{}{
|
||||
"trace_id": tc.TraceID,
|
||||
"span_id": tc.SpanID,
|
||||
}
|
||||
|
||||
if tc.ParentSpanID != [8]byte{} {
|
||||
m["parent_span_id"] = tc.ParentSpanID
|
||||
}
|
||||
|
||||
if tc.Op != "" {
|
||||
m["op"] = tc.Op
|
||||
}
|
||||
|
||||
if tc.Description != "" {
|
||||
m["description"] = tc.Description
|
||||
}
|
||||
|
||||
if tc.Status > 0 && tc.Status < maxSpanStatus {
|
||||
m["status"] = tc.Status
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Sampled signifies a sampling decision.
|
||||
type Sampled int8
|
||||
|
||||
// The possible trace sampling decisions are: SampledFalse, SampledUndefined
|
||||
// (default) and SampledTrue.
|
||||
const (
|
||||
SampledFalse Sampled = -1 + iota
|
||||
SampledUndefined
|
||||
SampledTrue
|
||||
SampledFalse Sampled = -1
|
||||
SampledUndefined Sampled = 0
|
||||
SampledTrue Sampled = 1
|
||||
)
|
||||
|
||||
func (s Sampled) String() string {
|
||||
@@ -531,23 +823,85 @@ func (s Sampled) Bool() bool {
|
||||
// A SpanOption is a function that can modify the properties of a span.
|
||||
type SpanOption func(s *Span)
|
||||
|
||||
// The TransactionName option sets the name of the current transaction.
|
||||
// WithTransactionName option sets the name of the current transaction.
|
||||
//
|
||||
// A span tree has a single transaction name, therefore using this option when
|
||||
// starting a span affects the span tree as a whole, potentially overwriting a
|
||||
// name set previously.
|
||||
func TransactionName(name string) SpanOption {
|
||||
func WithTransactionName(name string) SpanOption {
|
||||
return func(s *Span) {
|
||||
hubFromContext(s.Context()).Scope().SetTransaction(name)
|
||||
s.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
// WithDescription sets the description of a span.
|
||||
func WithDescription(description string) SpanOption {
|
||||
return func(s *Span) {
|
||||
s.Description = description
|
||||
}
|
||||
}
|
||||
|
||||
// WithOpName sets the operation name for a given span.
|
||||
func WithOpName(name string) SpanOption {
|
||||
return func(s *Span) {
|
||||
s.Op = name
|
||||
}
|
||||
}
|
||||
|
||||
// WithTransactionSource sets the source of the transaction name.
|
||||
//
|
||||
// Note: if the transaction source is not a valid source (as described
|
||||
// by the spec https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations),
|
||||
// it will be corrected to "custom" eventually, before the transaction is sent.
|
||||
func WithTransactionSource(source TransactionSource) SpanOption {
|
||||
return func(s *Span) {
|
||||
s.Source = source
|
||||
}
|
||||
}
|
||||
|
||||
// WithSpanSampled updates the sampling flag for a given span.
|
||||
func WithSpanSampled(sampled Sampled) SpanOption {
|
||||
return func(s *Span) {
|
||||
s.Sampled = sampled
|
||||
}
|
||||
}
|
||||
|
||||
// ContinueFromRequest returns a span option that updates the span to continue
|
||||
// an existing trace. If it cannot detect an existing trace in the request, the
|
||||
// span will be left unchanged.
|
||||
//
|
||||
// ContinueFromRequest is an alias for:
|
||||
//
|
||||
// ContinueFromHeaders(r.Header.Get(SentryTraceHeader), r.Header.Get(SentryBaggageHeader)).
|
||||
func ContinueFromRequest(r *http.Request) SpanOption {
|
||||
return ContinueFromHeaders(r.Header.Get(SentryTraceHeader), r.Header.Get(SentryBaggageHeader))
|
||||
}
|
||||
|
||||
// ContinueFromHeaders returns a span option that updates the span to continue
|
||||
// an existing TraceID and propagates the Dynamic Sampling context.
|
||||
func ContinueFromHeaders(trace, baggage string) SpanOption {
|
||||
return func(s *Span) {
|
||||
if trace != "" {
|
||||
s.updateFromSentryTrace([]byte(trace))
|
||||
}
|
||||
if baggage != "" {
|
||||
s.updateFromBaggage([]byte(baggage))
|
||||
}
|
||||
|
||||
// In case a sentry-trace header is present but there are no sentry-related
|
||||
// values in the baggage, create an empty, frozen DynamicSamplingContext.
|
||||
if trace != "" && !s.dynamicSamplingContext.HasEntries() {
|
||||
s.dynamicSamplingContext = DynamicSamplingContext{
|
||||
Frozen: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ContinueFromTrace returns a span option that updates the span to continue
|
||||
// an existing TraceID.
|
||||
func ContinueFromTrace(trace string) SpanOption {
|
||||
return func(s *Span) {
|
||||
trace := r.Header.Get("sentry-trace")
|
||||
if trace == "" {
|
||||
return
|
||||
}
|
||||
@@ -567,29 +921,65 @@ func TransactionFromContext(ctx context.Context) *Span {
|
||||
return nil
|
||||
}
|
||||
|
||||
// spanFromContext returns the last span stored in the context or a dummy
|
||||
// non-nil span.
|
||||
//
|
||||
// TODO(tracing): consider exporting this. Without this, users cannot retrieve a
|
||||
// span from a context since spanContextKey is not exported.
|
||||
//
|
||||
// This can be added retroactively, and in the meantime think better whether it
|
||||
// should return nil (like GetHubFromContext), always non-nil (like
|
||||
// HubFromContext), or both: two exported functions.
|
||||
//
|
||||
// Note the equivalence:
|
||||
//
|
||||
// SpanFromContext(ctx).StartChild(...) === StartSpan(ctx, ...)
|
||||
//
|
||||
// So we don't aim spanFromContext at creating spans, but mutating existing
|
||||
// spans that you'd have no access otherwise (because it was created in code you
|
||||
// do not control, for example SDK auto-instrumentation).
|
||||
//
|
||||
// For now we provide TransactionFromContext, which solves the more common case
|
||||
// of setting tags, etc, on the current transaction.
|
||||
func spanFromContext(ctx context.Context) *Span {
|
||||
// SpanFromContext returns the last span stored in the context, or nil if no span
|
||||
// is set on the context.
|
||||
func SpanFromContext(ctx context.Context) *Span {
|
||||
if span, ok := ctx.Value(spanContextKey{}).(*Span); ok {
|
||||
return span
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartTransaction will create a transaction (root span) if there's no existing
|
||||
// transaction in the context otherwise, it will return the existing transaction.
|
||||
func StartTransaction(ctx context.Context, name string, options ...SpanOption) *Span {
|
||||
currentTransaction, exists := ctx.Value(spanContextKey{}).(*Span)
|
||||
if exists {
|
||||
return currentTransaction
|
||||
}
|
||||
|
||||
options = append(options, WithTransactionName(name))
|
||||
return StartSpan(
|
||||
ctx,
|
||||
"",
|
||||
options...,
|
||||
)
|
||||
}
|
||||
|
||||
// HTTPtoSpanStatus converts an HTTP status code to a SpanStatus.
|
||||
func HTTPtoSpanStatus(code int) SpanStatus {
|
||||
if code < http.StatusBadRequest {
|
||||
return SpanStatusOK
|
||||
}
|
||||
if http.StatusBadRequest <= code && code < http.StatusInternalServerError {
|
||||
switch code {
|
||||
case http.StatusForbidden:
|
||||
return SpanStatusPermissionDenied
|
||||
case http.StatusNotFound:
|
||||
return SpanStatusNotFound
|
||||
case http.StatusTooManyRequests:
|
||||
return SpanStatusResourceExhausted
|
||||
case http.StatusRequestEntityTooLarge:
|
||||
return SpanStatusFailedPrecondition
|
||||
case http.StatusUnauthorized:
|
||||
return SpanStatusUnauthenticated
|
||||
case http.StatusConflict:
|
||||
return SpanStatusAlreadyExists
|
||||
default:
|
||||
return SpanStatusInvalidArgument
|
||||
}
|
||||
}
|
||||
if http.StatusInternalServerError <= code && code < 600 {
|
||||
switch code {
|
||||
case http.StatusGatewayTimeout:
|
||||
return SpanStatusDeadlineExceeded
|
||||
case http.StatusNotImplemented:
|
||||
return SpanStatusUnimplemented
|
||||
case http.StatusServiceUnavailable:
|
||||
return SpanStatusUnavailable
|
||||
default:
|
||||
return SpanStatusInternalError
|
||||
}
|
||||
}
|
||||
return SpanStatusUnknown
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user