opentelemetry-sdk 0.8.0 → 0.12.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/LICENSE +1 -1
  4. data/lib/opentelemetry-sdk.rb +1 -1
  5. data/lib/opentelemetry/sdk.rb +13 -1
  6. data/lib/opentelemetry/sdk/baggage.rb +1 -1
  7. data/lib/opentelemetry/sdk/baggage/builder.rb +1 -1
  8. data/lib/opentelemetry/sdk/baggage/manager.rb +1 -1
  9. data/lib/opentelemetry/sdk/configurator.rb +9 -2
  10. data/lib/opentelemetry/sdk/instrumentation_library.rb +1 -1
  11. data/lib/opentelemetry/sdk/internal.rb +12 -2
  12. data/lib/opentelemetry/sdk/resources.rb +1 -1
  13. data/lib/opentelemetry/sdk/resources/constants.rb +1 -1
  14. data/lib/opentelemetry/sdk/resources/resource.rb +2 -2
  15. data/lib/opentelemetry/sdk/trace.rb +1 -1
  16. data/lib/opentelemetry/sdk/trace/config.rb +1 -1
  17. data/lib/opentelemetry/sdk/trace/config/trace_config.rb +23 -21
  18. data/lib/opentelemetry/sdk/trace/event.rb +1 -1
  19. data/lib/opentelemetry/sdk/trace/export.rb +2 -1
  20. data/lib/opentelemetry/sdk/trace/export/batch_span_processor.rb +67 -30
  21. data/lib/opentelemetry/sdk/trace/export/console_span_exporter.rb +3 -3
  22. data/lib/opentelemetry/sdk/trace/export/in_memory_span_exporter.rb +5 -3
  23. data/lib/opentelemetry/sdk/trace/export/metrics_reporter.rb +59 -0
  24. data/lib/opentelemetry/sdk/trace/export/multi_span_exporter.rb +15 -17
  25. data/lib/opentelemetry/sdk/trace/export/noop_span_exporter.rb +6 -3
  26. data/lib/opentelemetry/sdk/trace/export/simple_span_processor.rb +13 -5
  27. data/lib/opentelemetry/sdk/trace/multi_span_processor.rb +21 -5
  28. data/lib/opentelemetry/sdk/trace/noop_span_processor.rb +5 -3
  29. data/lib/opentelemetry/sdk/trace/samplers.rb +7 -13
  30. data/lib/opentelemetry/sdk/trace/samplers/constant_sampler.rb +12 -5
  31. data/lib/opentelemetry/sdk/trace/samplers/decision.rb +1 -1
  32. data/lib/opentelemetry/sdk/trace/samplers/parent_based.rb +17 -4
  33. data/lib/opentelemetry/sdk/trace/samplers/result.rb +14 -3
  34. data/lib/opentelemetry/sdk/trace/samplers/trace_id_ratio_based.rb +8 -5
  35. data/lib/opentelemetry/sdk/trace/span.rb +12 -7
  36. data/lib/opentelemetry/sdk/trace/span_data.rb +1 -1
  37. data/lib/opentelemetry/sdk/trace/tracer.rb +9 -11
  38. data/lib/opentelemetry/sdk/trace/tracer_provider.rb +7 -4
  39. data/lib/opentelemetry/sdk/version.rb +2 -2
  40. metadata +21 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 735ead8ec769c6a55c3e3393341366ebd4a7e83bd4bdd8a71ca474b470ab0566
4
- data.tar.gz: 862d5aa24d401a6c215d852a4c601a58d858d452c9f3679cd33d71094de2eeb4
3
+ metadata.gz: 718ea2029bb234ea6ed56da8b65923595e275bb4730c3f3ea50bc20c4a01597d
4
+ data.tar.gz: 39e4031d0ef4ff426fe9bd776b4e85a2dd351a77c9dc8e19b80330dc5bf9d544
5
5
  SHA512:
