Skip to content

Commit 91fa0bb

Browse files
committed
Tracing.continue_from! with a block maintains active trace until the block ends
1 parent eae7fc6 commit 91fa0bb

File tree

6 files changed

+564
-280
lines changed

6 files changed

+564
-280
lines changed

docs/GettingStarted.md

Lines changed: 229 additions & 230 deletions
Large diffs are not rendered by default.

lib/datadog/tracing/trace_operation.rb

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ class TraceOperation
5555
:sampled,
5656
:service
5757

58+
# Creates a new TraceOperation.
59+
#
60+
# @param auto_finish [Boolean] when true, automatically finishes the trace when the local root span finishes.
61+
# When false, the trace remains unfinished until {#finish!} is called.
62+
# This is useful when this {TraceOperation} represents the continuation of a remote {TraceDigest},
63+
# in which case local root spans in this {TraceOperation} are children of the {TraceDigest}'s last active span.
5864
def initialize(
5965
logger: Datadog.logger,
6066
agent_sample_rate: nil,
@@ -80,7 +86,8 @@ def initialize(
8086
trace_state_unknown_fields: nil,
8187
remote_parent: false,
8288
tracer: nil, # DEV-3.0: deprecated, remove in 3.0
83-
baggage: nil
89+
baggage: nil,
90+
auto_finish: true
8491
)
8592
@logger = logger
8693

@@ -119,6 +126,7 @@ def initialize(
119126
@events = events || Events.new
120127
@finished = false
121128
@spans = []
129+
@auto_finish = auto_finish
122130
end
123131

124132
def full?
@@ -318,6 +326,29 @@ def flush!
318326
build_trace(spans, !finished)
319327
end
320328

329+
# When automatic context management is disabled (@auto_finish is false),
330+
# this method finishes the trace, marking it as completed.
331+
#
332+
# The trace will **not** automatically finish when its local root span
333+
# when @auto_finish is false, thus calling this method is mandatory
334+
# in such scenario.
335+
#
336+
# Unfinished spans are discarded.
337+
#
338+
# This method is idempotent and safe to call after the trace is finished.
339+
# It is also a no-op when @auto_finish is true, to prevent misuse.
340+
#
341+
# @!visibility private
342+
def finish!
343+
return if @auto_finish || finished?
344+
345+
@finished = true
346+
@active_span = nil
347+
@active_span_count = 0
348+
349+
events.trace_finished.publish(self)
350+
end
351+
321352
# Returns a set of trace headers used for continuing traces.
322353
# Used for propagation across execution contexts.
323354
# Data should reflect the active state of the trace.
@@ -349,7 +380,7 @@ def to_digest
349380
trace_state: @trace_state,
350381
trace_state_unknown_fields: @trace_state_unknown_fields,
351382
span_remote: @remote_parent && @active_span.nil?,
352-
baggage: (@baggage.nil? || @baggage.empty?) ? nil : @baggage
383+
baggage: @baggage.nil? || @baggage.empty? ? nil : @baggage
353384
).freeze
354385
end
355386

@@ -460,7 +491,7 @@ def activate_span!(span_op)
460491

461492
@active_span = span_op
462493

463-
set_root_span!(span_op) unless root_span
494+
set_local_root_span!(span_op)
464495
end
465496

466497
def deactivate_span!(span_op)
@@ -483,15 +514,22 @@ def start_span(span_op)
483514
logger.debug { "Error starting span on trace: #{e} Backtrace: #{e.backtrace.first(3)}" }
484515
end
485516

517+
# For traces with automatic context management (auto_finish),
518+
# when the local root span finishes, the trace also finishes.
519+
# The trace cannot receive new spans after finished.
520+
#
521+
# Without auto_finish, the trace can still receive spans
522+
# until explicitly finished.
486523
def finish_span(span, span_op, parent)
487524
# Save finished span & root span
488525
@spans << span unless span.nil?
489526

490527
# Deactivate the span, re-activate parent.
491528
deactivate_span!(span_op)
492529

493-
# Set finished, to signal root span has completed.
494-
@finished = true if span_op == root_span
530+
# Finish if the local root span is finished and automatic
531+
# context management is enabled.
532+
@finished = true if span_op == root_span && @auto_finish
495533

496534
# Update active span count
497535
@active_span_count -= 1
@@ -505,8 +543,8 @@ def finish_span(span, span_op, parent)
505543
logger.debug { "Error finishing span on trace: #{e} Backtrace: #{e.backtrace.first(3)}" }
506544
end
507545

508-
# Track the root span
509-
def set_root_span!(span)
546+
# Track the root {SpanOperation} object from the current execution context.
547+
def set_local_root_span!(span)
510548
return if span.nil? || root_span
511549

512550
@root_span = span
@@ -531,7 +569,7 @@ def build_trace(spans, partial = false)
531569
service: service,
532570
tags: meta,
533571
metrics: metrics,
534-
root_span_id: (!partial) ? root_span&.id : nil,
572+
root_span_id: !partial ? root_span&.id : nil,
535573
profiling_enabled: @profiling_enabled,
536574
apm_tracing_enabled: @apm_tracing_enabled
537575
)

