Class: Sentry::PropagationContext

Inherits:
Object
  • Object
show all
Defined in:
lib/sentry/propagation_context.rb

Constant Summary collapse

SENTRY_TRACE_REGEXP =
Regexp.new(
  "\\A([0-9a-f]{32})?" + # trace_id
  "-?([0-9a-f]{16})?" +  # span_id
  "-?([01])?\\z"         # sampled
)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(scope, env = nil) ⇒ PropagationContext

Returns a new instance of PropagationContext.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/sentry/propagation_context.rb', line 121

def initialize(scope, env = nil)
  @scope = scope
  @parent_span_id = nil
  @parent_sampled = nil
  @baggage = nil
  @incoming_trace = false
  @sample_rand = nil

  if env
    sentry_trace_header = env["HTTP_SENTRY_TRACE"] || env[SENTRY_TRACE_HEADER_NAME]
    baggage_header = env["HTTP_BAGGAGE"] || env[BAGGAGE_HEADER_NAME]

    if sentry_trace_header
      sentry_trace_data = self.class.extract_sentry_trace(sentry_trace_header)

      if sentry_trace_data
        incoming_baggage =
          if baggage_header && !baggage_header.empty?
            Baggage.from_incoming_header(baggage_header)
          else
            # If there's an incoming sentry-trace but no incoming baggage header,
            # for instance in traces coming from older SDKs,
            # baggage will be empty and frozen and won't be populated as head SDK.
            Baggage.new({})
          end

        if self.class.should_continue_trace?(incoming_baggage)
          @trace_id, @parent_span_id, @parent_sampled = sentry_trace_data
          @baggage = incoming_baggage
          @sample_rand = self.class.extract_sample_rand_from_baggage(@baggage, @trace_id)
          @baggage.freeze!
          @incoming_trace = true
        end
      end
    end
  end

  @trace_id ||= Utils.uuid
  @span_id = Utils.uuid.slice(0, 16)
  @sample_rand ||= self.class.generate_sample_rand(@baggage, @trace_id, @parent_sampled)
end

Instance Attribute Details

#baggageBaggage? (readonly)

This is only for accessing the current baggage variable. Please use the #get_baggage method for interfacing outside this class.

Returns:



34
35
36
# File 'lib/sentry/propagation_context.rb', line 34

def baggage
  @baggage
end

#incoming_traceBoolean (readonly)

Is there an incoming trace or not?

Returns:

  • (Boolean)


30
31
32
# File 'lib/sentry/propagation_context.rb', line 30

def incoming_trace
  @incoming_trace
end

#parent_sampledBoolean? (readonly)

The sampling decision of the parent transaction.

Returns:

  • (Boolean, nil)


27
28
29
# File 'lib/sentry/propagation_context.rb', line 27

def parent_sampled
  @parent_sampled
end

#parent_span_idString? (readonly)

Span parent’s span_id.

Returns:

  • (String, nil)


24
25
26
# File 'lib/sentry/propagation_context.rb', line 24

def parent_span_id
  @parent_span_id
end

#sample_randFloat? (readonly)

The propagated random value used for sampling decisions.

Returns:

  • (Float, nil)


37
38
39
# File 'lib/sentry/propagation_context.rb', line 37

def sample_rand
  @sample_rand
end

#span_idString (readonly)

An uuid that can be used to identify the span.

Returns:

  • (String)


21
22
23
# File 'lib/sentry/propagation_context.rb', line 21

def span_id
  @span_id
end

#trace_idString (readonly)

An uuid that can be used to identify a trace.

Returns:

  • (String)


18
19
20
# File 'lib/sentry/propagation_context.rb', line 18

def trace_id
  @trace_id
end

Class Method Details

.extract_sample_rand_from_baggage(baggage, trace_id = nil) ⇒ Object



94
95
96
97
98
99
100
101
102
# File 'lib/sentry/propagation_context.rb', line 94

def self.extract_sample_rand_from_baggage(baggage, trace_id = nil)
  return unless baggage&.items

  sample_rand_str = baggage.items["sample_rand"]
  return unless sample_rand_str

  generator = Utils::SampleRand.new(trace_id: trace_id)
  generator.generate_from_value(sample_rand_str)