6
- metadata.gz: 8cb9becaedfbf07126a03c68f1968c988d499042c587a4d4e1100048660d2fa699b809c7d687eb90700d63ecceda4b31172e2efd25ea147abb0ccd7afbe9b476
7
- data.tar.gz: 718f2a0a972e3a3f60beea5f4d3d8bff47f943fc24ecc2726182449f1cb3e958122e40971f5db7b96771b9380cd79b93f0ef7e3480d4319b5466d1c7ce3e0287
6
+ metadata.gz: caa3842133c57ab739e0c21d8e1cd5f9cd58293e1e34055eef6b18fd4ff0869a39edbe87bcbb0fef7e76ea733f9ea0f9ff84663cf70a3a88820b7074c78c156d
7
+ data.tar.gz: f3e8176d0b495ea3784b6f8e2b2676000e35b3db7a1fab7b0890014ff69accd9caa69a00af02b4668964d3837aabd470bdaec78bda0d06ada1a9cfe6f1b000a7
@@ -1,5 +1,40 @@
1
1
  # Release History: opentelemetry-sdk
2
2
 
3
+ ### v0.12.0 / 2020-12-24
4
+
5
+ * ADDED: Structured error handling
6
+ * ADDED: Pluggable ID generation
7
+ * FIXED: BSP dropped span buffer full reporting
8
+ * FIXED: Implement SDK environment variables
9
+ * FIXED: Remove incorrect TODO
10
+
11
+ ### v0.11.1 / 2020-12-16
12
+
13
+ * FIXED: BSP dropped span buffer full reporting
14
+
15
+ ### v0.11.0 / 2020-12-11
16
+
17
+ * ADDED: Metrics reporting from trace export
18
+ * FIXED: Copyright comments to not reference year
19
+
20
+ ### v0.10.0 / 2020-12-03
21
+
22
+ * BREAKING CHANGE: Allow samplers to modify tracestate
23
+
24
+ * FIXED: Allow samplers to modify tracestate
25
+
26
+ ### v0.9.0 / 2020-11-27
27
+
28
+ * BREAKING CHANGE: Pass full Context to samplers
29
+ * BREAKING CHANGE: Add timeout for force_flush and shutdown
30
+
31
+ * ADDED: Add OTEL_RUBY_BSP_START_THREAD_ON_BOOT env var
32
+ * ADDED: Add timeout for force_flush and shutdown
33
+ * FIXED: Signal at batch_size
34
+ * FIXED: SDK Span.recording? after finish
35
+ * FIXED: Pass full Context to samplers
36
+ * DOCS: Add documentation on usage scenarios for span processors
37
+
3
38
  ### v0.8.0 / 2020-10-27
4
39
 
5
40
  * BREAKING CHANGE: Move context/span methods to Trace module
data/LICENSE CHANGED
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright 2020 OpenTelemetry Authors
189
+ Copyright The OpenTelemetry Authors
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
7
7
  require 'opentelemetry'
8
+ require 'opentelemetry/common'
8
9
 
9
10
  # OpenTelemetry is an open source observability framework, providing a
10
11
  # general-purpose API, SDK, and related tools required for the instrumentation
@@ -17,6 +18,11 @@ module OpenTelemetry
17
18
  module SDK
18
19
  extend self
19
20
 
21
+ # ConfigurationError is an exception type used to wrap configuration errors
22
+ # passed to OpenTelemetry.error_handler. This can be used to distinguish
23
+ # errors reported during SDK configuration.
24
+ ConfigurationError = Class.new(OpenTelemetry::Error)
25
+
20
26
  # Configures SDK and instrumentation
21
27
  #
22
28
  # @yieldparam [Configurator] configurator Yields a configurator to the
@@ -56,6 +62,12 @@ module OpenTelemetry
56
62
  configurator = Configurator.new
57
63
  yield configurator if block_given?
58
64
  configurator.configure
65
+ rescue StandardError
66
+ begin
67
+ raise ConfigurationError
68
+ rescue ConfigurationError => e
69
+ OpenTelemetry.handle_error(exception: e, message: 'unexpected configuration error')
70
+ end
59
71
  end
60
72
  end
61
73
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -16,7 +16,7 @@ module OpenTelemetry
16
16
  private_constant :USE_MODE_UNSPECIFIED, :USE_MODE_ONE, :USE_MODE_ALL
17
17
 
18
18
  attr_writer :logger, :http_extractors, :http_injectors, :text_map_extractors,
19
- :text_map_injectors
19
+ :text_map_injectors, :error_handler, :id_generator
20
20
 
21
21
  def initialize
