Class: Datadog::Context

Inherits:
Object
  • Object
show all
Defined in:
lib/ddtrace/context.rb

Overview

Context is used to keep track of a hierarchy of spans for the current execution flow. During each logical execution, the same Context is used to represent a single logical trace, even if the trace is built asynchronously.

A single code execution may use multiple Context if part of the execution must not be related to the current tracing. As example, a delayed job may compose a standalone trace instead of being related to the same trace that generates the job itself. On the other hand, if it’s part of the same Context, it will be related to the original trace.

This data structure is thread-safe. rubocop:disable Metrics/ClassLength

Constant Summary collapse

DEFAULT_MAX_LENGTH =

100k spans is about a 100Mb footprint

100_000

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Context

Initialize a new thread-safe Context.



28
29
30
31
32
33
34
# File 'lib/ddtrace/context.rb', line 28

def initialize(options = {})
  @mutex = Mutex.new
  # max_length is the amount of spans above which, for a given trace,
  # the context will simply drop and ignore spans, avoiding high memory usage.
  @max_length = options.fetch(:max_length, DEFAULT_MAX_LENGTH)
  reset(options)
end

Instance Attribute Details

#max_lengthObject (readonly)

Returns the value of attribute max_length.



25
26
27
# File 'lib/ddtrace/context.rb', line 25

def max_length
  @max_length
end

Instance Method Details

#add_span(span) ⇒ Object

Add a span to the context trace list, keeping it as the last active span.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/ddtrace/context.rb', line 89

def add_span(span)
  @mutex.synchronize do
    # If hitting the hard limit, just drop spans. This is really a rare case
    # as it means despite the soft limit, the hard limit is reached, so the trace
    # by default has 10000 spans, all of which belong to unfinished parts of a
    # larger trace. This is a catch-all to reduce global memory usage.
    if @max_length > 0 && @trace.length >= @max_length
      # Detach the span from any context, it's being dropped and ignored.
      span.context = nil
      Datadog.logger.debug("context full, ignoring span #{span.name}")

      # If overflow has already occurred, don't send this metric.
      # Prevents metrics spam if buffer repeatedly overflows for the same trace.
      unless @overflow
        Datadog.health_metrics.error_context_overflow(1, tags: ["max_length:#{@max_length}"])
        @overflow = true
      end

      return
    end
    set_current_span(span)
    @current_root_span = span if @trace.empty?
    @trace << span
    span.context = self
  end
end

#annotate_for_flush!(span) ⇒ Object

Set tags to root span required for flush



222
223
224
225
# File 'lib/ddtrace/context.rb', line 222

def annotate_for_flush!(span)
  attach_sampling_priority(span) if @sampled && @sampling_priority
  attach_origin(span) if @origin
end

#close_span(span) ⇒ Object

Mark a span as a finished, increasing the internal counter to prevent cycles inside _trace list.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/ddtrace/context.rb', line 118

def close_span(span)
  @mutex.synchronize do
    @finished_spans += 1
    # Current span is only meaningful for linear tree-like traces,
    # in other cases, this is just broken and one should rely
    # on per-instrumentation code to retrieve handle parent/child relations.
    set_current_span(span.parent)
    return if span.tracer.nil?
    if span.parent.nil? && !all_spans_finished?
      if Datadog.configuration.diagnostics.debug
        opened_spans = @trace.length - @finished_spans
        Datadog.logger.debug("root span #{span.name} closed but has #{opened_spans} unfinished spans:")
      end

      @trace.reject(&:finished?).group_by(&:name).each do |unfinished_span_name, unfinished_spans|
        Datadog.logger.debug("unfinished span: #{unfinished_spans.first}") if Datadog.configuration.diagnostics.debug
        Datadog.health_metrics.error_unfinished_spans(
          unfinished_spans.length,
          tags: ["name:#{unfinished_span_name}"]
        )
      end
    end
  end
end

#current_root_spanObject



82
83
84
85
86
# File 'lib/ddtrace/context.rb', line 82

def current_root_span
  @mutex.synchronize do
    return @current_root_span
  end
end

#current_spanObject