end

.extract_sentry_trace(sentry_trace) ⇒ Array?

Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.

Parameters:

  • sentry_trace (String)

    the sentry-trace header value from the previous transaction.

Returns:

  • (Array, nil)


43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/sentry/propagation_context.rb', line 43

def self.extract_sentry_trace(sentry_trace)
  value = sentry_trace.to_s.strip
  return if value.empty?

  match = SENTRY_TRACE_REGEXP.match(value)
  return if match.nil?

  trace_id, parent_span_id, sampled_flag = match[1..3]
  parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"

  [trace_id, parent_span_id, parent_sampled]
end

.generate_sample_rand(baggage, trace_id, parent_sampled) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/sentry/propagation_context.rb', line 104

def self.generate_sample_rand(baggage, trace_id, parent_sampled)
  generator = Utils::SampleRand.new(trace_id: trace_id)

  if baggage&.items && !parent_sampled.nil?
    sample_rate_str = baggage.items["sample_rate"]
    sample_rate = sample_rate_str&.to_f

    if sample_rate && !parent_sampled.nil?
      generator.generate_from_sampling_decision(parent_sampled, sample_rate)
    else
      generator.generate_from_trace_id
    end
  else
    generator.generate_from_trace_id
  end
end

.should_continue_trace?(incoming_baggage) ⇒ Boolean

Determines whether we should continue an incoming trace based on org_id matching and the strict_trace_continuation configuration option.

Parameters:

  • incoming_baggage (Baggage)

    the baggage from the incoming request

Returns:

  • (Boolean)


61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/sentry/propagation_context.rb', line 61

def self.should_continue_trace?(incoming_baggage)
  return true unless Sentry.initialized?

  configuration = Sentry.configuration
  sdk_org_id = configuration.effective_org_id
  baggage_org_id = incoming_baggage.items["org_id"]

  # Mismatched org IDs always start a new trace regardless of strict mode
  if sdk_org_id && baggage_org_id && sdk_org_id != baggage_org_id
    Sentry.sdk_logger.debug(LOGGER_PROGNAME) do
      "Starting a new trace because org IDs don't match (incoming baggage org_id: #{baggage_org_id}, SDK org_id: #{sdk_org_id})"
    end

    return false
  end

  return true unless configuration.strict_trace_continuation

  # In strict mode, both must be present and match (unless both are missing)
  if sdk_org_id.nil? && baggage_org_id.nil?
    true
  elsif sdk_org_id.nil? || baggage_org_id.nil?
    Sentry.sdk_logger.debug(LOGGER_PROGNAME) do
      "Starting a new trace because strict trace continuation is enabled and one org ID is missing " \
        "(incoming baggage org_id: #{baggage_org_id.inspect}, SDK org_id: #{sdk_org_id.inspect})"
    end

    false
  else
    true
  end
end

Instance Method Details

#get_baggageBaggage?

Returns the Baggage from the propagation context or populates as head SDK if empty.

Returns:



181
182
183
184
# File 'lib/sentry/propagation_context.rb', line 181

def get_baggage
  populate_head_baggage if @baggage.nil? || @baggage.mutable
  @baggage
end

#get_dynamic_sampling_contextHash?

Returns the Dynamic Sampling Context from the baggage.

Returns:

  • (Hash, nil)


188
189
190
# File 'lib/sentry/propagation_context.rb', line 188

def get_dynamic_sampling_context
  get_baggage&.dynamic_sampling_context
end

#get_trace_contextHash

Returns the trace context that can be used to embed in an Event.

Returns:

  • (Hash)


165
166
167
168
169
170
171
# File 'lib/sentry/propagation_context.rb', line 165

def get_trace_context
  {
    trace_id: trace_id,
    span_id: span_id,
    parent_span_id: parent_span_id
  }
end

#get_traceparentString

Returns the sentry-trace header from the propagation context.

Returns:

  • (String)


175
176
177
# File 'lib/sentry/propagation_context.rb', line 175

def get_traceparent
  "#{trace_id}-#{span_id}"
end