22
22
  @instrumentation_names = []
@@ -28,12 +28,17 @@ module OpenTelemetry
28
28
  @span_processors = []
29
29
  @use_mode = USE_MODE_UNSPECIFIED
30
30
  @resource = Resources::Resource.telemetry_sdk
31
+ @id_generator = OpenTelemetry::Trace
31
32
  end
32
33
 
33
34
  def logger
34
35
  @logger ||= OpenTelemetry.logger
35
36
  end
36
37
 
38
+ def error_handler
39
+ @error_handler ||= OpenTelemetry.error_handler
40
+ end
41
+
37
42
  # Accepts a resource object that is merged with the default telemetry sdk
38
43
  # resource. The use of this method is optional, and is provided as means
39
44
  # to include additional resource information.
@@ -110,9 +115,11 @@ module OpenTelemetry
110
115
  # - install instrumentation
111
116
  def configure
112
117
  OpenTelemetry.logger = logger
118
+ OpenTelemetry.error_handler = error_handler
113
119
  OpenTelemetry.baggage = Baggage::Manager.new
114
120
  configure_propagation
115
121
  configure_span_processors
122
+ tracer_provider.id_generator = @id_generator
116
123
  OpenTelemetry.tracer_provider = tracer_provider
117
124
  install_instrumentation
118
125
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -45,7 +45,17 @@ module OpenTelemetry
45
45
  end
46
46
 
47
47
  def valid_attributes?(attrs)
48
- attrs.nil? || attrs.all? { |k, v| valid_key?(k) && valid_value?(v) }
48
+ attrs.nil? || attrs.all? do |k, v|
49
+ if !valid_key?(k)
50
+ OpenTelemetry.handle_error(message: "invalid attribute key type #{v.class}")
51
+ false
52
+ elsif !valid_value?(v)
53
+ OpenTelemetry.handle_error(message: "invalid attribute value type #{v.class}")
54
+ false
55
+ else
56
+ true
57
+ end
58
+ end
49
59
  end
50
60
  end
51
61
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2020 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -22,7 +22,7 @@ module OpenTelemetry
22
22
  def create(attributes = {})
23
23
  frozen_attributes = attributes.each_with_object({}) do |(k, v), memo|
24
24
  raise ArgumentError, 'attribute keys must be strings' unless k.is_a?(String)
25
- raise ArgumentError, 'attribute values must be strings, integers, floats, or booleans' unless Internal.valid_value?(v)
25
+ raise ArgumentError, 'attribute values must be (array of) strings, integers, floats, or booleans' unless Internal.valid_value?(v)
26
26
 
27
27
  memo[-k] = v.freeze
28
28
  end.freeze
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -10,20 +10,6 @@ module OpenTelemetry
10
10
  module Config
11
11
  # Class that holds global trace parameters.
12
12
  class TraceConfig
13
- DEFAULT_SAMPLER = Samplers.parent_based(root: Samplers::ALWAYS_ON)
14
- DEFAULT_MAX_ATTRIBUTES_COUNT = 32
15
- DEFAULT_MAX_EVENTS_COUNT = 128
16
- DEFAULT_MAX_LINKS_COUNT = 32
17
- DEFAULT_MAX_ATTRIBUTES_PER_EVENT = 32
18
- DEFAULT_MAX_ATTRIBUTES_PER_LINK = 32
19
-
20
- private_constant(:DEFAULT_SAMPLER,
21
- :DEFAULT_MAX_ATTRIBUTES_COUNT,
22
- :DEFAULT_MAX_EVENTS_COUNT,
23
- :DEFAULT_MAX_LINKS_COUNT,
24
- :DEFAULT_MAX_ATTRIBUTES_PER_EVENT,
25
- :DEFAULT_MAX_ATTRIBUTES_PER_LINK)
26
-
27
13
  # The global default sampler (see {Samplers}).
28
14
  attr_reader :sampler
29
15
 
@@ -46,12 +32,12 @@ module OpenTelemetry
46
32
  #
47
33
  # @return [TraceConfig] with the desired values.
48
34
  # @raise [ArgumentError] if any of the max numbers are not positive.
