Module: AllStak::Propagation

Defined in:
lib/allstak/propagation.rb

Constant Summary collapse

TRACE_ID_RE =
/\A[0-9a-f]{32}\z/.freeze
SPAN_ID_RE =
/\A[0-9a-f]{16}\z/.freeze
ZERO_TRACE_ID_RE =
/\A0{32}\z/.freeze
ZERO_SPAN_ID_RE =
/\A0{16}\z/.freeze

Class Method Summary collapse

Class Method Details

.apply_headers(headers, trace_id:, request_id: nil, span_id: nil, sampled: true) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/allstak/propagation.rb', line 80

def apply_headers(headers, trace_id:, request_id: nil, span_id: nil, sampled: true)
  wire_trace_id = normalize_trace_id(trace_id)
  wire_span_id = span_id && !span_id.to_s.empty? ? normalize_span_id(span_id) : nil
  headers["X-AllStak-Trace-Id"] = wire_trace_id
  headers["X-AllStak-Request-Id"] = request_id if request_id && !request_id.to_s.empty?
  if wire_span_id
    headers["X-AllStak-Span-Id"] = wire_span_id
    headers["traceparent"] = "00-#{wire_trace_id}-#{wire_span_id}-#{trace_flags(sampled)}"
  end
  headers["baggage"] = merge_baggage(headers["baggage"], trace_id: wire_trace_id, request_id: request_id, span_id: wire_span_id)
  headers["AllStak-Baggage"] = baggage(trace_id: wire_trace_id, request_id: request_id, span_id: wire_span_id)
end

.apply_request_headers(req, trace_id:, request_id: nil, span_id: nil, sampled: true) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/allstak/propagation.rb', line 93

def apply_request_headers(req, trace_id:, request_id: nil, span_id: nil, sampled: true)
  wire_trace_id = normalize_trace_id(trace_id)
  wire_span_id = span_id && !span_id.to_s.empty? ? normalize_span_id(span_id) : nil
  req["X-AllStak-Trace-Id"] ||= wire_trace_id
  req["X-AllStak-Request-Id"] ||= request_id if request_id && !request_id.to_s.empty?
  if wire_span_id
    req["X-AllStak-Span-Id"] ||= wire_span_id
    req["traceparent"] ||= "00-#{wire_trace_id}-#{wire_span_id}-#{trace_flags(sampled)}"
  end
  req["baggage"] = merge_baggage(req["baggage"], trace_id: wire_trace_id, request_id: request_id, span_id: wire_span_id)
  req["AllStak-Baggage"] = baggage(trace_id: wire_trace_id, request_id: request_id, span_id: wire_span_id)
end

.baggage(trace_id:, request_id: nil, span_id: nil) ⇒ Object



12
13
14
15
16
17
# File 'lib/allstak/propagation.rb', line 12

def baggage(trace_id:, request_id: nil, span_id: nil)
  parts = ["allstak-trace_id=#{trace_id}"]
  parts << "allstak-request_id=#{request_id}" if request_id && !request_id.to_s.empty?
  parts << "allstak-span_id=#{span_id}" if span_id && !span_id.to_s.empty?
  parts.join(",")
end

.merge_baggage(existing, trace_id:, request_id: nil, span_id: nil) ⇒ Object



19
20
21
22
23
24
# File 'lib/allstak/propagation.rb', line 19

def merge_baggage(existing, trace_id:, request_id: nil, span_id: nil)
  preserved = existing.to_s.split(",").map(&:strip).reject do |part|
    part.empty? || part.downcase.start_with?("allstak-")
  end
  (preserved + baggage(trace_id: trace_id, request_id: request_id, span_id: span_id).split(",")).join(",")
end

.normalize_span_id(span_id) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
# File 'lib/allstak/propagation.rb', line 55

def normalize_span_id(span_id)
  hex = span_id.to_s.gsub(/[^0-9a-f]/i, "").downcase
  candidate =
    if hex.length >= 16
      hex[0, 16]
    elsif !hex.empty?
      hex.ljust(16, "0")
    end
  return candidate if candidate && valid_span_id?(candidate)
  SecureRandom.hex(8)
end

.normalize_trace_id(trace_id) ⇒ Object



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

def normalize_trace_id(trace_id)
  hex = trace_id.to_s.gsub(/[^0-9a-f]/i, "").downcase
  candidate =
    if hex.length >= 32
      hex[0, 32]
    elsif !hex.empty?
      hex.ljust(32, "0")
    end
  return candidate if candidate && valid_trace_id?(candidate)
  SecureRandom.hex(16)
end

.parse_traceparent(header) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/allstak/propagation.rb', line 67

def parse_traceparent(header)
  match = /\A00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})\z/i.match(header.to_s.strip)
  return nil unless match
  trace_id = match[1].downcase
  parent_span_id = match[2].downcase
  return nil unless valid_trace_id?(trace_id) && valid_span_id?(parent_span_id)
  {
    trace_id: trace_id,
    parent_span_id: parent_span_id,
    sampled: (match[3].to_i(16) & 1) == 1
  }
end

.trace_flags(sampled) ⇒ Object

W3C traceparent trace-flags: “01” = sampled, “00” = not sampled. ‘sampled` defaults to true to preserve historical behavior for callers that do not pass an explicit sampling decision.



29
30
31
# File 'lib/allstak/propagation.rb', line 29

def trace_flags(sampled)
  sampled == false ? "00" : "01"
end

.valid_span_id?(span_id) ⇒ Boolean

Returns:

  • (Boolean)


38
39
40
41
# File 'lib/allstak/propagation.rb', line 38

def valid_span_id?(span_id)
  value = span_id.to_s.downcase
  value.match?(SPAN_ID_RE) && !value.match?(ZERO_SPAN_ID_RE)
end

.valid_trace_id?(trace_id) ⇒ Boolean

Returns:

  • (Boolean)


33
34
35
36
# File 'lib/allstak/propagation.rb', line 33

def valid_trace_id?(trace_id)
  value = trace_id.to_s.downcase
  value.match?(TRACE_ID_RE) && !value.match?(ZERO_TRACE_ID_RE)
end