-
Notifications
You must be signed in to change notification settings - Fork 251
/
Copy pathspan.rb
476 lines (431 loc) · 19.1 KB
/
span.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# frozen_string_literal: true
# Copyright The OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0
module OpenTelemetry
module SDK
module Trace
# Implementation of {OpenTelemetry::Trace::Span} that records trace events.
#
# This implementation includes reader methods intended to allow access to
# internal state by {SpanProcessor}s.
# Instrumentation should use the API provided by {OpenTelemetry::Trace::Span}
# and should consider {Span} to be write-only.
#
# rubocop:disable Metrics/ClassLength
class Span < OpenTelemetry::Trace::Span
DEFAULT_STATUS = OpenTelemetry::Trace::Status.unset
EMPTY_ATTRIBUTES = {}.freeze
private_constant :DEFAULT_STATUS, :EMPTY_ATTRIBUTES
# The following readers are intended for the use of SpanProcessors and
# should not be considered part of the public interface for instrumentation.
attr_reader :name, :status, :kind, :parent_span_id, :start_timestamp, :end_timestamp, :links, :resource, :instrumentation_scope
# Returns an InstrumentationScope struct, which is backwards compatible with InstrumentationLibrary.
# @deprecated Please use instrumentation_scope instead.
#
# @return InstrumentationScope
alias instrumentation_library instrumentation_scope
# Return a frozen copy of the current attributes. This is intended for
# use of SpanProcessors and should not be considered part of the public
# interface for instrumentation.
#
# @return [Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}] may be nil.
def attributes
# Don't bother synchronizing. Access by SpanProcessors is expected to
# be serialized.
@attributes&.clone.freeze
end
# Return a frozen copy of the current events. This is intended for use
# of SpanProcessors and should not be considered part of the public
# interface for instrumentation.
#
# @return [Array<Event>] may be nil.
def events
# Don't bother synchronizing. Access by SpanProcessors is expected to
# be serialized.
@events&.clone.freeze
end
# Return the flag whether this span is recording events
#
# @return [Boolean] true if this Span is active and recording information
# like events with the #add_event operation and attributes using
# #set_attribute.
def recording?
!@ended
end
# Set attribute
#
# Note that the OpenTelemetry project
# {https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md
# documents} certain "standard attributes" that have prescribed semantic
# meanings.
#
# @param [String] key
# @param [String, Boolean, Numeric, Array<String, Numeric, Boolean>] value
# Values must be non-nil and (array of) string, boolean or numeric type.
# Array values must not contain nil elements and all elements must be of
# the same basic type (string, numeric, boolean).
#
# @return [self] returns itself
def set_attribute(key, value)
@mutex.synchronize do
if @ended
OpenTelemetry.logger.warn('Calling set_attribute on an ended Span.')
else
@attributes ||= {}
@attributes[key] = value
trim_span_attributes(@attributes)
@total_recorded_attributes += 1
end
end
self
end
alias []= set_attribute
# Add attributes
#
# Note that the OpenTelemetry project
# {https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md
# documents} certain "standard attributes" that have prescribed semantic
# meanings.
#
# @param [Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}] attributes
# Values must be non-nil and (array of) string, boolean or numeric type.
# Array values must not contain nil elements and all elements must be of
# the same basic type (string, numeric, boolean).
#
# @return [self] returns itself
def add_attributes(attributes)
@mutex.synchronize do
if @ended
OpenTelemetry.logger.warn('Calling add_attributes on an ended Span.')
else
@attributes ||= {}
@attributes.merge!(attributes)
trim_span_attributes(@attributes)
@total_recorded_attributes += attributes.size
end
end
self
end
# Add a link to a {Span}.
#
# Adding links at span creation using the `links` option is preferred
# to calling add_link later, because head sampling decisions can only
# consider information present during span creation.
#
# Example:
#
# span.add_link(OpenTelemetry::Trace::Link.new(span_to_link_from.context))
#
# Note that the OpenTelemetry project
# {https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md
# documents} certain "standard attributes" that have prescribed semantic
# meanings.
#
# @param [OpenTelemetry::Trace::Link] the link object to add on the {Span}.
#
# @return [self] returns itself
def add_link(link)
@mutex.synchronize do
if @ended
OpenTelemetry.logger.warn('Calling add_link on an ended Span.')
else
@links ||= []
@links = trim_links(@links << link, @span_limits.link_count_limit, @span_limits.link_attribute_count_limit)
@total_recorded_links += 1
end
end
self
end
# Add an Event to a {Span}.
#
# Example:
#
# span.add_event('event', attributes: {'eager' => true})
#
# Note that the OpenTelemetry project
# {https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md
# documents} certain "standard event names and keys" which have
# prescribed semantic meanings.
#
# @param [String] name Name of the event.
# @param [optional Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}] attributes
# One or more key:value pairs, where the keys must be strings and the
# values may be (array of) string, boolean or numeric type.
# @param [optional Time] timestamp Optional timestamp for the event.
#
# @return [self] returns itself
def add_event(name, attributes: nil, timestamp: nil)
event = Event.new(name, truncate_attribute_values(attributes, @span_limits.event_attribute_length_limit), relative_timestamp(timestamp))
@mutex.synchronize do
if @ended
OpenTelemetry.logger.warn('Calling add_event on an ended Span.')
else
@events ||= []
@events = append_event(@events, event)
@total_recorded_events += 1
end
end
self
end
# Record an exception during the execution of this span. Multiple exceptions
# can be recorded on a span.
#
# @param [Exception] exception The exception to be recorded
# @param [optional Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}]
# attributes One or more key:value pairs, where the keys must be
# strings and the values may be (array of) string, boolean or numeric
# type.
#
# @return [void]
def record_exception(exception, attributes: nil)
event_attributes = {
'exception.type' => exception.class.to_s,
'exception.message' => exception.message,
'exception.stacktrace' => exception.full_message(highlight: false, order: :top).encode('UTF-8', invalid: :replace, undef: :replace, replace: '�')
}
event_attributes.merge!(attributes) unless attributes.nil?
add_event('exception', attributes: event_attributes)
end
# Sets the Status to the Span
#
# If used, this will override the default Span status. Default has code = Status::UNSET.
#
# An attempt to set the status with code == Status::UNSET is ignored.
# If the status is set with code == Status::OK, any further attempt to set the status
# is ignored.
#
# @param [Status] status The new status, which overrides the default Span
# status, which has code = Status::UNSET.
#
# @return [void]
def status=(status)
return if status.code == OpenTelemetry::Trace::Status::UNSET
@mutex.synchronize do
if @ended
OpenTelemetry.logger.warn('Calling status= on an ended Span.')
elsif @status.code != OpenTelemetry::Trace::Status::OK
@status = status
end
end
end
# Updates the Span name
#
# Upon this update, any sampling behavior based on Span name will depend
# on the implementation.
#
# @param [String] new_name The new operation name, which supersedes
# whatever was passed in when the Span was started
#
# @return [void]
def name=(new_name)
@mutex.synchronize do
if @ended
OpenTelemetry.logger.warn('Calling name= on an ended Span.')
else
@name = new_name
end
end
end
# Finishes the Span
#
# Implementations MUST ignore all subsequent calls to {#finish} (there
# might be exceptions when Tracer is streaming event and has no mutable
# state associated with the Span).
#
# Call to {#finish} MUST not have any effects on child spans. Those may
# still be running and can be ended later.
#
# This API MUST be non-blocking*.
#
# (*) not actually non-blocking. In particular, it synchronizes on an
# internal mutex, which will typically be uncontended, and
# {Export::BatchSpanProcessor} will also synchronize on a mutex, if that
# processor is used.
#
# @param [Time] end_timestamp optional end timestamp for the span.
#
# @return [self] returns itself
def finish(end_timestamp: nil)
@mutex.synchronize do
if @ended
OpenTelemetry.logger.warn('Calling finish on an ended Span.')
return self
end
@end_timestamp = relative_timestamp(end_timestamp)
@attributes = validated_attributes(@attributes).freeze
@events.freeze
@links.freeze
@ended = true
end
@span_processors.each { |processor| processor.on_finish(self) }
self
end
# @api private
#
# Returns a SpanData containing a snapshot of the Span fields. It is
# assumed that the Span has been finished, and that no further
# modifications will be made to the Span.
#
# This method should be called *only* from a SpanProcessor prior to
# calling the SpanExporter.
#
# @return [SpanData]
def to_span_data
SpanData.new(
@name,
@kind,
@status,
@parent_span_id,
@total_recorded_attributes,
@total_recorded_events,
@total_recorded_links,
@start_timestamp,
@end_timestamp,
@attributes,
@links,
@events,
@resource,
@instrumentation_scope,
context.span_id,
context.trace_id,
context.trace_flags,
context.tracestate
)
end
# @api private
def initialize(context, parent_context, parent_span, name, kind, parent_span_id, span_limits, span_processors, attributes, links, start_timestamp, resource, instrumentation_scope) # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
super(span_context: context)
@mutex = Mutex.new
@name = name
@kind = kind
@parent_span_id = parent_span_id.freeze || OpenTelemetry::Trace::INVALID_SPAN_ID
@span_limits = span_limits
@span_processors = span_processors
@resource = resource
@instrumentation_scope = instrumentation_scope
@ended = false
@status = DEFAULT_STATUS
@total_recorded_events = 0
@total_recorded_links = links&.size || 0
@total_recorded_attributes = attributes&.size || 0
@attributes = attributes
trim_span_attributes(@attributes)
@events = nil
@links = trim_links(links, span_limits.link_count_limit, span_limits.link_attribute_count_limit)
# Times are hard. Whenever an explicit timestamp is provided
# (for Events or for the Span start_timestamp or end_timestamp),
# we use that as the recorded timestamp. An implicit Event timestamp
# and end_timestamp is computed as a monotonic clock offset from
# the realtime start_timestamp. The realtime start_timestamp is
# computed as a monotonic clock offset from the realtime
# start_timestamp of its parent span, if available, or it is
# fetched from the realtime system clock.
#
# We therefore have 3 start timestamps. The first two are used
# internally (and by child spans) to compute other timestamps.
# The last is the start timestamp actually recorded in the
# SpanData.
@monotonic_start_timestamp = monotonic_now
@realtime_start_timestamp = if parent_span.recording?
relative_realtime(parent_span.realtime_start_timestamp, parent_span.monotonic_start_timestamp, @monotonic_start_timestamp)
else
realtime_now
end
@start_timestamp = if start_timestamp
time_in_nanoseconds(start_timestamp)
else
@realtime_start_timestamp
end
@end_timestamp = nil
@span_processors.each { |processor| processor.on_start(self, parent_context) }
end
# TODO: Java implementation overrides finalize to log if a span isn't finished.
protected
attr_reader :monotonic_start_timestamp, :realtime_start_timestamp
private
def validated_attributes(attrs)
return attrs if Internal.valid_attributes?(name, 'span', attrs)
attrs.keep_if { |key, value| Internal.valid_key?(key) && Internal.valid_value?(value) }
end
def trim_span_attributes(attrs)
return if attrs.nil?
if attrs.size > @span_limits.attribute_count_limit
n = @span_limits.attribute_count_limit
attrs.delete_if do |_key, _value|
n -= 1
n.negative?
end
end
truncate_attribute_values(attrs, @span_limits.attribute_length_limit)
nil
end
def truncate_attribute_values(attrs, attribute_length_limit)
return EMPTY_ATTRIBUTES if attrs.nil?
return attrs if attribute_length_limit.nil?
attrs.transform_values! { |value| OpenTelemetry::Common::Utilities.truncate_attribute_value(value, attribute_length_limit) }
attrs
end
def trim_links(links, link_count_limit, link_attribute_count_limit) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
# Fast path (likely) common cases.
return nil if links.nil?
if links.size <= link_count_limit &&
links.all? { |link| link.span_context.valid? && link.attributes.size <= link_attribute_count_limit && Internal.valid_attributes?(name, 'link', link.attributes) }
return links
end
# Slow path: trim attributes for each Link.
valid_links = links.select { |link| link.span_context.valid? }
excess_link_count = valid_links.size - link_count_limit
valid_links.pop(excess_link_count) if excess_link_count.positive?
valid_links.map! do |link|
attrs = Hash[link.attributes] # link.attributes is frozen, so we need an unfrozen copy to adjust.
attrs.keep_if { |key, value| Internal.valid_key?(key) && Internal.valid_value?(value) }
excess = attrs.size - link_attribute_count_limit
excess.times { attrs.shift } if excess.positive?
OpenTelemetry::Trace::Link.new(link.span_context, attrs)
end
end
def append_event(events, event) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
event_count_limit = @span_limits.event_count_limit
event_attribute_count_limit = @span_limits.event_attribute_count_limit
valid_attributes = Internal.valid_attributes?(name, 'event', event.attributes)
# Fast path (likely) common case.
if events.size < event_count_limit &&
event.attributes.size <= event_attribute_count_limit &&
valid_attributes
return events << event
end
# Slow path.
excess = events.size + 1 - event_count_limit
events.shift(excess) if excess.positive?
excess = event.attributes.size - event_attribute_count_limit
if excess.positive? || !valid_attributes
attrs = Hash[event.attributes] # event.attributes is frozen, so we need an unfrozen copy to adjust.
attrs.keep_if { |key, value| Internal.valid_key?(key) && Internal.valid_value?(value) }
excess = attrs.size - event_attribute_count_limit
excess.times { attrs.shift } if excess.positive?
event = Event.new(event.name, attrs.freeze, event.timestamp)
end
events << event
end
def relative_timestamp(timestamp)
return time_in_nanoseconds(timestamp) unless timestamp.nil?
relative_realtime(realtime_start_timestamp, monotonic_start_timestamp, monotonic_now)
end
def time_in_nanoseconds(timestamp)
(timestamp.to_r * 1_000_000_000).to_i
end
def relative_realtime(realtime_base, monotonic_base, now)
realtime_base + (now - monotonic_base)
end
def realtime_now
Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
end
def monotonic_now
Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
end
end
# rubocop:enable Metrics/ClassLength
end
end
end