49
- def initialize(sampler: DEFAULT_SAMPLER,
50
- max_attributes_count: DEFAULT_MAX_ATTRIBUTES_COUNT,
51
- max_events_count: DEFAULT_MAX_EVENTS_COUNT,
52
- max_links_count: DEFAULT_MAX_LINKS_COUNT,
53
- max_attributes_per_event: DEFAULT_MAX_ATTRIBUTES_PER_EVENT,
54
- max_attributes_per_link: DEFAULT_MAX_ATTRIBUTES_PER_LINK)
35
+ def initialize(sampler: sampler_from_environment(Samplers.parent_based(root: Samplers::ALWAYS_ON)),
36
+ max_attributes_count: Integer(ENV.fetch('OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT', 1000)),
37
+ max_events_count: Integer(ENV.fetch('OTEL_SPAN_EVENT_COUNT_LIMIT', 1000)),
38
+ max_links_count: Integer(ENV.fetch('OTEL_SPAN_LINK_COUNT_LIMIT', 1000)),
39
+ max_attributes_per_event: max_attributes_count,
40
+ max_attributes_per_link: max_attributes_count)
55
41
  raise ArgumentError, 'max_attributes_count must be positive' unless max_attributes_count.positive?
56
42
  raise ArgumentError, 'max_events_count must be positive' unless max_events_count.positive?
57
43
  raise ArgumentError, 'max_links_count must be positive' unless max_links_count.positive?
@@ -67,6 +53,22 @@ module OpenTelemetry
67
53
  end
68
54
 
69
55
  # TODO: from_proto
56
+ private
57
+
58
+ def sampler_from_environment(default_sampler) # rubocop:disable Metrics/CyclomaticComplexity
59
+ case ENV['OTEL_TRACE_SAMPLER']
60
+ when 'always_on' then Samplers::ALWAYS_ON
61
+ when 'always_off' then Samplers::ALWAYS_OFF
62
+ when 'traceidratio' then Samplers.trace_id_ratio_based(Float(ENV['OTEL_TRACE_SAMPLER_ARG']))
63
+ when 'parentbased_always_on' then Samplers.parent_based(root: Samplers::ALWAYS_ON)
64
+ when 'parentbased_always_off' then Samplers.parent_based(root: Samplers::ALWAYS_OFF)
65
+ when 'parentbased_traceidratio' then Samplers.parent_based(root: Samplers.trace_id_ratio_based(Float(ENV['OTEL_TRACE_SAMPLER_ARG'])))
66
+ else default_sampler
67
+ end
68
+ rescue StandardError => e
69
+ OpenTelemetry.handle_error(exception: e, message: "installing default sampler #{default_sampler.description}")
70
+ default_sampler
71
+ end
70
72
 
71
73
  # The default {TraceConfig}.
72
74
  DEFAULT = new
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -30,6 +30,7 @@ end
30
30
  require 'opentelemetry/sdk/trace/export/batch_span_processor'
31
31
  require 'opentelemetry/sdk/trace/export/console_span_exporter'
32
32
  require 'opentelemetry/sdk/trace/export/in_memory_span_exporter'
33
+ require 'opentelemetry/sdk/trace/export/metrics_reporter'
33
34
  require 'opentelemetry/sdk/trace/export/multi_span_exporter'
34
35
  require 'opentelemetry/sdk/trace/export/noop_span_exporter'
35
36
  require 'opentelemetry/sdk/trace/export/simple_span_processor'
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 OpenTelemetry Authors
3
+ # Copyright The OpenTelemetry Authors
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -13,6 +13,9 @@ module OpenTelemetry
13
13
  # Implementation of the duck type SpanProcessor that batches spans
14
14
  # exported by the SDK then pushes them to the exporter pipeline.
15
15
  #
16
+ # Typically, the BatchSpanProcessor will be more suitable for
17
+ # production environments than the SimpleSpanProcessor.
18
+ #
16
19
  # All spans reported by the SDK implementation are first added to a
17
20
  # synchronized queue (with a {max_queue_size} maximum size, after the
18
21
  # size is reached spans are dropped) and exported every
@@ -22,7 +25,7 @@ module OpenTelemetry
22
25
  # If the queue gets half full a preemptive notification is sent to the
23
26
  # worker thread that exports the spans to wake up and start a new
24
27
  # export cycle.
