diff --git a/api/lib/opentelemetry/distributed_context/propagation/http_text_format.rb b/api/lib/opentelemetry/distributed_context/propagation/http_text_format.rb index 29c473120f..f18eae453f 100644 --- a/api/lib/opentelemetry/distributed_context/propagation/http_text_format.rb +++ b/api/lib/opentelemetry/distributed_context/propagation/http_text_format.rb @@ -16,25 +16,27 @@ module Propagation # Propagation is usually implemented via library-specific request interceptors, where the client-side injects values # and the server-side extracts them. class HTTPTextFormat - # extract will return a SpanContext from the supplied carrier - # invalid headers will result in a new SpanContext - # @param [Carrier] the carrier to get the header from - # @yield [Carrier, String] the header key - # @return [SpanContext] the span context from the header, or a new one if parsing fails + # Return a remote {Trace::SpanContext} extracted 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. + # @yield [Carrier, String] the carrier and the header key. + # @return [SpanContext] the span context from the header, or a new one if parsing fails. def extract(carrier) raise ArgumentError, 'block must be supplied' unless block_given? header = yield carrier, TraceParent::TRACE_PARENT_HEADER tp = TraceParent.from_string(header) - SpanContext.new(trace_id: tp.trace_id, span_id: tp.span_id, flags: tp.flags) + Trace::SpanContext.new(trace_id: tp.trace_id, span_id: tp.span_id, trace_flags: tp.flags, remote: true) rescue OpenTelemetry::Error - SpanContext.new + Trace::SpanContext.new end - # inject will set the span context on the supplied carrier - # @param [Context] the carrier - # @yield [Carrier, String, String] carrier, header key, header value + # Set the span context on the supplied carrier. + # + # @param [SpanContext] context The active {Trace::SpanContext}. + # @yield [Carrier, String, String] carrier, header key, header value. def inject(context, carrier) raise ArgumentError, 'block must be supplied' unless block_given? diff --git a/api/lib/opentelemetry/distributed_context/propagation/trace_parent.rb b/api/lib/opentelemetry/distributed_context/propagation/trace_parent.rb index d68861faca..33b2dd42db 100644 --- a/api/lib/opentelemetry/distributed_context/propagation/trace_parent.rb +++ b/api/lib/opentelemetry/distributed_context/propagation/trace_parent.rb @@ -8,7 +8,7 @@ module DistributedContext module Propagation # A TraceParent is an implementation of the W3C trace context specification # https://www.w3.org/TR/trace-context/ - # {SpanContext} + # {Trace::SpanContext} class TraceParent InvalidFormatError = Class.new(Error) InvalidVersionError = Class.new(Error) @@ -25,15 +25,15 @@ class TraceParent private_constant :REGEXP class << self - # Creates a new {TraceParent} from a supplied {SpanContext} - # @param [SpanContext] the context + # Creates a new {TraceParent} from a supplied {Trace::SpanContext} + # @param [SpanContext] ctx The context # @return [TraceParent] a trace parent def from_context(ctx) new(trace_id: ctx.trace_id, span_id: ctx.span_id, flags: ctx.trace_flags) end # Deserializes the {TraceParent} from the string representation - # @param [String] the serialized trace parent + # @param [String] string The serialized trace parent # @return [TraceParent] a trace_parent # @raise [InvalidFormatError] on an invalid format # @raise [InvalidVerionError] on an invalid version diff --git a/api/lib/opentelemetry/trace/span_context.rb b/api/lib/opentelemetry/trace/span_context.rb index 2495065ecf..05ff98336c 100644 --- a/api/lib/opentelemetry/trace/span_context.rb +++ b/api/lib/opentelemetry/trace/span_context.rb @@ -7,25 +7,45 @@ module OpenTelemetry module Trace # A SpanContext contains the state that must propagate to child {Span}s and across process boundaries. - # It contains the identifiers (a trace ID and span ID) associated with the {Span} and a set of - # {TraceFlags}. + # It contains the identifiers (a trace ID and span ID) associated with the {Span}, a set of + # {TraceFlags}, and a boolean indicating that the SpanContext was extracted from the wire. class SpanContext attr_reader :trace_id, :span_id, :trace_flags + # Returns a new {SpanContext}. + # + # @param [optional String] trace_id The trace ID associated with a {Span}. + # @param [optional String] span_id The span ID associated with a {Span}. + # @param [optional TraceFlags] trace_flags The trace flags associated with a {Span}. + # @param [optional Boolean] remote Whether the {SpanContext} was extracted from the wire. + # @return [SpanContext] def initialize( trace_id: Trace.generate_trace_id, span_id: Trace.generate_span_id, - trace_flags: TraceFlags::DEFAULT + trace_flags: TraceFlags::DEFAULT, + remote: false ) @trace_id = trace_id @span_id = span_id @trace_flags = trace_flags + @remote = remote end + # Returns true if the {SpanContext} has a non-zero trace ID and non-zero span ID. + # + # @return [Boolean] def valid? @trace_id != INVALID_TRACE_ID && @span_id != INVALID_SPAN_ID end + # Returns true if the {SpanContext} was propagated from a remote parent. + # + # @return [Boolean] + def remote? + @remote + end + + # Represents an invalid {SpanContext}, with an invalid trace ID and an invalid span ID. INVALID = new(trace_id: INVALID_TRACE_ID, span_id: INVALID_SPAN_ID) end end diff --git a/api/test/opentelemetry/distributed_context/propagation/http_text_format_test.rb b/api/test/opentelemetry/distributed_context/propagation/http_text_format_test.rb new file mode 100644 index 0000000000..d600360524 --- /dev/null +++ b/api/test/opentelemetry/distributed_context/propagation/http_text_format_test.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +# Copyright 2019 OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::DistributedContext::Propagation::HTTPTextFormat do + SpanContext = OpenTelemetry::Trace::SpanContext + HTTPTextFormat = + OpenTelemetry::DistributedContext::Propagation::HTTPTextFormat + + let(:formatter) { HTTPTextFormat.new } + let(:valid_header) do + '00-000000000000000000000000000000AA-00000000000000ea-01' + end + let(:invalid_header) do + 'FF-000000000000000000000000000000AA-00000000000000ea-01' + end + + describe '#extract' do + it 'requires a block' do + proc { formatter.extract({}) }.must_raise(ArgumentError) + end + + it 'yields the carrier and the header key' do + carrier = {} + yielded = false + formatter.extract(carrier) do |c, key| + c.must_equal(carrier) + key.must_equal('traceparent') + yielded = true + valid_header + end + yielded.must_equal(true) + end + + it 'returns a remote SpanContext with fields from the traceparent header' do + context = formatter.extract({}) { valid_header } + context.must_be :remote? + context.trace_id.must_equal('000000000000000000000000000000aa') + context.span_id.must_equal('00000000000000ea') + context.trace_flags.must_be :sampled? + end + + it 'returns a valid non-remote SpanContext on error' do + context = formatter.extract({}) { invalid_header } + context.wont_be :remote? + context.must_be :valid? + end + end + + describe '#inject' do + it 'requires a block' do + proc { formatter.inject(SpanContext.new, {}) }.must_raise(ArgumentError) + end + + it 'yields the carrier, key, and traceparent value from the context' do + context = SpanContext.new(trace_id: 'f' * 32, span_id: '1' * 16) + carrier = {} + yielded = false + formatter.inject(context, carrier) do |c, k, v| + c.must_equal(carrier) + k.must_equal('traceparent') + v.must_equal('00-ffffffffffffffffffffffffffffffff-1111111111111111-00') + yielded = true + c + end + yielded.must_equal(true) + end + end + + describe '#fields' do + it 'returns an array with the W3C traceparent header' do + formatter.fields.must_equal(['traceparent']) + end + end +end diff --git a/api/test/opentelemetry/trace/span_context_test.rb b/api/test/opentelemetry/trace/span_context_test.rb index 95eceec59b..0876e1645d 100644 --- a/api/test/opentelemetry/trace/span_context_test.rb +++ b/api/test/opentelemetry/trace/span_context_test.rb @@ -23,6 +23,17 @@ end end + describe '#remote?' do + it 'is false by default' do + span_context.wont_be(:remote?) + end + + it 'reflects the value passed in' do + context = OpenTelemetry::Trace::SpanContext.new(remote: true) + context.must_be(:remote?) + end + end + describe '#trace_id' do it 'reflects the value passed in' do trace_id = OpenTelemetry::Trace.generate_trace_id