opentelemetry-sdk 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -0
  3. data/LICENSE +201 -0
  4. data/lib/opentelemetry/sdk.rb +18 -0
  5. data/lib/opentelemetry/sdk/internal.rb +32 -0
  6. data/lib/opentelemetry/sdk/resources.rb +15 -0
  7. data/lib/opentelemetry/sdk/resources/resource.rb +75 -0
  8. data/lib/opentelemetry/sdk/trace.rb +24 -0
  9. data/lib/opentelemetry/sdk/trace/config.rb +18 -0
  10. data/lib/opentelemetry/sdk/trace/config/trace_config.rb +77 -0
  11. data/lib/opentelemetry/sdk/trace/export.rb +35 -0
  12. data/lib/opentelemetry/sdk/trace/export/batch_span_processor.rb +149 -0
  13. data/lib/opentelemetry/sdk/trace/export/console_span_exporter.rb +40 -0
  14. data/lib/opentelemetry/sdk/trace/export/in_memory_span_exporter.rb +86 -0
  15. data/lib/opentelemetry/sdk/trace/export/multi_span_exporter.rb +64 -0
  16. data/lib/opentelemetry/sdk/trace/export/noop_span_exporter.rb +42 -0
  17. data/lib/opentelemetry/sdk/trace/export/simple_span_processor.rb +63 -0
  18. data/lib/opentelemetry/sdk/trace/multi_span_processor.rb +51 -0
  19. data/lib/opentelemetry/sdk/trace/noop_span_processor.rb +41 -0
  20. data/lib/opentelemetry/sdk/trace/samplers.rb +106 -0
  21. data/lib/opentelemetry/sdk/trace/samplers/decision.rb +26 -0
  22. data/lib/opentelemetry/sdk/trace/samplers/probability_sampler.rb +74 -0
  23. data/lib/opentelemetry/sdk/trace/samplers/result.rb +54 -0
  24. data/lib/opentelemetry/sdk/trace/span.rb +317 -0
  25. data/lib/opentelemetry/sdk/trace/span_data.rb +32 -0
  26. data/lib/opentelemetry/sdk/trace/tracer.rb +67 -0
  27. data/lib/opentelemetry/sdk/trace/tracer_factory.rb +84 -0
  28. data/lib/opentelemetry/sdk/version.rb +12 -0
  29. metadata +196 -0
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Trace
10
+ # Implementation of the SpanProcessor duck type that simply forwards all
11
+ # received events to a list of SpanProcessors.
12
+ class MultiSpanProcessor
13
+ # Creates a new {MultiSpanProcessor}.
14
+ #
15
+ # @param [Enumerable<SpanProcessor>] span_processors a collection of
16
+ # SpanProcessors.
17
+ # @return [MultiSpanProcessor]
18
+ def initialize(span_processors)
19
+ @span_processors = span_processors.to_a.freeze
20
+ end
21
+
22
+ # Called when a {Span} is started, if the {Span#recording?}
23
+ # returns true.
24
+ #
25
+ # This method is called synchronously on the execution thread, should
26
+ # not throw or block the execution thread.
27
+ #
28
+ # @param [Span] span the {Span} that just started.
29
+ def on_start(span)
30
+ @span_processors.each { |processor| processor.on_start(span) }
31
+ end
32
+
33
+ # Called when a {Span} is ended, if the {Span#recording?}
34
+ # returns true.
35
+ #
36
+ # This method is called synchronously on the execution thread, should
37
+ # not throw or block the execution thread.
38
+ #
39
+ # @param [Span] span the {Span} that just ended.
40
+ def on_finish(span)
41
+ @span_processors.each { |processor| processor.on_finish(span) }
42
+ end
43
+
44
+ # Called when {TracerFactory#shutdown} is called.
45
+ def shutdown
46
+ @span_processors.each(&:shutdown)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ require 'singleton'
8
+
9
+ module OpenTelemetry
10
+ module SDK
11
+ module Trace
12
+ # NoopSpanProcessor is a singleton implementation of the duck type
13
+ # SpanProcessor that provides synchronous no-op hooks for when a
14
+ # {Span} is started or when a {Span} is ended.
15
+ class NoopSpanProcessor
16
+ include Singleton
17
+
18
+ # Called when a {Span} is started, if the {Span#recording?}
19
+ # returns true.
20
+ #
21
+ # This method is called synchronously on the execution thread, should
22
+ # not throw or block the execution thread.
23
+ #
24
+ # @param [Span] span the {Span} that just started.
25
+ def on_start(span); end
26
+
27
+ # Called when a {Span} is ended, if the {Span#recording?}
28
+ # returns true.
29
+ #
30
+ # This method is called synchronously on the execution thread, should
31
+ # not throw or block the execution thread.
32
+ #
33
+ # @param [Span] span the {Span} that just ended.
34
+ def on_finish(span); end
35
+
36
+ # Called when {TracerFactory#shutdown} is called.
37
+ def shutdown; end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ require 'opentelemetry/sdk/trace/samplers/decision'
8
+ require 'opentelemetry/sdk/trace/samplers/result'
9
+ require 'opentelemetry/sdk/trace/samplers/probability_sampler'
10
+
11
+ module OpenTelemetry
12
+ module SDK
13
+ module Trace
14
+ # The Samplers module contains the sampling logic for OpenTelemetry. The
15
+ # reference implementation provides a {ProbabilitySampler}, {ALWAYS_ON},
16
+ # {ALWAYS_OFF}, and {ALWAYS_PARENT}.
17
+ #
18
+ # Custom samplers can be provided by SDK users. The required interface is
19
+ # a callable with the signature:
20
+ #
21
+ # (trace_id:, span_id:, parent_context:, hint:, links:, name:, kind:, attributes:) -> Result
22
+ #
23
+ # Where:
24
+ #
25
+ # @param [String] trace_id The trace_id of the {Span} to be created.
26
+ # @param [String] span_id The span_id of the {Span} to be created.
27
+ # @param [OpenTelemetry::Trace::SpanContext] parent_context The
28
+ # {OpenTelemetry::Trace::SpanContext} of a parent span, typically
29
+ # extracted from the wire. Can be nil for a root span.
30
+ # @param [Symbol] hint A {OpenTelemetry::Trace::SamplingHint} about
31
+ # whether the {Span} should be sampled and/or record events.
32
+ # @param [Enumerable<Link>] links A collection of links to be associated
33
+ # with the {Span} to be created. Can be nil.
34
+ # @param [String] name Name of the {Span} to be created.
35
+ # @param [Symbol] kind The {OpenTelemetry::Trace::SpanKind} of the {Span}
36
+ # to be created. Can be nil.
37
+ # @param [Hash<String, Object>] attributes Attributes to be attached
38
+ # to the {Span} to be created. Can be nil.
39
+ # @return [Result] The sampling result.
40
+ module Samplers
41
+ RECORD_AND_SAMPLED = Result.new(decision: Decision::RECORD_AND_SAMPLED)
42
+ NOT_RECORD = Result.new(decision: Decision::NOT_RECORD)
43
+ RECORD = Result.new(decision: Decision::RECORD)
44
+ SAMPLING_HINTS = [Decision::NOT_RECORD, Decision::RECORD, Decision::RECORD_AND_SAMPLED].freeze
45
+ APPLY_PROBABILITY_TO_SYMBOLS = %i[root_spans root_spans_and_remote_parent all_spans].freeze
46
+
47
+ private_constant(:RECORD_AND_SAMPLED, :NOT_RECORD, :RECORD, :SAMPLING_HINTS, :APPLY_PROBABILITY_TO_SYMBOLS)
48
+
49
+ # rubocop:disable Lint/UnusedBlockArgument
50
+
51
+ # Ignores all values in hint and returns a {Result} with
52
+ # {Decision::RECORD_AND_SAMPLED}.
53
+ ALWAYS_ON = ->(trace_id:, span_id:, parent_context:, hint:, links:, name:, kind:, attributes:) { RECORD_AND_SAMPLED }
54
+
55
+ # Ignores all values in hint and returns a {Result} with
56
+ # {Decision::NOT_RECORD}.
57
+ ALWAYS_OFF = ->(trace_id:, span_id:, parent_context:, hint:, links:, name:, kind:, attributes:) { NOT_RECORD }
58
+
59
+ # Ignores all values in hint and returns a {Result} with
60
+ # {Decision::RECORD_AND_SAMPLED} if the parent context is sampled or
61
+ # {Decision::NOT_RECORD} otherwise, or if there is no parent context.
62
+ # rubocop:disable Style/Lambda
63
+ ALWAYS_PARENT = ->(trace_id:, span_id:, parent_context:, hint:, links:, name:, kind:, attributes:) do
64
+ if parent_context&.trace_flags&.sampled?
65
+ RECORD_AND_SAMPLED
66
+ else
67
+ NOT_RECORD
68
+ end
69
+ end
70
+ # rubocop:enable Style/Lambda
71
+ # rubocop:enable Lint/UnusedBlockArgument
72
+
73
+ # Returns a new sampler. The probability of sampling a trace is equal
74
+ # to that of the specified probability.
75
+ #
76
+ # @param [Numeric] probability The desired probability of sampling.
77
+ # Must be within [0.0, 1.0].
78
+ # @param [optional Enumerable<Symbol>] ignore_hints Sampling hints to
79
+ # ignore. Defaults to ignore {OpenTelemetry::Trace::SamplingHint::RECORD}.
80
+ # @param [optional Boolean] ignore_parent Whether to ignore parent
81
+ # sampling. Defaults to not ignore parent sampling.
82
+ # @param [optional Symbol] apply_probability_to Whether to apply
83
+ # probability sampling to root spans, root spans and remote parents,
84
+ # or all spans. Allowed values include :root_spans, :root_spans_and_remote_parent,
85
+ # and :all_spans. Defaults to :root_spans_and_remote_parent.
86
+ # @raise [ArgumentError] if probability is out of range
87
+ # @raise [ArgumentError] if ignore_hints contains invalid hints
88
+ # @raise [ArgumentError] if apply_probability_to is not one of the allowed symbols
89
+ def self.probability(probability,
90
+ ignore_hints: [OpenTelemetry::Trace::SamplingHint::RECORD],
91
+ ignore_parent: false,
92
+ apply_probability_to: :root_spans_and_remote_parent)
93
+ raise ArgumentError, 'probability must be in range [0.0, 1.0]' unless (0.0..1.0).include?(probability)
94
+ raise ArgumentError, 'ignore_hints' unless (ignore_hints.to_a - SAMPLING_HINTS).empty?
95
+ raise ArgumentError, 'apply_probability_to' unless APPLY_PROBABILITY_TO_SYMBOLS.include?(apply_probability_to)
96
+
97
+ ProbabilitySampler.new(probability,
98
+ ignore_hints: ignore_hints.to_a,
99
+ ignore_parent: ignore_parent,
100
+ apply_to_remote_parent: apply_probability_to != :root_spans,
101
+ apply_to_all_spans: apply_probability_to == :all_spans)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Trace
10
+ module Samplers
11
+ # The Decision module contains a set of constants to be used in the
12
+ # decision part of a sampling {Result}.
13
+ module Decision
14
+ # Decision to not record events and not sample.
15
+ NOT_RECORD = OpenTelemetry::Trace::SamplingHint::NOT_RECORD
16
+
17
+ # Decision to record events and not sample.
18
+ RECORD = OpenTelemetry::Trace::SamplingHint::RECORD
19
+
20
+ # Decision to record events and sample.
21
+ RECORD_AND_SAMPLED = OpenTelemetry::Trace::SamplingHint::RECORD_AND_SAMPLED
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Trace
10
+ module Samplers
11
+ # @api private
12
+ #
13
+ # Implements sampling based on a probability.
14
+ class ProbabilitySampler
15
+ HINT_RECORD_AND_SAMPLED = OpenTelemetry::Trace::SamplingHint::RECORD_AND_SAMPLED
16
+ HINT_RECORD = OpenTelemetry::Trace::SamplingHint::RECORD
17
+
18
+ private_constant(:HINT_RECORD_AND_SAMPLED, :HINT_RECORD)
19
+
20
+ def initialize(probability, ignore_hints:, ignore_parent:, apply_to_remote_parent:, apply_to_all_spans:)
21
+ @probability = probability
22
+ @id_upper_bound = format('%016x', (probability * (2**64 - 1)).ceil)
23
+ @ignored_hints = ignore_hints
24
+ @use_parent_sampled_flag = !ignore_parent
25
+ @apply_to_remote_parent = apply_to_remote_parent
26
+ @apply_to_all_spans = apply_to_all_spans
27
+ end
28
+
29
+ # @api private
30
+ #
31
+ # Callable interface for probability sampler. See {Samplers}.
32
+ def call(trace_id:, span_id:, parent_context:, hint:, links:, name:, kind:, attributes:)
33
+ # Ignored for sampling decision: links, name, kind, attributes.
34
+
35
+ hint = nil if @ignored_hints.include?(hint)
36
+
37
+ sampled = sample?(hint, trace_id, parent_context)
38
+ recording = hint == HINT_RECORD || sampled
39
+
40
+ if sampled && recording
41
+ RECORD_AND_SAMPLED
42
+ elsif recording
43
+ RECORD
44
+ else
45
+ NOT_RECORD
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def sample?(hint, trace_id, parent_context)
52
+ if parent_context.nil?
53
+ hint == HINT_RECORD_AND_SAMPLED || sample_trace_id?(trace_id)
54
+ else
55
+ parent_sampled?(parent_context) || hint == HINT_RECORD_AND_SAMPLED || sample_trace_id_for_child?(parent_context, trace_id)
56
+ end
57
+ end
58
+
59
+ def parent_sampled?(parent_context)
60
+ @use_parent_sampled_flag && parent_context.trace_flags.sampled?
61
+ end
62
+
63
+ def sample_trace_id_for_child?(parent_context, trace_id)
64
+ (@apply_to_all_spans || (@apply_to_remote_parent && parent_context.remote?)) && sample_trace_id?(trace_id)
65
+ end
66
+
67
+ def sample_trace_id?(trace_id)
68
+ @probability == 1.0 || trace_id[16, 16] < @id_upper_bound
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Trace
10
+ module Samplers
11
+ # The Result class represents an arbitrary sampling result. It has
12
+ # boolean values for the sampling decision and whether to record
13
+ # events, and a collection of attributes to be attached to a sampled
14
+ # root span.
15
+ class Result
16
+ EMPTY_HASH = {}.freeze
17
+ DECISIONS = [Decision::RECORD, Decision::NOT_RECORD, Decision::RECORD_AND_SAMPLED].freeze
18
+ private_constant(:EMPTY_HASH, :DECISIONS)
19
+
20
+ # Returns a frozen hash of attributes to be attached span.
21
+ #
22
+ # @return [Hash<String, Object>]
23
+ attr_reader :attributes
24
+
25
+ # Returns a new sampling result with the specified decision and
26
+ # attributes.
27
+ #
28
+ # @param [Symbol] decision Whether or not a span should be sampled
29
+ # and/or record events.
30
+ # @param [optional Hash<String, Object>] attributes A frozen or freezable hash
31
+ # containing attributes to be attached to the span.
32
+ def initialize(decision:, attributes: nil)
33
+ @decision = decision
34
+ @attributes = attributes.freeze || EMPTY_HASH
35
+ end
36
+
37
+ # Returns true if this span should be sampled.
38
+ #
39
+ # @return [Boolean] sampling decision
40
+ def sampled?
41
+ @decision == Decision::RECORD_AND_SAMPLED
42
+ end
43
+
44
+ # Returns true if this span should record events, attributes, status, etc.
45
+ #
46
+ # @return [Boolean] recording decision
47
+ def recording?
48
+ @decision != Decision::NOT_RECORD
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,317 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Trace
10
+ # Implementation of {OpenTelemetry::Trace::Span} that records trace events.
11
+ #
12
+ # This implementation includes reader methods intended to allow access to
13
+ # internal state by SpanProcessors (see {NoopSpanProcessor} for the interface).
14
+ # Instrumentation should use the API provided by {OpenTelemetry::Trace::Span}
15
+ # and should consider {Span} to be write-only.
16
+ #
17
+ # rubocop:disable Metrics/ClassLength
18
+ class Span < OpenTelemetry::Trace::Span
19
+ # The following readers are intended for the use of SpanProcessors and
20
+ # should not be considered part of the public interface for instrumentation.
21
+ attr_reader :name, :status, :kind, :parent_span_id, :start_timestamp, :end_timestamp, :links, :library_resource
22
+
23
+ # Return a frozen copy of the current attributes. This is intended for
24
+ # use of SpanProcesses and should not be considered part of the public
25
+ # interface for instrumentation.
26
+ #
27
+ # @return [Hash<String, Object>] may be nil.
28
+ def attributes
29
+ # Don't bother synchronizing. Access by SpanProcessors is expected to
30
+ # be serialized.
31
+ @attributes&.clone.freeze
32
+ end
33
+
34
+ # Return a frozen copy of the current events. This is intended for use
35
+ # of SpanProcessors and should not be considered part of the public
36
+ # interface for instrumentation.
37
+ #
38
+ # @return [Array<Event>] may be nil.
39
+ def events
40
+ # Don't bother synchronizing. Access by SpanProcessors is expected to
41
+ # be serialized.
42
+ @events&.clone.freeze
43
+ end
44
+
45
+ # Return the flag whether this span is recording events
46
+ #
47
+ # @return [Boolean] true if this Span is active and recording information
48
+ # like events with the #add_event operation and attributes using
49
+ # #set_attribute.
50
+ def recording?
51
+ true
52
+ end
53
+
54
+ # Set attribute
55
+ #
56
+ # Note that the OpenTelemetry project
57
+ # {https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md
58
+ # documents} certain "standard attributes" that have prescribed semantic
59
+ # meanings.
60
+ #
61
+ # @param [String] key
62
+ # @param [String, Boolean, Numeric] value
63
+ #
64
+ # @return [self] returns itself
65
+ def set_attribute(key, value)
66
+ super
67
+ @mutex.synchronize do
68
+ if @ended
69
+ OpenTelemetry.logger.warn('Calling set_attribute on an ended Span.')
70
+ else
71
+ @attributes ||= {}
72
+ @attributes[key] = value
73
+ trim_span_attributes(@attributes)
74
+ @total_recorded_attributes += 1
75
+ end
76
+ end
77
+ self
78
+ end
79
+
80
+ # Add an Event to a {Span}. This can be accomplished eagerly or lazily.
81
+ # Lazy evaluation is useful when the event attributes are expensive to
82
+ # build and where the cost can be avoided for an unsampled {Span}.
83
+ #
84
+ # Eager example:
85
+ #
86
+ # span.add_event(name: 'event', attributes: {'eager' => true})
87
+ #
88
+ # Lazy example:
89
+ #
90
+ # span.add_event { OpenTelemetry::Trace::Event.new(name: 'event', attributes: {'eager' => false}) }
91
+ #
92
+ # Note that the OpenTelemetry project
93
+ # {https://github.com/open-telemetry/opentelemetry-specification/blob/master/semantic-conventions.md
94
+ # documents} certain "standard event names and keys" which have
95
+ # prescribed semantic meanings.
96
+ #
97
+ # @param [optional String] name Optional name of the event. This is
98
+ # required if a block is not given.
99
+ # @param [optional Hash<String, Object>] attributes One or more key:value
100
+ # pairs, where the keys must be strings and the values may be string,
101
+ # boolean or numeric type. This argument should only be used when
102
+ # passing in a name.
103
+ # @param [optional Time] timestamp Optional timestamp for the event.
104
+ # This argument should only be used when passing in a name.
105
+ #
106
+ # @return [self] returns itself
107
+ def add_event(name: nil, attributes: nil, timestamp: nil)
108
+ super
109
+ event = block_given? ? yield : OpenTelemetry::Trace::Event.new(name: name, attributes: attributes, timestamp: timestamp || Time.now)
110
+ @mutex.synchronize do
111
+ if @ended
112
+ OpenTelemetry.logger.warn('Calling add_event on an ended Span.')
113
+ else
114
+ @events ||= []
115
+ @events = append_event(@events, event)
116
+ @total_recorded_events += 1
117
+ end
118
+ end
119
+ self
120
+ end
121
+
122
+ # Sets the Status to the Span
123
+ #
124
+ # If used, this will override the default Span status. Default is OK.
125
+ #
126
+ # Only the value of the last call will be recorded, and implementations
127
+ # are free to ignore previous calls.
128
+ #
129
+ # @param [Status] status The new status, which overrides the default Span
130
+ # status, which is OK.
131
+ #
132
+ # @return [void]
133
+ def status=(status)
134
+ super
135
+ @mutex.synchronize do
136
+ if @ended
137
+ OpenTelemetry.logger.warn('Calling status= on an ended Span.')
138
+ else
139
+ @status = status
140
+ end
141
+ end
142
+ end
143
+
144
+ # Updates the Span name
145
+ #
146
+ # Upon this update, any sampling behavior based on Span name will depend
147
+ # on the implementation.
148
+ #
149
+ # @param [String] new_name The new operation name, which supersedes
150
+ # whatever was passed in when the Span was started
151
+ #
152
+ # @return [void]
153
+ def name=(new_name)
154
+ super
155
+ @mutex.synchronize do
156
+ if @ended
157
+ OpenTelemetry.logger.warn('Calling name= on an ended Span.')
158
+ else
159
+ @name = new_name
160
+ end
161
+ end
162
+ end
163
+
164
+ # Finishes the Span
165
+ #
166
+ # Implementations MUST ignore all subsequent calls to {#finish} (there
167
+ # might be exceptions when Tracer is streaming event and has no mutable
168
+ # state associated with the Span).
169
+ #
170
+ # Call to {#finish} MUST not have any effects on child spans. Those may
171
+ # still be running and can be ended later.
172
+ #
173
+ # This API MUST be non-blocking*.
174
+ #
175
+ # (*) not actually non-blocking. In particular, it synchronizes on an
176
+ # internal mutex, which will typically be uncontended, and
177
+ # {Export::BatchSpanProcessor} will also synchronize on a mutex, if that
178
+ # processor is used.
179
+ #
180
+ # @param [Time] end_timestamp optional end timestamp for the span.
181
+ #
182
+ # @return [self] returns itself
183
+ def finish(end_timestamp: nil)
184
+ @mutex.synchronize do
185
+ if @ended
186
+ OpenTelemetry.logger.warn('Calling finish on an ended Span.')
187
+ return self
188
+ end
189
+ @end_timestamp = end_timestamp || Time.now
190
+ @attributes.freeze
191
+ @events.freeze
192
+ @ended = true
193
+ end
194
+ @span_processor.on_finish(self)
195
+ self
196
+ end
197
+
198
+ # @api private
199
+ #
200
+ # Returns a SpanData containing a snapshot of the Span fields. It is
201
+ # assumed that the Span has been finished, and that no further
202
+ # modifications will be made to the Span.
203
+ #
204
+ # This method should be called *only* from a SpanProcessor prior to
205
+ # calling the SpanExporter.
206
+ #
207
+ # @return [SpanData]
208
+ def to_span_data
209
+ SpanData.new(
210
+ @name,
211
+ @kind,
212
+ @status,
213
+ @parent_span_id,
214
+ @child_count,
215
+ @total_recorded_attributes,
216
+ @total_recorded_events,
217
+ @total_recorded_links,
218
+ @start_timestamp,
219
+ @end_timestamp,
220
+ @attributes,
221
+ @links,
222
+ @events,
223
+ @library_resource,
224
+ context.span_id,
225
+ context.trace_id,
226
+ context.trace_flags
227
+ )
228
+ end
229
+
230
+ # @api private
231
+ def initialize(context, name, kind, parent_span_id, trace_config, span_processor, attributes, links, start_timestamp, library_resource) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
232
+ super(span_context: context)
233
+ @mutex = Mutex.new
234
+ @name = name
235
+ @kind = kind
236
+ @parent_span_id = parent_span_id.freeze || OpenTelemetry::Trace::INVALID_SPAN_ID
237
+ @trace_config = trace_config
238
+ @span_processor = span_processor
239
+ @library_resource = library_resource
240
+ @ended = false
241
+ @status = nil
242
+ @child_count = 0
243
+ @total_recorded_events = 0
244
+ @total_recorded_links = links&.size || 0
245
+ @total_recorded_attributes = attributes&.size || 0
246
+ @start_timestamp = start_timestamp
247
+ @end_timestamp = nil
248
+ @attributes = attributes.nil? ? nil : Hash[attributes] # We need a mutable copy of attributes.
249
+ trim_span_attributes(@attributes)
250
+ @events = nil
251
+ @links = trim_links(links, trace_config.max_links_count, trace_config.max_attributes_per_link)
252
+ @span_processor.on_start(self)
253
+ end
254
+
255
+ # TODO: Java implementation overrides finalize to log if a span isn't finished.
256
+
257
+ private
258
+
259
+ def trim_span_attributes(attrs)
260
+ return if attrs.nil?
261
+
262
+ excess = attrs.size - @trace_config.max_attributes_count
263
+ # TODO: with Ruby 2.5, replace with the more efficient
264
+ # attrs.shift(excess) if excess.positive?
265
+ excess.times { attrs.shift } if excess.positive?
266
+ nil
267
+ end
268
+
269
+ def trim_links(links, max_links_count, max_attributes_per_link) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
270
+ # Fast path (likely) common cases.
271
+ return nil if links.nil?
272
+
273
+ if links.size <= max_links_count &&
274
+ links.all? { |link| link.attributes.size <= max_attributes_per_link && Internal.valid_attributes?(link.attributes) }
275
+ return links.frozen? ? links : links.clone.freeze
276
+ end
277
+
278
+ # Slow path: trim attributes for each Link.
279
+ links.last(max_links_count).map! do |link|
280
+ attrs = Hash[link.attributes] # link.attributes is frozen, so we need an unfrozen copy to adjust.
281
+ attrs.keep_if { |key, value| Internal.valid_key?(key) && Internal.valid_value?(value) }
282
+ excess = attrs.size - max_attributes_per_link
283
+ excess.times { attrs.shift } if excess.positive?
284
+ OpenTelemetry::Trace::Link.new(link.context, attrs)
285
+ end.freeze
286
+ end
287
+
288
+ def append_event(events, event) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
289
+ max_events_count = @trace_config.max_events_count
290
+ max_attributes_per_event = @trace_config.max_attributes_per_event
291
+
292
+ # Fast path (likely) common case.
293
+ if events.size < max_events_count &&
294
+ event.attributes.size <= max_attributes_per_event &&
295
+ Internal.valid_attributes?(event.attributes)
296
+ return events << event
297
+ end
298
+
299
+ # Slow path.
300
+ excess = events.size + 1 - max_events_count
301
+ events.shift(excess) if excess.positive?
302
+
303
+ excess = event.attributes.size - max_attributes_per_event
304
+ if excess.positive? || !Internal.valid_attributes?(event.attributes)
305
+ attrs = Hash[event.attributes] # event.attributes is frozen, so we need an unfrozen copy to adjust.
306
+ attrs.keep_if { |key, value| Internal.valid_key?(key) && Internal.valid_value?(value) }
307
+ excess = attrs.size - max_attributes_per_event
308
+ excess.times { attrs.shift } if excess.positive?
309
+ event = OpenTelemetry::Trace::Event.new(name: event.name, attributes: attrs, timestamp: event.timestamp)
310
+ end
311
+ events << event
312
+ end
313
+ end
314
+ # rubocop:enable Metrics/ClassLength
315
+ end
316
+ end
317
+ end