25
- class BatchSpanProcessor
28
+ class BatchSpanProcessor # rubocop:disable Metrics/ClassLength
26
29
  # Returns a new instance of the {BatchSpanProcessor}.
27
30
  #
28
31
  # @param [SpanExporter] exporter
@@ -44,42 +47,46 @@ module OpenTelemetry
44
47
  exporter_timeout_millis: Float(ENV.fetch('OTEL_BSP_EXPORT_TIMEOUT_MILLIS', 30_000)),
45
48
  schedule_delay_millis: Float(ENV.fetch('OTEL_BSP_SCHEDULE_DELAY_MILLIS', 5_000)),
46
49
  max_queue_size: Integer(ENV.fetch('OTEL_BSP_MAX_QUEUE_SIZE', 2048)),
47
- max_export_batch_size: Integer(ENV.fetch('OTEL_BSP_MAX_EXPORT_BATCH_SIZE', 512)))
50
+ max_export_batch_size: Integer(ENV.fetch('OTEL_BSP_MAX_EXPORT_BATCH_SIZE', 512)),
51
+ start_thread_on_boot: String(ENV['OTEL_RUBY_BSP_START_THREAD_ON_BOOT']) !~ /false/i,
52
+ metrics_reporter: nil)
48
53
  raise ArgumentError if max_export_batch_size > max_queue_size
49
54
 
50
55
  @exporter = exporter
51
56
  @exporter_timeout_seconds = exporter_timeout_millis / 1000.0
52
57
  @mutex = Mutex.new
58
+ @export_mutex = Mutex.new
53
59
  @condition = ConditionVariable.new
54
60
  @keep_running = true
55
61
  @delay_seconds = schedule_delay_millis / 1000.0
56
62
  @max_queue_size = max_queue_size
57
63
  @batch_size = max_export_batch_size
64
+ @metrics_reporter = metrics_reporter || OpenTelemetry::SDK::Trace::Export::MetricsReporter
58
65
  @spans = []
59
66
  @pid = nil
60
67
  @thread = nil
61
- reset_on_fork
68
+ reset_on_fork(restart_thread: start_thread_on_boot)
62
69
  end
63
70
 
64
- # does nothing for this processor
65
- def on_start(span, parent_context)
66
- # noop
67
- end
71
+ # Does nothing for this processor
72
+ def on_start(_span, _parent_context); end
68
73
 
69
- # adds a span to the batcher, threadsafe may block on lock
74
+ # Adds a span to the batch. Thread-safe; may block on lock.
70
75
  def on_finish(span) # rubocop:disable Metrics/AbcSize
71
76
  return unless span.context.trace_flags.sampled?
72
77
 
73
78
  lock do
74
79
  reset_on_fork
75
80
  n = spans.size + 1 - max_queue_size
76
- spans.shift(n) if n.positive?
81
+ if n.positive?
82
+ spans.shift(n)
83
+ report_dropped_spans(n, reason: 'buffer-full')
84
+ end
77
85
  spans << span
78
- @condition.signal if spans.size > max_queue_size / 2
86
+ @condition.signal if spans.size > batch_size
79
87
  end
80
88
  end
81
89
 
82
- # TODO: test this explicitly.
83
90
  # Export all ended spans to the configured `Exporter` that have not yet
84
91
  # been exported.
85
92
  #
@@ -88,42 +95,64 @@ module OpenTelemetry
88
95
  # the process after an invocation, but before the `Processor` exports
89
96
  # the completed spans.
90
97
  #
98
+ # @param [optional Numeric] timeout An optional timeout in seconds.
91
99
  # @return [Integer] SUCCESS if no error occurred, FAILURE if a
92
100
  # non-specific failure occurred, TIMEOUT if a timeout occurred.
93
- def force_flush
101
+ def force_flush(timeout: nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
102
+ start_time = Time.now
94
103
  snapshot = lock do
95
104
  reset_on_fork(restart_thread: false) if @keep_running
96
105
  spans.shift(spans.size)
97
106
  end
98
107
  until snapshot.empty?
108
+ remaining_timeout = OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time)
109
+ return TIMEOUT if remaining_timeout&.zero?
110
+
99
111
  batch = snapshot.shift(@batch_size).map!(&:to_span_data)