Return the last active span that corresponds to the last inserted item in the trace list. This cannot be considered as the current active span in asynchronous environments, because some spans can be closed earlier while child spans still need to finish their traced execution.



76
77
78
79
80
# File 'lib/ddtrace/context.rb', line 76

def current_span
  @mutex.synchronize do
    return @current_span
  end
end

#delete_span_ifArray<Span>

Delete any span matching the condition. This is thread safe.

Returns:

  • (Array<Span>)

    deleted spans



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/ddtrace/context.rb', line 197

def delete_span_if
  @mutex.synchronize do
    [].tap do |deleted_spans|
      @trace.delete_if do |span|
        finished = span.finished?

        next unless yield span

        deleted_spans << span

        # We need to detach the span from the context, else, some code
        # finishing it afterwards would mess up with the number of
        # finished_spans and possibly cause other side effects.
        span.context = nil
        # Acknowledge there's one span less to finish, if needed.
        # It's very important to keep this balanced.
        @finished_spans -= 1 if finished

        true
      end
    end
  end
end

#finished?Boolean

Returns if the trace for the current Context is finished or not. A Context is considered finished if all spans in this context are finished.

Returns:

  • (Boolean)


145
146
147
148
149
# File 'lib/ddtrace/context.rb', line 145

def finished?
  @mutex.synchronize do
    return all_spans_finished?
  end
end

#finished_span_countObject

@@return [Numeric] numbers of finished spans



152
153
154
155
156
# File 'lib/ddtrace/context.rb', line 152

def finished_span_count
  @mutex.synchronize do
    @finished_spans
  end
end

#getArray<Array<Span>, Boolean>

Returns both the trace list generated in the current context and if the context is sampled or not.

It returns [nil,@sampled] if the Context is not finished.

If a trace is returned, the Context will be reset so that it can be re-used immediately.

This operation is thread-safe.

Returns:

  • (Array<Array<Span>, Boolean>)

    finished trace and sampled flag



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/ddtrace/context.rb', line 178

def get
  @mutex.synchronize do
    trace = @trace
    sampled = @sampled

    # still return sampled attribute, even if context is not finished
    return nil, sampled unless all_spans_finished?

    # Root span is finished at this point, we can configure it
    annotate_for_flush!(@current_root_span)

    reset
    [trace, sampled]
  end
end

#originObject



60
61
62
63
64
# File 'lib/ddtrace/context.rb', line 60

def origin
  @mutex.synchronize do
    @origin
  end
end

#origin=(origin) ⇒ Object



66
67
68
69
70
# File 'lib/ddtrace/context.rb', line 66

def origin=(origin)
  @mutex.synchronize do
    @origin = origin
  end
end

#sampled?Boolean

Returns true if the context is sampled, that is, if it should be kept and sent to the trace agent.

Returns:

  • (Boolean)


160
161
162
163
164
# File 'lib/ddtrace/context.rb', line 160

def sampled?
  @mutex.synchronize do
    return @sampled
  end
end

#sampling_priorityObject



48
49
50
51
52
# File 'lib/ddtrace/context.rb', line 48

def sampling_priority
  @mutex.synchronize do
    @sampling_priority
  end
end

#sampling_priority=(priority) ⇒ Object



54
55
56
57
58
# File 'lib/ddtrace/context.rb', line 54

def sampling_priority=(priority)
  @mutex.synchronize do
    @sampling_priority = priority
  end
end

#span_idObject



42
43
44
45
46
# File 'lib/ddtrace/context.rb', line 42

def span_id
  @mutex.synchronize do
    @parent_span_id
  end
end

#to_sObject

Return a string representation of the context.



228
229
230
231
232
233
# File 'lib/ddtrace/context.rb', line 228

def to_s
  @mutex.synchronize do
    # rubocop:disable Metrics/LineLength
    "Context(trace.length:#{@trace.length},sampled:#{@sampled},finished_spans:#{@finished_spans},current_span:#{@current_span})"
  end
end

#trace_idObject



36
37
38
39
40
# File 'lib/ddtrace/context.rb', line 36

def trace_id
  @mutex.synchronize do
    @parent_trace_id
  end
end