Span vs Context vs SpanContext
Context
官方解释:Context specs
Context
是一个包含{ CURRENT_SPAN_KEY => span, k1 => v1, k2 => v2 ... }
的map,其中可能有CURRENT_SPAN_KEY
只向当前Span
每个线程都会维护一个context栈,栈底是懒生成的
Context::ROOT
(new(EMPTY_ENTRIES)
)# opentelemetry-api-1.1.0/lib/opentelemetry/context.rb
module OpenTelemetry
class Context
def set_value(key, value)
new_entries = @entries.dup
new_entries[key] = value
Context.new(new_entries)
end
class << self
def with_value(key, value)
ctx = current.set_value(key, value)
token = attach(ctx)
yield ctx, value
ensure
detach(token)
end
def current
stack.last || ROOT
end
def attach(context)
stack.push(context)
end
def detach(token)
stack.pop
end
private
def stack
Thread.current[STACK_KEY] ||= []
end
end
end
end
SpanContext
官方解释:SpanContext specs
SpanContext
是一个包含traceid、spanid等每个span独有信息的对象# opentelemetry-api-1.1.0/lib/opentelemetry/trace/span_context.rb
module OpenTelemetry
module Trace
class SpanContext
def initialize(
trace_id: Trace.generate_trace_id,
span_id: Trace.generate_span_id,
trace_flags: TraceFlags::DEFAULT,
tracestate: Tracestate::DEFAULT,
remote: false
)
@trace_id = trace_id
@span_id = span_id
@trace_flags = trace_flags
@tracestate = tracestate
@remote = remote
end
end
end
end
Span
官方解释:Span specs
Span
内含SpanContext
,并且记有attributes、links等信息# opentelemetry-api-1.1.0/lib/opentelemetry/trace/span.rb
module OpenTelemetry
module Trace
class Span
attr_reader :context
def initialize(span_context: nil)
@context = span_context || SpanContext.new
end
end
end
end
# opentelemetry-sdk-1.2.1/lib/opentelemetry/sdk/trace/span.rb
module OpenTelemetry
module SDK
module Trace
class Span < OpenTelemetry::Trace::Span
def initialize(context, parent_context, parent_span, name, kind,
parent_span_id, span_limits, span_processors, attributes, links,
start_timestamp, resource, instrumentation_scope
)
super(span_context: context)
@mutex = Mutex.new
@name = name
@kind = kind
# ...
end
end
end
end
end
交互
每次
in_span
都会以Context.current
为parent,创建一个新的Span
(内含新的SpanContext
),并将原context的键值对dup,更新CURRENT_SPAN_KEY
为新的span,然后入栈对于跨进程(跨线程)的跟踪,需要在下层
OpenTelemetry::Context.with_current(extracted_ctx){ tracer.in_span(span_name){} }
,使in_span
能关联上层context的Span
# opentelemetry-api-1.1.0/lib/opentelemetry/trace/tracer.rb
class OpenTelemetry::Trace::Tracer
def in_span(name, attributes: nil, links: nil, start_timestamp: nil, kind: nil, &block)
span = start_span name# ...
Trace.with_span(span, &block)
# ...
end
end
# opentelemetry-sdk-1.2.1/lib/opentelemetry/sdk/trace/tracer.rb
module OpenTelemetry
module SDK
module Trace
class Tracer < OpenTelemetry::Trace::Tracer
def start_span(name, with_parent: nil, attributes: nil, links: nil, start_timestamp: nil, kind: nil)
with_parent ||= Context.current
return super(name, with_parent: with_parent, attributes: attributes, links: links, start_timestamp: start_timestamp, kind: kind) if Common::Utilities.untraced?(with_parent)
name ||= 'empty'
kind ||= :internal
@tracer_provider.internal_start_span(name, kind, attributes, links, start_timestamp, with_parent, @instrumentation_scope)
end
end
end
end
end
# opentelemetry-sdk-1.2.1/lib/opentelemetry/sdk/trace/tracer_provider.rb
module OpenTelemetry
module SDK
module Trace
# {TracerProvider} is the SDK implementation of {OpenTelemetry::Trace::TracerProvider}.
class TracerProvider < OpenTelemetry::Trace::TracerProvider
def internal_start_span(name, kind, attributes, links, start_timestamp, parent_context, instrumentation_scope) # rubocop:disable Metrics/MethodLength
parent_span = OpenTelemetry::Trace.current_span(parent_context)
parent_span_context = parent_span.context
if parent_span_context.valid?
parent_span_id = parent_span_context.span_id
trace_id = parent_span_context.trace_id
end
trace_id ||= @id_generator.generate_trace_id
result = @sampler.should_sample?(trace_id: trace_id, parent_context: parent_context, links: links, name: name, kind: kind, attributes: attributes)
span_id = @id_generator.generate_span_id
if result.recording? && !@stopped
trace_flags = result.sampled? ? OpenTelemetry::Trace::TraceFlags::SAMPLED : OpenTelemetry::Trace::TraceFlags::DEFAULT
context = OpenTelemetry::Trace::SpanContext.new(trace_id: trace_id, span_id: span_id, trace_flags: trace_flags, tracestate: result.tracestate)
attributes = attributes&.merge(result.attributes) || result.attributes
Span.new(
context,
parent_context,
parent_span,
name,
kind,
parent_span_id,
@span_limits,
@span_processors,
attributes,
links,
start_timestamp,
@resource,
instrumentation_scope
)
else
OpenTelemetry::Trace.non_recording_span(OpenTelemetry::Trace::SpanContext.new(trace_id: trace_id, span_id: span_id, tracestate: result.tracestate))
end
end
end
end
end
end
# opentelemetry-api-1.1.0/lib/opentelemetry/trace.rb
module OpenTelemetry::Trace
def with_span(span)
Context.with_value(CURRENT_SPAN_KEY, span) { |c, s| yield s, c }
end
end
另外可见,如果使用过
OpenTelemetry::Common::Utilities.untraced{ ... }
,则会创建一个{UNTRACED_KEY => true}
的context,在此context内不会创建新的Span
# opentelemetry-common-0.19.7/lib/opentelemetry/common/utilities.rb
module OpenTelemetry
module Common
module Utilities
def untraced
Context.with_value(UNTRACED_KEY, true) do |ctx, _|
yield ctx
end
end
def untraced?(context = nil)
context ||= Context.current
!!context.value(UNTRACED_KEY)
end
end
end
end