lib/datadog/tracing/tracer.rb

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,10 @@ def trace(
149149
context = call_context
150150
active_trace = context.active_trace
151151
trace = if continue_from || active_trace.nil?
152-
start_trace(continue_from: continue_from)
153-
else
154-
active_trace
155-
end
152+
start_trace(continue_from: continue_from)
153+
else
154+
active_trace
155+
end
156156
rescue => e
157157
logger.debug { "Failed to trace: #{e}" }
158158

@@ -241,10 +241,21 @@ def active_correlation(key = nil)
241241
trace.to_correlation
242242
end
243243

244-
# Setup a new trace to continue from where another
244+
# Setup a new trace execution context to continue from where another
245245
# trace left off.
246+
# This is useful to continue distributed or async traces.
247+
#
248+
# The first span created in the restored context is a direct child of the
249+
# active span from when the {Datadog::Tracing::TraceDigest} was created.
250+
#
251+
# When no block is given, the trace context is restored in the current thread.
252+
# It remains active until the first span created in this restored context is finished.
253+
# After that, if a new span is created, it start a new, unrelated trace.
246254
#
247-
# Used to continue distributed or async traces.
255+
# When a block is given, the trace context is restored inside the block execution.
256+
# It remains active until the block ends, even when the first span created inside
257+
# the block finishes. This means that multiple spans can be direct children of the
258+
# active span from when the {Datadog::Tracing::TraceDigest} was created.
248259
#
249260
# @param [Datadog::Tracing::TraceDigest] digest continue from the {Datadog::Tracing::TraceDigest}.
250261
# @param [Thread] key Thread to retrieve trace from. Defaults to current thread. For internal use only.
@@ -260,13 +271,32 @@ def continue_trace!(digest, key = nil, &block)
260271
# Start a new trace from the digest
261272
context = call_context(key)
262273
original_trace = active_trace(key)
263-
trace = start_trace(continue_from: digest)
274+
# When we want the trace to be bound to a block, we cannot let
275+
# it auto finish when the local root span finishes. This would
276+
# create mutiple traces inside the block. Instead, we'll
277+
# expliclity finish the trace after the block finishes.
278+
auto_finish = !block
279+
280+
trace = start_trace(continue_from: digest, auto_finish: auto_finish)
264281

265282
# If block hasn't been given; we need to manually deactivate
266283
# this trace. Subscribe to the trace finished event to do this.
267284
subscribe_trace_deactivation!(context, trace, original_trace) unless block
268285

269-
context.activate!(trace, &block)
286+
if block
287+
# When a block is given, the trace will be active until the block finishes.
288+
context.activate!(trace) do
289+
yield
290+
ensure # We have to flush even when an error occurs
291+
# On block completion, force the trace to finish and flush its finished spans.
292+
# Unfinished spans are lost as the {TraceOperation} has ended.
293+
trace.finish!
294+
flush_trace(trace)
295+
end
296+
else
297+
# Otherwise, the trace will be bound to the current thread after this point
298+
context.activate!(trace)
299+
end
270300
end
271301

272302
# Sample a span, tagging the trace as appropriate.
@@ -329,15 +359,15 @@ def call_context(key = nil)
329359
@provider.context(key)
330360
end
331361

332-
def build_trace(digest = nil)
362+
def build_trace(digest, auto_finish)
333363
# Resolve hostname if configured
334364
hostname = Core::Environment::Socket.hostname if Datadog.configuration.tracing.report_hostname
335-
hostname = (hostname && !hostname.empty?) ? hostname : nil
365+
hostname = hostname && !hostname.empty? ? hostname : nil
336366

337367
if digest
338368
sampling_priority = if propagate_sampling_priority?(upstream_tags: digest.trace_distributed_tags)
339-
digest.trace_sampling_priority
340-
end
369+
digest.trace_sampling_priority
370+
end
341371
TraceOperation.new(
342372
logger: logger,
343373
hostname: hostname,
@@ -353,7 +383,8 @@ def build_trace(digest = nil)
353383
trace_state_unknown_fields: digest.trace_state_unknown_fields,
354384
remote_parent: digest.span_remote,
355385
tracer: self,
356-
baggage: digest.baggage
386+
baggage: digest.baggage,
387+
auto_finish: auto_finish
357388
)
358389
else
359390
TraceOperation.new(
@@ -362,13 +393,13 @@ def build_trace(digest = nil)
362393
profiling_enabled: profiling_enabled,
363394
apm_tracing_enabled: apm_tracing_enabled,
364395
remote_parent: false,
365-
tracer: self
396+
tracer: self,
397+
auto_finish: auto_finish
366398
)
367399
end
368400
end
369401
# rubocop:enable Metrics/MethodLength
370402

371-
# rubocop:disable Metrics/MethodLength
372403
def bind_trace_events!(trace_op)
373404
events = trace_op.send(:events)
374405

@@ -387,13 +418,12 @@ def bind_trace_events!(trace_op)
387418
flush_trace(event_trace_op)
388419
end
389420
end
390-
# rubocop:enable Metrics/MethodLength
391421

392422
# Creates a new TraceOperation, with events bounds to this Tracer instance.
393423
# @return [TraceOperation]
394-
def start_trace(continue_from: nil)
424+
def start_trace(continue_from: nil, auto_finish: true)
395425
# Build a new trace using digest if provided.
396-
trace = build_trace(continue_from)
426+
trace = build_trace(continue_from, auto_finish)
397427

398428
# Bind trace events: sample trace, set default service, flush spans.
399429
bind_trace_events!(trace)
@@ -402,7 +432,6 @@ def start_trace(continue_from: nil)
402432
end
403433

404434
# rubocop:disable Lint/UnderscorePrefixedVariableName
405-
# rubocop:disable Metrics/MethodLength
406435
def start_span(
407436
name,
408437
continue_from: nil,
@@ -454,18 +483,17 @@ def start_span(
454483
span
455484
end
456485
end
457-
# rubocop:enable Lint/UnderscorePrefixedVariableName
458-
# rubocop:enable Metrics/MethodLength
459486

487+
# rubocop:enable Lint/UnderscorePrefixedVariableName
460488
def resolve_tags(tags, service)
461489
merged_tags = if @tags.any? && tags
462-
# Combine default tags with provided tags,
463-
# preferring provided tags.
464-
@tags.merge(tags)
465-
else
466-
# Use provided tags or default tags if none.
467-
tags || @tags.dup
468-
end
490+
# Combine default tags with provided tags,
491+
# preferring provided tags.
492+
@tags.merge(tags)
493+
else
494+
# Use provided tags or default tags if none.
495+
tags || @tags.dup
496+
end
469497
# Remove version tag if service is not the default service
470498
if merged_tags.key?(Core::Environment::Ext::TAG_VERSION) && service && service != @default_service
471499
merged_tags.delete(Core::Environment::Ext::TAG_VERSION)

sig/datadog/tracing/trace_operation.rbs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ module Datadog
44
include Metadata::Tagging
55

66
DEFAULT_MAX_LENGTH: ::Integer
7-
7+
88
@logger: Core::Logger
9-
9+
1010
attr_reader logger: Core::Logger
1111

1212
attr_accessor agent_sample_rate: untyped
@@ -17,17 +17,23 @@ module Datadog
1717
attr_accessor sample_rate: untyped
1818
attr_accessor remote_parent: untyped
1919
attr_accessor sampling_priority: untyped
20+
attr_accessor baggage: untyped
2021
attr_reader active_span_count: untyped
2122
attr_reader active_span: untyped
2223
attr_reader id: untyped
2324
attr_reader max_length: untyped
2425
attr_reader parent_span_id: untyped
26+
attr_reader trace_state: untyped
27+
attr_reader trace_state_unknown_fields: untyped
2528
attr_writer name: untyped
2629
attr_writer resource: untyped
2730
attr_writer sampled: untyped
2831
attr_writer service: untyped
2932

30-
def initialize: (?agent_sample_rate: untyped?, ?events: untyped?, ?hostname: untyped?, ?id: untyped?, ?max_length: untyped, ?name: untyped?, ?origin: untyped?, ?parent_span_id: untyped?, ?rate_limiter_rate: untyped?, ?resource: untyped?, ?rule_sample_rate: untyped?, ?sample_rate: untyped?, ?sampled: untyped?, ?sampling_priority: untyped?, ?service: untyped?, ?profiling_enabled: untyped?, ?apm_tracing_enabled: untyped?, ?tags: untyped?, ?metrics: untyped?, ?remote_parent: untyped?) -> void
33+
def initialize: (?logger: untyped, ?agent_sample_rate: untyped?, ?events: untyped?, ?hostname: untyped?, ?id: untyped?, ?max_length: untyped, ?name: untyped?, ?origin: untyped?, ?parent_span_id: untyped?, ?rate_limiter_rate: untyped?, ?resource: untyped?, ?rule_sample_rate: untyped?, ?sample_rate: untyped?, ?sampled: untyped?, ?sampling_priority: untyped?, ?service: untyped?, ?profiling_enabled: untyped?, ?apm_tracing_enabled: untyped?, ?tags: untyped?, ?metrics: untyped?, ?trace_state: untyped?, ?trace_state_unknown_fields: untyped?, ?remote_parent: untyped?, ?tracer: untyped?, ?baggage: untyped?, ?auto_finish: bool) -> void
34+
35+
def finish!: -> void
36+
3137
def full?: () -> untyped
3238
def finished_span_count: () -> untyped
3339
def finished?: () -> untyped

0 commit comments

Comments
 (0)