# frozen_string_literal: true # Copyright The OpenTelemetry Authors # # SPDX-License-Identifier: Apache-2.0 # OpenTelemetry is an open source observability framework, providing a # general-purpose API, SDK, and related tools required for the instrumentation # of cloud-native software, frameworks, and libraries. # # The OpenTelemetry module provides global accessors for telemetry objects. # See the documentation for the `opentelemetry-api` gem for details. module OpenTelemetry # Namespace for OpenTelemetry propagator extension libraries module Propagator # Namespace for OpenTelemetry B3 propagation module B3 # Extracts trace context using the b3 single or multi header formats, favouring b3 single header. module TextMapExtractor B3_CONTEXT_REGEX = /\A(?(?:[0-9a-f]{16}){1,2})-(?[0-9a-f]{16})(?:-(?[01d](?![0-9a-f])))?(?:-(?[0-9a-f]{16}))?\z/ B3_TRACE_ID_REGEX = /\A(?:[0-9a-f]{16}){1,2}\z/ B3_SPAN_ID_REGEX = /\A[0-9a-f]{16}\z/ SAMPLED_VALUES = %w[1 true].freeze B3_CONTEXT_KEY = 'b3' B3_TRACE_ID_KEY = 'X-B3-TraceId' B3_SPAN_ID_KEY = 'X-B3-SpanId' B3_SAMPLED_KEY = 'X-B3-Sampled' B3_FLAGS_KEY = 'X-B3-Flags' private_constant :B3_CONTEXT_REGEX, :B3_TRACE_ID_REGEX, :B3_SPAN_ID_REGEX, :SAMPLED_VALUES private_constant :B3_CONTEXT_KEY, :B3_TRACE_ID_KEY, :B3_SPAN_ID_KEY, :B3_SAMPLED_KEY, :B3_FLAGS_KEY # Extract trace context from the supplied carrier. The b3 single header takes # precedence over the multi-header format. # If extraction fails, the original context will be returned. # # @param [Carrier] carrier The carrier to get the header from # @param [optional Context] context Context to be updated with the trace context # extracted from the carrier. Defaults to +Context.current+. # @param [optional Getter] getter If the optional getter is provided, it # will be used to read the header from the carrier, otherwise the default # text map getter will be used. # # @return [Context] context updated with extracted baggage, or the original context # if extraction fails def extract(carrier, context: Context.current, getter: Context::Propagation.text_map_getter) extract_b3_single_header(carrier, context, getter) || extract_b3_multi_header(carrier, context, getter) || context end private def extract_b3_single_header(carrier, context, getter) match = getter.get(carrier, B3_CONTEXT_KEY)&.match(B3_CONTEXT_REGEX) return unless match debug = match['sampling_state'] == 'd' sampled = debug || match['sampling_state'] == '1' extracted_context(match['trace_id'], match['span_id'], sampled, debug, context) end def extract_b3_multi_header(carrier, context, getter) trace_id_hex = getter.get(carrier, B3_TRACE_ID_KEY) return unless B3_TRACE_ID_REGEX.match?(trace_id_hex) span_id_hex = getter.get(carrier, B3_SPAN_ID_KEY) return unless B3_SPAN_ID_REGEX.match?(span_id_hex) sampled = getter.get(carrier, B3_SAMPLED_KEY) flags = getter.get(carrier, B3_FLAGS_KEY) debug = flags == '1' sampled = debug || SAMPLED_VALUES.include?(sampled) extracted_context(trace_id_hex, span_id_hex, sampled, debug, context) end def extracted_context(trace_id_hex, span_id_hex, sampled, debug, context) span_context = Trace::SpanContext.new( trace_id: to_trace_id(trace_id_hex), span_id: to_span_id(span_id_hex), trace_flags: to_trace_flags(sampled), remote: true ) span = OpenTelemetry::Trace.non_recording_span(span_context) context = B3.context_with_debug(context) if debug Trace.context_with_span(span, parent_context: context) end def to_trace_flags(sampled) if sampled Trace::TraceFlags::SAMPLED else Trace::TraceFlags::DEFAULT end end # Convert an id from a hex encoded string to byte array, optionally left # padding to the correct length. Assumes the input id has already been # validated to be 16 or 32 characters in length. def to_trace_id(hex_id) if hex_id.length == 32 Array(hex_id).pack('H*') else [0, hex_id].pack('qH*') end end # Convert an id from a hex encoded string to byte array. def to_span_id(hex_id) Array(hex_id).pack('H*') end end end end end