100
- result_code = @exporter.export(batch)
101
- report_result(result_code, batch)
112
+ result_code = export_batch(batch, timeout: remaining_timeout)
113
+ return result_code unless result_code == SUCCESS
102
114
  end
115
+
103
116
  SUCCESS
117
+ ensure
118
+ # Unshift the remaining spans if we timed out. We drop excess spans from
119
+ # the snapshot because they're older than any spans in the spans buffer.
120
+ lock do
121
+ n = spans.size + snapshot.size - max_queue_size
122
+ if n.positive?
123
+ snapshot.shift(n)
124
+ report_dropped_spans(n, reason: 'buffer-full')
125
+ end
126
+ spans.unshift(snapshot) unless snapshot.empty?
127
+ @condition.signal if spans.size > max_queue_size / 2
128
+ end
104
129
  end
105
130
 
106
- # shuts the consumer thread down and flushes the current accumulated buffer
107
- # will block until the thread is finished
131
+ # Shuts the consumer thread down and flushes the current accumulated buffer
132
+ # will block until the thread is finished.
108
133
  #
134
+ # @param [optional Numeric] timeout An optional timeout in seconds.
109
135
  # @return [Integer] SUCCESS if no error occurred, FAILURE if a
110
136
  # non-specific failure occurred, TIMEOUT if a timeout occurred.
111
- def shutdown
137
+ def shutdown(timeout: nil)
138
+ start_time = Time.now
112
139
  lock do
113
140
  @keep_running = false
114
141
  @condition.signal
115
142
  end
116
143
 
117
- @thread.join
118
- force_flush
119
- @exporter.shutdown
144
+ @thread.join(timeout)
145
+ force_flush(timeout: OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time))
146
+ @exporter.shutdown(timeout: OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time))
147
+ dropped_spans = lock { spans.size }
148
+ report_dropped_spans(dropped_spans, reason: 'terminating') if dropped_spans.positive?
120
149
  end
121
150
 
122
151
  private
123
152
 
124
153
  attr_reader :spans, :max_queue_size, :batch_size
125
154
 
126
- def work
155
+ def work # rubocop:disable Metrics/AbcSize
127
156
  loop do
128
157
  batch = lock do
129
158
  reset_on_fork(restart_thread: false)
@@ -134,6 +163,8 @@ module OpenTelemetry
134
163
  fetch_batch
135
164
  end
136
165
 
166
+ @metrics_reporter.observe_value('otel.bsp.buffer_utilization', value: spans.size / max_queue_size.to_f)
167
+
137
168
  export_batch(batch)
138
169
  end
139
170
  end
@@ -147,19 +178,25 @@ module OpenTelemetry
147
178
  @thread = Thread.new { work } if restart_thread
148
179
  end
149
180
 
150
- def export_batch(batch)
151
- result_code = export_with_timeout(batch)
181
+ def export_batch(batch, timeout: @exporter_timeout_seconds)
182
+ result_code = @export_mutex.synchronize { @exporter.export(batch, timeout: timeout) }
152
183
  report_result(result_code, batch)
184
+ result_code
153
185
  end
154
186
 
155
- def export_with_timeout(batch)
156
- Timeout.timeout(@exporter_timeout_seconds) { @exporter.export(batch) }
157
- rescue Timeout::Error
158
- FAILURE
187
+ def report_result(result_code, batch)
188
+ if result_code == SUCCESS
189
+ @metrics_reporter.add_to_counter('otel.bsp.export.success')
190
+ @metrics_reporter.add_to_counter('otel.bsp.exported_spans', increment: batch.size)
191
+ else
192
+ OpenTelemetry.handle_error(message: "Unable to export #{batch.size} spans")
193
+ @metrics_reporter.add_to_counter('otel.bsp.export.failure')
194
+ report_dropped_spans(batch.size, reason: 'export-failure')
195
+ end
159
196
  end
160
197
 
161
- def report_result(result_code, batch)
162
- OpenTelemetry.logger.error("Unable to export #{batch.size} spans") unless result_code == SUCCESS
198
+ def report_dropped_spans(count, reason:)
199
+ @metrics_reporter.add_to_counter('otel.bsp.dropped_spans', increment: count, labels: { 'reason' => reason })
163
200
  end
164
201
 
165
202
  def fetch_batch