opentelemetry-sdk 0.8.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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