From 6bca875fb114ebc7787100b2a314a1f89599cdef Mon Sep 17 00:00:00 2001 From: Francis Bogsanyi Date: Wed, 31 Mar 2021 11:45:59 -0400 Subject: [PATCH] fix!: refactor propagators to add #fields (#638) * fix!: refactor propagators to add #fields * fix API tests * fix SDK tests * fix B3 tests * update ottrace propagator * add fields method to OTTrace propagator * Update Jaeger propagator. (#663) * test fixes * fix tests & composite prop * fix composite prop test * fix: b3 extract priority * fix: feedback * fix: merge conflicts * fix: update XRay propagator Co-authored-by: Francis Hwang --- api/lib/opentelemetry.rb | 5 +- api/lib/opentelemetry/baggage/propagation.rb | 27 +- .../baggage/propagation/text_map_extractor.rb | 58 --- .../baggage/propagation/text_map_injector.rb | 76 ---- .../propagation/text_map_propagator.rb | 109 +++++ api/lib/opentelemetry/context/propagation.rb | 40 +- .../propagation/composite_propagator.rb | 72 ---- .../composite_text_map_propagator.rb | 105 +++++ .../context/propagation/noop_extractor.rb | 26 -- .../context/propagation/noop_injector.rb | 26 -- .../propagation/noop_text_map_propagator.rb | 51 +++ .../{propagator.rb => text_map_propagator.rb} | 39 +- .../trace/propagation/trace_context.rb | 25 +- .../trace_context/text_map_extractor.rb | 52 --- .../trace_context/text_map_injector.rb | 49 --- .../trace_context/text_map_propagator.rb | 73 ++++ .../propagation/text_map_extractor_test.rb | 62 --- ...or_test.rb => text_map_propagator_test.rb} | 69 +++- .../opentelemetry/baggage/propagation_test.rb | 19 +- ... => composite_text_map_propagator_test.rb} | 24 +- .../propagation/noop_extractor_test.rb | 18 - .../context/propagation/noop_injector_test.rb | 18 - .../noop_text_map_propagator_test.rb | 28 ++ ...or_test.rb => text_map_propagator_test.rb} | 23 +- .../trace_context/text_map_extractor_test.rb | 57 --- ...or_test.rb => text_map_propagator_test.rb} | 63 ++- .../trace_context/trace_context_test.rb | 19 +- api/test/opentelemetry_test.rb | 4 +- .../event_handler_test.rb | 5 +- .../delayed_job/plugins/tracer_plugin_test.rb | 5 +- .../ethon/instrumentation_test.rb | 5 +- .../excon/instrumentation_test.rb | 5 +- .../middlewares/tracer_middleware_test.rb | 5 +- .../http/patches/client_test.rb | 11 +- .../http_client/patches/client_test.rb | 11 +- .../instrumentation/mongo/subscriber_test.rb | 5 +- .../net/http/instrumentation_test.rb | 5 +- .../restclient/instrumentation_test.rb | 5 +- .../b3/lib/opentelemetry/propagator/b3.rb | 19 +- .../lib/opentelemetry/propagator/b3/multi.rb | 29 +- .../propagator/b3/multi/text_map_extractor.rb | 98 ----- .../propagator/b3/multi/text_map_injector.rb | 55 --- .../b3/multi/text_map_propagator.rb | 64 +++ .../lib/opentelemetry/propagator/b3/single.rb | 25 +- .../b3/single/text_map_extractor.rb | 79 ---- .../propagator/b3/single/text_map_injector.rb | 55 --- .../b3/single/text_map_propagator.rb | 64 +++ .../propagator/b3/text_map_extractor.rb | 116 ++++++ .../b3/test/multi/text_map_extractor_test.rb | 118 ------ .../b3/test/multi/text_map_injector_test.rb | 115 ------ .../b3/test/multi/text_map_propagator_test.rb | 213 ++++++++++ propagator/b3/test/multi_test.rb | 19 +- .../b3/test/single/text_map_extractor_test.rb | 90 ----- .../b3/test/single/text_map_injector_test.rb | 100 ----- .../test/single/text_map_propagator_test.rb | 175 ++++++++ propagator/b3/test/single_test.rb | 19 +- propagator/jaeger/README.md | 8 +- .../lib/opentelemetry/propagator/jaeger.rb | 29 +- .../propagator/jaeger/text_map_injector.rb | 68 ---- ...ap_extractor.rb => text_map_propagator.rb} | 91 +++-- propagator/jaeger/test/jaeger_test.rb | 19 +- .../jaeger/test/text_map_extractor_test.rb | 159 -------- .../jaeger/test/text_map_injector_test.rb | 179 --------- .../jaeger/test/text_map_propagator_test.rb | 321 +++++++++++++++ .../lib/opentelemetry/propagator/ottrace.rb | 25 +- .../propagator/ottrace/text_map_injector.rb | 74 ---- ...ap_extractor.rb => text_map_propagator.rb} | 75 +++- .../ottrace/text_map_extractor_test.rb | 233 ----------- .../ottrace/text_map_injector_test.rb | 216 ---------- .../ottrace/text_map_propagator_test.rb | 376 ++++++++++++++++++ .../opentelemetry/propagator/ottrace_test.rb | 19 +- .../xray/lib/opentelemetry/propagator/xray.rb | 41 +- .../propagator/xray/text_map_extractor.rb | 92 ----- .../propagator/xray/text_map_injector.rb | 56 --- .../propagator/xray/text_map_propagator.rb | 125 ++++++ .../xray/test/text_map_extractor_test.rb | 81 ---- .../xray/test/text_map_injector_test.rb | 100 ----- .../xray/test/text_map_propagator_test.rb | 170 ++++++++ propagator/xray/test/xray_test.rb | 19 +- sdk/lib/opentelemetry/sdk/configurator.rb | 35 +- .../opentelemetry/sdk/configurator_test.rb | 83 +--- 81 files changed, 2458 insertions(+), 2988 deletions(-) delete mode 100644 api/lib/opentelemetry/baggage/propagation/text_map_extractor.rb delete mode 100644 api/lib/opentelemetry/baggage/propagation/text_map_injector.rb create mode 100644 api/lib/opentelemetry/baggage/propagation/text_map_propagator.rb delete mode 100644 api/lib/opentelemetry/context/propagation/composite_propagator.rb create mode 100644 api/lib/opentelemetry/context/propagation/composite_text_map_propagator.rb delete mode 100644 api/lib/opentelemetry/context/propagation/noop_extractor.rb delete mode 100644 api/lib/opentelemetry/context/propagation/noop_injector.rb create mode 100644 api/lib/opentelemetry/context/propagation/noop_text_map_propagator.rb rename api/lib/opentelemetry/context/propagation/{propagator.rb => text_map_propagator.rb} (67%) delete mode 100644 api/lib/opentelemetry/trace/propagation/trace_context/text_map_extractor.rb delete mode 100644 api/lib/opentelemetry/trace/propagation/trace_context/text_map_injector.rb create mode 100644 api/lib/opentelemetry/trace/propagation/trace_context/text_map_propagator.rb delete mode 100644 api/test/opentelemetry/baggage/propagation/text_map_extractor_test.rb rename api/test/opentelemetry/baggage/propagation/{text_map_injector_test.rb => text_map_propagator_test.rb} (60%) rename api/test/opentelemetry/context/propagation/{composite_propagator_test.rb => composite_text_map_propagator_test.rb} (85%) delete mode 100644 api/test/opentelemetry/context/propagation/noop_extractor_test.rb delete mode 100644 api/test/opentelemetry/context/propagation/noop_injector_test.rb create mode 100644 api/test/opentelemetry/context/propagation/noop_text_map_propagator_test.rb rename api/test/opentelemetry/context/propagation/{propagator_test.rb => text_map_propagator_test.rb} (56%) delete mode 100644 api/test/opentelemetry/trace/propagation/trace_context/text_map_extractor_test.rb rename api/test/opentelemetry/trace/propagation/trace_context/{text_map_injector_test.rb => text_map_propagator_test.rb} (50%) delete mode 100644 propagator/b3/lib/opentelemetry/propagator/b3/multi/text_map_extractor.rb delete mode 100644 propagator/b3/lib/opentelemetry/propagator/b3/multi/text_map_injector.rb create mode 100644 propagator/b3/lib/opentelemetry/propagator/b3/multi/text_map_propagator.rb delete mode 100644 propagator/b3/lib/opentelemetry/propagator/b3/single/text_map_extractor.rb delete mode 100644 propagator/b3/lib/opentelemetry/propagator/b3/single/text_map_injector.rb create mode 100644 propagator/b3/lib/opentelemetry/propagator/b3/single/text_map_propagator.rb create mode 100644 propagator/b3/lib/opentelemetry/propagator/b3/text_map_extractor.rb delete mode 100644 propagator/b3/test/multi/text_map_extractor_test.rb delete mode 100644 propagator/b3/test/multi/text_map_injector_test.rb create mode 100644 propagator/b3/test/multi/text_map_propagator_test.rb delete mode 100644 propagator/b3/test/single/text_map_extractor_test.rb delete mode 100644 propagator/b3/test/single/text_map_injector_test.rb create mode 100644 propagator/b3/test/single/text_map_propagator_test.rb delete mode 100644 propagator/jaeger/lib/opentelemetry/propagator/jaeger/text_map_injector.rb rename propagator/jaeger/lib/opentelemetry/propagator/jaeger/{text_map_extractor.rb => text_map_propagator.rb} (50%) delete mode 100644 propagator/jaeger/test/text_map_extractor_test.rb delete mode 100644 propagator/jaeger/test/text_map_injector_test.rb create mode 100644 propagator/jaeger/test/text_map_propagator_test.rb delete mode 100644 propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_injector.rb rename propagator/ottrace/lib/opentelemetry/propagator/ottrace/{text_map_extractor.rb => text_map_propagator.rb} (50%) delete mode 100644 propagator/ottrace/test/opentelemetry/propagator/ottrace/text_map_extractor_test.rb delete mode 100644 propagator/ottrace/test/opentelemetry/propagator/ottrace/text_map_injector_test.rb create mode 100644 propagator/ottrace/test/opentelemetry/propagator/ottrace/text_map_propagator_test.rb delete mode 100644 propagator/xray/lib/opentelemetry/propagator/xray/text_map_extractor.rb delete mode 100644 propagator/xray/lib/opentelemetry/propagator/xray/text_map_injector.rb create mode 100644 propagator/xray/lib/opentelemetry/propagator/xray/text_map_propagator.rb delete mode 100644 propagator/xray/test/text_map_extractor_test.rb delete mode 100644 propagator/xray/test/text_map_injector_test.rb create mode 100644 propagator/xray/test/text_map_propagator_test.rb diff --git a/api/lib/opentelemetry.rb b/api/lib/opentelemetry.rb index ee61731e42..b0d33e1242 100644 --- a/api/lib/opentelemetry.rb +++ b/api/lib/opentelemetry.rb @@ -71,9 +71,6 @@ def baggage # @return [Context::Propagation::Propagator] a propagator instance def propagation - @propagation ||= Context::Propagation::Propagator.new( - Context::Propagation::NoopInjector.new, - Context::Propagation::NoopExtractor.new - ) + @propagation ||= Context::Propagation::NoopTextMapPropagator.new end end diff --git a/api/lib/opentelemetry/baggage/propagation.rb b/api/lib/opentelemetry/baggage/propagation.rb index d2c792d792..603a5b1e8b 100644 --- a/api/lib/opentelemetry/baggage/propagation.rb +++ b/api/lib/opentelemetry/baggage/propagation.rb @@ -5,32 +5,23 @@ # SPDX-License-Identifier: Apache-2.0 require 'opentelemetry/baggage/propagation/context_keys' -require 'opentelemetry/baggage/propagation/text_map_injector' -require 'opentelemetry/baggage/propagation/text_map_extractor' +require 'opentelemetry/baggage/propagation/text_map_propagator' module OpenTelemetry module Baggage - # The Baggage::Propagation module contains injectors and - # extractors for sending and receiving baggage over the wire + # The Baggage::Propagation module contains a text map propagator for + # sending and receiving baggage over the wire. module Propagation extend self - BAGGAGE_KEY = 'baggage' - TEXT_MAP_EXTRACTOR = TextMapExtractor.new - TEXT_MAP_INJECTOR = TextMapInjector.new + TEXT_MAP_PROPAGATOR = TextMapPropagator.new - private_constant :BAGGAGE_KEY, :TEXT_MAP_INJECTOR, :TEXT_MAP_EXTRACTOR + private_constant :TEXT_MAP_PROPAGATOR - # Returns an extractor that extracts context using the W3C Baggage - # format - def text_map_injector - TEXT_MAP_INJECTOR - end - - # Returns an injector that injects context using the W3C Baggage - # format - def text_map_extractor - TEXT_MAP_EXTRACTOR + # Returns a text map propagator that propagates context using the + # W3C Baggage format. + def text_map_propagator + TEXT_MAP_PROPAGATOR end end end diff --git a/api/lib/opentelemetry/baggage/propagation/text_map_extractor.rb b/api/lib/opentelemetry/baggage/propagation/text_map_extractor.rb deleted file mode 100644 index d0cbe17622..0000000000 --- a/api/lib/opentelemetry/baggage/propagation/text_map_extractor.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'cgi' - -module OpenTelemetry - module Baggage - module Propagation - # Extracts baggage from carriers in the W3C Baggage format - class TextMapExtractor - # Returns a new TextMapExtractor that extracts context using the specified - # getter - # - # @param [optional Getter] default_getter The default getter used to read - # headers from a carrier during extract. Defaults to a - # {OpenTelemetry::Context::Propagation::TextMapGetter} instance. - # @return [TextMapExtractor] - def initialize(default_getter = Context::Propagation.text_map_getter) - @default_getter = default_getter - end - - # Extract remote baggage from the supplied carrier. - # If extraction fails, the original context will be returned - # - # @param [Carrier] carrier The carrier to get the header from - # @param [Context] context The context to be updated with extracted baggage - # @param [optional Getter] getter If the optional getter is provided, it - # will be used to read the header from the carrier, otherwise the default - # getter will be used. - # @return [Context] context updated with extracted baggage, or the original context - # if extraction fails - def extract(carrier, context, getter = nil) - getter ||= @default_getter - header = getter.get(carrier, BAGGAGE_KEY) - - entries = header.gsub(/\s/, '').split(',') - - OpenTelemetry.baggage.build(context: context) do |builder| - entries.each do |entry| - # Note metadata is currently unused in OpenTelemetry, but is part - # the W3C spec where it's referred to as properties. We preserve - # the properties (as-is) so that they can be propagated elsewhere. - kv, meta = entry.split(';', 2) - k, v = kv.split('=').map!(&CGI.method(:unescape)) - - builder.set_value(k, v, metadata: meta) - end - end - rescue StandardError - context - end - end - end - end -end diff --git a/api/lib/opentelemetry/baggage/propagation/text_map_injector.rb b/api/lib/opentelemetry/baggage/propagation/text_map_injector.rb deleted file mode 100644 index 62ea60f8e2..0000000000 --- a/api/lib/opentelemetry/baggage/propagation/text_map_injector.rb +++ /dev/null @@ -1,76 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'cgi' - -module OpenTelemetry - module Baggage - module Propagation - # Injects baggage using the W3C Baggage format - class TextMapInjector - # Maximums according to W3C Baggage spec - MAX_ENTRIES = 180 - MAX_ENTRY_LENGTH = 4096 - MAX_TOTAL_LENGTH = 8192 - private_constant :MAX_ENTRIES, :MAX_ENTRY_LENGTH, :MAX_TOTAL_LENGTH - - # Returns a new TextMapInjector that injects context using the specified - # setter - # - # @param [optional Setter] default_setter The default setter used to - # write context into a carrier during inject. Defaults to a - # {OpenTelemetry::Context::Propagation::TextMapSetter} instance. - # @return [TextMapInjector] - def initialize(default_setter = Context::Propagation.text_map_setter) - @default_setter = default_setter - end - - # Inject in-process baggage into the supplied carrier. - # - # @param [Carrier] carrier The carrier to inject baggage into - # @param [Context] context The context to read baggage from - # @param [optional Setter] setter If the optional setter is provided, it - # will be used to write context into the carrier, otherwise the default - # setter will be used. - # @return [Object] carrier with injected baggage - def inject(carrier, context, setter = nil) - return carrier unless (baggage = OpenTelemetry.baggage.raw_entries(context: context)) && !baggage.empty? - - setter ||= @default_setter - encoded_baggage = encode(baggage) - setter.set(carrier, BAGGAGE_KEY, encoded_baggage) unless encoded_baggage&.empty? - carrier - end - - private - - def encode(baggage) - result = +'' - encoded_count = 0 - baggage.each_pair do |key, entry| - break unless encoded_count < MAX_ENTRIES - - encoded_entry = encode_value(key, entry) - next unless encoded_entry.size <= MAX_ENTRY_LENGTH && - encoded_entry.size + result.size <= MAX_TOTAL_LENGTH - - result << encoded_entry << ',' - encoded_count += 1 - end - result.chop! - end - - def encode_value(key, entry) - result = +"#{CGI.escape(key.to_s)}=#{CGI.escape(entry.value.to_s)}" - # We preserve metadata recieved on extract and assume it's already formatted - # for transport. It's sent as-is without further processing. - result << ";#{entry.metadata}" if entry.metadata - result - end - end - end - end -end diff --git a/api/lib/opentelemetry/baggage/propagation/text_map_propagator.rb b/api/lib/opentelemetry/baggage/propagation/text_map_propagator.rb new file mode 100644 index 0000000000..84c2f77d90 --- /dev/null +++ b/api/lib/opentelemetry/baggage/propagation/text_map_propagator.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'cgi' + +module OpenTelemetry + module Baggage + module Propagation + # Propagates baggage using the W3C Baggage format + class TextMapPropagator + # Maximums according to W3C Baggage spec + MAX_ENTRIES = 180 + MAX_ENTRY_LENGTH = 4096 + MAX_TOTAL_LENGTH = 8192 + + BAGGAGE_KEY = 'baggage' + FIELDS = [BAGGAGE_KEY].freeze + + private_constant :BAGGAGE_KEY, :FIELDS, :MAX_ENTRIES, :MAX_ENTRY_LENGTH, :MAX_TOTAL_LENGTH + + # Inject in-process baggage into the supplied carrier. + # + # @param [Carrier] carrier The mutable carrier to inject baggage into + # @param [Context] context The context to read baggage from + # @param [optional Setter] setter If the optional setter is provided, it + # will be used to write context into the carrier, otherwise the default + # text map setter will be used. + def inject(carrier, context: Context.current, setter: Context::Propagation.text_map_setter) + baggage = OpenTelemetry.baggage.raw_entries(context: context) + + return if baggage.nil? || baggage.empty? + + encoded_baggage = encode(baggage) + setter.set(carrier, BAGGAGE_KEY, encoded_baggage) unless encoded_baggage&.empty? + nil + end + + # Extract remote baggage from the supplied carrier. + # 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 baggage + # 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) + header = getter.get(carrier, BAGGAGE_KEY) + + entries = header.gsub(/\s/, '').split(',') + + OpenTelemetry.baggage.build(context: context) do |builder| + entries.each do |entry| + # Note metadata is currently unused in OpenTelemetry, but is part + # the W3C spec where it's referred to as properties. We preserve + # the properties (as-is) so that they can be propagated elsewhere. + kv, meta = entry.split(';', 2) + k, v = kv.split('=').map!(&CGI.method(:unescape)) + builder.set_value(k, v, metadata: meta) + end + end + rescue StandardError => e + OpenTelemetry.logger.debug "Error extracting W3C baggage: #{e.message}" + context + end + + # Returns the predefined propagation fields. If your carrier is reused, you + # should delete the fields returned by this method before calling +inject+. + # + # @return [Array] a list of fields that will be used by this propagator. + def fields + FIELDS + end + + private + + def encode(baggage) + result = +'' + encoded_count = 0 + baggage.each_pair do |key, entry| + break unless encoded_count < MAX_ENTRIES + + encoded_entry = encode_value(key, entry) + next unless encoded_entry.size <= MAX_ENTRY_LENGTH && + encoded_entry.size + result.size <= MAX_TOTAL_LENGTH + + result << encoded_entry << ',' + encoded_count += 1 + end + result.chop! + end + + def encode_value(key, entry) + result = +"#{CGI.escape(key.to_s)}=#{CGI.escape(entry.value.to_s)}" + # We preserve metadata recieved on extract and assume it's already formatted + # for transport. It's sent as-is without further processing. + result << ";#{entry.metadata}" if entry.metadata + result + end + end + end + end +end diff --git a/api/lib/opentelemetry/context/propagation.rb b/api/lib/opentelemetry/context/propagation.rb index 82e9d9cf53..2f139df022 100644 --- a/api/lib/opentelemetry/context/propagation.rb +++ b/api/lib/opentelemetry/context/propagation.rb @@ -4,18 +4,48 @@ # # SPDX-License-Identifier: Apache-2.0 -require 'opentelemetry/context/propagation/composite_propagator' -require 'opentelemetry/context/propagation/noop_extractor' -require 'opentelemetry/context/propagation/noop_injector' -require 'opentelemetry/context/propagation/propagator' +require 'opentelemetry/context/propagation/composite_text_map_propagator' +require 'opentelemetry/context/propagation/noop_text_map_propagator' +require 'opentelemetry/context/propagation/rack_env_getter' require 'opentelemetry/context/propagation/text_map_getter' +require 'opentelemetry/context/propagation/text_map_propagator' require 'opentelemetry/context/propagation/text_map_setter' -require 'opentelemetry/context/propagation/rack_env_getter' module OpenTelemetry class Context # The propagation module contains APIs and utilities to interact with context # and propagate across process boundaries. + # + # The API implicitly defines 3 interfaces: TextMapPropagator, TextMapInjector + # and TextMapExtractor. Concrete implementations of TextMapPropagator are + # provided. Custom text map propagators can leverage these implementations + # or simply implement the expected interface. The interfaces are described + # below. + # + # The TextMapPropagator interface: + # + # inject(carrier, context:, setter:) + # extract(carrier, context:, getter:) -> Context + # fields -> Array + # + # The TextMapInjector interface: + # + # inject(carrier, context:, setter:) + # fields -> Array + # + # The TextMapExtractor interface: + # + # extract(carrier, context:, getter:) -> Context + # + # The API provides 3 TextMapPropagator implementations: + # - A default NoopTextMapPropagator that implements +inject+ and +extract+ + # methods as no-ops. Its +fields+ method returns an empty list. + # - A TextMapPropagator that composes an Injector and an Extractor. Its + # +fields+ method delegates to the provided Injector. + # - A CompositeTextMapPropagator that wraps either a list of text map + # propagators or a list of Injectors and a list of Extractors. Its + # +fields+ method returns the union of fields returned by the Injectors + # it wraps. module Propagation extend self diff --git a/api/lib/opentelemetry/context/propagation/composite_propagator.rb b/api/lib/opentelemetry/context/propagation/composite_propagator.rb deleted file mode 100644 index 6c93f0cd52..0000000000 --- a/api/lib/opentelemetry/context/propagation/composite_propagator.rb +++ /dev/null @@ -1,72 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -module OpenTelemetry - class Context - module Propagation - # A composite propagator composes a list of injectors and extractors into - # single interface exposing inject and extract methods. Injection and - # extraction will preserve the order of the injectors and extractors - # passed in during initialization. - class CompositePropagator - # Returns a Propagator that extracts using the provided extractors - # and injectors. - # - # @param [Array<#inject>] injectors - # @param [Array<#extract>] extractors - def initialize(injectors, extractors) - @injectors = injectors - @extractors = extractors - end - - # Runs injectors in order and returns a carrier. If an injection fails - # a warning will be logged and remaining injectors will be executed. - # Always returns a valid carrier. - # - # @param [Object] carrier A carrier to inject context into - # context into - # @param [optional Context] context Context to be injected into carrier. - # Defaults to +Context.current+ - # @param [optional Setter] setter If the optional setter is provided, it - # will be used to write context into the carrier, otherwise the default - # setter will be used. - # - # @return [Object] carrier - def inject(carrier, context: Context.current, setter: Context::Propagation.text_map_setter) - @injectors.inject(carrier) do |memo, injector| - injector.inject(memo, context, setter) - rescue => e # rubocop:disable Style/RescueStandardError - OpenTelemetry.logger.warn "Error in CompositePropagator#inject #{e.message}" - carrier - end - end - - # Runs extractors in order and returns a Context updated with the - # results of each extraction. If an extraction fails, a warning will be - # logged and remaining extractors will continue to be executed. Always - # returns a valid context. - # - # @param [Object] carrier The carrier to extract context from - # @param [optional Context] context Context to be updated with the state - # 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 - # getter will be used. - # - # @return [Context] a new context updated with state extracted from the - # carrier - def extract(carrier, context: Context.current, getter: Context::Propagation.text_map_getter) - @extractors.inject(context) do |ctx, extractor| - extractor.extract(carrier, ctx, getter) - rescue => e # rubocop:disable Style/RescueStandardError - OpenTelemetry.logger.warn "Error in CompositePropagator#extract #{e.message}" - ctx - end - end - end - end - end -end diff --git a/api/lib/opentelemetry/context/propagation/composite_text_map_propagator.rb b/api/lib/opentelemetry/context/propagation/composite_text_map_propagator.rb new file mode 100644 index 0000000000..19318f5c69 --- /dev/null +++ b/api/lib/opentelemetry/context/propagation/composite_text_map_propagator.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + class Context + module Propagation + # A composite text map propagator either composes a list of injectors and a + # list of extractors, or wraps a list of propagators, into a single interface + # exposing inject and extract methods. Injection and extraction will preserve + # the order of the injectors and extractors (or propagators) passed in during + # initialization. + class CompositeTextMapPropagator + class << self + private :new # rubocop:disable Style/AccessModifierDeclarations + + # Returns a Propagator that extracts using the provided extractors + # and injectors. + # + # @param [Array<#inject, #fields>] injectors An array of text map injectors + # @param [Array<#extract>] extractors An array of text map extractors + def compose(injectors:, extractors:) + raise ArgumentError, 'injectors and extractors must both be non-nil arrays' unless injectors.is_a?(Array) && extractors.is_a?(Array) + + new(injectors: injectors, extractors: extractors) + end + + # Returns a Propagator that extracts using the provided propagators. + # + # @param [Array<#inject, #extract, #fields>] propagators An array of + # text map propagators + def compose_propagators(propagators) + raise ArgumentError, 'propagators must be a non-nil array' unless propagators.is_a?(Array) + return NoopTextMapPropagator.new if propagators.empty? + return propagators.first if propagators.size == 1 + + new(propagators: propagators) + end + end + + # @api private + def initialize(injectors: nil, extractors: nil, propagators: nil) + @injectors = injectors + @extractors = extractors + @propagators = propagators + end + + # Runs injectors or propagators in order. If an injection fails + # a warning will be logged and remaining injectors will be executed. + # + # @param [Object] carrier A mutable carrier to inject context into. + # @param [optional Context] context Context to be injected into carrier. Defaults + # to +Context.current+. + # @param [optional Setter] setter If the optional setter is provided, it + # will be used to write context into the carrier, otherwise the default + # setter will be used. + def inject(carrier, context: Context.current, setter: Context::Propagation.text_map_setter) + injectors = @injectors || @propagators + injectors.each do |injector| + injector.inject(carrier, context: context, setter: setter) + rescue StandardError => e + OpenTelemetry.logger.warn "Error in CompositePropagator#inject #{e.message}" + end + nil + end + + # Runs extractors or propagators in order and returns a Context updated + # with the results of each extraction. If an extraction fails, a warning + # will be logged and remaining extractors will continue to be executed. Always + # returns a valid context. + # + # @param [Object] carrier The carrier to extract context from. + # @param [optional Context] context Context to be updated with the state + # 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 + # getter will be used. + # + # @return [Context] a new context updated with state extracted from the + # carrier + def extract(carrier, context: Context.current, getter: Context::Propagation.text_map_getter) + extractors = @extractors || @propagators + extractors.inject(context) do |ctx, extractor| + extractor.extract(carrier, context: ctx, getter: getter) + rescue StandardError => e + OpenTelemetry.logger.warn "Error in CompositePropagator#extract #{e.message}" + ctx + end + end + + # Returns the union of the propagation fields returned by the composed injectors + # or propagators. If your carrier is reused, you should delete the fields returned + # by this method before calling +inject+. + # + # @return [Array] a list of fields that will be used by this propagator. + def fields + injectors = @injectors || @propagators + injectors.flat_map(&fields).uniq + end + end + end + end +end diff --git a/api/lib/opentelemetry/context/propagation/noop_extractor.rb b/api/lib/opentelemetry/context/propagation/noop_extractor.rb deleted file mode 100644 index 577390d36f..0000000000 --- a/api/lib/opentelemetry/context/propagation/noop_extractor.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -module OpenTelemetry - class Context - module Propagation - # A no-op extractor implementation - class NoopExtractor - # Extract a context from the given carrier - # - # @param [Object] carrier The carrier to extract the context from - # @param [Context] context The context to be upated with the extracted - # context - # @param [optional Callable] getter An optional callable that takes a carrier and a key and - # and returns the value associated with the key - # @return [Context] - def extract(carrier, context, getter = nil) - context - end - end - end - end -end diff --git a/api/lib/opentelemetry/context/propagation/noop_injector.rb b/api/lib/opentelemetry/context/propagation/noop_injector.rb deleted file mode 100644 index 869bd5a319..0000000000 --- a/api/lib/opentelemetry/context/propagation/noop_injector.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -module OpenTelemetry - class Context - module Propagation - # A no-op injector implementation - class NoopInjector - # Inject the given context into the specified carrier - # - # @param [Object] carrier The carrier to inject the provided context - # into - # @param [Context] context The context to be injected - # @param [optional Callable] setter An optional callable that takes a carrier and a key and - # a value and assigns the key-value pair in the carrier - # @return [Object] carrier - def inject(carrier, context, &setter) - carrier - end - end - end - end -end diff --git a/api/lib/opentelemetry/context/propagation/noop_text_map_propagator.rb b/api/lib/opentelemetry/context/propagation/noop_text_map_propagator.rb new file mode 100644 index 0000000000..af6161b25e --- /dev/null +++ b/api/lib/opentelemetry/context/propagation/noop_text_map_propagator.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + class Context + module Propagation + # A no-op text map propagator implementation + class NoopTextMapPropagator + EMPTY_LIST = [].freeze + private_constant(:EMPTY_LIST) + + # Injects the provided context into a carrier. + # + # @param [Object] carrier A mutable carrier to inject context into. + # @param [optional Context] context Context to be injected into carrier. Defaults + # to +Context.current+. + # @param [optional Setter] setter If the optional setter is provided, it + # will be used to write context into the carrier, otherwise the default + # setter will be used. + def inject(carrier, context: Context.current, setter: Context::Propagation.text_map_setter); end + + # Extracts and returns context from a carrier. Returns the provided + # context and logs a warning if an error if extraction fails. + # + # @param [Object] carrier The carrier to extract context from. + # @param [optional Context] context Context to be updated with the state + # 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 + # getter will be used. + # + # @return [Context] a new context updated with state extracted from the + # carrier + def extract(carrier, context: Context.current, getter: Context::Propagation.text_map_getter) + context + end + + # Returns the predefined propagation fields. If your carrier is reused, you + # should delete the fields returned by this method before calling +inject+. + # + # @return [Array] a list of fields that will be used by this propagator. + def fields + EMPTY_LIST + end + end + end + end +end diff --git a/api/lib/opentelemetry/context/propagation/propagator.rb b/api/lib/opentelemetry/context/propagation/text_map_propagator.rb similarity index 67% rename from api/lib/opentelemetry/context/propagation/propagator.rb rename to api/lib/opentelemetry/context/propagation/text_map_propagator.rb index 5c96edf00d..7d31ce91ad 100644 --- a/api/lib/opentelemetry/context/propagation/propagator.rb +++ b/api/lib/opentelemetry/context/propagation/text_map_propagator.rb @@ -7,45 +7,44 @@ module OpenTelemetry class Context module Propagation - # A propagator composes an extractor and injector into a single interface - # exposing inject and extract methods - class Propagator + # A text map propagator that composes an extractor and injector into a + # single interface exposing inject and extract methods. + class TextMapPropagator # Returns a Propagator that delegates inject and extract to the provided # injector and extractor # # @param [#inject] injector # @param [#extract] extractor def initialize(injector, extractor) + raise ArgumentError, 'injector and extractor must both be non-nil' if injector.nil? || extractor.nil? + @injector = injector @extractor = extractor end - # Returns a carrier with the provided context injected according the - # underlying injector. Returns the carrier and logs a warning if - # injection fails. + # Injects the provided context into a carrier using the underlying + # injector. Logs a warning if injection fails. # - # @param [Object] carrier A carrier to inject context into - # context into + # @param [Object] carrier A mutable carrier to inject context into. # @param [optional Context] context Context to be injected into carrier. Defaults - # to +Context.current+ + # to +Context.current+. # @param [optional Setter] setter If the optional setter is provided, it # will be used to write context into the carrier, otherwise the default # setter will be used. - # - # @return [Object] carrier def inject(carrier, context: Context.current, setter: Context::Propagation.text_map_setter) @injector.inject(carrier, context, setter) - rescue => e # rubocop:disable Style/RescueStandardError + nil + rescue StandardError => e OpenTelemetry.logger.warn "Error in Propagator#inject #{e.message}" - carrier + nil end # Extracts and returns context from a carrier. Returns the provided # context and logs a warning if an error if extraction fails. # - # @param [Object] carrier The carrier to extract context from + # @param [Object] carrier The carrier to extract context from. # @param [optional Context] context Context to be updated with the state - # extracted from the carrier. Defaults to +Context.current+ + # 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 # getter will be used. @@ -54,10 +53,18 @@ def inject(carrier, context: Context.current, setter: Context::Propagation.text_ # carrier def extract(carrier, context: Context.current, getter: Context::Propagation.text_map_getter) @extractor.extract(carrier, context, getter) - rescue => e # rubocop:disable Style/RescueStandardError + rescue StandardError => e OpenTelemetry.logger.warn "Error in Propagator#extract #{e.message}" context end + + # Returns the predefined propagation fields. If your carrier is reused, you + # should delete the fields returned by this method before calling +inject+. + # + # @return [Array] a list of fields that will be used by this propagator. + def fields + @injector.fields + end end end end diff --git a/api/lib/opentelemetry/trace/propagation/trace_context.rb b/api/lib/opentelemetry/trace/propagation/trace_context.rb index 819f5866e9..f811787809 100644 --- a/api/lib/opentelemetry/trace/propagation/trace_context.rb +++ b/api/lib/opentelemetry/trace/propagation/trace_context.rb @@ -5,8 +5,7 @@ # SPDX-License-Identifier: Apache-2.0 require 'opentelemetry/trace/propagation/trace_context/trace_parent' -require 'opentelemetry/trace/propagation/trace_context/text_map_extractor' -require 'opentelemetry/trace/propagation/trace_context/text_map_injector' +require 'opentelemetry/trace/propagation/trace_context/text_map_propagator' module OpenTelemetry module Trace @@ -15,24 +14,14 @@ module Propagation # for context propagation in the W3C Trace Context format. module TraceContext extend self - TRACEPARENT_KEY = 'traceparent' - TRACESTATE_KEY = 'tracestate' - TEXT_MAP_EXTRACTOR = TextMapExtractor.new - TEXT_MAP_INJECTOR = TextMapInjector.new + TEXT_MAP_PROPAGATOR = TextMapPropagator.new - private_constant :TRACEPARENT_KEY, :TRACESTATE_KEY, - :TEXT_MAP_INJECTOR, :TEXT_MAP_EXTRACTOR + private_constant :TEXT_MAP_PROPAGATOR - # Returns an extractor that extracts context using the W3C Trace Context - # format - def text_map_extractor - TEXT_MAP_EXTRACTOR - end - - # Returns an injector that injects context using the W3C Trace Context - # format - def text_map_injector - TEXT_MAP_INJECTOR + # Returns a text map propagator that propagates context using the + # W3C Trace Context format. + def text_map_propagator + TEXT_MAP_PROPAGATOR end end end diff --git a/api/lib/opentelemetry/trace/propagation/trace_context/text_map_extractor.rb b/api/lib/opentelemetry/trace/propagation/trace_context/text_map_extractor.rb deleted file mode 100644 index 85bd3a70f7..0000000000 --- a/api/lib/opentelemetry/trace/propagation/trace_context/text_map_extractor.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 -module OpenTelemetry - module Trace - module Propagation - module TraceContext - # Extracts context from carriers in the W3C Trace Context format - class TextMapExtractor - # Returns a new TextMapExtractor that extracts context using the - # specified getter - # - # @param [optional Getter] default_getter The default getter used to read - # headers from a carrier during extract. Defaults to a +TextMapGetter+ - # instance. - # @return [TextMapExtractor] - def initialize(default_getter = Context::Propagation.text_map_getter) - @default_getter = default_getter - end - - # Extract a remote {Trace::SpanContext} from the supplied carrier. - # Invalid headers will result in a new, valid, non-remote {Trace::SpanContext}. - # - # @param [Carrier] carrier The carrier to get the header from. - # @param [Context] context The context to be updated with extracted context - # @param [optional Getter] getter If the optional getter is provided, it - # will be used to read the header from the carrier, otherwise the default - # getter will be used. - # @return [Context] Updated context with span context from the header, or the original - # context if parsing fails. - def extract(carrier, context, getter = nil) - getter ||= @default_getter - tp = TraceParent.from_string(getter.get(carrier, TRACEPARENT_KEY)) - tracestate = Tracestate.from_string(getter.get(carrier, TRACESTATE_KEY)) - - span_context = Trace::SpanContext.new(trace_id: tp.trace_id, - span_id: tp.span_id, - trace_flags: tp.flags, - tracestate: tracestate, - remote: true) - span = Trace::Span.new(span_context: span_context) - OpenTelemetry::Trace.context_with_span(span) - rescue OpenTelemetry::Error - context - end - end - end - end - end -end diff --git a/api/lib/opentelemetry/trace/propagation/trace_context/text_map_injector.rb b/api/lib/opentelemetry/trace/propagation/trace_context/text_map_injector.rb deleted file mode 100644 index 37480c63de..0000000000 --- a/api/lib/opentelemetry/trace/propagation/trace_context/text_map_injector.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 -module OpenTelemetry - module Trace - module Propagation - module TraceContext - # Injects context into carriers using the W3C Trace Context format - class TextMapInjector - # Returns a new TextMapInjector that injects context using the - # specified setter - # - # @param [optional Setter] default_setter The default setter used to - # write context into a carrier during inject. Defaults to a - # {TextMapSetter} instance. - # @return [TextMapInjector] - def initialize(default_setter = Context::Propagation.text_map_setter) - @default_setter = default_setter - end - - # Set the span context on the supplied carrier. - # - # @param [Context] context The active {Context}. - # @param [optional Setter] setter If the optional setter is provided, it - # will be used to write context into the carrier, otherwise the default - # setter will be used. - # @return [Object] the carrier with context injected - def inject(carrier, context, setter = nil) - return carrier unless (span_context = span_context_from(context)) - - setter ||= @default_setter - setter.set(carrier, TRACEPARENT_KEY, TraceParent.from_span_context(span_context).to_s) - setter.set(carrier, TRACESTATE_KEY, span_context.tracestate.to_s) unless span_context.tracestate.empty? - - carrier - end - - private - - def span_context_from(context) - OpenTelemetry::Trace.current_span(context).context - end - end - end - end - end -end diff --git a/api/lib/opentelemetry/trace/propagation/trace_context/text_map_propagator.rb b/api/lib/opentelemetry/trace/propagation/trace_context/text_map_propagator.rb new file mode 100644 index 0000000000..589b8c6371 --- /dev/null +++ b/api/lib/opentelemetry/trace/propagation/trace_context/text_map_propagator.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Trace + module Propagation + module TraceContext + # Propagates baggage using the W3C Trace Context format + class TextMapPropagator + TRACEPARENT_KEY = 'traceparent' + TRACESTATE_KEY = 'tracestate' + FIELDS = [TRACEPARENT_KEY, TRACESTATE_KEY].freeze + + private_constant :TRACEPARENT_KEY, :TRACESTATE_KEY, :FIELDS + + # Inject trace context into the supplied carrier. + # + # @param [Carrier] carrier The mutable carrier to inject trace context into + # @param [Context] context The context to read trace context from + # @param [optional Setter] setter If the optional setter is provided, it + # will be used to write context into the carrier, otherwise the default + # text map setter will be used. + def inject(carrier, context: Context.current, setter: Context::Propagation.text_map_setter) + span_context = Trace.current_span(context).context + return unless span_context.valid? + + setter.set(carrier, TRACEPARENT_KEY, TraceParent.from_span_context(span_context).to_s) + setter.set(carrier, TRACESTATE_KEY, span_context.tracestate.to_s) unless span_context.tracestate.empty? + nil + end + + # Extract trace context from the supplied carrier. + # 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) + tp = TraceParent.from_string(getter.get(carrier, TRACEPARENT_KEY)) + tracestate = Tracestate.from_string(getter.get(carrier, TRACESTATE_KEY)) + + span_context = Trace::SpanContext.new(trace_id: tp.trace_id, + span_id: tp.span_id, + trace_flags: tp.flags, + tracestate: tracestate, + remote: true) + span = Trace::Span.new(span_context: span_context) + OpenTelemetry::Trace.context_with_span(span) + rescue OpenTelemetry::Error + context + end + + # Returns the predefined propagation fields. If your carrier is reused, you + # should delete the fields returned by this method before calling +inject+. + # + # @return [Array] a list of fields that will be used by this propagator. + def fields + FIELDS + end + end + end + end + end +end diff --git a/api/test/opentelemetry/baggage/propagation/text_map_extractor_test.rb b/api/test/opentelemetry/baggage/propagation/text_map_extractor_test.rb deleted file mode 100644 index fbcf5ebe20..0000000000 --- a/api/test/opentelemetry/baggage/propagation/text_map_extractor_test.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Baggage::Propagation::TextMapExtractor do - let(:extractor) do - OpenTelemetry::Baggage::Propagation::TextMapExtractor.new - end - let(:header_key) { 'baggage' } - - before do - @original_baggage_mgr = OpenTelemetry.baggage - OpenTelemetry.baggage = OpenTelemetry::Baggage::Manager.new - end - - after do - OpenTelemetry.baggage = @original_baggage_mgr - end - - describe '#extract' do - describe 'valid headers' do - it 'extracts key-value pairs' do - carrier = { header_key => 'key1=val1,key2=val2' } - context = extractor.extract(carrier, Context.empty) - assert_value(context, 'key1', 'val1') - assert_value(context, 'key2', 'val2') - end - - it 'extracts entries with spaces' do - carrier = { header_key => ' key1 = val1, key2=val2 ' } - context = extractor.extract(carrier, Context.empty) - assert_value(context, 'key1', 'val1') - assert_value(context, 'key2', 'val2') - end - - it 'preserves properties' do - carrier = { header_key => 'key1=val1,key2=val2;prop1=propval1;prop2=propval2' } - context = extractor.extract(carrier, Context.empty) - assert_value(context, 'key1', 'val1') - assert_value(context, 'key2', 'val2', 'prop1=propval1;prop2=propval2') - end - - it 'extracts urlencoded entries' do - carrier = { header_key => 'key%3A1=val1%2C1,key%3A2=val2%2C2' } - context = extractor.extract(carrier, Context.empty) - assert_value(context, 'key:1', 'val1,1') - assert_value(context, 'key:2', 'val2,2') - end - end - end -end - -def assert_value(context, key, value, metadata = nil) - entry = OpenTelemetry.baggage.raw_entries(context: context)[key] - _(entry).wont_be_nil - _(entry.value).must_equal(value) - _(entry.metadata).must_equal(metadata) -end diff --git a/api/test/opentelemetry/baggage/propagation/text_map_injector_test.rb b/api/test/opentelemetry/baggage/propagation/text_map_propagator_test.rb similarity index 60% rename from api/test/opentelemetry/baggage/propagation/text_map_injector_test.rb rename to api/test/opentelemetry/baggage/propagation/text_map_propagator_test.rb index 254dc3b508..d725d53e18 100644 --- a/api/test/opentelemetry/baggage/propagation/text_map_injector_test.rb +++ b/api/test/opentelemetry/baggage/propagation/text_map_propagator_test.rb @@ -6,9 +6,9 @@ require 'test_helper' -describe OpenTelemetry::Baggage::Propagation::TextMapInjector do - let(:injector) do - OpenTelemetry::Baggage::Propagation::TextMapInjector.new +describe OpenTelemetry::Baggage::Propagation::TextMapPropagator do + let(:propagator) do + OpenTelemetry::Baggage::Propagation::TextMapPropagator.new end let(:header_key) do 'baggage' @@ -26,6 +26,38 @@ OpenTelemetry.baggage = @original_baggage_mgr end + describe '#extract' do + describe 'valid headers' do + it 'extracts key-value pairs' do + carrier = { header_key => 'key1=val1,key2=val2' } + context = propagator.extract(carrier, context: Context.empty) + assert_value(context, 'key1', 'val1') + assert_value(context, 'key2', 'val2') + end + + it 'extracts entries with spaces' do + carrier = { header_key => ' key1 = val1, key2=val2 ' } + context = propagator.extract(carrier, context: Context.empty) + assert_value(context, 'key1', 'val1') + assert_value(context, 'key2', 'val2') + end + + it 'preserves properties' do + carrier = { header_key => 'key1=val1,key2=val2;prop1=propval1;prop2=propval2' } + context = propagator.extract(carrier, context: Context.empty) + assert_value(context, 'key1', 'val1') + assert_value(context, 'key2', 'val2', 'prop1=propval1;prop2=propval2') + end + + it 'extracts urlencoded entries' do + carrier = { header_key => 'key%3A1=val1%2C1,key%3A2=val2%2C2' } + context = propagator.extract(carrier, context: Context.empty) + assert_value(context, 'key:1', 'val1,1') + assert_value(context, 'key:2', 'val2,2') + end + end + end + describe '#inject' do it 'injects baggage' do context = OpenTelemetry.baggage.build(context: OpenTelemetry::Context.empty) do |b| @@ -33,7 +65,8 @@ b.set_value('key2', 'val2') end - carrier = injector.inject({}, context) + carrier = {} + propagator.inject(carrier, context: context) _(carrier[header_key]).must_equal('key1=val1,key2=val2') end @@ -44,7 +77,8 @@ b.set_value('key2', 3.14) end - carrier = injector.inject({}, context) + carrier = {} + propagator.inject(carrier, context: context) _(carrier[header_key]).must_equal('key1=1,key2=3.14') end @@ -55,13 +89,15 @@ b.set_value('key2', false) end - carrier = injector.inject({}, context) + carrier = {} + propagator.inject(carrier, context: context) _(carrier[header_key]).must_equal('key1=true,key2=false') end it 'does not inject baggage key is not present' do - carrier = injector.inject({}, Context.empty) + carrier = {} + propagator.inject(carrier, context: Context.empty) _(carrier).must_be(:empty?) end @@ -71,7 +107,8 @@ b.set_value('key2', 'val2', metadata: 'prop1=propval1;prop2=propval2') end - carrier = injector.inject({}, context) + carrier = {} + propagator.inject(carrier, context: context) _(carrier[header_key]).must_equal('key1=val1,key2=val2;prop1=propval1;prop2=propval2') end @@ -82,7 +119,8 @@ end end - carrier = injector.inject({}, context) + carrier = {} + propagator.inject(carrier, context: context) result = carrier[header_key] # expect keys indexed from 0 to 180 to be in baggage, but only 0 to 179 in the result @@ -99,7 +137,8 @@ b.set_value('key2', 'val2') end - carrier = injector.inject({}, context) + carrier = {} + propagator.inject(carrier, context: context) result = carrier[header_key] _(result).wont_include('key1') @@ -116,7 +155,8 @@ keys.zip(values).each { |k, v| b.set_value(k, v) } end - carrier = injector.inject({}, context) + carrier = {} + propagator.inject(carrier, context: context) result = carrier[header_key] keys.take(81).each { |k| _(result).must_include(k) } @@ -125,3 +165,10 @@ end end end + +def assert_value(context, key, value, metadata = nil) + entry = OpenTelemetry.baggage.raw_entries(context: context)[key] + _(entry).wont_be_nil + _(entry.value).must_equal(value) + _(entry.metadata).must_equal(metadata) +end diff --git a/api/test/opentelemetry/baggage/propagation_test.rb b/api/test/opentelemetry/baggage/propagation_test.rb index cf5aecf811..7106f60276 100644 --- a/api/test/opentelemetry/baggage/propagation_test.rb +++ b/api/test/opentelemetry/baggage/propagation_test.rb @@ -7,20 +7,11 @@ require 'test_helper' describe OpenTelemetry::Baggage::Propagation do - describe '#text_map_extractor' do - it 'returns an instance of TextMapExtractor' do - extractor = OpenTelemetry::Baggage::Propagation.text_map_extractor - _(extractor).must_be_instance_of( - OpenTelemetry::Baggage::Propagation::TextMapExtractor - ) - end - end - - describe '#text_map_injector' do - it 'returns an instance of TextMapInjector' do - injector = OpenTelemetry::Baggage::Propagation.text_map_injector - _(injector).must_be_instance_of( - OpenTelemetry::Baggage::Propagation::TextMapInjector + describe '#text_map_propagator' do + it 'returns an instance of TextMapPropagator' do + propagator = OpenTelemetry::Baggage::Propagation.text_map_propagator + _(propagator).must_be_instance_of( + OpenTelemetry::Baggage::Propagation::TextMapPropagator ) end end diff --git a/api/test/opentelemetry/context/propagation/composite_propagator_test.rb b/api/test/opentelemetry/context/propagation/composite_text_map_propagator_test.rb similarity index 85% rename from api/test/opentelemetry/context/propagation/composite_propagator_test.rb rename to api/test/opentelemetry/context/propagation/composite_text_map_propagator_test.rb index e22d064d5d..9124056183 100644 --- a/api/test/opentelemetry/context/propagation/composite_propagator_test.rb +++ b/api/test/opentelemetry/context/propagation/composite_text_map_propagator_test.rb @@ -6,7 +6,7 @@ require 'test_helper' -describe OpenTelemetry::Context::Propagation::CompositePropagator do +describe OpenTelemetry::Context::Propagation::CompositeTextMapPropagator do Context = OpenTelemetry::Context class TestInjector @@ -14,7 +14,7 @@ def initialize(key) @key = key end - def inject(carrier, context, setter = nil) + def inject(carrier, context:, setter: nil) if setter setter.set(carrier, @key, context[@key]) else @@ -29,26 +29,26 @@ def initialize(key) @key = key end - def extract(carrier, context, getter = nil) + def extract(carrier, context:, getter: nil) value = getter ? getter.get(carrier, @key) : carrier[@key] context.set_value(@key, value) end end class BuggyInjector - def inject(carrier, context, setter = nil) + def inject(carrier, context:, setter: nil) raise 'oops' end end class BuggyExtractor - def extract(carrier, context, getter = nil) + def extract(carrier, context:, getter: nil) raise 'oops' end end let(:propagator) do - OpenTelemetry::Context::Propagation::CompositePropagator.new(injectors, extractors) + OpenTelemetry::Context::Propagation::CompositeTextMapPropagator.compose(injectors: injectors, extractors: extractors) end after do @@ -62,7 +62,8 @@ def extract(carrier, context, getter = nil) describe '#inject' do it 'injects values from current context into carrier' do Context.with_values('k1' => 'v1', 'k2' => 'v2', 'k3' => 'v3') do - carrier = propagator.inject({}) + carrier = {} + propagator.inject(carrier) _(carrier).must_equal('k1' => 'v1', 'k2' => 'v2', 'k3' => 'v3') end end @@ -70,7 +71,8 @@ def extract(carrier, context, getter = nil) it 'accepts explicit context' do Context.with_values('k1' => 'v1', 'k2' => 'v2') do ctx = Context.current.set_value('k3', 'v3') do - carrier = propagator.inject({}, context: ctx) + carrier = {} + propagator.inject(carrier, context: ctx) _(carrier).must_equal('k1' => 'v1', 'k2' => 'v2', 'k3' => 'v3') end end @@ -83,7 +85,8 @@ def set(carrier, key, value) carrier[key.upcase] = value.upcase end end.new - result = propagator.inject({}, setter: setter) + result = {} + propagator.inject(result, setter: setter) _(result).must_equal('K1' => 'V1', 'K2' => 'V2', 'K3' => 'V3') end end @@ -138,7 +141,8 @@ def get(carrier, key) describe '#inject' do it 'injects values from working injectors' do Context.with_values('k1' => 'v1', 'k2' => 'v2', 'k3' => 'v3') do - carrier = propagator.inject({}) + carrier = {} + propagator.inject(carrier) _(carrier).must_equal('k1' => 'v1', 'k3' => 'v3') end end diff --git a/api/test/opentelemetry/context/propagation/noop_extractor_test.rb b/api/test/opentelemetry/context/propagation/noop_extractor_test.rb deleted file mode 100644 index 72f0c81492..0000000000 --- a/api/test/opentelemetry/context/propagation/noop_extractor_test.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Context::Propagation::NoopExtractor do - describe '#extractor' do - it 'returns the original context' do - context = OpenTelemetry::Context.empty.set_value('k1', 'v1') - extractor = OpenTelemetry::Context::Propagation::NoopExtractor.new - result = extractor.extract({ 'foo' => 'bar' }, context) - _(result).must_equal(context) - end - end -end diff --git a/api/test/opentelemetry/context/propagation/noop_injector_test.rb b/api/test/opentelemetry/context/propagation/noop_injector_test.rb deleted file mode 100644 index 96190170a8..0000000000 --- a/api/test/opentelemetry/context/propagation/noop_injector_test.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Context::Propagation::NoopInjector do - describe '#inject' do - it 'returns the carrier unmodified' do - context = OpenTelemetry::Context.empty.set_value('k1', 'v1') - injector = OpenTelemetry::Context::Propagation::NoopInjector.new - carrier = injector.inject({}, context) - _(carrier).must_equal({}) - end - end -end diff --git a/api/test/opentelemetry/context/propagation/noop_text_map_propagator_test.rb b/api/test/opentelemetry/context/propagation/noop_text_map_propagator_test.rb new file mode 100644 index 0000000000..9586f6b99e --- /dev/null +++ b/api/test/opentelemetry/context/propagation/noop_text_map_propagator_test.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Context::Propagation::NoopTextMapPropagator do + describe '#extract' do + it 'returns the original context' do + context = OpenTelemetry::Context.empty.set_value('k1', 'v1') + propagator = OpenTelemetry::Context::Propagation::NoopTextMapPropagator.new + result = propagator.extract({ 'foo' => 'bar' }, context: context) + _(result).must_equal(context) + end + end + + describe '#inject' do + it 'does not modify the carrier' do + context = OpenTelemetry::Context.empty.set_value('k1', 'v1') + propagator = OpenTelemetry::Context::Propagation::NoopTextMapPropagator.new + carrier = {} + propagator.inject(carrier, context: context) + _(carrier).must_equal({}) + end + end +end diff --git a/api/test/opentelemetry/context/propagation/propagator_test.rb b/api/test/opentelemetry/context/propagation/text_map_propagator_test.rb similarity index 56% rename from api/test/opentelemetry/context/propagation/propagator_test.rb rename to api/test/opentelemetry/context/propagation/text_map_propagator_test.rb index e6fdc9b37f..aed20747b1 100644 --- a/api/test/opentelemetry/context/propagation/propagator_test.rb +++ b/api/test/opentelemetry/context/propagation/text_map_propagator_test.rb @@ -6,18 +6,22 @@ require 'test_helper' -describe OpenTelemetry::Context::Propagation::Propagator do +describe OpenTelemetry::Context::Propagation::TextMapPropagator do let(:context) { OpenTelemetry::Context.empty } let(:propagator) do - OpenTelemetry::Context::Propagation::Propagator.new(mock_injector, mock_extractor) + OpenTelemetry::Context::Propagation::TextMapPropagator.new(mock_injector, mock_extractor) end describe 'with working injectors / extractors' do let(:mock_injector) do - Minitest::Mock.new.expect(:inject, {}, [Hash, OpenTelemetry::Context, Context::Propagation::TextMapSetter]) + Minitest::Mock.new + .expect(:inject, {}, [Hash, OpenTelemetry::Context, Context::Propagation::TextMapSetter]) + .expect(:nil?, false) end let(:mock_extractor) do - Minitest::Mock.new.expect(:extract, context, [Hash, OpenTelemetry::Context, Context::Propagation::TextMapGetter]) + Minitest::Mock.new + .expect(:extract, context, [Hash, OpenTelemetry::Context, Context::Propagation::TextMapGetter]) + .expect(:nil?, false) end describe '#inject' do @@ -37,15 +41,20 @@ describe 'with buggy injectors / extractors' do let(:mock_injector) do - Minitest::Mock.new.expect(:inject, {}) { raise 'oops' } + Minitest::Mock.new + .expect(:nil?, false) + .expect(:inject, {}) { raise 'oops' } end let(:mock_extractor) do - Minitest::Mock.new.expect(:extract, context) { raise 'oops' } + Minitest::Mock.new + .expect(:nil?, false) + .expect(:extract, context) { raise 'oops' } end describe '#inject' do it 'returns carrier' do - result = propagator.inject({}) + result = {} + propagator.inject(result) _(result).must_equal({}) mock_injector.verify end diff --git a/api/test/opentelemetry/trace/propagation/trace_context/text_map_extractor_test.rb b/api/test/opentelemetry/trace/propagation/trace_context/text_map_extractor_test.rb deleted file mode 100644 index 4838517f7a..0000000000 --- a/api/test/opentelemetry/trace/propagation/trace_context/text_map_extractor_test.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Trace::Propagation::TraceContext::TextMapExtractor do - let(:traceparent_key) { 'traceparent' } - let(:tracestate_key) { 'tracestate' } - let(:extractor) do - OpenTelemetry::Trace::Propagation::TraceContext::TextMapExtractor.new - end - let(:valid_traceparent_header) do - '00-000000000000000000000000000000AA-00000000000000ea-01' - end - let(:invalid_traceparent_header) do - 'FF-000000000000000000000000000000AA-00000000000000ea-01' - end - let(:tracestate_header) { 'vendorname=opaquevalue' } - let(:tracestate) { OpenTelemetry::Trace::Tracestate.from_hash('vendorname' => 'opaquevalue') } - let(:carrier) do - { - traceparent_key => valid_traceparent_header, - tracestate_key => tracestate_header - } - end - let(:context) { Context.empty } - - describe '#extract' do - it 'returns a remote SpanContext with fields from the traceparent and tracestate headers' do - ctx = extractor.extract(carrier, context) { |c, k| c[k] } - span_context = OpenTelemetry::Trace.current_span(ctx).context - _(span_context).must_be :remote? - _(span_context.trace_id).must_equal(("\0" * 15 + "\xaa").b) - _(span_context.span_id).must_equal(("\0" * 7 + "\xea").b) - _(span_context.trace_flags).must_be :sampled? - _(span_context.tracestate).must_equal(tracestate) - end - - it 'uses a default getter if one is not provided' do - ctx = extractor.extract(carrier, context) - span_context = OpenTelemetry::Trace.current_span(ctx).context - _(span_context).must_be :remote? - _(span_context.trace_id).must_equal(("\0" * 15 + "\xaa").b) - _(span_context.span_id).must_equal(("\0" * 7 + "\xea").b) - _(span_context.trace_flags).must_be :sampled? - _(span_context.tracestate).must_equal(tracestate) - end - - it 'returns original context on error' do - ctx = extractor.extract({}, context) { invalid_traceparent_header } - _(ctx).must_equal(context) - end - end -end diff --git a/api/test/opentelemetry/trace/propagation/trace_context/text_map_injector_test.rb b/api/test/opentelemetry/trace/propagation/trace_context/text_map_propagator_test.rb similarity index 50% rename from api/test/opentelemetry/trace/propagation/trace_context/text_map_injector_test.rb rename to api/test/opentelemetry/trace/propagation/trace_context/text_map_propagator_test.rb index 47201131d2..9c29a47d6b 100644 --- a/api/test/opentelemetry/trace/propagation/trace_context/text_map_injector_test.rb +++ b/api/test/opentelemetry/trace/propagation/trace_context/text_map_propagator_test.rb @@ -6,14 +6,14 @@ require 'test_helper' -describe OpenTelemetry::Trace::Propagation::TraceContext::TextMapInjector do +describe OpenTelemetry::Trace::Propagation::TraceContext::TextMapPropagator do Span = OpenTelemetry::Trace::Span SpanContext = OpenTelemetry::Trace::SpanContext let(:traceparent_key) { 'traceparent' } let(:tracestate_key) { 'tracestate' } - let(:injector) do - OpenTelemetry::Trace::Propagation::TraceContext::TextMapInjector.new + let(:propagator) do + OpenTelemetry::Trace::Propagation::TraceContext::TextMapPropagator.new end let(:valid_traceparent_header) do '00-000000000000000000000000000000AA-00000000000000ea-01' @@ -22,43 +22,82 @@ 'FF-000000000000000000000000000000AA-00000000000000ea-01' end let(:tracestate_header) { 'vendorname=opaquevalue' } - let(:context) do - span_context = SpanContext.new(trace_id: ("\xff" * 16).b, span_id: ("\x11" * 8).b) - span = Span.new(span_context: span_context) - OpenTelemetry::Trace.context_with_span(span, parent_context: Context.empty) + let(:tracestate) { OpenTelemetry::Trace::Tracestate.from_hash('vendorname' => 'opaquevalue') } + let(:carrier) do + { + traceparent_key => valid_traceparent_header, + tracestate_key => tracestate_header + } end + let(:context) { Context.empty } let(:context_with_tracestate) do span_context = SpanContext.new(trace_id: ("\xff" * 16).b, span_id: ("\x11" * 8).b, tracestate: tracestate_header) span = Span.new(span_context: span_context) OpenTelemetry::Trace.context_with_span(span, parent_context: Context.empty) end + let(:context_without_tracestate) do + span_context = SpanContext.new(trace_id: ("\xff" * 16).b, span_id: ("\x11" * 8).b) + span = Span.new(span_context: span_context) + OpenTelemetry::Trace.context_with_span(span, parent_context: Context.empty) + end + + describe '#extract' do + it 'returns a remote SpanContext with fields from the traceparent and tracestate headers' do + ctx = propagator.extract(carrier, context: context) { |c, k| c[k] } + span_context = OpenTelemetry::Trace.current_span(ctx).context + _(span_context).must_be :remote? + _(span_context.trace_id).must_equal(("\0" * 15 + "\xaa").b) + _(span_context.span_id).must_equal(("\0" * 7 + "\xea").b) + _(span_context.trace_flags).must_be :sampled? + _(span_context.tracestate).must_equal(tracestate) + end + + it 'uses a default getter if one is not provided' do + ctx = propagator.extract(carrier, context: context) + span_context = OpenTelemetry::Trace.current_span(ctx).context + _(span_context).must_be :remote? + _(span_context.trace_id).must_equal(("\0" * 15 + "\xaa").b) + _(span_context.span_id).must_equal(("\0" * 7 + "\xea").b) + _(span_context.trace_flags).must_be :sampled? + _(span_context.tracestate).must_equal(tracestate) + end + + it 'returns original context on error' do + ctx = propagator.extract({}, context: context) { invalid_traceparent_header } + _(ctx).must_equal(context) + end + end describe '#inject' do it 'writes traceparent into the carrier' do carrier = {} - injector.inject(carrier, context) + propagator.inject(carrier, context: context_without_tracestate) _(carrier[traceparent_key]).must_equal('00-ffffffffffffffffffffffffffffffff-1111111111111111-00') end it 'does not write the tracestate into carrier, if nil' do - carrier = injector.inject({}, context) + carrier = {} + propagator.inject(carrier, context: context_without_tracestate) _(carrier).wont_include(tracestate_key) end it 'writes the tracestate into the context, if provided' do - carrier = injector.inject({}, context_with_tracestate) + carrier = {} + propagator.inject(carrier, context: context_with_tracestate) _(carrier).must_include(tracestate_key) end it 'uses the default setter if one is not provided' do - carrier = injector.inject({}, context_with_tracestate) + carrier = {} + propagator.inject(carrier, context: context_with_tracestate) _(carrier[traceparent_key]).must_equal('00-ffffffffffffffffffffffffffffffff-1111111111111111-00') _(carrier[tracestate_key]).must_equal(tracestate_header) end it 'propagates remote context without current span' do - carrier = injector.inject({}, context_with_tracestate) + carrier = {} + propagator.inject(carrier, context: context_with_tracestate) _(carrier[traceparent_key]).must_equal('00-ffffffffffffffffffffffffffffffff-1111111111111111-00') _(carrier[tracestate_key]).must_equal(tracestate_header) end diff --git a/api/test/opentelemetry/trace/propagation/trace_context/trace_context_test.rb b/api/test/opentelemetry/trace/propagation/trace_context/trace_context_test.rb index 8c5a90155a..5b6f9a4520 100644 --- a/api/test/opentelemetry/trace/propagation/trace_context/trace_context_test.rb +++ b/api/test/opentelemetry/trace/propagation/trace_context/trace_context_test.rb @@ -7,20 +7,11 @@ require 'test_helper' describe OpenTelemetry::Trace::Propagation::TraceContext do - describe '#text_map_extractor' do - it 'returns an instance of TextMapExtractor' do - extractor = OpenTelemetry::Trace::Propagation::TraceContext.text_map_extractor - _(extractor).must_be_instance_of( - OpenTelemetry::Trace::Propagation::TraceContext::TextMapExtractor - ) - end - end - - describe '#text_map_injector, #rack_injector' do - it 'returns an instance of TextMapInjector' do - injector = OpenTelemetry::Trace::Propagation::TraceContext.text_map_injector - _(injector).must_be_instance_of( - OpenTelemetry::Trace::Propagation::TraceContext::TextMapInjector + describe '#text_map_propagator' do + it 'returns an instance of TextMapPropagator' do + propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator + _(propagator).must_be_instance_of( + OpenTelemetry::Trace::Propagation::TraceContext::TextMapPropagator ) end end diff --git a/api/test/opentelemetry_test.rb b/api/test/opentelemetry_test.rb index ab8cb8b5dd..e7b2f2cad0 100644 --- a/api/test/opentelemetry_test.rb +++ b/api/test/opentelemetry_test.rb @@ -142,9 +142,9 @@ def error(message) end describe '.propagation' do - it 'returns instance of Context::Propagation::Propagation by default' do + it 'returns instance of Context::Propagation::NoopTextMapPropagator by default' do _(OpenTelemetry.propagation).must_be_instance_of( - OpenTelemetry::Context::Propagation::Propagator + OpenTelemetry::Context::Propagation::NoopTextMapPropagator ) end diff --git a/instrumentation/active_model_serializers/test/opentelemetry/instrumentation/active_model_serializers/event_handler_test.rb b/instrumentation/active_model_serializers/test/opentelemetry/instrumentation/active_model_serializers/event_handler_test.rb index 421ed591f1..639a6785d2 100644 --- a/instrumentation/active_model_serializers/test/opentelemetry/instrumentation/active_model_serializers/event_handler_test.rb +++ b/instrumentation/active_model_serializers/test/opentelemetry/instrumentation/active_model_serializers/event_handler_test.rb @@ -22,10 +22,7 @@ # this is currently a noop but this will future proof the test @orig_propagation = OpenTelemetry.propagation - propagator = OpenTelemetry::Context::Propagation::Propagator.new( - OpenTelemetry::Trace::Propagation::TraceContext.text_map_injector, - OpenTelemetry::Trace::Propagation::TraceContext.text_map_extractor - ) + propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator OpenTelemetry.propagation = propagator end diff --git a/instrumentation/delayed_job/test/opentelemetry/instrumentation/delayed_job/plugins/tracer_plugin_test.rb b/instrumentation/delayed_job/test/opentelemetry/instrumentation/delayed_job/plugins/tracer_plugin_test.rb index edeafee245..58b3028826 100644 --- a/instrumentation/delayed_job/test/opentelemetry/instrumentation/delayed_job/plugins/tracer_plugin_test.rb +++ b/instrumentation/delayed_job/test/opentelemetry/instrumentation/delayed_job/plugins/tracer_plugin_test.rb @@ -45,10 +45,7 @@ def job_data # this is currently a noop but this will future proof the test @orig_propagation = OpenTelemetry.propagation - propagator = OpenTelemetry::Context::Propagation::Propagator.new( - OpenTelemetry::Trace::Propagation::TraceContext.text_map_injector, - OpenTelemetry::Trace::Propagation::TraceContext.text_map_extractor - ) + propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator OpenTelemetry.propagation = propagator end diff --git a/instrumentation/ethon/test/opentelemetry/instrumentation/ethon/instrumentation_test.rb b/instrumentation/ethon/test/opentelemetry/instrumentation/ethon/instrumentation_test.rb index 6de9cd56f9..cf687efe49 100644 --- a/instrumentation/ethon/test/opentelemetry/instrumentation/ethon/instrumentation_test.rb +++ b/instrumentation/ethon/test/opentelemetry/instrumentation/ethon/instrumentation_test.rb @@ -19,10 +19,7 @@ # this is currently a noop but this will future proof the test @orig_propagation = OpenTelemetry.propagation - propagator = OpenTelemetry::Context::Propagation::Propagator.new( - OpenTelemetry::Trace::Propagation::TraceContext.text_map_injector, - OpenTelemetry::Trace::Propagation::TraceContext.text_map_extractor - ) + propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator OpenTelemetry.propagation = propagator end diff --git a/instrumentation/excon/test/opentelemetry/instrumentation/excon/instrumentation_test.rb b/instrumentation/excon/test/opentelemetry/instrumentation/excon/instrumentation_test.rb index ed53a9df66..695208e0cc 100644 --- a/instrumentation/excon/test/opentelemetry/instrumentation/excon/instrumentation_test.rb +++ b/instrumentation/excon/test/opentelemetry/instrumentation/excon/instrumentation_test.rb @@ -22,10 +22,7 @@ # this is currently a noop but this will future proof the test @orig_propagation = OpenTelemetry.propagation - propagator = OpenTelemetry::Context::Propagation::Propagator.new( - OpenTelemetry::Trace::Propagation::TraceContext.text_map_injector, - OpenTelemetry::Trace::Propagation::TraceContext.text_map_extractor - ) + propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator OpenTelemetry.propagation = propagator end diff --git a/instrumentation/faraday/test/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware_test.rb b/instrumentation/faraday/test/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware_test.rb index ab56b27dfd..7d98b641bf 100644 --- a/instrumentation/faraday/test/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware_test.rb +++ b/instrumentation/faraday/test/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware_test.rb @@ -30,10 +30,7 @@ # this is currently a noop but this will future proof the test @orig_propagation = OpenTelemetry.propagation - propagator = OpenTelemetry::Context::Propagation::Propagator.new( - OpenTelemetry::Trace::Propagation::TraceContext.text_map_injector, - OpenTelemetry::Trace::Propagation::TraceContext.text_map_extractor - ) + propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator OpenTelemetry.propagation = propagator end diff --git a/instrumentation/http/test/instrumentation/http/patches/client_test.rb b/instrumentation/http/test/instrumentation/http/patches/client_test.rb index 41dc3e8e69..ce75c0b132 100644 --- a/instrumentation/http/test/instrumentation/http/patches/client_test.rb +++ b/instrumentation/http/test/instrumentation/http/patches/client_test.rb @@ -16,14 +16,21 @@ before do exporter.reset + @orig_propagation = OpenTelemetry.propagation + propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator + OpenTelemetry.propagation = propagator instrumentation.install({}) stub_request(:get, 'http://example.com/success').to_return(status: 200) stub_request(:post, 'http://example.com/failure').to_return(status: 500) stub_request(:get, 'https://example.com/timeout').to_timeout end - # Force re-install of instrumentation - after { instrumentation.instance_variable_set(:@installed, false) } + after do + # Force re-install of instrumentation + instrumentation.instance_variable_set(:@installed, false) + + OpenTelemetry.propagation = @orig_propagation + end describe '#perform' do it 'traces a simple request' do diff --git a/instrumentation/http_client/test/instrumentation/http_client/patches/client_test.rb b/instrumentation/http_client/test/instrumentation/http_client/patches/client_test.rb index 895f38bd62..67af8d262d 100644 --- a/instrumentation/http_client/test/instrumentation/http_client/patches/client_test.rb +++ b/instrumentation/http_client/test/instrumentation/http_client/patches/client_test.rb @@ -16,14 +16,21 @@ before do exporter.reset + @orig_propagation = OpenTelemetry.propagation + propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator + OpenTelemetry.propagation = propagator instrumentation.install({}) stub_request(:get, 'http://example.com/success').to_return(status: 200) stub_request(:post, 'http://example.com/failure').to_return(status: 500) stub_request(:get, 'https://example.com/timeout').to_timeout end - # Force re-install of instrumentation - after { instrumentation.instance_variable_set(:@installed, false) } + after do + # Force re-install of instrumentation + instrumentation.instance_variable_set(:@installed, false) + + OpenTelemetry.propagation = @orig_propagation + end describe '#do_request' do it 'traces a simple request' do diff --git a/instrumentation/mongo/test/opentelemetry/instrumentation/mongo/subscriber_test.rb b/instrumentation/mongo/test/opentelemetry/instrumentation/mongo/subscriber_test.rb index 56a0150b0a..cc1982ced0 100644 --- a/instrumentation/mongo/test/opentelemetry/instrumentation/mongo/subscriber_test.rb +++ b/instrumentation/mongo/test/opentelemetry/instrumentation/mongo/subscriber_test.rb @@ -26,10 +26,7 @@ # this is currently a noop but this will future proof the test @orig_propagation = OpenTelemetry.propagation - propagator = OpenTelemetry::Context::Propagation::Propagator.new( - OpenTelemetry::Trace::Propagation::TraceContext.text_map_injector, - OpenTelemetry::Trace::Propagation::TraceContext.text_map_extractor - ) + propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator OpenTelemetry.propagation = propagator end diff --git a/instrumentation/net_http/test/opentelemetry/instrumentation/net/http/instrumentation_test.rb b/instrumentation/net_http/test/opentelemetry/instrumentation/net/http/instrumentation_test.rb index b5a7b64189..3b1cad4261 100644 --- a/instrumentation/net_http/test/opentelemetry/instrumentation/net/http/instrumentation_test.rb +++ b/instrumentation/net_http/test/opentelemetry/instrumentation/net/http/instrumentation_test.rb @@ -22,10 +22,7 @@ # this is currently a noop but this will future proof the test @orig_propagation = OpenTelemetry.propagation - propagator = OpenTelemetry::Context::Propagation::Propagator.new( - OpenTelemetry::Trace::Propagation::TraceContext.text_map_injector, - OpenTelemetry::Trace::Propagation::TraceContext.text_map_extractor - ) + propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator OpenTelemetry.propagation = propagator instrumentation.install end diff --git a/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/instrumentation_test.rb b/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/instrumentation_test.rb index a65bee6d81..2fff491e8a 100644 --- a/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/instrumentation_test.rb +++ b/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/instrumentation_test.rb @@ -21,10 +21,7 @@ # this is currently a noop but this will future proof the test @orig_propagation = OpenTelemetry.propagation - propagator = OpenTelemetry::Context::Propagation::Propagator.new( - OpenTelemetry::Trace::Propagation::TraceContext.text_map_injector, - OpenTelemetry::Trace::Propagation::TraceContext.text_map_extractor - ) + propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator OpenTelemetry.propagation = propagator end diff --git a/propagator/b3/lib/opentelemetry/propagator/b3.rb b/propagator/b3/lib/opentelemetry/propagator/b3.rb index 6fecac5f5b..1032b14bd2 100644 --- a/propagator/b3/lib/opentelemetry/propagator/b3.rb +++ b/propagator/b3/lib/opentelemetry/propagator/b3.rb @@ -31,28 +31,11 @@ def context_with_debug(context) def debug?(context) context.value(DEBUG_CONTEXT_KEY) end - - # @api private - # 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 - - # @api private - # 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 require_relative './b3/version' +require_relative './b3/text_map_extractor' require_relative './b3/multi' require_relative './b3/single' diff --git a/propagator/b3/lib/opentelemetry/propagator/b3/multi.rb b/propagator/b3/lib/opentelemetry/propagator/b3/multi.rb index a4c80bbcb4..5d3af9be8c 100644 --- a/propagator/b3/lib/opentelemetry/propagator/b3/multi.rb +++ b/propagator/b3/lib/opentelemetry/propagator/b3/multi.rb @@ -4,8 +4,7 @@ # # SPDX-License-Identifier: Apache-2.0 -require_relative './multi/text_map_extractor' -require_relative './multi/text_map_injector' +require_relative './multi/text_map_propagator' # OpenTelemetry is an open source observability framework, providing a # general-purpose API, SDK, and related tools required for the instrumentation @@ -18,30 +17,18 @@ module OpenTelemetry module Propagator # Namespace for OpenTelemetry B3 propagation module B3 - # Namespace for OpenTelemetry b3 multi header encoding + # Namespace for OpenTelemetry B3 multi header encoding module Multi extend self - 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' - TEXT_MAP_EXTRACTOR = TextMapExtractor.new - TEXT_MAP_INJECTOR = TextMapInjector.new + TEXT_MAP_PROPAGATOR = TextMapPropagator.new - private_constant :B3_TRACE_ID_KEY, :B3_SPAN_ID_KEY, :B3_SAMPLED_KEY, - :B3_FLAGS_KEY, :TEXT_MAP_INJECTOR, :TEXT_MAP_EXTRACTOR + private_constant :TEXT_MAP_PROPAGATOR - # Returns an extractor that extracts context in the B3 multi header - # format - def text_map_injector - TEXT_MAP_INJECTOR - end - - # Returns an injector that injects context in the B3 multi header - # format - def text_map_extractor - TEXT_MAP_EXTRACTOR + # Returns a text map propagator that propagates context using the + # B3 multi header format. + def text_map_propagator + TEXT_MAP_PROPAGATOR end end end diff --git a/propagator/b3/lib/opentelemetry/propagator/b3/multi/text_map_extractor.rb b/propagator/b3/lib/opentelemetry/propagator/b3/multi/text_map_extractor.rb deleted file mode 100644 index 94490530dd..0000000000 --- a/propagator/b3/lib/opentelemetry/propagator/b3/multi/text_map_extractor.rb +++ /dev/null @@ -1,98 +0,0 @@ -# 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 - # Namespace for OpenTelemetry b3 multi header encoding - module Multi - # Extracts context from carriers in the b3 single header format - class TextMapExtractor - B3_TRACE_ID_REGEX = /\A(?:[0-9a-f]{16}){1,2}\z/.freeze - B3_SPAN_ID_REGEX = /\A[0-9a-f]{16}\z/.freeze - SAMPLED_VALUES = %w[1 true].freeze - DEBUG_FLAG = '1' - private_constant :B3_TRACE_ID_REGEX, :B3_SPAN_ID_REGEX, :SAMPLED_VALUES, :DEBUG_FLAG - - # Returns a new TextMapExtractor that extracts b3 context using the - # specified getter - # - # @param [optional Getter] default_getter The default getter used to read - # headers from a carrier during extract. Defaults to a - # {OpenTelemetry::Context:Propagation::TextMapGetter} instance. - # @return [TextMapExtractor] - def initialize(default_getter = Context::Propagation.text_map_getter) - @default_getter = default_getter - end - - # Extract b3 context from the supplied carrier and set the active span - # in the given context. The original context will be returned if b3 - # cannot be extracted from the carrier. - # - # @param [Carrier] carrier The carrier to get the header from. - # @param [Context] context The context to be updated with extracted context - # @param [optional Getter] getter If the optional getter is provided, it - # will be used to read the header from the carrier, otherwise the default - # getter will be used. - # @return [Context] Updated context with active span derived from the header, or the original - # context if parsing fails. - def extract(carrier, context, getter = nil) - getter ||= @default_getter - - trace_id_hex = getter.get(carrier, B3_TRACE_ID_KEY) - return context unless valid_trace_id?(trace_id_hex) - - span_id_hex = getter.get(carrier, B3_SPAN_ID_KEY) - return context unless valid_span_id?(span_id_hex) - - sampled = getter.get(carrier, B3_SAMPLED_KEY) - flags = getter.get(carrier, B3_FLAGS_KEY) - - context = B3.context_with_debug(context) if flags == DEBUG_FLAG - - span_context = Trace::SpanContext.new( - trace_id: B3.to_trace_id(trace_id_hex), - span_id: B3.to_span_id(span_id_hex), - trace_flags: to_trace_flags(sampled, flags), - remote: true - ) - - span = Trace::Span.new(span_context: span_context) - Trace.context_with_span(span, parent_context: context) - rescue OpenTelemetry::Error - context - end - - private - - def to_trace_flags(sampled, b3_flags) - if b3_flags == DEBUG_FLAG || SAMPLED_VALUES.include?(sampled) - Trace::TraceFlags::SAMPLED - else - Trace::TraceFlags::DEFAULT - end - end - - def valid_trace_id?(trace_id) - B3_TRACE_ID_REGEX.match?(trace_id) - end - - def valid_span_id?(span_id) - B3_SPAN_ID_REGEX.match?(span_id) - end - end - end - end - end -end diff --git a/propagator/b3/lib/opentelemetry/propagator/b3/multi/text_map_injector.rb b/propagator/b3/lib/opentelemetry/propagator/b3/multi/text_map_injector.rb deleted file mode 100644 index 801bb16b5b..0000000000 --- a/propagator/b3/lib/opentelemetry/propagator/b3/multi/text_map_injector.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 -module OpenTelemetry - # Namespace for OpenTelemetry propagator extension libraries - module Propagator - # Namespace for OpenTelemetry B3 propagation - module B3 - # Namespace for OpenTelemetry b3 single header encoding - module Multi - # Injects context into carriers using the b3 single header format - class TextMapInjector - # Returns a new TextMapInjector that injects b3 context using the - # specified setter - # - # @param [optional Setter] default_setter The default setter used to - # write context into a carrier during inject. Defaults to a - # {OpenTelemetry::Context:Propagation::TextMapSetter} instance. - # @return [TextMapInjector] - def initialize(default_setter = Context::Propagation.text_map_setter) - @default_setter = default_setter - end - - # Set the span context on the supplied carrier. - # - # @param [Context] context The active Context. - # @param [optional Setter] setter If the optional setter is provided, it - # will be used to write context into the carrier, otherwise the default - # setter will be used. - # @return [Object] the carrier with context injected - def inject(carrier, context, setter = nil) - span_context = Trace.current_span(context).context - return unless span_context.valid? - - setter ||= @default_setter - setter.set(carrier, B3_TRACE_ID_KEY, span_context.hex_trace_id) - setter.set(carrier, B3_SPAN_ID_KEY, span_context.hex_span_id) - - if B3.debug?(context) - setter.set(carrier, B3_FLAGS_KEY, '1') - elsif span_context.trace_flags.sampled? - setter.set(carrier, B3_SAMPLED_KEY, '1') - else - setter.set(carrier, B3_SAMPLED_KEY, '0') - end - - carrier - end - end - end - end - end -end diff --git a/propagator/b3/lib/opentelemetry/propagator/b3/multi/text_map_propagator.rb b/propagator/b3/lib/opentelemetry/propagator/b3/multi/text_map_propagator.rb new file mode 100644 index 0000000000..26f51646dc --- /dev/null +++ b/propagator/b3/lib/opentelemetry/propagator/b3/multi/text_map_propagator.rb @@ -0,0 +1,64 @@ +# 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 + # Namespace for OpenTelemetry B3 multi header encoding + module Multi + # Propagates trace context using the B3 multi header format + class TextMapPropagator + include TextMapExtractor + + FIELDS = [B3_TRACE_ID_KEY, B3_SPAN_ID_KEY, B3_SAMPLED_KEY, B3_FLAGS_KEY].freeze + + private_constant :FIELDS + + # Inject trace context into the supplied carrier. + # + # @param [Carrier] carrier The mutable carrier to inject trace context into + # @param [Context] context The context to read trace context from + # @param [optional Setter] setter If the optional setter is provided, it + # will be used to write context into the carrier, otherwise the default + # text map setter will be used. + def inject(carrier, context: Context.current, setter: Context::Propagation.text_map_setter) + span_context = Trace.current_span(context).context + return unless span_context.valid? + + setter.set(carrier, B3_TRACE_ID_KEY, span_context.hex_trace_id) + setter.set(carrier, B3_SPAN_ID_KEY, span_context.hex_span_id) + + if B3.debug?(context) + setter.set(carrier, B3_FLAGS_KEY, '1') + elsif span_context.trace_flags.sampled? + setter.set(carrier, B3_SAMPLED_KEY, '1') + else + setter.set(carrier, B3_SAMPLED_KEY, '0') + end + + nil + end + + # Returns the predefined propagation fields. If your carrier is reused, you + # should delete the fields returned by this method before calling +inject+. + # + # @return [Array] a list of fields that will be used by this propagator. + def fields + FIELDS + end + end + end + end + end +end diff --git a/propagator/b3/lib/opentelemetry/propagator/b3/single.rb b/propagator/b3/lib/opentelemetry/propagator/b3/single.rb index c1528b475a..f53ceb63ba 100644 --- a/propagator/b3/lib/opentelemetry/propagator/b3/single.rb +++ b/propagator/b3/lib/opentelemetry/propagator/b3/single.rb @@ -4,8 +4,7 @@ # # SPDX-License-Identifier: Apache-2.0 -require_relative './single/text_map_extractor' -require_relative './single/text_map_injector' +require_relative './single/text_map_propagator' # OpenTelemetry is an open source observability framework, providing a # general-purpose API, SDK, and related tools required for the instrumentation @@ -18,26 +17,18 @@ module OpenTelemetry module Propagator # Namespace for OpenTelemetry B3 propagation module B3 - # Namespace for OpenTelemetry b3 single header encoding + # Namespace for OpenTelemetry B3 single header encoding module Single extend self - B3_CONTEXT_KEY = 'b3' - TEXT_MAP_EXTRACTOR = TextMapExtractor.new - TEXT_MAP_INJECTOR = TextMapInjector.new + TEXT_MAP_PROPAGATOR = TextMapPropagator.new - private_constant :B3_CONTEXT_KEY, :TEXT_MAP_INJECTOR, :TEXT_MAP_EXTRACTOR + private_constant :TEXT_MAP_PROPAGATOR - # Returns an extractor that extracts context in the B3 single header - # format - def text_map_injector - TEXT_MAP_INJECTOR - end - - # Returns an injector that injects context in the B3 single header - # format - def text_map_extractor - TEXT_MAP_EXTRACTOR + # Returns a text map propagator that propagates context using the + # B3 single header format. + def text_map_propagator + TEXT_MAP_PROPAGATOR end end end diff --git a/propagator/b3/lib/opentelemetry/propagator/b3/single/text_map_extractor.rb b/propagator/b3/lib/opentelemetry/propagator/b3/single/text_map_extractor.rb deleted file mode 100644 index c88df13914..0000000000 --- a/propagator/b3/lib/opentelemetry/propagator/b3/single/text_map_extractor.rb +++ /dev/null @@ -1,79 +0,0 @@ -# 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 - # Namespace for OpenTelemetry b3 single header encoding - module Single - # Extracts context from carriers in the b3 single header format - class TextMapExtractor - B3_CONTEXT_REGEX = /\A(?(?:[0-9a-f]{16}){1,2})-(?[0-9a-f]{16})(?:-(?[01d](?![0-9a-f])))?(?:-(?[0-9a-f]{16}))?\z/.freeze - SAMPLED_VALUES = %w[1 d].freeze - - # Returns a new TextMapExtractor that extracts b3 context using the - # specified getter - # - # @param [optional Getter] default_getter The default getter used to read - # headers from a carrier during extract. Defaults to a - # {OpenTelemetry::Context:Propagation::TextMapGetter} instance. - # @return [TextMapExtractor] - def initialize(default_getter = Context::Propagation.text_map_getter) - @default_getter = default_getter - end - - # Extract b3 context from the supplied carrier and set the active span - # in the given context. The original context will be returned if b3 - # cannot be extracted from the carrier. - # - # @param [Carrier] carrier The carrier to get the header from. - # @param [Context] context The context to be updated with extracted context - # @param [optional Getter] getter If the optional getter is provided, it - # will be used to read the header from the carrier, otherwise the default - # getter will be used. - # @return [Context] Updated context with active span derived from the header, or the original - # context if parsing fails. - def extract(carrier, context, getter = nil) - getter ||= @default_getter - header = getter.get(carrier, B3_CONTEXT_KEY) - return context unless (match = header.match(B3_CONTEXT_REGEX)) - - span_context = Trace::SpanContext.new( - trace_id: B3.to_trace_id(match['trace_id']), - span_id: B3.to_span_id(match['span_id']), - trace_flags: to_trace_flags(match['sampling_state']), - remote: true - ) - - span = Trace::Span.new(span_context: span_context) - context = B3.context_with_debug(context) if match['sampling_state'] == 'd' - Trace.context_with_span(span, parent_context: context) - rescue OpenTelemetry::Error - context - end - - private - - def to_trace_flags(sampling_state) - if SAMPLED_VALUES.include?(sampling_state) - Trace::TraceFlags::SAMPLED - else - Trace::TraceFlags::DEFAULT - end - end - end - end - end - end -end diff --git a/propagator/b3/lib/opentelemetry/propagator/b3/single/text_map_injector.rb b/propagator/b3/lib/opentelemetry/propagator/b3/single/text_map_injector.rb deleted file mode 100644 index b98b24aae5..0000000000 --- a/propagator/b3/lib/opentelemetry/propagator/b3/single/text_map_injector.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 -module OpenTelemetry - # Namespace for OpenTelemetry propagator extension libraries - module Propagator - # Namespace for OpenTelemetry B3 propagation - module B3 - # Namespace for OpenTelemetry b3 single header encoding - module Single - # Injects context into carriers using the b3 single header format - class TextMapInjector - # Returns a new TextMapInjector that injects b3 context using the - # specified setter - # - # @param [optional Setter] default_setter The default setter used to - # write context into a carrier during inject. Defaults to a - # {OpenTelemetry::Context:Propagation::TextMapSetter} instance. - # @return [TextMapInjector] - def initialize(default_setter = Context::Propagation.text_map_setter) - @default_setter = default_setter - end - - # Set the span context on the supplied carrier. - # - # @param [Context] context The active Context. - # @param [optional Setter] setter If the optional setter is provided, it - # will be used to write context into the carrier, otherwise the default - # setter will be used. - # @return [Object] the carrier with context injected - def inject(carrier, context, setter = nil) - span_context = Trace.current_span(context).context - return unless span_context.valid? - - sampling_state = if B3.debug?(context) - 'd' - elsif span_context.trace_flags.sampled? - '1' - else - '0' - end - - b3_value = "#{span_context.hex_trace_id}-#{span_context.hex_span_id}-#{sampling_state}" - - setter ||= @default_setter - setter.set(carrier, B3_CONTEXT_KEY, b3_value) - carrier - end - end - end - end - end -end diff --git a/propagator/b3/lib/opentelemetry/propagator/b3/single/text_map_propagator.rb b/propagator/b3/lib/opentelemetry/propagator/b3/single/text_map_propagator.rb new file mode 100644 index 0000000000..cf6ef6dece --- /dev/null +++ b/propagator/b3/lib/opentelemetry/propagator/b3/single/text_map_propagator.rb @@ -0,0 +1,64 @@ +# 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 + # Namespace for OpenTelemetry b3 single header encoding + module Single + # Propagates trace context using the b3 single header format + class TextMapPropagator + include TextMapExtractor + + FIELDS = [B3_CONTEXT_KEY].freeze + + private_constant :FIELDS + + # Inject trace context into the supplied carrier. + # + # @param [Carrier] carrier The mutable carrier to inject trace context into + # @param [Context] context The context to read trace context from + # @param [optional Setter] setter If the optional setter is provided, it + # will be used to write context into the carrier, otherwise the default + # text map setter will be used. + def inject(carrier, context: Context.current, setter: Context::Propagation.text_map_setter) + span_context = Trace.current_span(context).context + return unless span_context.valid? + + sampling_state = if B3.debug?(context) + 'd' + elsif span_context.trace_flags.sampled? + '1' + else + '0' + end + + b3_value = "#{span_context.hex_trace_id}-#{span_context.hex_span_id}-#{sampling_state}" + + setter.set(carrier, B3_CONTEXT_KEY, b3_value) + nil + end + + # Returns the predefined propagation fields. If your carrier is reused, you + # should delete the fields returned by this method before calling +inject+. + # + # @return [Array] a list of fields that will be used by this propagator. + def fields + FIELDS + end + end + end + end + end +end diff --git a/propagator/b3/lib/opentelemetry/propagator/b3/text_map_extractor.rb b/propagator/b3/lib/opentelemetry/propagator/b3/text_map_extractor.rb new file mode 100644 index 0000000000..62f82f1b40 --- /dev/null +++ b/propagator/b3/lib/opentelemetry/propagator/b3/text_map_extractor.rb @@ -0,0 +1,116 @@ +# 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/.freeze + B3_TRACE_ID_REGEX = /\A(?:[0-9a-f]{16}){1,2}\z/.freeze + B3_SPAN_ID_REGEX = /\A[0-9a-f]{16}\z/.freeze + 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 = Trace::Span.new(span_context: 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 diff --git a/propagator/b3/test/multi/text_map_extractor_test.rb b/propagator/b3/test/multi/text_map_extractor_test.rb deleted file mode 100644 index efc9654ce0..0000000000 --- a/propagator/b3/test/multi/text_map_extractor_test.rb +++ /dev/null @@ -1,118 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Propagator::B3::Multi::TextMapExtractor do - let(:extractor) { OpenTelemetry::Propagator::B3::Multi::TextMapExtractor.new } - let(:trace_id_key) { 'X-B3-TraceId' } - let(:span_id_key) { 'X-B3-SpanId' } - let(:parent_span_id_key) { 'X-B3-ParentSpanId' } - let(:sampled_key) { 'X-B3-Sampled' } - let(:flags_key) { 'X-B3-Flags' } - - describe('#extract') do - it 'extracts context with trace id, span id, sampling flag, parent span id' do - parent_context = OpenTelemetry::Context.empty - carrier = { - trace_id_key => '80f198ee56343ba864fe8b2a57d3eff7', - span_id_key => 'e457b5a2e4d86bd1', - sampled_key => '1', - parent_span_id_key => '05e3ac9a4f6e3b90' - } - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - _(extracted_context).must_be(:remote?) - end - - it 'extracts context with trace id, span id, sampling flag' do - parent_context = OpenTelemetry::Context.empty - carrier = { - trace_id_key => '80f198ee56343ba864fe8b2a57d3eff7', - span_id_key => 'e457b5a2e4d86bd1', - sampled_key => '1' - } - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - _(extracted_context).must_be(:remote?) - end - - it 'extracts context with trace id, span id' do - parent_context = OpenTelemetry::Context.empty - carrier = { - trace_id_key => '80f198ee56343ba864fe8b2a57d3eff7', - span_id_key => 'e457b5a2e4d86bd1' - } - - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) - _(extracted_context).must_be(:remote?) - end - - it 'pads 8 byte id' do - parent_context = OpenTelemetry::Context.empty - carrier = { - trace_id_key => '64fe8b2a57d3eff7', - span_id_key => 'e457b5a2e4d86bd1' - } - - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('000000000000000064fe8b2a57d3eff7') - end - - it 'converts debug flag to sampled' do - parent_context = OpenTelemetry::Context.empty - carrier = { - trace_id_key => '80f198ee56343ba864fe8b2a57d3eff7', - span_id_key => 'e457b5a2e4d86bd1', - sampled_key => '1' - } - - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - end - - it 'handles malformed trace id' do - parent_context = OpenTelemetry::Context.empty - carrier = { - trace_id_key => '80f198ee56343ba864feb2a', - span_id_key => 'e457b5a2e4d86bd1' - } - - context = extractor.extract(carrier, parent_context) - - _(context).must_equal(parent_context) - end - - it 'handles malformed span id' do - parent_context = OpenTelemetry::Context.empty - carrier = { - trace_id_key => '80f198ee56343ba864fe8b2a57d3eff7', - span_id_key => 'e457b5a2e4d1' - } - - context = extractor.extract(carrier, parent_context) - - _(context).must_equal(parent_context) - end - end -end diff --git a/propagator/b3/test/multi/text_map_injector_test.rb b/propagator/b3/test/multi/text_map_injector_test.rb deleted file mode 100644 index e46d6d5fd1..0000000000 --- a/propagator/b3/test/multi/text_map_injector_test.rb +++ /dev/null @@ -1,115 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Propagator::B3::Multi::TextMapInjector do - Span = OpenTelemetry::Trace::Span - SpanContext = OpenTelemetry::Trace::SpanContext - TraceFlags = OpenTelemetry::Trace::TraceFlags - - let(:injector) { OpenTelemetry::Propagator::B3::Multi::TextMapInjector.new } - let(:trace_id_key) { 'X-B3-TraceId' } - let(:span_id_key) { 'X-B3-SpanId' } - let(:parent_span_id_key) { 'X-B3-ParentSpanId' } - let(:sampled_key) { 'X-B3-Sampled' } - let(:flags_key) { 'X-B3-Flags' } - let(:all_keys) { [trace_id_key, span_id_key, parent_span_id_key, sampled_key, flags_key] } - - describe '#inject' do - it 'injects context with sampled trace flags' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - trace_flags: TraceFlags::SAMPLED - ) - - carrier = {} - injector.inject(carrier, context) - - _(carrier[trace_id_key]).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(carrier[span_id_key]).must_equal('e457b5a2e4d86bd1') - _(carrier[sampled_key]).must_equal('1') - _(carrier.key?(flags_key)).must_equal(false) - _(carrier.key?(parent_span_id_key)).must_equal(false) - end - - it 'injects context with default trace flags' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - trace_flags: TraceFlags::DEFAULT - ) - - carrier = {} - injector.inject(carrier, context) - - _(carrier[trace_id_key]).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(carrier[span_id_key]).must_equal('e457b5a2e4d86bd1') - _(carrier[sampled_key]).must_equal('0') - _(carrier.key?(flags_key)).must_equal(false) - _(carrier.key?(parent_span_id_key)).must_equal(false) - end - - it 'injects debug flag when present' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - b3_debug: true - ) - - carrier = {} - injector.inject(carrier, context) - - _(carrier[trace_id_key]).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(carrier[span_id_key]).must_equal('e457b5a2e4d86bd1') - _(carrier[flags_key]).must_equal('1') - _(carrier.key?(sampled_key)).must_equal(false) - _(carrier.key?(parent_span_id_key)).must_equal(false) - end - - it 'no-ops if trace id invalid' do - context = create_context( - trace_id: '0' * 32, - span_id: 'e457b5a2e4d86bd1' - ) - - carrier = {} - injector.inject(carrier, context) - - all_keys.each { |k| _(carrier.key?(k)).must_equal(false) } - end - - it 'no-ops if trace id invalid' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: '0' * 16 - ) - - carrier = {} - injector.inject(carrier, context) - - all_keys.each { |k| _(carrier.key?(k)).must_equal(false) } - end - end - - def create_context(trace_id:, - span_id:, - trace_flags: TraceFlags::DEFAULT, - b3_debug: false) - context = OpenTelemetry::Trace.context_with_span( - Span.new( - span_context: SpanContext.new( - trace_id: Array(trace_id).pack('H*'), - span_id: Array(span_id).pack('H*'), - trace_flags: trace_flags - ) - ) - ) - context = OpenTelemetry::Propagator::B3.context_with_debug(context) if b3_debug - context - end -end diff --git a/propagator/b3/test/multi/text_map_propagator_test.rb b/propagator/b3/test/multi/text_map_propagator_test.rb new file mode 100644 index 0000000000..fbb9457715 --- /dev/null +++ b/propagator/b3/test/multi/text_map_propagator_test.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Propagator::B3::Multi::TextMapPropagator do + let(:propagator) { OpenTelemetry::Propagator::B3::Multi::TextMapPropagator.new } + let(:trace_id_key) { 'X-B3-TraceId' } + let(:span_id_key) { 'X-B3-SpanId' } + let(:parent_span_id_key) { 'X-B3-ParentSpanId' } + let(:sampled_key) { 'X-B3-Sampled' } + let(:flags_key) { 'X-B3-Flags' } + let(:all_keys) { [trace_id_key, span_id_key, parent_span_id_key, sampled_key, flags_key] } + + describe('#extract') do + it 'extracts context with trace id, span id, sampling flag, parent span id' do + parent_context = OpenTelemetry::Context.empty + carrier = { + trace_id_key => '80f198ee56343ba864fe8b2a57d3eff7', + span_id_key => 'e457b5a2e4d86bd1', + sampled_key => '1', + parent_span_id_key => '05e3ac9a4f6e3b90' + } + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + _(extracted_context).must_be(:remote?) + end + + it 'extracts context with trace id, span id, sampling flag' do + parent_context = OpenTelemetry::Context.empty + carrier = { + trace_id_key => '80f198ee56343ba864fe8b2a57d3eff7', + span_id_key => 'e457b5a2e4d86bd1', + sampled_key => '1' + } + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + _(extracted_context).must_be(:remote?) + end + + it 'extracts context with trace id, span id' do + parent_context = OpenTelemetry::Context.empty + carrier = { + trace_id_key => '80f198ee56343ba864fe8b2a57d3eff7', + span_id_key => 'e457b5a2e4d86bd1' + } + + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) + _(extracted_context).must_be(:remote?) + end + + it 'pads 8 byte id' do + parent_context = OpenTelemetry::Context.empty + carrier = { + trace_id_key => '64fe8b2a57d3eff7', + span_id_key => 'e457b5a2e4d86bd1' + } + + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('000000000000000064fe8b2a57d3eff7') + end + + it 'converts debug flag to sampled' do + parent_context = OpenTelemetry::Context.empty + carrier = { + trace_id_key => '80f198ee56343ba864fe8b2a57d3eff7', + span_id_key => 'e457b5a2e4d86bd1', + sampled_key => '1' + } + + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + end + + it 'handles malformed trace id' do + parent_context = OpenTelemetry::Context.empty + carrier = { + trace_id_key => '80f198ee56343ba864feb2a', + span_id_key => 'e457b5a2e4d86bd1' + } + + context = propagator.extract(carrier, context: parent_context) + + _(context).must_equal(parent_context) + end + + it 'handles malformed span id' do + parent_context = OpenTelemetry::Context.empty + carrier = { + trace_id_key => '80f198ee56343ba864fe8b2a57d3eff7', + span_id_key => 'e457b5a2e4d1' + } + + context = propagator.extract(carrier, context: parent_context) + + _(context).must_equal(parent_context) + end + end + + describe '#inject' do + it 'injects context with sampled trace flags' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + trace_flags: OpenTelemetry::Trace::TraceFlags::SAMPLED + ) + + carrier = {} + propagator.inject(carrier, context: context) + + _(carrier[trace_id_key]).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(carrier[span_id_key]).must_equal('e457b5a2e4d86bd1') + _(carrier[sampled_key]).must_equal('1') + _(carrier.key?(flags_key)).must_equal(false) + _(carrier.key?(parent_span_id_key)).must_equal(false) + end + + it 'injects context with default trace flags' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + trace_flags: OpenTelemetry::Trace::TraceFlags::DEFAULT + ) + + carrier = {} + propagator.inject(carrier, context: context) + + _(carrier[trace_id_key]).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(carrier[span_id_key]).must_equal('e457b5a2e4d86bd1') + _(carrier[sampled_key]).must_equal('0') + _(carrier.key?(flags_key)).must_equal(false) + _(carrier.key?(parent_span_id_key)).must_equal(false) + end + + it 'injects debug flag when present' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + b3_debug: true + ) + + carrier = {} + propagator.inject(carrier, context: context) + + _(carrier[trace_id_key]).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(carrier[span_id_key]).must_equal('e457b5a2e4d86bd1') + _(carrier[flags_key]).must_equal('1') + _(carrier.key?(sampled_key)).must_equal(false) + _(carrier.key?(parent_span_id_key)).must_equal(false) + end + + it 'no-ops if trace id invalid' do + context = create_context( + trace_id: '0' * 32, + span_id: 'e457b5a2e4d86bd1' + ) + + carrier = {} + propagator.inject(carrier, context: context) + + all_keys.each { |k| _(carrier.key?(k)).must_equal(false) } + end + + it 'no-ops if trace id invalid' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: '0' * 16 + ) + + carrier = {} + propagator.inject(carrier, context: context) + + all_keys.each { |k| _(carrier.key?(k)).must_equal(false) } + end + end + + def create_context(trace_id:, + span_id:, + trace_flags: OpenTelemetry::Trace::TraceFlags::DEFAULT, + b3_debug: false) + context = OpenTelemetry::Trace.context_with_span( + OpenTelemetry::Trace::Span.new( + span_context: OpenTelemetry::Trace::SpanContext.new( + trace_id: Array(trace_id).pack('H*'), + span_id: Array(span_id).pack('H*'), + trace_flags: trace_flags + ) + ) + ) + context = OpenTelemetry::Propagator::B3.context_with_debug(context) if b3_debug + context + end +end diff --git a/propagator/b3/test/multi_test.rb b/propagator/b3/test/multi_test.rb index d98271f091..fdaf91d7e0 100644 --- a/propagator/b3/test/multi_test.rb +++ b/propagator/b3/test/multi_test.rb @@ -7,20 +7,11 @@ require 'test_helper' describe OpenTelemetry::Propagator::B3::Multi do - describe '#text_map_extractor' do - it 'returns an instance of TextMapExtractor' do - extractor = OpenTelemetry::Propagator::B3::Multi.text_map_extractor - _(extractor).must_be_instance_of( - OpenTelemetry::Propagator::B3::Multi::TextMapExtractor - ) - end - end - - describe '#text_map_injector' do - it 'returns an instance of TextMapInjector' do - injector = OpenTelemetry::Propagator::B3::Multi.text_map_injector - _(injector).must_be_instance_of( - OpenTelemetry::Propagator::B3::Multi::TextMapInjector + describe '#text_map_propagator' do + it 'returns an instance of TextMapPropagator' do + propagator = OpenTelemetry::Propagator::B3::Multi.text_map_propagator + _(propagator).must_be_instance_of( + OpenTelemetry::Propagator::B3::Multi::TextMapPropagator ) end end diff --git a/propagator/b3/test/single/text_map_extractor_test.rb b/propagator/b3/test/single/text_map_extractor_test.rb deleted file mode 100644 index 9a04e0f941..0000000000 --- a/propagator/b3/test/single/text_map_extractor_test.rb +++ /dev/null @@ -1,90 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Propagator::B3::Single::TextMapExtractor do - let(:extractor) { OpenTelemetry::Propagator::B3::Single::TextMapExtractor.new } - - describe('#extract') do - it 'extracts context with trace id, span id, sampling flag, parent span id' do - parent_context = OpenTelemetry::Context.empty - carrier = { 'b3' => '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1-05e3ac9a4f6e3b90' } - - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - _(extracted_context).must_be(:remote?) - end - - it 'extracts context with trace id, span id, sampling flag' do - parent_context = OpenTelemetry::Context.empty - carrier = { 'b3' => '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1' } - - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - _(extracted_context).must_be(:remote?) - end - - it 'extracts context with trace id, span id' do - parent_context = OpenTelemetry::Context.empty - carrier = { 'b3' => '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1' } - - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) - _(extracted_context).must_be(:remote?) - end - - it 'pads 8 byte id' do - parent_context = OpenTelemetry::Context.empty - carrier = { 'b3' => '64fe8b2a57d3eff7-e457b5a2e4d86bd1' } - - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('000000000000000064fe8b2a57d3eff7') - end - - it 'converts debug flag to sampled' do - parent_context = OpenTelemetry::Context.empty - carrier = { 'b3' => '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-d' } - - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - end - - it 'handles malformed trace id' do - parent_context = OpenTelemetry::Context.empty - carrier = { 'b3' => '80f198ee56343ba864feb2a-e457b5a2e4d86bd1' } - - context = extractor.extract(carrier, parent_context) - - _(context).must_equal(parent_context) - end - - it 'handles malformed span id' do - parent_context = OpenTelemetry::Context.empty - carrier = { 'b3' => '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d1' } - - context = extractor.extract(carrier, parent_context) - - _(context).must_equal(parent_context) - end - end -end diff --git a/propagator/b3/test/single/text_map_injector_test.rb b/propagator/b3/test/single/text_map_injector_test.rb deleted file mode 100644 index b111114fbe..0000000000 --- a/propagator/b3/test/single/text_map_injector_test.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Propagator::B3::Single::TextMapInjector do - Span = OpenTelemetry::Trace::Span - SpanContext = OpenTelemetry::Trace::SpanContext - TraceFlags = OpenTelemetry::Trace::TraceFlags - - let(:injector) { OpenTelemetry::Propagator::B3::Single::TextMapInjector.new } - - describe '#inject' do - it 'injects context with sampled trace flags' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - trace_flags: TraceFlags::SAMPLED - ) - - carrier = {} - injector.inject(carrier, context) - - expected_b3 = '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1' - _(carrier['b3']).must_equal(expected_b3) - end - - it 'injects context with default trace flags' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - trace_flags: TraceFlags::DEFAULT - ) - - carrier = {} - injector.inject(carrier, context) - - expected_b3 = '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-0' - _(carrier['b3']).must_equal(expected_b3) - end - - it 'injects debug flag when present' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - b3_debug: true - ) - - carrier = {} - injector.inject(carrier, context) - - expected_b3 = '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-d' - _(carrier['b3']).must_equal(expected_b3) - end - - it 'no-ops if trace id invalid' do - context = create_context( - trace_id: '0' * 32, - span_id: 'e457b5a2e4d86bd1' - ) - - carrier = {} - injector.inject(carrier, context) - - _(carrier.key?('b3')).must_equal(false) - end - - it 'no-ops if trace id invalid' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: '0' * 16 - ) - - carrier = {} - injector.inject(carrier, context) - - _(carrier.key?('b3')).must_equal(false) - end - end - - def create_context(trace_id:, - span_id:, - trace_flags: TraceFlags::DEFAULT, - b3_debug: false) - context = OpenTelemetry::Trace.context_with_span( - Span.new( - span_context: SpanContext.new( - trace_id: Array(trace_id).pack('H*'), - span_id: Array(span_id).pack('H*'), - trace_flags: trace_flags - ) - ) - ) - context = OpenTelemetry::Propagator::B3.context_with_debug(context) if b3_debug - context - end -end diff --git a/propagator/b3/test/single/text_map_propagator_test.rb b/propagator/b3/test/single/text_map_propagator_test.rb new file mode 100644 index 0000000000..d732dcff63 --- /dev/null +++ b/propagator/b3/test/single/text_map_propagator_test.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Propagator::B3::Single::TextMapPropagator do + let(:propagator) { OpenTelemetry::Propagator::B3::Single::TextMapPropagator.new } + + describe('#extract') do + it 'extracts context with trace id, span id, sampling flag, parent span id' do + parent_context = OpenTelemetry::Context.empty + carrier = { 'b3' => '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1-05e3ac9a4f6e3b90' } + + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + _(extracted_context).must_be(:remote?) + end + + it 'extracts context with trace id, span id, sampling flag' do + parent_context = OpenTelemetry::Context.empty + carrier = { 'b3' => '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1' } + + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + _(extracted_context).must_be(:remote?) + end + + it 'extracts context with trace id, span id' do + parent_context = OpenTelemetry::Context.empty + carrier = { 'b3' => '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1' } + + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) + _(extracted_context).must_be(:remote?) + end + + it 'pads 8 byte id' do + parent_context = OpenTelemetry::Context.empty + carrier = { 'b3' => '64fe8b2a57d3eff7-e457b5a2e4d86bd1' } + + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('000000000000000064fe8b2a57d3eff7') + end + + it 'converts debug flag to sampled' do + parent_context = OpenTelemetry::Context.empty + carrier = { 'b3' => '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-d' } + + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + end + + it 'handles malformed trace id' do + parent_context = OpenTelemetry::Context.empty + carrier = { 'b3' => '80f198ee56343ba864feb2a-e457b5a2e4d86bd1' } + + context = propagator.extract(carrier, context: parent_context) + + _(context).must_equal(parent_context) + end + + it 'handles malformed span id' do + parent_context = OpenTelemetry::Context.empty + carrier = { 'b3' => '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d1' } + + context = propagator.extract(carrier, context: parent_context) + + _(context).must_equal(parent_context) + end + end + + describe '#inject' do + it 'injects context with sampled trace flags' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + trace_flags: OpenTelemetry::Trace::TraceFlags::SAMPLED + ) + + carrier = {} + propagator.inject(carrier, context: context) + + expected_b3 = '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1' + _(carrier['b3']).must_equal(expected_b3) + end + + it 'injects context with default trace flags' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + trace_flags: OpenTelemetry::Trace::TraceFlags::DEFAULT + ) + + carrier = {} + propagator.inject(carrier, context: context) + + expected_b3 = '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-0' + _(carrier['b3']).must_equal(expected_b3) + end + + it 'injects debug flag when present' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + b3_debug: true + ) + + carrier = {} + propagator.inject(carrier, context: context) + + expected_b3 = '80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-d' + _(carrier['b3']).must_equal(expected_b3) + end + + it 'no-ops if trace id invalid' do + context = create_context( + trace_id: '0' * 32, + span_id: 'e457b5a2e4d86bd1' + ) + + carrier = {} + propagator.inject(carrier, context: context) + + _(carrier.key?('b3')).must_equal(false) + end + + it 'no-ops if trace id invalid' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: '0' * 16 + ) + + carrier = {} + propagator.inject(carrier, context: context) + + _(carrier.key?('b3')).must_equal(false) + end + end + + def create_context(trace_id:, + span_id:, + trace_flags: OpenTelemetry::Trace::TraceFlags::DEFAULT, + b3_debug: false) + context = OpenTelemetry::Trace.context_with_span( + OpenTelemetry::Trace::Span.new( + span_context: OpenTelemetry::Trace::SpanContext.new( + trace_id: Array(trace_id).pack('H*'), + span_id: Array(span_id).pack('H*'), + trace_flags: trace_flags + ) + ) + ) + context = OpenTelemetry::Propagator::B3.context_with_debug(context) if b3_debug + context + end +end diff --git a/propagator/b3/test/single_test.rb b/propagator/b3/test/single_test.rb index 7441109b19..46078b156d 100644 --- a/propagator/b3/test/single_test.rb +++ b/propagator/b3/test/single_test.rb @@ -7,20 +7,11 @@ require 'test_helper' describe OpenTelemetry::Propagator::B3::Single do - describe '#text_map_extractor' do - it 'returns an instance of TextMapExtractor' do - extractor = OpenTelemetry::Propagator::B3::Single.text_map_extractor - _(extractor).must_be_instance_of( - OpenTelemetry::Propagator::B3::Single::TextMapExtractor - ) - end - end - - describe '#text_map_injector, #rack_injector' do - it 'returns an instance of TextMapInjector' do - injector = OpenTelemetry::Propagator::B3::Single.text_map_injector - _(injector).must_be_instance_of( - OpenTelemetry::Propagator::B3::Single::TextMapInjector + describe '#text_map_propagator' do + it 'returns an instance of TextMapPropagator' do + propagator = OpenTelemetry::Propagator::B3::Single.text_map_propagator + _(propagator).must_be_instance_of( + OpenTelemetry::Propagator::B3::Single::TextMapPropagator ) end end diff --git a/propagator/jaeger/README.md b/propagator/jaeger/README.md index eb1923caf1..66191bd177 100644 --- a/propagator/jaeger/README.md +++ b/propagator/jaeger/README.md @@ -1,9 +1,9 @@ # opentelemetry-propagator-jaeger -The `opentelemetry-propagator-jaeger` gem contains injectors and extractors for the -[Jaeger native propagation format][jaeger-spec] They can be used in -conjunction with each other as well as with injectors and extractors for -other formats to support a variety of context propagation scenarios. +The `opentelemetry-propagator-jaeger` gem contains a propagator for the +[Jaeger native propagation format][jaeger-spec] This can be used on its +own as well as with propagators for other formats to support a variety +of context propagation scenarios. ## What is OpenTelemetry? diff --git a/propagator/jaeger/lib/opentelemetry/propagator/jaeger.rb b/propagator/jaeger/lib/opentelemetry/propagator/jaeger.rb index d2fba8c6ed..c3bd020eb0 100644 --- a/propagator/jaeger/lib/opentelemetry/propagator/jaeger.rb +++ b/propagator/jaeger/lib/opentelemetry/propagator/jaeger.rb @@ -4,8 +4,7 @@ # # SPDX-License-Identifier: Apache-2.0 -require_relative './jaeger/text_map_extractor' -require_relative './jaeger/text_map_injector' +require_relative './jaeger/text_map_propagator' # OpenTelemetry is an open source observability framework, providing a # general-purpose API, SDK, and related tools required for the instrumentation @@ -23,28 +22,14 @@ module Jaeger DEBUG_CONTEXT_KEY = Context.create_key('jaeger-debug-key') private_constant :DEBUG_CONTEXT_KEY - TEXT_MAP_EXTRACTOR = TextMapExtractor.new - TEXT_MAP_INJECTOR = TextMapInjector.new + TEXT_MAP_PROPAGATOR = TextMapPropagator.new - private_constant :TEXT_MAP_INJECTOR, :TEXT_MAP_EXTRACTOR + private_constant :TEXT_MAP_PROPAGATOR - IDENTITY_KEY = 'uber-trace-id' - DEFAULT_FLAG_BIT = 0x0 - SAMPLED_FLAG_BIT = 0x01 - DEBUG_FLAG_BIT = 0x02 - - private_constant :IDENTITY_KEY, :DEFAULT_FLAG_BIT, :SAMPLED_FLAG_BIT, :DEBUG_FLAG_BIT - - # Returns an extractor that extracts context in the Jaeger single header - # format - def text_map_injector - TEXT_MAP_INJECTOR - end - - # Returns an injector that injects context in the Jaeger single header - # format - def text_map_extractor - TEXT_MAP_EXTRACTOR + # Returns a text map propagator that propagates context in the Jaeger + # format. + def text_map_propagator + TEXT_MAP_PROPAGATOR end # @api private diff --git a/propagator/jaeger/lib/opentelemetry/propagator/jaeger/text_map_injector.rb b/propagator/jaeger/lib/opentelemetry/propagator/jaeger/text_map_injector.rb deleted file mode 100644 index bcf745ac0e..0000000000 --- a/propagator/jaeger/lib/opentelemetry/propagator/jaeger/text_map_injector.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'cgi' - -module OpenTelemetry - # Namespace for OpenTelemetry propagator extension libraries - module Propagator - # Namespace for OpenTelemetry Jaeger propagation - module Jaeger - # Injects context into carriers - class TextMapInjector - # Returns a new TextMapInjector that extracts Jaeger context using the - # specified header keys - # - # @param [optional Setter] default_setter The default setter used to - # write context into a carrier during inject. Defaults to a - # {OpenTelemetry::Context:Propagation::TextMapSetter} instance. - # @return [TextMapInjector] - def initialize(default_setter = Context::Propagation.text_map_setter) - @default_setter = default_setter - end - - # Set the span context on the supplied carrier. - # - # @param [Context] context The active Context. - # @param [optional Setter] setter If the optional setter is provided, it - # will be used to write context into the carrier, otherwise the default - # setter will be used. - # @return [Object] the carrier with context injected - def inject(carrier, context, setter = nil) - span_context = Trace.current_span(context).context - return unless span_context.valid? - - flags = to_flags(context, span_context) - trace_span_identity_value = [ - span_context.hex_trace_id, span_context.hex_span_id, '0', flags - ].join(':') - setter ||= @default_setter - setter.set(carrier, IDENTITY_KEY, trace_span_identity_value) - OpenTelemetry.baggage.values(context: context).each do |key, value| - baggage_key = 'uberctx-' + key - encoded_value = CGI.escape(value) - setter.set(carrier, baggage_key, encoded_value) - end - carrier - end - - private - - def to_flags(context, span_context) - if span_context.trace_flags == Trace::TraceFlags::SAMPLED - if Jaeger.debug?(context) - SAMPLED_FLAG_BIT | DEBUG_FLAG_BIT - else - SAMPLED_FLAG_BIT - end - else - DEFAULT_FLAG_BIT - end - end - end - end - end -end diff --git a/propagator/jaeger/lib/opentelemetry/propagator/jaeger/text_map_extractor.rb b/propagator/jaeger/lib/opentelemetry/propagator/jaeger/text_map_propagator.rb similarity index 50% rename from propagator/jaeger/lib/opentelemetry/propagator/jaeger/text_map_extractor.rb rename to propagator/jaeger/lib/opentelemetry/propagator/jaeger/text_map_propagator.rb index e582611e36..2910fd71ef 100644 --- a/propagator/jaeger/lib/opentelemetry/propagator/jaeger/text_map_extractor.rb +++ b/propagator/jaeger/lib/opentelemetry/propagator/jaeger/text_map_propagator.rb @@ -17,36 +17,33 @@ module OpenTelemetry module Propagator # Namespace for OpenTelemetry Jaeger propagation module Jaeger - # Extracts context from carriers - class TextMapExtractor + # Propagates trace context using the Jaeger format + class TextMapPropagator + IDENTITY_KEY = 'uber-trace-id' + DEFAULT_FLAG_BIT = 0x0 + SAMPLED_FLAG_BIT = 0x01 + DEBUG_FLAG_BIT = 0x02 + FIELDS = [IDENTITY_KEY].freeze TRACE_SPAN_IDENTITY_REGEX = /\A(?(?:[0-9a-f]){1,32}):(?([0-9a-f]){1,16}):[0-9a-f]{1,16}:(?[0-9a-f]{1,2})\z/.freeze ZERO_ID_REGEX = /^0+$/.freeze - # Returns a new TextMapExtractor that extracts Jaeger context using the - # specified header keys - # - # @param [optional Getter] default_getter The default getter used to read - # headers from a carrier during extract. Defaults to a - # {OpenTelemetry::Context:Propagation::TextMapGetter} instance. - # @return [TextMapExtractor] - def initialize(default_getter = Context::Propagation.text_map_getter) - @default_getter = default_getter - end + private_constant \ + :IDENTITY_KEY, :DEFAULT_FLAG_BIT, :SAMPLED_FLAG_BIT, :DEBUG_FLAG_BIT, + :FIELDS, :TRACE_SPAN_IDENTITY_REGEX, :ZERO_ID_REGEX - # Extract Jaeger context from the supplied carrier and set the active - # span in the given context. The original context will be return if - # Jaeger cannot be extracted from the carrier. + # Extract trace context from the supplied carrier. + # If extraction fails, the original context will be returned # - # @param [Carrier] carrier The carrier to get the header from. - # @param [Context] context The context to be updated with extracted - # context + # @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 - # getter will be used. - # @return [Context] Updated context with active span derived from the - # header, or the original context if parsing fails. - def extract(carrier, context, getter = nil) - getter ||= @default_getter + # 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) header = getter.get(carrier, IDENTITY_KEY) return context unless (match = header.match(TRACE_SPAN_IDENTITY_REGEX)) return context if match['trace_id'] =~ ZERO_ID_REGEX @@ -55,10 +52,42 @@ def extract(carrier, context, getter = nil) sampling_flags = match['sampling_flags'].to_i span = build_span(match, sampling_flags) context = Jaeger.context_with_debug(context) if sampling_flags & DEBUG_FLAG_BIT != 0 - context = set_baggage(carrier, context, getter) + context = context_with_extracted_baggage(carrier, context, getter) Trace.context_with_span(span, parent_context: context) end + # Inject trace context into the supplied carrier. + # + # @param [Carrier] carrier The mutable carrier to inject trace context into + # @param [Context] context The context to read trace context from + # @param [optional Setter] setter If the optional setter is provided, it + # will be used to write context into the carrier, otherwise the default + # text map setter will be used. + def inject(carrier, context: Context.current, setter: Context::Propagation.text_map_setter) + span_context = Trace.current_span(context).context + return unless span_context.valid? + + flags = to_jaeger_flags(context, span_context) + trace_span_identity_value = [ + span_context.hex_trace_id, span_context.hex_span_id, '0', flags + ].join(':') + setter.set(carrier, IDENTITY_KEY, trace_span_identity_value) + OpenTelemetry.baggage.values(context: context).each do |key, value| + baggage_key = 'uberctx-' + key + encoded_value = CGI.escape(value) + setter.set(carrier, baggage_key, encoded_value) + end + carrier + end + + # Returns the predefined propagation fields. If your carrier is reused, you + # should delete the fields returned by this method before calling +inject+. + # + # @return [Array] a list of fields that will be used by this propagator. + def fields + FIELDS + end + private def build_span(match, sampling_flags) @@ -72,7 +101,7 @@ def build_span(match, sampling_flags) Trace::Span.new(span_context: span_context) end - def set_baggage(carrier, context, getter) + def context_with_extracted_baggage(carrier, context, getter) baggage_key_prefix = 'uberctx-' OpenTelemetry.baggage.build(context: context) do |b| getter.keys(carrier).each do |carrier_key| @@ -86,6 +115,18 @@ def set_baggage(carrier, context, getter) end end + def to_jaeger_flags(context, span_context) + if span_context.trace_flags == Trace::TraceFlags::SAMPLED + if Jaeger.debug?(context) + SAMPLED_FLAG_BIT | DEBUG_FLAG_BIT + else + SAMPLED_FLAG_BIT + end + else + DEFAULT_FLAG_BIT + end + end + def to_trace_flags(sampling_flags) if (sampling_flags & SAMPLED_FLAG_BIT) != 0 Trace::TraceFlags::SAMPLED diff --git a/propagator/jaeger/test/jaeger_test.rb b/propagator/jaeger/test/jaeger_test.rb index 4e9e7ea80d..509ef5d42e 100644 --- a/propagator/jaeger/test/jaeger_test.rb +++ b/propagator/jaeger/test/jaeger_test.rb @@ -7,20 +7,11 @@ require 'test_helper' describe OpenTelemetry::Propagator::Jaeger do - describe '#text_map_extractor' do - it 'returns an instance of TextMapExtractor' do - extractor = OpenTelemetry::Propagator::Jaeger.text_map_extractor - _(extractor).must_be_instance_of( - OpenTelemetry::Propagator::Jaeger::TextMapExtractor - ) - end - end - - describe '#text_map_injector' do - it 'returns an instance of TextMapInjector' do - injector = OpenTelemetry::Propagator::Jaeger.text_map_injector - _(injector).must_be_instance_of( - OpenTelemetry::Propagator::Jaeger::TextMapInjector + describe '#text_map_propagator' do + it 'returns an instance of TextMapPropagator' do + propagator = OpenTelemetry::Propagator::Jaeger.text_map_propagator + _(propagator).must_be_instance_of( + OpenTelemetry::Propagator::Jaeger::TextMapPropagator ) end end diff --git a/propagator/jaeger/test/text_map_extractor_test.rb b/propagator/jaeger/test/text_map_extractor_test.rb deleted file mode 100644 index e348421855..0000000000 --- a/propagator/jaeger/test/text_map_extractor_test.rb +++ /dev/null @@ -1,159 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' -require 'opentelemetry-sdk' - -describe OpenTelemetry::Propagator::Jaeger::TextMapExtractor do - let(:extractor) { OpenTelemetry::Propagator::Jaeger::TextMapExtractor.new } - - before do - OpenTelemetry::SDK::Configurator.new.configure - end - - def extract_context(header) - parent_context = OpenTelemetry::Context.empty - carrier = { 'uber-trace-id' => header } - extractor.extract(carrier, parent_context) - end - - def extract_span_context(header) - context = extract_context(header) - OpenTelemetry::Trace.current_span(context).context - end - - def extracted_context_must_equal_parent_context(header) - parent_context = OpenTelemetry::Context.empty - carrier = { - 'uber-trace-id' => header - } - context = extractor.extract(carrier, parent_context) - _(context).must_equal(parent_context) - end - - describe('#extract') do - it 'extracts context with trace id, span id, sampling flag' do - span_context = extract_span_context( - '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:0:1' - ) - _(span_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(span_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(span_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - _(span_context).must_be(:remote?) - end - - it 'extracts context with trace id, span id, sampling flag, parent span id' do - span_context = extract_span_context( - '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:ef62c5754687a53a:1' - ) - _(span_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(span_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(span_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - _(span_context).must_be(:remote?) - end - - it 'extracts context with trace id, span id' do - span_context = extract_span_context( - '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:0:0' - ) - _(span_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(span_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(span_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) - _(span_context).must_be(:remote?) - end - - it 'extracts context with a shorter trace id and span id' do - span_context = extract_span_context( - '8ee56343ba864fe8b2a57d3eff7:5a2e4d86bd1:0:1' - ) - _(span_context.hex_trace_id).must_equal('000008ee56343ba864fe8b2a57d3eff7') - _(span_context.hex_span_id).must_equal('000005a2e4d86bd1') - end - - it 'extracts context with 64-bit trace ids' do - span_context = extract_span_context( - '80f198ee56343ba8:e457b5a2e4d86bd1:0:1' - ) - _(span_context.hex_trace_id).must_equal('000000000000000080f198ee56343ba8') - end - - it 'extracts context with a shorter trace id that can be included in a 64-bit hex string' do - span_context = extract_span_context( - '98ee56343ba8:e457b5a2e4d86bd1:0:1' - ) - _(span_context.hex_trace_id).must_equal('0000000000000000000098ee56343ba8') - end - - it 'converts debug flag to sampled' do - context = extract_context( - '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:0:3' - ) - span_context = OpenTelemetry::Trace.current_span(context).context - - _(span_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - _(OpenTelemetry::Propagator::Jaeger.debug?(context)).must_equal(true) - end - - it 'extracts baggage' do - parent_context = OpenTelemetry::Context.empty - carrier = { - 'uber-trace-id' => '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:0:1', - 'uberctx-key1' => 'value1', - 'uberctx-key2' => 'value2' - } - - context = extractor.extract(carrier, parent_context) - _(OpenTelemetry.baggage.value('key1', context: context)).must_equal('value1') - _(OpenTelemetry.baggage.value('key2', context: context)).must_equal('value2') - end - - it 'extracts URL-encoded baggage entries' do - parent_context = OpenTelemetry::Context.empty - carrier = { - 'uber-trace-id' => '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:0:1', - 'uberctx-key1' => 'value%201%20%2F%20blah' - } - - context = extractor.extract(carrier, parent_context) - _(OpenTelemetry.baggage.value('key1', context: context)).must_equal('value 1 / blah') - end - - it 'extracts baggage with different keys' do - rack_extractor = OpenTelemetry::Propagator::Jaeger::TextMapExtractor.new( - OpenTelemetry::Context::Propagation.rack_env_getter - ) - parent_context = OpenTelemetry::Context.empty - carrier = { - 'HTTP_UBER_TRACE_ID' => '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:0:1', - 'HTTP_UBERCTX_KEY_1' => 'value1', - 'HTTP_UBERCTX_KEY_2' => 'value2' - } - - context = rack_extractor.extract(carrier, parent_context) - span_context = OpenTelemetry::Trace.current_span(context).context - _(span_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(OpenTelemetry.baggage.value('key-1', context: context)).must_equal('value1') - _(OpenTelemetry.baggage.value('key-2', context: context)).must_equal('value2') - end - - it 'handles trace ids and span ids that are too long' do - extracted_context_must_equal_parent_context( - '80f198ee56343ba864fe8b2a57d3eff7eff7:e457b5a2e4d86bd1:0:1' - ) - extracted_context_must_equal_parent_context( - '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd16bd1:0:1' - ) - end - - it 'handles invalid 0 trace id and 0 span id' do - extracted_context_must_equal_parent_context('0:e457b5a2e4d86bd1:0:1') - extracted_context_must_equal_parent_context( - '80f198ee56343ba864fe8b2a57d3eff7:0:0:1' - ) - extracted_context_must_equal_parent_context('00:00:0:1') - end - end -end diff --git a/propagator/jaeger/test/text_map_injector_test.rb b/propagator/jaeger/test/text_map_injector_test.rb deleted file mode 100644 index 72486e0903..0000000000 --- a/propagator/jaeger/test/text_map_injector_test.rb +++ /dev/null @@ -1,179 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' -require 'opentelemetry-sdk' - -describe OpenTelemetry::Propagator::Jaeger::TextMapInjector do - let(:injector) { OpenTelemetry::Propagator::Jaeger::TextMapInjector.new } - let(:identity_key) { 'uber-trace-id' } - - before do - OpenTelemetry::SDK::Configurator.new.configure - end - - describe '#inject' do - it 'injects context with sampled trace flags' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - trace_flags: OpenTelemetry::Trace::TraceFlags::SAMPLED - ) - carrier = {} - injector.inject(carrier, context) - trace_span_identity_value = carrier[identity_key] - trace_id, span_id, parent_span_id, flags = trace_span_identity_value.split(/:/) - _(trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(span_id).must_equal('e457b5a2e4d86bd1') - _(parent_span_id).must_equal('0') - _(flags).must_equal('1') - end - - it 'injects context with default trace flags' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - trace_flags: OpenTelemetry::Trace::TraceFlags::DEFAULT - ) - carrier = {} - injector.inject(carrier, context) - trace_span_identity_value = carrier[identity_key] - trace_id, span_id, parent_span_id, flags = trace_span_identity_value.split(/:/) - _(trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(span_id).must_equal('e457b5a2e4d86bd1') - _(parent_span_id).must_equal('0') - _(flags).must_equal('0') - end - - it 'injects debug flag when present' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - trace_flags: OpenTelemetry::Trace::TraceFlags::SAMPLED, - jaeger_debug: true - ) - carrier = {} - injector.inject(carrier, context) - trace_span_identity_value = carrier[identity_key] - trace_id, span_id, parent_span_id, flags = trace_span_identity_value.split(/:/) - _(trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(span_id).must_equal('e457b5a2e4d86bd1') - _(parent_span_id).must_equal('0') - _(flags).must_equal('3') - end - - it 'injects baggage' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1' - ) - context = OpenTelemetry.baggage.build(context: context) do |baggage| - baggage.set_value('key1', 'value1') - baggage.set_value('key2', 'value2') - end - carrier = {} - injector.inject(carrier, context) - _(carrier['uberctx-key1']).must_equal('value1') - _(carrier['uberctx-key2']).must_equal('value2') - end - - it 'URL-encodes baggage values before injecting' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1' - ) - context = OpenTelemetry.baggage.build(context: context) do |baggage| - baggage.set_value('key1', 'value 1 / blah') - end - carrier = {} - injector.inject(carrier, context) - _(carrier['uberctx-key1']).must_equal('value+1+%2F+blah') - end - - it 'injects to rack keys' do - rack_env_setter = Object.new - def rack_env_setter.set(carrier, key, value) - rack_key = 'HTTP_' + key - rack_key.tr!('-', '_') - rack_key.upcase! - carrier[rack_key] = value - end - rack_injector = OpenTelemetry::Propagator::Jaeger::TextMapInjector.new( - rack_env_setter - ) - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - trace_flags: OpenTelemetry::Trace::TraceFlags::SAMPLED - ) - context = OpenTelemetry.baggage.build(context: context) do |baggage| - baggage.set_value('key-1', 'value1') - baggage.set_value('key-2', 'value2') - end - carrier = {} - rack_injector.inject(carrier, context) - trace_span_identity_value = carrier['HTTP_UBER_TRACE_ID'] - trace_id, span_id, parent_span_id, flags = trace_span_identity_value.split(/:/) - _(trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(span_id).must_equal('e457b5a2e4d86bd1') - _(parent_span_id).must_equal('0') - _(flags).must_equal('1') - _(carrier['HTTP_UBERCTX_KEY_1']).must_equal('value1') - _(carrier['HTTP_UBERCTX_KEY_2']).must_equal('value2') - end - - it 'does not inject the debug flag when the sample flag is not set' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - trace_flags: OpenTelemetry::Trace::TraceFlags::DEFAULT, - jaeger_debug: true - ) - carrier = {} - injector.inject(carrier, context) - trace_span_identity_value = carrier[identity_key] - _trace_id, _span_id, _parent_span_id, flags = trace_span_identity_value.split(/:/) - _(flags).must_equal('0') - end - - it 'no-ops if trace id invalid' do - context = create_context( - trace_id: '0' * 32, - span_id: 'e457b5a2e4d86bd1' - ) - carrier = {} - injector.inject(carrier, context) - _(carrier.keys.empty?).must_equal(true) - end - - it 'no-ops if span id invalid' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: '0' * 16 - ) - carrier = {} - injector.inject(carrier, context) - _(carrier.keys.empty?).must_equal(true) - end - end - - def create_context(trace_id:, - span_id:, - trace_flags: OpenTelemetry::Trace::TraceFlags::DEFAULT, - jaeger_debug: false) - context = OpenTelemetry::Trace.context_with_span( - OpenTelemetry::Trace::Span.new( - span_context: OpenTelemetry::Trace::SpanContext.new( - trace_id: Array(trace_id).pack('H*'), - span_id: Array(span_id).pack('H*'), - trace_flags: trace_flags - ) - ) - ) - context = OpenTelemetry::Propagator::Jaeger.context_with_debug(context) if jaeger_debug - context - end -end diff --git a/propagator/jaeger/test/text_map_propagator_test.rb b/propagator/jaeger/test/text_map_propagator_test.rb new file mode 100644 index 0000000000..39a9dd5530 --- /dev/null +++ b/propagator/jaeger/test/text_map_propagator_test.rb @@ -0,0 +1,321 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' +require 'opentelemetry-sdk' + +describe OpenTelemetry::Propagator::Jaeger::TextMapPropagator do + let(:propagator) { OpenTelemetry::Propagator::Jaeger::TextMapPropagator.new } + + before do + OpenTelemetry::SDK::Configurator.new.configure + end + + describe('#extract') do + def extract_context(header) + parent_context = OpenTelemetry::Context.empty + carrier = { 'uber-trace-id' => header } + propagator.extract(carrier, context: parent_context) + end + + def extract_span_context(header) + context = extract_context(header) + OpenTelemetry::Trace.current_span(context).context + end + + def extracted_context_must_equal_parent_context(header) + parent_context = OpenTelemetry::Context.empty + carrier = { + 'uber-trace-id' => header + } + context = propagator.extract(carrier, context: parent_context) + _(context).must_equal(parent_context) + end + + it 'extracts context with trace id, span id, sampling flag' do + span_context = extract_span_context( + '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:0:1' + ) + _(span_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(span_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(span_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + _(span_context).must_be(:remote?) + end + + it 'extracts context with trace id, span id, sampling flag, parent span id' do + span_context = extract_span_context( + '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:ef62c5754687a53a:1' + ) + _(span_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(span_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(span_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + _(span_context).must_be(:remote?) + end + + it 'extracts context with trace id, span id' do + span_context = extract_span_context( + '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:0:0' + ) + _(span_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(span_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(span_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) + _(span_context).must_be(:remote?) + end + + it 'extracts context with a shorter trace id and span id' do + span_context = extract_span_context( + '8ee56343ba864fe8b2a57d3eff7:5a2e4d86bd1:0:1' + ) + _(span_context.hex_trace_id).must_equal('000008ee56343ba864fe8b2a57d3eff7') + _(span_context.hex_span_id).must_equal('000005a2e4d86bd1') + end + + it 'extracts context with 64-bit trace ids' do + span_context = extract_span_context( + '80f198ee56343ba8:e457b5a2e4d86bd1:0:1' + ) + _(span_context.hex_trace_id).must_equal('000000000000000080f198ee56343ba8') + end + + it 'extracts context with a shorter trace id that can be included in a 64-bit hex string' do + span_context = extract_span_context( + '98ee56343ba8:e457b5a2e4d86bd1:0:1' + ) + _(span_context.hex_trace_id).must_equal('0000000000000000000098ee56343ba8') + end + + it 'converts debug flag to sampled' do + context = extract_context( + '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:0:3' + ) + span_context = OpenTelemetry::Trace.current_span(context).context + + _(span_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + _(OpenTelemetry::Propagator::Jaeger.debug?(context)).must_equal(true) + end + + it 'extracts baggage' do + parent_context = OpenTelemetry::Context.empty + carrier = { + 'uber-trace-id' => '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:0:1', + 'uberctx-key1' => 'value1', + 'uberctx-key2' => 'value2' + } + + context = propagator.extract(carrier, context: parent_context) + _(OpenTelemetry.baggage.value('key1', context: context)).must_equal('value1') + _(OpenTelemetry.baggage.value('key2', context: context)).must_equal('value2') + end + + it 'extracts URL-encoded baggage entries' do + parent_context = OpenTelemetry::Context.empty + carrier = { + 'uber-trace-id' => '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:0:1', + 'uberctx-key1' => 'value%201%20%2F%20blah' + } + + context = propagator.extract(carrier, context: parent_context) + _(OpenTelemetry.baggage.value('key1', context: context)).must_equal('value 1 / blah') + end + + it 'extracts baggage with different keys' do + parent_context = OpenTelemetry::Context.empty + carrier = { + 'HTTP_UBER_TRACE_ID' => '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd1:0:1', + 'HTTP_UBERCTX_KEY_1' => 'value1', + 'HTTP_UBERCTX_KEY_2' => 'value2' + } + + context = propagator.extract( + carrier, + context: parent_context, + getter: OpenTelemetry::Context::Propagation.rack_env_getter + ) + span_context = OpenTelemetry::Trace.current_span(context).context + _(span_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(OpenTelemetry.baggage.value('key-1', context: context)).must_equal('value1') + _(OpenTelemetry.baggage.value('key-2', context: context)).must_equal('value2') + end + + it 'handles trace ids and span ids that are too long' do + extracted_context_must_equal_parent_context( + '80f198ee56343ba864fe8b2a57d3eff7eff7:e457b5a2e4d86bd1:0:1' + ) + extracted_context_must_equal_parent_context( + '80f198ee56343ba864fe8b2a57d3eff7:e457b5a2e4d86bd16bd1:0:1' + ) + end + + it 'handles invalid 0 trace id and 0 span id' do + extracted_context_must_equal_parent_context('0:e457b5a2e4d86bd1:0:1') + extracted_context_must_equal_parent_context( + '80f198ee56343ba864fe8b2a57d3eff7:0:0:1' + ) + extracted_context_must_equal_parent_context('00:00:0:1') + end + end + + describe '#inject' do + let(:identity_key) { 'uber-trace-id' } + + def create_context(trace_id:, + span_id:, + trace_flags: OpenTelemetry::Trace::TraceFlags::DEFAULT, + jaeger_debug: false) + context = OpenTelemetry::Trace.context_with_span( + OpenTelemetry::Trace::Span.new( + span_context: OpenTelemetry::Trace::SpanContext.new( + trace_id: Array(trace_id).pack('H*'), + span_id: Array(span_id).pack('H*'), + trace_flags: trace_flags + ) + ) + ) + context = OpenTelemetry::Propagator::Jaeger.context_with_debug(context) if jaeger_debug + context + end + + it 'injects context with sampled trace flags' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + trace_flags: OpenTelemetry::Trace::TraceFlags::SAMPLED + ) + carrier = {} + propagator.inject(carrier, context: context) + trace_span_identity_value = carrier[identity_key] + trace_id, span_id, parent_span_id, flags = trace_span_identity_value.split(/:/) + _(trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(span_id).must_equal('e457b5a2e4d86bd1') + _(parent_span_id).must_equal('0') + _(flags).must_equal('1') + end + + it 'injects context with default trace flags' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + trace_flags: OpenTelemetry::Trace::TraceFlags::DEFAULT + ) + carrier = {} + propagator.inject(carrier, context: context) + trace_span_identity_value = carrier[identity_key] + trace_id, span_id, parent_span_id, flags = trace_span_identity_value.split(/:/) + _(trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(span_id).must_equal('e457b5a2e4d86bd1') + _(parent_span_id).must_equal('0') + _(flags).must_equal('0') + end + + it 'injects debug flag when present' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + trace_flags: OpenTelemetry::Trace::TraceFlags::SAMPLED, + jaeger_debug: true + ) + carrier = {} + propagator.inject(carrier, context: context) + trace_span_identity_value = carrier[identity_key] + trace_id, span_id, parent_span_id, flags = trace_span_identity_value.split(/:/) + _(trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(span_id).must_equal('e457b5a2e4d86bd1') + _(parent_span_id).must_equal('0') + _(flags).must_equal('3') + end + + it 'injects baggage' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1' + ) + context = OpenTelemetry.baggage.build(context: context) do |baggage| + baggage.set_value('key1', 'value1') + baggage.set_value('key2', 'value2') + end + carrier = {} + propagator.inject(carrier, context: context) + _(carrier['uberctx-key1']).must_equal('value1') + _(carrier['uberctx-key2']).must_equal('value2') + end + + it 'URL-encodes baggage values before injecting' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1' + ) + context = OpenTelemetry.baggage.build(context: context) do |baggage| + baggage.set_value('key1', 'value 1 / blah') + end + carrier = {} + propagator.inject(carrier, context: context) + _(carrier['uberctx-key1']).must_equal('value+1+%2F+blah') + end + + it 'injects to rack keys' do + rack_env_setter = Object.new + def rack_env_setter.set(carrier, key, value) + rack_key = 'HTTP_' + key + rack_key.tr!('-', '_') + rack_key.upcase! + carrier[rack_key] = value + end + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + trace_flags: OpenTelemetry::Trace::TraceFlags::SAMPLED + ) + context = OpenTelemetry.baggage.build(context: context) do |baggage| + baggage.set_value('key-1', 'value1') + baggage.set_value('key-2', 'value2') + end + carrier = {} + propagator.inject(carrier, context: context, setter: rack_env_setter) + trace_span_identity_value = carrier['HTTP_UBER_TRACE_ID'] + trace_id, span_id, parent_span_id, flags = trace_span_identity_value.split(/:/) + _(trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(span_id).must_equal('e457b5a2e4d86bd1') + _(parent_span_id).must_equal('0') + _(flags).must_equal('1') + _(carrier['HTTP_UBERCTX_KEY_1']).must_equal('value1') + _(carrier['HTTP_UBERCTX_KEY_2']).must_equal('value2') + end + + it 'does not inject the debug flag when the sample flag is not set' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + trace_flags: OpenTelemetry::Trace::TraceFlags::DEFAULT, + jaeger_debug: true + ) + carrier = {} + propagator.inject(carrier, context: context) + trace_span_identity_value = carrier[identity_key] + _trace_id, _span_id, _parent_span_id, flags = trace_span_identity_value.split(/:/) + _(flags).must_equal('0') + end + + it 'no-ops if trace id invalid' do + context = create_context( + trace_id: '0' * 32, + span_id: 'e457b5a2e4d86bd1' + ) + carrier = {} + propagator.inject(carrier, context: context) + _(carrier.keys.empty?).must_equal(true) + end + + it 'no-ops if span id invalid' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: '0' * 16 + ) + carrier = {} + propagator.inject(carrier, context: context) + _(carrier.keys.empty?).must_equal(true) + end + end +end diff --git a/propagator/ottrace/lib/opentelemetry/propagator/ottrace.rb b/propagator/ottrace/lib/opentelemetry/propagator/ottrace.rb index 6e6bda6093..cc306c4829 100644 --- a/propagator/ottrace/lib/opentelemetry/propagator/ottrace.rb +++ b/propagator/ottrace/lib/opentelemetry/propagator/ottrace.rb @@ -6,6 +6,7 @@ require 'opentelemetry-api' require 'opentelemetry/propagator/ottrace/version' +require 'opentelemetry/propagator/ottrace/text_map_propagator' # OpenTelemetry is an open source observability framework, providing a # general-purpose API, SDK, and related tools required for the instrumentation @@ -20,27 +21,15 @@ module Propagator module OTTrace extend self - TRACE_ID_HEADER = 'ot-tracer-traceid' - SPAN_ID_HEADER = 'ot-tracer-spanid' - SAMPLED_HEADER = 'ot-tracer-sampled' - BAGGAGE_HEADER_PREFIX = 'ot-baggage-' + TEXT_MAP_PROPAGATOR = TextMapPropagator.new - # https://github.com/open-telemetry/opentelemetry-specification/blob/14d123c121b6caa53bffd011292c42a181c9ca26/specification/context/api-propagators.md#textmap-propagator0 - VALID_BAGGAGE_HEADER_NAME_CHARS = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/.freeze - INVALID_BAGGAGE_HEADER_VALUE_CHARS = /[^\t\u0020-\u007E\u0080-\u00FF]/.freeze + private_constant :TEXT_MAP_PROPAGATOR - ## Returns an extractor that extracts context from OTTrace carrier - def text_map_extractor - TextMapExtractor.new - end - - ## Returns an injector that injects context into a carrier - def text_map_injector - TextMapInjector.new + # Returns a text map propagator that propagates context using the + # OTTrace format. + def text_map_propagator + TEXT_MAP_PROPAGATOR end end end end - -require_relative './ottrace/text_map_injector' -require_relative './ottrace/text_map_extractor' diff --git a/propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_injector.rb b/propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_injector.rb deleted file mode 100644 index 9fbe63e022..0000000000 --- a/propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_injector.rb +++ /dev/null @@ -1,74 +0,0 @@ -# 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 OTTrace propagation - module OTTrace - # Injects context into carriers using the OTTrace format - class TextMapInjector - TRACE_ID_64_BIT_WIDTH = 64 / 4 - - # Returns a new TextMapInjector that injects context using the specified setter - # - # @param [optional Setter] default_setter The default setter used to - # write context into a carrier during inject. Defaults to a - # {OpenTelemetry::Context:Propagation::TextMapSetter} instance. - # @return [TextMapInjector] - def initialize(default_setter: Context::Propagation.text_map_setter) - @default_setter = default_setter - end - - # @param [Object] carrier to update with context. - # @param [Context] context The active Context. - # @param [optional Setter] setter If the optional setter is provided, it - # will be used to write context into the carrier, otherwise the default - # setter will be used. - def inject(carrier, context, setter = nil) - setter ||= default_setter - span_context = Trace.current_span(context).context - return unless span_context.valid? - - inject_span_context(span_context: span_context, carrier: carrier, setter: setter) - inject_baggage(context: context, carrier: carrier, setter: setter) - - nil - end - - private - - attr_reader :default_setter - - def inject_span_context(span_context:, carrier:, setter:) - setter.set(carrier, TRACE_ID_HEADER, span_context.hex_trace_id[TRACE_ID_64_BIT_WIDTH, TRACE_ID_64_BIT_WIDTH]) - setter.set(carrier, SPAN_ID_HEADER, span_context.hex_span_id) - setter.set(carrier, SAMPLED_HEADER, span_context.trace_flags.sampled?.to_s) - end - - def inject_baggage(context:, carrier:, setter:) - baggage.values(context: context) - .select { |key, value| valid_baggage_entry?(key, value) } - .each { |key, value| setter.set(carrier, "#{BAGGAGE_HEADER_PREFIX}#{key}", value) } - end - - def valid_baggage_entry?(key, value) - VALID_BAGGAGE_HEADER_NAME_CHARS =~ key && INVALID_BAGGAGE_HEADER_VALUE_CHARS !~ value - end - - def baggage - OpenTelemetry.baggage - end - end - end - end -end diff --git a/propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_extractor.rb b/propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_propagator.rb similarity index 50% rename from propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_extractor.rb rename to propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_propagator.rb index bde6ce73e9..4a957e56d5 100644 --- a/propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_extractor.rb +++ b/propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_propagator.rb @@ -15,36 +15,38 @@ module OpenTelemetry module Propagator # Namespace for OpenTelemetry OTTrace propagation module OTTrace - # Extracts context from carriers using OTTrace header format - class TextMapExtractor + # Propagates context using OTTrace header format + class TextMapPropagator PADDING = '0' * 16 VALID_TRACE_ID_REGEX = /^[0-9a-f]{32}$/.freeze VALID_SPAN_ID_REGEX = /^[0-9a-f]{16}$/.freeze + TRACE_ID_64_BIT_WIDTH = 64 / 4 + TRACE_ID_HEADER = 'ot-tracer-traceid' + SPAN_ID_HEADER = 'ot-tracer-spanid' + SAMPLED_HEADER = 'ot-tracer-sampled' + BAGGAGE_HEADER_PREFIX = 'ot-baggage-' + FIELDS = [TRACE_ID_HEADER, SPAN_ID_HEADER, SAMPLED_HEADER].freeze - # Returns a new TextMapExtractor that extracts OTTrace context using the - # specified getter - # - # @param [optional Getter] default_getter The default getter used to read - # headers from a carrier during extract. Defaults to a - # {OpenTelemetry::Context:Propagation::TextMapGetter} instance. - # @return [TextMapExtractor] - def initialize(default_getter: Context::Propagation.text_map_getter) - @default_getter = default_getter - end + # https://github.com/open-telemetry/opentelemetry-specification/blob/14d123c121b6caa53bffd011292c42a181c9ca26/specification/context/api-propagators.md#textmap-propagator0 + VALID_BAGGAGE_HEADER_NAME_CHARS = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/.freeze + INVALID_BAGGAGE_HEADER_VALUE_CHARS = /[^\t\u0020-\u007E\u0080-\u00FF]/.freeze + + private_constant :PADDING, :VALID_TRACE_ID_REGEX, :VALID_SPAN_ID_REGEX, :TRACE_ID_64_BIT_WIDTH, :TRACE_ID_HEADER, + :SPAN_ID_HEADER, :SAMPLED_HEADER, :BAGGAGE_HEADER_PREFIX, :FIELDS, :VALID_BAGGAGE_HEADER_NAME_CHARS, + :INVALID_BAGGAGE_HEADER_VALUE_CHARS # Extract OTTrace context from the supplied carrier and set the active span # in the given context. The original context will be returned if OTTrace # cannot be extracted from the carrier. # # @param [Carrier] carrier The carrier to get the header from. - # @param [Context] context The context to be updated with extracted context + # @param [optional Context] context The context to be updated with extracted context # @param [optional Getter] getter If the optional getter is provided, it # will be used to read the header from the carrier, otherwise the default # getter will be used. # @return [Context] Updated context with active span derived from the header, or the original # context if parsing fails. - def extract(carrier, context, getter = nil) - getter ||= default_getter + def extract(carrier, context: Context.current, getter: Context::Propagation.text_map_getter) trace_id = optionally_pad_trace_id(getter.get(carrier, TRACE_ID_HEADER)) span_id = getter.get(carrier, SPAN_ID_HEADER) sampled = getter.get(carrier, SAMPLED_HEADER) @@ -62,9 +64,30 @@ def extract(carrier, context, getter = nil) Trace.context_with_span(span, parent_context: set_baggage(carrier: carrier, context: context, getter: getter)) end - private + # @param [Object] carrier to update with context. + # @param [optional Context] context The active Context. + # @param [optional Setter] setter If the optional setter is provided, it + # will be used to write context into the carrier, otherwise the default + # setter will be used. + def inject(carrier, context: Context.current, setter: Context::Propagation.text_map_setter) + span_context = Trace.current_span(context).context + return unless span_context.valid? + + inject_span_context(span_context: span_context, carrier: carrier, setter: setter) + inject_baggage(context: context, carrier: carrier, setter: setter) - attr_reader :default_getter + nil + end + + # Returns the predefined propagation fields. If your carrier is reused, you + # should delete the fields returned by this method before calling +inject+. + # + # @return [Array] a list of fields that will be used by this propagator. + def fields + FIELDS + end + + private def valid?(trace_id:, span_id:) !(VALID_TRACE_ID_REGEX !~ trace_id || VALID_SPAN_ID_REGEX !~ span_id) @@ -80,7 +103,7 @@ def optionally_pad_trace_id(trace_id) def set_baggage(carrier:, context:, getter:) baggage.build(context: context) do |builder| - prefix = OTTrace::BAGGAGE_HEADER_PREFIX + prefix = BAGGAGE_HEADER_PREFIX getter.keys(carrier).each do |carrier_key| baggage_key = carrier_key.start_with?(prefix) && carrier_key[prefix.length..-1] next unless baggage_key @@ -97,6 +120,22 @@ def set_baggage(carrier:, context:, getter:) def baggage OpenTelemetry.baggage end + + def inject_span_context(span_context:, carrier:, setter:) + setter.set(carrier, TRACE_ID_HEADER, span_context.hex_trace_id[TRACE_ID_64_BIT_WIDTH, TRACE_ID_64_BIT_WIDTH]) + setter.set(carrier, SPAN_ID_HEADER, span_context.hex_span_id) + setter.set(carrier, SAMPLED_HEADER, span_context.trace_flags.sampled?.to_s) + end + + def inject_baggage(context:, carrier:, setter:) + baggage.values(context: context) + .select { |key, value| valid_baggage_entry?(key, value) } + .each { |key, value| setter.set(carrier, "#{BAGGAGE_HEADER_PREFIX}#{key}", value) } + end + + def valid_baggage_entry?(key, value) + VALID_BAGGAGE_HEADER_NAME_CHARS =~ key && INVALID_BAGGAGE_HEADER_VALUE_CHARS !~ value + end end end end diff --git a/propagator/ottrace/test/opentelemetry/propagator/ottrace/text_map_extractor_test.rb b/propagator/ottrace/test/opentelemetry/propagator/ottrace/text_map_extractor_test.rb deleted file mode 100644 index 9747b10309..0000000000 --- a/propagator/ottrace/test/opentelemetry/propagator/ottrace/text_map_extractor_test.rb +++ /dev/null @@ -1,233 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Propagator::OTTrace::TextMapExtractor do - class FakeGetter - def get(carrier, key) - case key - when OpenTelemetry::Propagator::OTTrace::TRACE_ID_HEADER, OpenTelemetry::Propagator::OTTrace::SPAN_ID_HEADER - carrier[key].reverse - when OpenTelemetry::Propagator::OTTrace::SAMPLED_HEADER - carrier[key] != 'true' - end - end - - def keys(carrier) - [] - end - end - - let(:baggage) do - OpenTelemetry.baggage - end - - let(:extractor) do - OpenTelemetry::Propagator::OTTrace::TextMapExtractor.new - end - - let(:parent_context) do - OpenTelemetry::Context.empty - end - - let(:trace_id_header) do - '80f198ee56343ba864fe8b2a57d3eff7' - end - - let(:span_id_header) do - 'e457b5a2e4d86bd1' - end - - let(:sampled_header) do - 'true' - end - - let(:carrier) do - { - OpenTelemetry::Propagator::OTTrace::TRACE_ID_HEADER => trace_id_header, - OpenTelemetry::Propagator::OTTrace::SPAN_ID_HEADER => span_id_header, - OpenTelemetry::Propagator::OTTrace::SAMPLED_HEADER => sampled_header - } - end - - describe '#extract' do - describe 'given an empty context' do - let(:carrier) do - {} - end - - it 'skips context extraction' do - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('0' * 32) - _(extracted_context.hex_span_id).must_equal('0' * 16) - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) - _(extracted_context).wont_be(:remote?) - end - end - - describe 'given a minimal context' do - it 'extracts parent context' do - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - _(extracted_context).must_be(:remote?) - end - end - - describe 'given a context with sampling disabled' do - let(:sampled_header) do - 'false' - end - - it 'extracts parent context' do - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) - _(extracted_context).must_be(:remote?) - end - end - - describe 'given context with a 64 bit/16 HEXDIGIT trace id' do - let(:trace_id_header) do - '64fe8b2a57d3eff7' - end - - it 'pads the trace id' do - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('000000000000000064fe8b2a57d3eff7') - _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - _(extracted_context).must_be(:remote?) - end - end - - describe 'given context with a malformed trace id' do - let(:trace_id_header) do - 'abc123' - end - - it 'skips content extraction' do - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context).must_be_same_as(OpenTelemetry::Trace::SpanContext::INVALID) - end - end - - describe 'given context with a malformed span id' do - let(:span_id_header) do - 'abc123' - end - - it 'skips content extraction' do - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context).must_be_same_as(OpenTelemetry::Trace::SpanContext::INVALID) - end - end - - describe 'baggage handling' do - before do - OpenTelemetry.baggage = OpenTelemetry::Baggage::Manager.new - end - - after do - OpenTelemetry.baggage = nil - end - - describe 'given valid baggage items' do - it 'extracts baggage items' do - carrier_with_baggage = carrier.merge( - "#{OpenTelemetry::Propagator::OTTrace::BAGGAGE_HEADER_PREFIX}foo" => 'bar', - "#{OpenTelemetry::Propagator::OTTrace::BAGGAGE_HEADER_PREFIX}bar" => 'baz' - ) - - context = extractor.extract(carrier_with_baggage, parent_context) - _(OpenTelemetry.baggage.value('foo', context: context)).must_equal('bar') - _(OpenTelemetry.baggage.value('bar', context: context)).must_equal('baz') - end - end - - describe 'given invalid baggage keys' do - it 'omits entries' do - carrier_with_baggage = carrier.merge( - "#{OpenTelemetry::Propagator::OTTrace::BAGGAGE_HEADER_PREFIX}fθθ" => 'bar', - "#{OpenTelemetry::Propagator::OTTrace::BAGGAGE_HEADER_PREFIX}bar" => 'baz' - ) - - context = extractor.extract(carrier_with_baggage, parent_context) - _(OpenTelemetry.baggage.value('fθθ', context: context)).must_be_nil - _(OpenTelemetry.baggage.value('bar', context: context)).must_equal('baz') - end - end - - describe 'given invalid baggage values' do - it 'omits entries' do - carrier_with_baggage = carrier.merge( - "#{OpenTelemetry::Propagator::OTTrace::BAGGAGE_HEADER_PREFIX}foo" => 'bαr', - "#{OpenTelemetry::Propagator::OTTrace::BAGGAGE_HEADER_PREFIX}bar" => 'baz' - ) - - context = extractor.extract(carrier_with_baggage, parent_context) - _(OpenTelemetry.baggage.value('foo', context: context)).must_be_nil - _(OpenTelemetry.baggage.value('bar', context: context)).must_equal('baz') - end - end - end - - describe 'given an alternative getter parameter' do - it 'will use the alternative getter instead of the constructor provided one' do - context = extractor.extract(carrier, parent_context, FakeGetter.new) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('7ffe3d75a2b8ef468ab34365ee891f08') - _(extracted_context.hex_span_id).must_equal('1db68d4e2a5b754e') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) - _(extracted_context).must_be(:remote?) - end - end - - describe 'given a missing getter parameter' do - it 'will use the default getter' do - context = extractor.extract(carrier, parent_context, nil) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - _(extracted_context).must_be(:remote?) - end - end - - describe 'given an alternative default getter' do - let(:extractor) do - OpenTelemetry::Propagator::OTTrace::TextMapExtractor.new(default_getter: FakeGetter.new) - end - - it 'will use the alternative getter' do - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('7ffe3d75a2b8ef468ab34365ee891f08') - _(extracted_context.hex_span_id).must_equal('1db68d4e2a5b754e') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) - _(extracted_context).must_be(:remote?) - end - end - end -end diff --git a/propagator/ottrace/test/opentelemetry/propagator/ottrace/text_map_injector_test.rb b/propagator/ottrace/test/opentelemetry/propagator/ottrace/text_map_injector_test.rb deleted file mode 100644 index 0645c7760f..0000000000 --- a/propagator/ottrace/test/opentelemetry/propagator/ottrace/text_map_injector_test.rb +++ /dev/null @@ -1,216 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Propagator::OTTrace::TextMapInjector do - class FakeSetter - def set(carrier, key, value) - carrier[key] = "#{key} = #{value}" - end - end - - let(:span_id) do - 'e457b5a2e4d86bd1' - end - - let(:truncated_trace_id) do - '64fe8b2a57d3eff7' - end - - let(:trace_id) do - '80f198ee56343ba864fe8b2a57d3eff7' - end - - let(:trace_flags) do - OpenTelemetry::Trace::TraceFlags::DEFAULT - end - - let(:context) do - OpenTelemetry::Trace.context_with_span( - OpenTelemetry::Trace::Span.new( - span_context: OpenTelemetry::Trace::SpanContext.new( - trace_id: Array(trace_id).pack('H*'), - span_id: Array(span_id).pack('H*'), - trace_flags: trace_flags - ) - ) - ) - end - - let(:baggage) do - OpenTelemetry.baggage - end - - let(:injector) do - OpenTelemetry::Propagator::OTTrace::TextMapInjector.new - end - - describe '#inject' do - describe 'when provided invalid trace ids' do - let(:trace_id) do - '0' * 32 - end - - it 'skips injecting context' do - carrier = {} - injector.inject(carrier, context) - - _(carrier).must_be_empty - end - end - - describe 'when provided invalid span ids' do - let(:span_id) do - '0' * 16 - end - - it 'skips injecting context' do - carrier = {} - injector.inject(carrier, context) - - _(carrier).must_be_empty - end - end - - describe 'given a minimal context' do - it 'injects OpenTracing headers' do - carrier = {} - injector.inject(carrier, context) - - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::TRACE_ID_HEADER)).must_equal(truncated_trace_id) - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::SPAN_ID_HEADER)).must_equal(span_id) - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::SAMPLED_HEADER)).must_equal('false') - end - end - - describe 'given a sampled trace flag' do - let(:trace_flags) do - OpenTelemetry::Trace::TraceFlags::SAMPLED - end - - it 'injects OpenTracing headers' do - carrier = {} - injector.inject(carrier, context) - - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::TRACE_ID_HEADER)).must_equal(truncated_trace_id) - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::SPAN_ID_HEADER)).must_equal(span_id) - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::SAMPLED_HEADER)).must_equal('true') - end - end - - describe 'given a trace id that exceeds the 64 BIT/16 HEXDIG limit' do - let(:trace_id) do - '80f198ee56343ba864fe8b2a57d3eff7' - end - - it 'injects truncates the trace id header' do - carrier = {} - injector.inject(carrier, context) - - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::TRACE_ID_HEADER)).must_equal('64fe8b2a57d3eff7') - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::SPAN_ID_HEADER)).must_equal(span_id) - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::SAMPLED_HEADER)).must_equal('false') - end - end - - describe 'baggage handling' do - before do - OpenTelemetry.baggage = OpenTelemetry::Baggage::Manager.new - end - - after do - OpenTelemetry.baggage = nil - end - - describe 'given valid baggage items' do - it 'injects baggage items' do - context_with_baggage = baggage.build(context: context) do |builder| - builder.set_value('foo', 'bar') - builder.set_value('bar', 'baz') - end - - carrier = {} - injector.inject(carrier, context_with_baggage) - - _(carrier.fetch('ot-baggage-foo')).must_equal('bar') - _(carrier.fetch('ot-baggage-bar')).must_equal('baz') - end - end - - describe 'given invalid baggage keys' do - it 'omits entries' do - context_with_baggage = baggage.build(context: context) do |builder| - builder.set_value('fθθ', 'bar') - builder.set_value('bar', 'baz') - end - - carrier = {} - injector.inject(carrier, context_with_baggage) - - _(carrier.keys).wont_include('ot-baggage-f00') - _(carrier.fetch('ot-baggage-bar')).must_equal('baz') - end - end - - describe 'given invalid baggage values' do - it 'omits entries' do - context_with_baggage = baggage.build(context: context) do |builder| - builder.set_value('foo', 'bαr') - builder.set_value('bar', 'baz') - end - - carrier = {} - injector.inject(carrier, context_with_baggage) - - _(carrier.keys).wont_include('ot-baggage-foo') - _(carrier.fetch('ot-baggage-bar')).must_equal('baz') - end - end - end - - describe 'given an alternative setter parameter' do - it 'will use the alternative setter instead of the constructor provided one' do - carrier = {} - - alternate_setter = FakeSetter.new - injector.inject(carrier, context, alternate_setter) - - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::TRACE_ID_HEADER)).must_equal('ot-tracer-traceid = 64fe8b2a57d3eff7') - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::SPAN_ID_HEADER)).must_equal('ot-tracer-spanid = e457b5a2e4d86bd1') - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::SAMPLED_HEADER)).must_equal('ot-tracer-sampled = false') - end - end - - describe 'given a missing setter parameter' do - it 'uses the default setter' do - carrier = {} - - injector.inject(carrier, context, nil) - - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::TRACE_ID_HEADER)).must_equal(truncated_trace_id) - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::SPAN_ID_HEADER)).must_equal(span_id) - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::SAMPLED_HEADER)).must_equal('false') - end - end - - describe 'given an alternative default setter' do - let(:injector) do - OpenTelemetry::Propagator::OTTrace::TextMapInjector.new(default_setter: FakeSetter.new) - end - - it 'will use the alternative setter' do - carrier = {} - - injector.inject(carrier, context) - - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::TRACE_ID_HEADER)).must_equal('ot-tracer-traceid = 64fe8b2a57d3eff7') - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::SPAN_ID_HEADER)).must_equal('ot-tracer-spanid = e457b5a2e4d86bd1') - _(carrier.fetch(OpenTelemetry::Propagator::OTTrace::SAMPLED_HEADER)).must_equal('ot-tracer-sampled = false') - end - end - end -end diff --git a/propagator/ottrace/test/opentelemetry/propagator/ottrace/text_map_propagator_test.rb b/propagator/ottrace/test/opentelemetry/propagator/ottrace/text_map_propagator_test.rb new file mode 100644 index 0000000000..8c20c583ea --- /dev/null +++ b/propagator/ottrace/test/opentelemetry/propagator/ottrace/text_map_propagator_test.rb @@ -0,0 +1,376 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Propagator::OTTrace::TextMapPropagator do + class FakeGetter + def get(carrier, key) + case key + when 'ot-tracer-traceid', 'ot-tracer-spanid' + carrier[key].reverse + when 'ot-tracer-sampled' + carrier[key] != 'true' + end + end + + def keys(carrier) + [] + end + end + + class FakeSetter + def set(carrier, key, value) + carrier[key] = "#{key} = #{value}" + end + end + + let(:span_id) do + 'e457b5a2e4d86bd1' + end + + let(:truncated_trace_id) do + '64fe8b2a57d3eff7' + end + + let(:trace_id) do + '80f198ee56343ba864fe8b2a57d3eff7' + end + + let(:trace_flags) do + OpenTelemetry::Trace::TraceFlags::DEFAULT + end + + let(:context) do + OpenTelemetry::Trace.context_with_span( + OpenTelemetry::Trace::Span.new( + span_context: OpenTelemetry::Trace::SpanContext.new( + trace_id: Array(trace_id).pack('H*'), + span_id: Array(span_id).pack('H*'), + trace_flags: trace_flags + ) + ) + ) + end + + let(:baggage) do + OpenTelemetry.baggage + end + + let(:propagator) do + OpenTelemetry::Propagator::OTTrace::TextMapPropagator.new + end + + let(:parent_context) do + OpenTelemetry::Context.empty + end + + let(:trace_id_header) do + '80f198ee56343ba864fe8b2a57d3eff7' + end + + let(:span_id_header) do + 'e457b5a2e4d86bd1' + end + + let(:sampled_header) do + 'true' + end + + let(:carrier) do + { + 'ot-tracer-traceid' => trace_id_header, + 'ot-tracer-spanid' => span_id_header, + 'ot-tracer-sampled' => sampled_header + } + end + + describe '#extract' do + describe 'given an empty context' do + let(:carrier) do + {} + end + + it 'skips context extraction' do + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('0' * 32) + _(extracted_context.hex_span_id).must_equal('0' * 16) + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) + _(extracted_context).wont_be(:remote?) + end + end + + describe 'given a minimal context' do + it 'extracts parent context' do + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + _(extracted_context).must_be(:remote?) + end + end + + describe 'given a context with sampling disabled' do + let(:sampled_header) do + 'false' + end + + it 'extracts parent context' do + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) + _(extracted_context).must_be(:remote?) + end + end + + describe 'given context with a 64 bit/16 HEXDIGIT trace id' do + let(:trace_id_header) do + '64fe8b2a57d3eff7' + end + + it 'pads the trace id' do + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('000000000000000064fe8b2a57d3eff7') + _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + _(extracted_context).must_be(:remote?) + end + end + + describe 'given context with a malformed trace id' do + let(:trace_id_header) do + 'abc123' + end + + it 'skips content extraction' do + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context).must_be_same_as(OpenTelemetry::Trace::SpanContext::INVALID) + end + end + + describe 'given context with a malformed span id' do + let(:span_id_header) do + 'abc123' + end + + it 'skips content extraction' do + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context).must_be_same_as(OpenTelemetry::Trace::SpanContext::INVALID) + end + end + + describe 'baggage handling' do + before do + OpenTelemetry.baggage = OpenTelemetry::Baggage::Manager.new + end + + after do + OpenTelemetry.baggage = nil + end + + describe 'given valid baggage items' do + it 'extracts baggage items' do + carrier_with_baggage = carrier.merge( + 'ot-baggage-foo' => 'bar', + 'ot-baggage-bar' => 'baz' + ) + + context = propagator.extract(carrier_with_baggage, context: parent_context) + _(OpenTelemetry.baggage.value('foo', context: context)).must_equal('bar') + _(OpenTelemetry.baggage.value('bar', context: context)).must_equal('baz') + end + end + + describe 'given invalid baggage keys' do + it 'omits entries' do + carrier_with_baggage = carrier.merge( + 'ot-baggage-f00' => 'bar', + 'ot-baggage-bar' => 'baz' + ) + + context = propagator.extract(carrier_with_baggage, context: parent_context) + _(OpenTelemetry.baggage.value('fθθ', context: context)).must_be_nil + _(OpenTelemetry.baggage.value('bar', context: context)).must_equal('baz') + end + end + + describe 'given invalid baggage values' do + it 'omits entries' do + carrier_with_baggage = carrier.merge( + 'ot-baggage-foo' => 'bαr', + 'ot-baggage-bar' => 'baz' + ) + + context = propagator.extract(carrier_with_baggage, context: parent_context) + _(OpenTelemetry.baggage.value('foo', context: context)).must_be_nil + _(OpenTelemetry.baggage.value('bar', context: context)).must_equal('baz') + end + end + end + + describe 'given an alternative getter parameter' do + it 'will use the alternative getter instead of the constructor provided one' do + context = propagator.extract(carrier, context: parent_context, getter: FakeGetter.new) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('7ffe3d75a2b8ef468ab34365ee891f08') + _(extracted_context.hex_span_id).must_equal('1db68d4e2a5b754e') + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) + _(extracted_context).must_be(:remote?) + end + end + end + + describe '#inject' do + describe 'when provided invalid trace ids' do + let(:trace_id) do + '0' * 32 + end + + it 'skips injecting context' do + carrier = {} + propagator.inject(carrier, context: context) + + _(carrier).must_be_empty + end + end + + describe 'when provided invalid span ids' do + let(:span_id) do + '0' * 16 + end + + it 'skips injecting context' do + carrier = {} + propagator.inject(carrier, context: context) + + _(carrier).must_be_empty + end + end + + describe 'given a minimal context' do + it 'injects OpenTracing headers' do + carrier = {} + propagator.inject(carrier, context: context) + + _(carrier.fetch('ot-tracer-traceid')).must_equal(truncated_trace_id) + _(carrier.fetch('ot-tracer-spanid')).must_equal(span_id) + _(carrier.fetch('ot-tracer-sampled')).must_equal('false') + end + end + + describe 'given a sampled trace flag' do + let(:trace_flags) do + OpenTelemetry::Trace::TraceFlags::SAMPLED + end + + it 'injects OpenTracing headers' do + carrier = {} + propagator.inject(carrier, context: context) + + _(carrier.fetch('ot-tracer-traceid')).must_equal(truncated_trace_id) + _(carrier.fetch('ot-tracer-spanid')).must_equal(span_id) + _(carrier.fetch('ot-tracer-sampled')).must_equal('true') + end + end + + describe 'given a trace id that exceeds the 64 BIT/16 HEXDIG limit' do + let(:trace_id) do + '80f198ee56343ba864fe8b2a57d3eff7' + end + + it 'injects truncates the trace id header' do + carrier = {} + propagator.inject(carrier, context: context) + + _(carrier.fetch('ot-tracer-traceid')).must_equal('64fe8b2a57d3eff7') + _(carrier.fetch('ot-tracer-spanid')).must_equal(span_id) + _(carrier.fetch('ot-tracer-sampled')).must_equal('false') + end + end + + describe 'baggage handling' do + before do + OpenTelemetry.baggage = OpenTelemetry::Baggage::Manager.new + end + + after do + OpenTelemetry.baggage = nil + end + + describe 'given valid baggage items' do + it 'injects baggage items' do + context_with_baggage = baggage.build(context: context) do |builder| + builder.set_value('foo', 'bar') + builder.set_value('bar', 'baz') + end + + carrier = {} + propagator.inject(carrier, context: context_with_baggage) + + _(carrier.fetch('ot-baggage-foo')).must_equal('bar') + _(carrier.fetch('ot-baggage-bar')).must_equal('baz') + end + end + + describe 'given invalid baggage keys' do + it 'omits entries' do + context_with_baggage = baggage.build(context: context) do |builder| + builder.set_value('fθθ', 'bar') + builder.set_value('bar', 'baz') + end + + carrier = {} + propagator.inject(carrier, context: context_with_baggage) + + _(carrier.keys).wont_include('ot-baggage-f00') + _(carrier.fetch('ot-baggage-bar')).must_equal('baz') + end + end + + describe 'given invalid baggage values' do + it 'omits entries' do + context_with_baggage = baggage.build(context: context) do |builder| + builder.set_value('foo', 'bαr') + builder.set_value('bar', 'baz') + end + + carrier = {} + propagator.inject(carrier, context: context_with_baggage) + + _(carrier.keys).wont_include('ot-baggage-foo') + _(carrier.fetch('ot-baggage-bar')).must_equal('baz') + end + end + end + + describe 'given an alternative setter parameter' do + it 'will use the alternative setter instead of the constructor provided one' do + carrier = {} + + alternate_setter = FakeSetter.new + propagator.inject(carrier, context: context, setter: alternate_setter) + + _(carrier.fetch('ot-tracer-traceid')).must_equal('ot-tracer-traceid = 64fe8b2a57d3eff7') + _(carrier.fetch('ot-tracer-spanid')).must_equal('ot-tracer-spanid = e457b5a2e4d86bd1') + _(carrier.fetch('ot-tracer-sampled')).must_equal('ot-tracer-sampled = false') + end + end + end +end diff --git a/propagator/ottrace/test/opentelemetry/propagator/ottrace_test.rb b/propagator/ottrace/test/opentelemetry/propagator/ottrace_test.rb index 24081466cc..722e090eae 100644 --- a/propagator/ottrace/test/opentelemetry/propagator/ottrace_test.rb +++ b/propagator/ottrace/test/opentelemetry/propagator/ottrace_test.rb @@ -7,20 +7,11 @@ require 'test_helper' describe OpenTelemetry::Propagator::OTTrace do - describe '#text_map_extractor' do - it 'returns an instance of TextMapExtractor' do - extractor = OpenTelemetry::Propagator::OTTrace.text_map_extractor - _(extractor).must_be_instance_of( - OpenTelemetry::Propagator::OTTrace::TextMapExtractor - ) - end - end - - describe '#text_map_injector' do - it 'returns an instance of TextMapInjector' do - extractor = OpenTelemetry::Propagator::OTTrace.text_map_injector - _(extractor).must_be_instance_of( - OpenTelemetry::Propagator::OTTrace::TextMapInjector + describe '#text_map_propagator' do + it 'returns an instance of TextMapPropagator' do + propagator = OpenTelemetry::Propagator::OTTrace.text_map_propagator + _(propagator).must_be_instance_of( + OpenTelemetry::Propagator::OTTrace::TextMapPropagator ) end end diff --git a/propagator/xray/lib/opentelemetry/propagator/xray.rb b/propagator/xray/lib/opentelemetry/propagator/xray.rb index 2eceddd4fa..951a766fbb 100644 --- a/propagator/xray/lib/opentelemetry/propagator/xray.rb +++ b/propagator/xray/lib/opentelemetry/propagator/xray.rb @@ -11,8 +11,7 @@ # The OpenTelemetry module provides global accessors for telemetry objects. # See the documentation for the `opentelemetry-api` gem for details. -require_relative './xray/text_map_extractor' -require_relative './xray/text_map_injector' +require_relative './xray/text_map_propagator' module OpenTelemetry # Namespace for OpenTelemetry propagator extension libraries @@ -22,7 +21,8 @@ module XRay extend self DEBUG_CONTEXT_KEY = Context.create_key('xray-debug-key') - private_constant :DEBUG_CONTEXT_KEY + TEXT_MAP_PROPAGATOR = TextMapPropagator.new + private_constant :DEBUG_CONTEXT_KEY, :TEXT_MAP_PROPAGATOR # @api private # Returns a new context with the xray debug flag enabled @@ -33,38 +33,13 @@ def context_with_debug(context) # @api private # Read the XRay debug flag from the provided context def debug?(context) - context.value(DEBUG_CONTEXT_KEY) + !context.value(DEBUG_CONTEXT_KEY).nil? end - # @api private - # Convert an id from a hex encoded string to byte array. Assumes the input id has already been - # validated to be 35 characters in length. - def to_trace_id(hex_id) - Array(hex_id[2..8] + hex_id[10..hex_id.length]).pack('H*') - end - - # @api private - # Convert an id from a hex encoded string to byte array. - def to_span_id(hex_id) - Array(hex_id).pack('H*') - end - - XRAY_CONTEXT_KEY = 'X-Amzn-Trace-Id' - TEXT_MAP_EXTRACTOR = TextMapExtractor.new - TEXT_MAP_INJECTOR = TextMapInjector.new - - private_constant :XRAY_CONTEXT_KEY, :TEXT_MAP_INJECTOR, :TEXT_MAP_EXTRACTOR - - # Returns an extractor that extracts context in the XRay single header - # format - def text_map_injector - TEXT_MAP_INJECTOR - end - - # Returns an injector that injects context in the XRay single header - # format - def text_map_extractor - TEXT_MAP_EXTRACTOR + # Returns a text map propagator that propagates context in the XRay + # format. + def text_map_propagator + TEXT_MAP_PROPAGATOR end end end diff --git a/propagator/xray/lib/opentelemetry/propagator/xray/text_map_extractor.rb b/propagator/xray/lib/opentelemetry/propagator/xray/text_map_extractor.rb deleted file mode 100644 index 6d89209d5b..0000000000 --- a/propagator/xray/lib/opentelemetry/propagator/xray/text_map_extractor.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -# Copyright 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 XRay propagation - module XRay - # Extracts context from carriers in the xray single header format - class TextMapExtractor - XRAY_CONTEXT_REGEX = /\ARoot=(?([a-z0-9\-]{35}))(?:;Parent=(?([a-z0-9]{16})))?(?:;Sampled=(?[01d](?![0-9a-f])))?(?:;(?.*))?\Z/.freeze - SAMPLED_VALUES = %w[1 d].freeze - - # Returns a new TextMapExtractor that extracts XRay context using the - # specified getter - # - # @param [optional Getter] default_getter The default getter used to read - # headers from a carrier during extract. Defaults to a - # {OpenTelemetry::Context:Propagation::TextMapGetter} instance. - # @return [TextMapExtractor] - def initialize(default_getter = Context::Propagation.text_map_getter) - @default_getter = default_getter - end - - # Extract xray context from the supplied carrier and set the active span - # in the given context. The original context will be returned if xray - # cannot be extracted from the carrier. - # - # @param [Carrier] carrier The carrier to get the header from. - # @param [Context] context The context to be updated with extracted context - # @param [optional Getter] getter An optional getter that takes a carrier and a key and - # returns the value associated with the key. If omitted the default getter will be used - # which expects the carrier to respond to [] and []=. - # @return [Context] Updated context with active span derived from the header, or the original - # context if parsing fails. - def extract(carrier, context, getter = nil) - getter ||= @default_getter - header = getter.get(carrier, XRAY_CONTEXT_KEY) - match = parse_header(header) - return context unless match - - span_context = Trace::SpanContext.new( - trace_id: XRay.to_trace_id(match['trace_id']), - span_id: XRay.to_span_id(match['span_id']), - trace_flags: to_trace_flags(match['sampling_state']), - tracestate: to_trace_state(match['trace_state']), - remote: true - ) - - span = Trace::Span.new(span_context: span_context) - context = XRay.context_with_debug(context) if match['sampling_state'] == 'd' - Trace.context_with_span(span, parent_context: context) - rescue OpenTelemetry::Error - context - end - - private - - def parse_header(header) - return nil unless (match = header.match(XRAY_CONTEXT_REGEX)) - return nil unless match['trace_id'] - return nil unless match['span_id'] - - match - end - - def to_trace_flags(sampling_state) - if SAMPLED_VALUES.include?(sampling_state) - Trace::TraceFlags::SAMPLED - else - Trace::TraceFlags::DEFAULT - end - end - - def to_trace_state(trace_state) - return nil unless trace_state - - Trace::Tracestate.from_string(trace_state.gsub(';', ',')) - end - end - end - end -end diff --git a/propagator/xray/lib/opentelemetry/propagator/xray/text_map_injector.rb b/propagator/xray/lib/opentelemetry/propagator/xray/text_map_injector.rb deleted file mode 100644 index b405ec0eaf..0000000000 --- a/propagator/xray/lib/opentelemetry/propagator/xray/text_map_injector.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -# Copyright OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 -module OpenTelemetry - # Namespace for OpenTelemetry propagator extension libraries - module Propagator - # Namespace for OpenTelemetry XRay propagation - module XRay - # Injects context into carriers using the xray single header format - class TextMapInjector - # Returns a new TextMapInjector that injects XRay context using the - # specified setter - # - # @param [optional Setter] default_setter The default setter used to - # write context into a carrier during inject. Defaults to a - # {OpenTelemetry::Context:Propagation::TextMapSetter} instance. - # @return [TextMapInjector] - def initialize(default_setter = Context::Propagation.text_map_setter) - @default_setter = default_setter - end - - # Set the span context on the supplied carrier. - # - # @param [Context] context The active Context. - # @param [optional Setter] setter If the optional setter is provided, it - # will be used to write context into the carrier, otherwise the default - # setter will be used. - # @return [Object] the carrier with context injected - def inject(carrier, context, setter = nil) - span_context = Trace.current_span(context).context - return unless span_context.valid? - - sampling_state = if XRay.debug?(context) - 'd' - elsif span_context.trace_flags.sampled? - '1' - else - '0' - end - - ot_trace_id = span_context.hex_trace_id - xray_trace_id = "1-#{ot_trace_id[0..6]}-#{ot_trace_id[7..ot_trace_id.length]}" - parent_id = span_context.hex_span_id - - xray_value = "Root=#{xray_trace_id};Parent=#{parent_id};Sampled=#{sampling_state}" - - setter ||= @default_setter - setter.set(carrier, XRAY_CONTEXT_KEY, xray_value) - carrier - end - end - end - end -end diff --git a/propagator/xray/lib/opentelemetry/propagator/xray/text_map_propagator.rb b/propagator/xray/lib/opentelemetry/propagator/xray/text_map_propagator.rb new file mode 100644 index 0000000000..c6339691a1 --- /dev/null +++ b/propagator/xray/lib/opentelemetry/propagator/xray/text_map_propagator.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +# Copyright 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 XRay propagation + module XRay + # Propagates context in carriers in the xray single header format + class TextMapPropagator + XRAY_CONTEXT_KEY = 'X-Amzn-Trace-Id' + XRAY_CONTEXT_REGEX = /\ARoot=(?([a-z0-9\-]{35}))(?:;Parent=(?([a-z0-9]{16})))?(?:;Sampled=(?[01d](?![0-9a-f])))?(?:;(?.*))?\Z/.freeze + SAMPLED_VALUES = %w[1 d].freeze + FIELDS = [XRAY_CONTEXT_KEY].freeze + + private_constant :XRAY_CONTEXT_KEY, :XRAY_CONTEXT_REGEX, :SAMPLED_VALUES, :FIELDS + + # Extract trace context from the supplied carrier. + # 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) + header = getter.get(carrier, XRAY_CONTEXT_KEY) + match = parse_header(header) + return context unless match + + span_context = Trace::SpanContext.new( + trace_id: to_trace_id(match['trace_id']), + span_id: to_span_id(match['span_id']), + trace_flags: to_trace_flags(match['sampling_state']), + tracestate: to_trace_state(match['trace_state']), + remote: true + ) + + span = Trace::Span.new(span_context: span_context) + context = XRay.context_with_debug(context) if match['sampling_state'] == 'd' + Trace.context_with_span(span, parent_context: context) + rescue OpenTelemetry::Error + context + end + + # Inject trace context into the supplied carrier. + # + # @param [Carrier] carrier The mutable carrier to inject trace context into + # @param [Context] context The context to read trace context from + # @param [optional Setter] setter If the optional setter is provided, it + # will be used to write context into the carrier, otherwise the default + # text map setter will be used. + def inject(carrier, context: Context.current, setter: Context::Propagation.text_map_setter) + span_context = Trace.current_span(context).context + return unless span_context.valid? + + sampling_state = if XRay.debug?(context) + 'd' + elsif span_context.trace_flags.sampled? + '1' + else + '0' + end + + ot_trace_id = span_context.hex_trace_id + xray_trace_id = "1-#{ot_trace_id[0..6]}-#{ot_trace_id[7..ot_trace_id.length]}" + parent_id = span_context.hex_span_id + + xray_value = "Root=#{xray_trace_id};Parent=#{parent_id};Sampled=#{sampling_state}" + + setter.set(carrier, XRAY_CONTEXT_KEY, xray_value) + nil + end + + private + + def parse_header(header) + return nil unless (match = header.match(XRAY_CONTEXT_REGEX)) + return nil unless match['trace_id'] + return nil unless match['span_id'] + + match + end + + # Convert an id from a hex encoded string to byte array. Assumes the input id has already been + # validated to be 35 characters in length. + def to_trace_id(hex_id) + Array(hex_id[2..8] + hex_id[10..hex_id.length]).pack('H*') + end + + # Convert an id from a hex encoded string to byte array. + def to_span_id(hex_id) + Array(hex_id).pack('H*') + end + + def to_trace_flags(sampling_state) + if SAMPLED_VALUES.include?(sampling_state) + Trace::TraceFlags::SAMPLED + else + Trace::TraceFlags::DEFAULT + end + end + + def to_trace_state(trace_state) + return nil unless trace_state + + Trace::Tracestate.from_string(trace_state.gsub(';', ',')) + end + end + end + end +end diff --git a/propagator/xray/test/text_map_extractor_test.rb b/propagator/xray/test/text_map_extractor_test.rb deleted file mode 100644 index 9d5b639254..0000000000 --- a/propagator/xray/test/text_map_extractor_test.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -# Copyright OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Propagator::XRay::TextMapExtractor do - let(:extractor) { OpenTelemetry::Propagator::XRay::TextMapExtractor.new } - - describe('#extract') do - it 'extracts context with trace id, span id, sampling flag, trace state' do - parent_context = OpenTelemetry::Context.empty - carrier = { 'X-Amzn-Trace-Id' => 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=1;Foo=Bar;Fizz=Buzz' } - - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - _(extracted_context.tracestate.to_s).must_equal(OpenTelemetry::Trace::Tracestate.from_string('Foo=Bar,Fizz=Buzz').to_s) - _(extracted_context).must_be(:remote?) - end - - it 'extracts context with trace id, span id, sampling flag' do - parent_context = OpenTelemetry::Context.empty - carrier = { 'X-Amzn-Trace-Id' => 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=1' } - - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - _(extracted_context).must_be(:remote?) - end - - it 'extracts context with trace id, span id' do - parent_context = OpenTelemetry::Context.empty - carrier = { 'X-Amzn-Trace-Id' => 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1' } - - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') - _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) - _(extracted_context).must_be(:remote?) - end - - it 'converts debug flag to sampled' do - parent_context = OpenTelemetry::Context.empty - carrier = { 'X-Amzn-Trace-Id' => 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=d' } - - context = extractor.extract(carrier, parent_context) - extracted_context = OpenTelemetry::Trace.current_span(context).context - - _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) - end - - it 'handles malformed trace id' do - parent_context = OpenTelemetry::Context.empty - carrier = { 'X-Amzn-Trace-Id' => 'Root=180f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=1' } - - context = extractor.extract(carrier, parent_context) - - _(context).must_equal(parent_context) - end - - it 'handles malformed span id' do - parent_context = OpenTelemetry::Context.empty - carrier = { 'X-Amzn-Trace-Id' => 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=457b5a2e4d86bd1;Sampled=1' } - - context = extractor.extract(carrier, parent_context) - - _(context).must_equal(parent_context) - end - end -end diff --git a/propagator/xray/test/text_map_injector_test.rb b/propagator/xray/test/text_map_injector_test.rb deleted file mode 100644 index 54a153c1d6..0000000000 --- a/propagator/xray/test/text_map_injector_test.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true - -# Copyright OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Propagator::XRay::TextMapInjector do - Span = OpenTelemetry::Trace::Span - SpanContext = OpenTelemetry::Trace::SpanContext - TraceFlags = OpenTelemetry::Trace::TraceFlags - - let(:injector) { OpenTelemetry::Propagator::XRay::TextMapInjector.new } - - describe '#inject' do - it 'injects context with sampled trace flags' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - trace_flags: TraceFlags::SAMPLED - ) - - carrier = {} - injector.inject(carrier, context) - - expected_xray = 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=1' - _(carrier['X-Amzn-Trace-Id']).must_equal(expected_xray) - end - - it 'injects context with default trace flags' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - trace_flags: TraceFlags::DEFAULT - ) - - carrier = {} - injector.inject(carrier, context) - - expected_xray = 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=0' - _(carrier['X-Amzn-Trace-Id']).must_equal(expected_xray) - end - - it 'injects debug flag when present' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: 'e457b5a2e4d86bd1', - xray_debug: true - ) - - carrier = {} - injector.inject(carrier, context) - - expected_xray = 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=d' - _(carrier['X-Amzn-Trace-Id']).must_equal(expected_xray) - end - - it 'no-ops if trace id invalid' do - context = create_context( - trace_id: '0' * 32, - span_id: 'e457b5a2e4d86bd1' - ) - - carrier = {} - injector.inject(carrier, context) - - _(carrier.key?('X-Amzn-Trace-Id')).must_equal(false) - end - - it 'no-ops if span id invalid' do - context = create_context( - trace_id: '80f198ee56343ba864fe8b2a57d3eff7', - span_id: '0' * 16 - ) - - carrier = {} - injector.inject(carrier, context) - - _(carrier.key?('X-Amzn-Trace-Id')).must_equal(false) - end - end - - def create_context(trace_id:, - span_id:, - trace_flags: TraceFlags::DEFAULT, - xray_debug: false) - context = OpenTelemetry::Trace.context_with_span( - Span.new( - span_context: SpanContext.new( - trace_id: Array(trace_id).pack('H*'), - span_id: Array(span_id).pack('H*'), - trace_flags: trace_flags - ) - ) - ) - context = OpenTelemetry::Propagator::XRay.context_with_debug(context) if xray_debug - context - end -end diff --git a/propagator/xray/test/text_map_propagator_test.rb b/propagator/xray/test/text_map_propagator_test.rb new file mode 100644 index 0000000000..c99c2e7849 --- /dev/null +++ b/propagator/xray/test/text_map_propagator_test.rb @@ -0,0 +1,170 @@ +# frozen_string_literal: true + +# Copyright OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Propagator::XRay::TextMapPropagator do + Span = OpenTelemetry::Trace::Span + SpanContext = OpenTelemetry::Trace::SpanContext + TraceFlags = OpenTelemetry::Trace::TraceFlags + + let(:propagator) { OpenTelemetry::Propagator::XRay::TextMapPropagator.new } + + describe('#extract') do + it 'extracts context with trace id, span id, sampling flag, trace state' do + parent_context = OpenTelemetry::Context.empty + carrier = { 'X-Amzn-Trace-Id' => 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=1;Foo=Bar;Fizz=Buzz' } + + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + _(extracted_context.tracestate.to_s).must_equal(OpenTelemetry::Trace::Tracestate.from_string('Foo=Bar,Fizz=Buzz').to_s) + _(extracted_context).must_be(:remote?) + end + + it 'extracts context with trace id, span id, sampling flag' do + parent_context = OpenTelemetry::Context.empty + carrier = { 'X-Amzn-Trace-Id' => 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=1' } + + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + _(extracted_context).must_be(:remote?) + end + + it 'extracts context with trace id, span id' do + parent_context = OpenTelemetry::Context.empty + carrier = { 'X-Amzn-Trace-Id' => 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1' } + + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.hex_trace_id).must_equal('80f198ee56343ba864fe8b2a57d3eff7') + _(extracted_context.hex_span_id).must_equal('e457b5a2e4d86bd1') + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::DEFAULT) + _(extracted_context).must_be(:remote?) + end + + it 'converts debug flag to sampled' do + parent_context = OpenTelemetry::Context.empty + carrier = { 'X-Amzn-Trace-Id' => 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=d' } + + context = propagator.extract(carrier, context: parent_context) + extracted_context = OpenTelemetry::Trace.current_span(context).context + + _(extracted_context.trace_flags).must_equal(OpenTelemetry::Trace::TraceFlags::SAMPLED) + end + + it 'handles malformed trace id' do + parent_context = OpenTelemetry::Context.empty + carrier = { 'X-Amzn-Trace-Id' => 'Root=180f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=1' } + + context = propagator.extract(carrier, context: parent_context) + + _(context).must_equal(parent_context) + end + + it 'handles malformed span id' do + parent_context = OpenTelemetry::Context.empty + carrier = { 'X-Amzn-Trace-Id' => 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=457b5a2e4d86bd1;Sampled=1' } + + context = propagator.extract(carrier, context: parent_context) + + _(context).must_equal(parent_context) + end + end + + describe '#inject' do + it 'injects context with sampled trace flags' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + trace_flags: TraceFlags::SAMPLED + ) + + carrier = {} + propagator.inject(carrier, context: context) + + expected_xray = 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=1' + _(carrier['X-Amzn-Trace-Id']).must_equal(expected_xray) + end + + it 'injects context with default trace flags' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + trace_flags: TraceFlags::DEFAULT + ) + + carrier = {} + propagator.inject(carrier, context: context) + + expected_xray = 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=0' + _(carrier['X-Amzn-Trace-Id']).must_equal(expected_xray) + end + + it 'injects debug flag when present' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: 'e457b5a2e4d86bd1', + xray_debug: true + ) + + carrier = {} + propagator.inject(carrier, context: context) + + expected_xray = 'Root=1-80f198e-e56343ba864fe8b2a57d3eff7;Parent=e457b5a2e4d86bd1;Sampled=d' + _(carrier['X-Amzn-Trace-Id']).must_equal(expected_xray) + end + + it 'no-ops if trace id invalid' do + context = create_context( + trace_id: '0' * 32, + span_id: 'e457b5a2e4d86bd1' + ) + + carrier = {} + propagator.inject(carrier, context: context) + + _(carrier.key?('X-Amzn-Trace-Id')).must_equal(false) + end + + it 'no-ops if span id invalid' do + context = create_context( + trace_id: '80f198ee56343ba864fe8b2a57d3eff7', + span_id: '0' * 16 + ) + + carrier = {} + propagator.inject(carrier, context: context) + + _(carrier.key?('X-Amzn-Trace-Id')).must_equal(false) + end + end + + def create_context(trace_id:, + span_id:, + trace_flags: TraceFlags::DEFAULT, + xray_debug: false) + context = OpenTelemetry::Trace.context_with_span( + Span.new( + span_context: SpanContext.new( + trace_id: Array(trace_id).pack('H*'), + span_id: Array(span_id).pack('H*'), + trace_flags: trace_flags + ) + ) + ) + context = OpenTelemetry::Propagator::XRay.context_with_debug(context) if xray_debug + context + end +end diff --git a/propagator/xray/test/xray_test.rb b/propagator/xray/test/xray_test.rb index 1ed556b394..b7f7b867b0 100644 --- a/propagator/xray/test/xray_test.rb +++ b/propagator/xray/test/xray_test.rb @@ -7,20 +7,11 @@ require 'test_helper' describe OpenTelemetry::Propagator::XRay do - describe '#text_map_extractor' do - it 'returns an instance of TextMapExtractor' do - extractor = OpenTelemetry::Propagator::XRay.text_map_extractor - _(extractor).must_be_instance_of( - OpenTelemetry::Propagator::XRay::TextMapExtractor - ) - end - end - - describe '#text_map_injector, #rack_injector' do - it 'returns an instance of TextMapInjector' do - injector = OpenTelemetry::Propagator::XRay.text_map_injector - _(injector).must_be_instance_of( - OpenTelemetry::Propagator::XRay::TextMapInjector + describe '#text_map_propagator' do + it 'returns an instance of TextMapPropagator' do + propagator = OpenTelemetry::Propagator::XRay.text_map_propagator + _(propagator).must_be_instance_of( + OpenTelemetry::Propagator::XRay::TextMapPropagator ) end end diff --git a/sdk/lib/opentelemetry/sdk/configurator.rb b/sdk/lib/opentelemetry/sdk/configurator.rb index a385493992..4ad17a7e44 100644 --- a/sdk/lib/opentelemetry/sdk/configurator.rb +++ b/sdk/lib/opentelemetry/sdk/configurator.rb @@ -15,14 +15,12 @@ class Configurator # rubocop:disable Metrics/ClassLength private_constant :USE_MODE_UNSPECIFIED, :USE_MODE_ONE, :USE_MODE_ALL - attr_writer :logger, :extractors, :injectors, :error_handler, - :id_generator + attr_writer :logger, :propagators, :error_handler, :id_generator def initialize @instrumentation_names = [] @instrumentation_config_map = {} - @injectors = nil - @extractors = nil + @propagators = nil @span_processors = [] @use_mode = USE_MODE_UNSPECIFIED @resource = Resources::Resource.default @@ -161,14 +159,11 @@ def wrapped_exporter_from_env end end - def configure_propagation # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity - propagators = ENV.fetch('OTEL_PROPAGATORS', 'tracecontext,baggage').split(',') - injectors, extractors = propagators.uniq.collect do |propagator| + def configure_propagation # rubocop:disable Metrics/CyclomaticComplexity + propagators = ENV.fetch('OTEL_PROPAGATORS', 'tracecontext,baggage').split(',').uniq.collect do |propagator| case propagator - when 'tracecontext' - [OpenTelemetry::Trace::Propagation::TraceContext.text_map_injector, OpenTelemetry::Trace::Propagation::TraceContext.text_map_extractor] - when 'baggage' - [OpenTelemetry::Baggage::Propagation.text_map_injector, OpenTelemetry::Baggage::Propagation.text_map_extractor] + when 'tracecontext' then OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator + when 'baggage' then OpenTelemetry::Baggage::Propagation.text_map_propagator when 'b3' then fetch_propagator(propagator, 'OpenTelemetry::Propagator::B3::Single') when 'b3multi' then fetch_propagator(propagator, 'OpenTelemetry::Propagator::B3::Multi', 'b3') when 'jaeger' then fetch_propagator(propagator, 'OpenTelemetry::Propagator::Jaeger') @@ -176,27 +171,17 @@ def configure_propagation # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticC when 'ottrace' then fetch_propagator(propagator, 'OpenTelemetry::Propagator::OTTrace') else OpenTelemetry.logger.warn "The #{propagator} propagator is unknown and cannot be configured" - [Context::Propagation::NoopInjector.new, Context::Propagation::NoopExtractor.new] + Context::Propagation::NoopTextMapPropagator.new end - end.transpose - OpenTelemetry.propagation = create_propagator(@injectors || injectors.compact, - @extractors || extractors.compact) - end - - def create_propagator(injectors, extractors) - if injectors.size > 1 || extractors.size > 1 - Context::Propagation::CompositePropagator.new(injectors, extractors) - else - Context::Propagation::Propagator.new(injectors.first, extractors.first) end + OpenTelemetry.propagation = Context::Propagation::CompositeTextMapPropagator.compose_propagators((@propagators || propagators).compact) end def fetch_propagator(name, class_name, gem_suffix = name) - propagator_class = Kernel.const_get(class_name) - [propagator_class.text_map_injector, propagator_class.text_map_extractor] + Kernel.const_get(class_name).text_map_propagator rescue NameError OpenTelemetry.logger.warn "The #{name} propagator cannot be configured - please add opentelemetry-propagator-#{gem_suffix} to your Gemfile" - [nil, nil] + nil end def fetch_exporter(name, class_name) diff --git a/sdk/test/opentelemetry/sdk/configurator_test.rb b/sdk/test/opentelemetry/sdk/configurator_test.rb index 8df759e86b..62695a672e 100644 --- a/sdk/test/opentelemetry/sdk/configurator_test.rb +++ b/sdk/test/opentelemetry/sdk/configurator_test.rb @@ -116,76 +116,32 @@ end end - describe 'injectors' do + describe 'propagators' do it 'defaults to trace context and baggage' do configurator.configure - expected_injectors = [ - OpenTelemetry::Trace::Propagation::TraceContext.text_map_injector, - OpenTelemetry::Baggage::Propagation.text_map_injector + expected_propagators = [ + OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator, + OpenTelemetry::Baggage::Propagation.text_map_propagator ] - _(injectors_for(OpenTelemetry.propagation)).must_equal(expected_injectors) + _(propagators_for(OpenTelemetry.propagation)).must_equal(expected_propagators) end it 'is user settable' do - injector = OpenTelemetry::Context::Propagation::NoopInjector.new - configurator.injectors = [injector] + propagator = OpenTelemetry::Context::Propagation::NoopTextMapPropagator.new + configurator.propagators = [propagator] configurator.configure - _(injectors_for(OpenTelemetry.propagation)).must_equal([injector]) + _(OpenTelemetry.propagation).must_equal(propagator) end it 'can be set by environment variable' do - expected_injectors = [OpenTelemetry::Baggage::Propagation.text_map_injector] - - with_env('OTEL_PROPAGATORS' => 'baggage') do - configurator.configure - end - - _(injectors_for(OpenTelemetry.propagation)).must_equal(expected_injectors) - end - - it 'defaults to none with invalid env var' do - with_env('OTEL_PROPAGATORS' => 'unladen_swallow') do - configurator.configure - end - - _(injectors_for(OpenTelemetry.propagation).length).must_equal(1) - _(injectors_for(OpenTelemetry.propagation).first).must_be_instance_of( - Context::Propagation::NoopInjector - ) - end - end - - describe '#extractors' do - it 'defaults to trace context and baggage' do - configurator.configure - - expected_extractors = [ - OpenTelemetry::Trace::Propagation::TraceContext.text_map_extractor, - OpenTelemetry::Baggage::Propagation.text_map_extractor - ] - - _(extractors_for(OpenTelemetry.propagation)).must_equal(expected_extractors) - end - - it 'is user settable' do - extractor = OpenTelemetry::Context::Propagation::NoopExtractor.new - configurator.extractors = [extractor] - configurator.configure - - _(extractors_for(OpenTelemetry.propagation)).must_equal([extractor]) - end - - it 'can be set by environment variable' do - expected_extractors = [OpenTelemetry::Baggage::Propagation.text_map_extractor] - with_env('OTEL_PROPAGATORS' => 'baggage') do configurator.configure end - _(extractors_for(OpenTelemetry.propagation)).must_equal(expected_extractors) + _(OpenTelemetry.propagation).must_equal(OpenTelemetry::Baggage::Propagation.text_map_propagator) end it 'defaults to none with invalid env var' do @@ -193,9 +149,8 @@ configurator.configure end - _(extractors_for(OpenTelemetry.propagation).length).must_equal(1) - _(extractors_for(OpenTelemetry.propagation).first).must_be_instance_of( - Context::Propagation::NoopExtractor + _(OpenTelemetry.propagation).must_be_instance_of( + Context::Propagation::NoopTextMapPropagator ) end end @@ -316,19 +271,11 @@ end end - def extractors_for(propagator) - if propagator.instance_of? Context::Propagation::CompositePropagator - propagator.instance_variable_get(:@extractors) - else - [propagator.instance_variable_get(:@extractor)] - end - end - - def injectors_for(propagator) - if propagator.instance_of? Context::Propagation::CompositePropagator - propagator.instance_variable_get(:@injectors) + def propagators_for(propagator) + if propagator.instance_of? Context::Propagation::CompositeTextMapPropagator + propagator.instance_variable_get(:@propagators) else - [propagator.instance_variable_get(:@injector)] + [propagator] end end end