opentelemetry-sdk 0.14.0 → 1.0.0.rc2

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -44
  3. data/README.md +2 -2
  4. data/lib/opentelemetry/sdk.rb +4 -3
  5. data/lib/opentelemetry/sdk/configurator.rb +52 -33
  6. data/lib/opentelemetry/sdk/forwarding_logger.rb +69 -0
  7. data/lib/opentelemetry/sdk/internal.rb +3 -3
  8. data/lib/opentelemetry/sdk/resources/constants.rb +48 -3
  9. data/lib/opentelemetry/sdk/resources/resource.rb +8 -1
  10. data/lib/opentelemetry/sdk/trace.rb +2 -3
  11. data/lib/opentelemetry/sdk/trace/event.rb +7 -36
  12. data/lib/opentelemetry/sdk/trace/export.rb +1 -2
  13. data/lib/opentelemetry/sdk/trace/export/batch_span_processor.rb +10 -5
  14. data/lib/opentelemetry/sdk/trace/export/console_span_exporter.rb +4 -0
  15. data/lib/opentelemetry/sdk/trace/export/in_memory_span_exporter.rb +23 -4
  16. data/lib/opentelemetry/sdk/trace/export/simple_span_processor.rb +4 -2
  17. data/lib/opentelemetry/sdk/trace/export/{noop_span_exporter.rb → span_exporter.rb} +18 -7
  18. data/lib/opentelemetry/sdk/trace/span.rb +83 -39
  19. data/lib/opentelemetry/sdk/trace/span_data.rb +25 -18
  20. data/lib/opentelemetry/sdk/trace/span_limits.rb +60 -0
  21. data/lib/opentelemetry/sdk/trace/{noop_span_processor.rb → span_processor.rb} +5 -8
  22. data/lib/opentelemetry/sdk/trace/tracer.rb +1 -37
  23. data/lib/opentelemetry/sdk/trace/tracer_provider.rb +106 -19
  24. data/lib/opentelemetry/sdk/version.rb +1 -1
  25. metadata +43 -20
  26. data/lib/opentelemetry/sdk/baggage.rb +0 -16
  27. data/lib/opentelemetry/sdk/baggage/builder.rb +0 -40
  28. data/lib/opentelemetry/sdk/baggage/manager.rb +0 -97
  29. data/lib/opentelemetry/sdk/trace/config.rb +0 -18
  30. data/lib/opentelemetry/sdk/trace/config/trace_config.rb +0 -79
  31. data/lib/opentelemetry/sdk/trace/export/multi_span_exporter.rb +0 -59
  32. data/lib/opentelemetry/sdk/trace/multi_span_processor.rb +0 -86
@@ -44,13 +44,13 @@ module OpenTelemetry
44
44
  valid_simple_value?(value) || valid_array_value?(value)
45
45
  end
46
46
 
47
- def valid_attributes?(attrs)
47
+ def valid_attributes?(owner, kind, attrs)
48
48
  attrs.nil? || attrs.all? do |k, v|
49
49
  if !valid_key?(k)
50
- OpenTelemetry.handle_error(message: "invalid attribute key type #{v.class}")
50
+ OpenTelemetry.handle_error(message: "invalid #{kind} attribute key type #{k.class} on span '#{owner}'")
51
51
  false
52
52
  elsif !valid_value?(v)
53
- OpenTelemetry.handle_error(message: "invalid attribute value type #{v.class}")
53
+ OpenTelemetry.handle_error(message: "invalid #{kind} attribute value type #{v.class} for key '#{k}' on span '#{owner}'")
54
54
  false
55
55
  else
56
56
  true
@@ -67,14 +67,59 @@ module OpenTelemetry
67
67
  # The name of the cluster that the pod is running in.
68
68
  cluster_name: 'k8s.cluster.name',
69
69
 
70
+ # The name of the Node.
71
+ node_name: 'k8s.node.name',
72
+
73
+ # The UID of the Node.
74
+ node_uid: 'k8s.node.uid',
75
+
70
76
  # The name of the namespace that the pod is running in.
71
77
  namespace_name: 'k8s.namespace.name',
72
78
 
73
79
  # The name of the pod.
74
80
  pod_name: 'k8s.pod.name',
75
81
 
82
+ # The UID of the Pod.
83
+ pod_uid: 'k8s.pod.uid',
84
+
85
+ # The name of the Container in a Pod template.
86
+ container_name: 'k8s.container.name',
87
+
88
+ # The UID of the ReplicaSet.
89
+ replicaset_uid: 'k8s.replicaset.uid',
90
+
91
+ # The name of the ReplicaSet.
92
+ replicaset_name: 'k8s.replicaset.name',
93
+
94
+ # The UID of the Deployment.
95
+ deployment_uid: 'k8s.deployment.uid',
96
+
76
97
  # The name of the deployment.
77
- deployment_name: 'k8s.deployment.name'
98
+ deployment_name: 'k8s.deployment.name',
99
+
100
+ # The UID of the StatefulSet.
101
+ statefulset_uid: 'k8s.statefulset.uid',
102
+
103
+ # The name of the StatefulSet.
104
+ statefulset_name: 'k8s.statefulset.name',
105
+
106
+ # The UID of the DaemonSet.
107
+ daemonset_uid: 'k8s.daemonset.uid',
108
+
109
+ # The name of the DaemonSet.
110
+ daemonset_name: 'k8s.daemonset.name',
111
+
112
+ # The UID of the Job.
113
+ job_uid: 'k8s.job.uid',
114
+
115
+ # The name of the Job.
116
+ job_name: 'k8s.job.name',
117
+
118
+ # The UID of the CronJob.
119
+ cronjob_uid: 'k8s.cronjob.uid',
120
+
121
+ # The name of the CronJob.
122
+ cronjob_name: 'k8s.cronjob.name'
78
123
  }.freeze
79
124
 
80
125
  # Attributes defining an operating system process.
@@ -151,8 +196,8 @@ module OpenTelemetry
151
196
  # A specific geographical location where different entities can run.
152
197
  region: 'cloud.region',
153
198
 
154
- # Zones are a sub set of the region connected through low-latency links.
155
- zone: 'cloud.zone'
199
+ # Availability zones are a sub set of the region connected through low-latency links.
200
+ availability_zone: 'cloud.availability_zone'
156
201
  }.freeze
157
202
  end
158
203
  end
@@ -31,7 +31,7 @@ module OpenTelemetry
31
31
  end
32
32
 
33
33
  def default
34
- @default ||= create(Constants::SERVICE_RESOURCE[:name] => 'unknown_service').merge(process).merge(telemetry_sdk)
34
+ @default ||= create(Constants::SERVICE_RESOURCE[:name] => 'unknown_service').merge(process).merge(telemetry_sdk).merge(service_name_from_env)
35
35
  end
36
36
 
37
37
  def telemetry_sdk
@@ -64,6 +64,13 @@ module OpenTelemetry
64
64
 
65
65
  create(resource_attributes)
66
66
  end
67
+
68
+ private
69
+
70
+ def service_name_from_env
71
+ service_name = ENV['OTEL_SERVICE_NAME']
72
+ create(Constants::SERVICE_RESOURCE[:name] => service_name) unless service_name.nil?
73
+ end
67
74
  end
68
75
 
69
76
  # @api private
@@ -14,12 +14,11 @@ module OpenTelemetry
14
14
  end
15
15
 
16
16
  require 'opentelemetry/sdk/trace/samplers'
17
- require 'opentelemetry/sdk/trace/config'
17
+ require 'opentelemetry/sdk/trace/span_limits'
18
18
  require 'opentelemetry/sdk/trace/event'
19
19
  require 'opentelemetry/sdk/trace/export'
20
- require 'opentelemetry/sdk/trace/multi_span_processor'
21
- require 'opentelemetry/sdk/trace/noop_span_processor'
22
20
  require 'opentelemetry/sdk/trace/span_data'
21
+ require 'opentelemetry/sdk/trace/span_processor'
23
22
  require 'opentelemetry/sdk/trace/span'
24
23
  require 'opentelemetry/sdk/trace/tracer'
25
24
  require 'opentelemetry/sdk/trace/tracer_provider'
@@ -7,42 +7,13 @@
7
7
  module OpenTelemetry
8
8
  module SDK
9
9
  module Trace
10
- # A text annotation with a set of attributes and a timestamp.
11
- class Event
12
- EMPTY_ATTRIBUTES = {}.freeze
13
-
14
- private_constant :EMPTY_ATTRIBUTES
15
-
16
- # Returns the name of this event
17
- #
18
- # @return [String]
19
- attr_reader :name
20
-
21
- # Returns the frozen attributes for this event
22
- #
23
- # @return [Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}]
24
- attr_reader :attributes
25
-
26
- # Returns the timestamp for this event
27
- #
28
- # @return [Time]
29
- attr_reader :timestamp
30
-
31
- # Returns a new immutable {Event}.
32
- #
33
- # @param [String] name The name of this event
34
- # @param [optional Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}]
35
- # attributes A hash of attributes for this event. Attributes will be
36
- # frozen during Event initialization.
37
- # @param [optional Time] timestamp The timestamp for this event.
38
- # Defaults to Time.now.
39
- # @return [Event]
40
- def initialize(name:, attributes: nil, timestamp: nil)
41
- @name = name
42
- @attributes = attributes.freeze || EMPTY_ATTRIBUTES
43
- @timestamp = timestamp || Time.now
44
- end
45
- end
10
+ # A text annotation with a set of attributes and a timestamp for export.
11
+ #
12
+ # Field types are as follows:
13
+ # name: String
14
+ # attributes: frozen Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}
15
+ # timestamp: Integer nanoseconds since Epoch
16
+ Event = Struct.new(:name, :attributes, :timestamp)
46
17
  end
47
18
  end
48
19
  end
@@ -31,6 +31,5 @@ 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
33
  require 'opentelemetry/sdk/trace/export/metrics_reporter'
34
- require 'opentelemetry/sdk/trace/export/multi_span_exporter'
35
- require 'opentelemetry/sdk/trace/export/noop_span_exporter'
34
+ require 'opentelemetry/sdk/trace/export/span_exporter'
36
35
  require 'opentelemetry/sdk/trace/export/simple_span_processor'
@@ -28,7 +28,8 @@ module OpenTelemetry
28
28
  class BatchSpanProcessor # rubocop:disable Metrics/ClassLength
29
29
  # Returns a new instance of the {BatchSpanProcessor}.
30
30
  #
31
- # @param [SpanExporter] exporter
31
+ # @param [SpanExporter] exporter the (duck type) SpanExporter to where the
32
+ # recorded Spans are pushed after batching.
32
33
  # @param [Numeric] exporter_timeout the delay interval between two
33
34
  # consecutive exports. Defaults to the value of the OTEL_BSP_EXPORT_TIMEOUT
34
35
  # environment variable, if set, or 30,000 (30 seconds).
@@ -43,7 +44,7 @@ module OpenTelemetry
43
44
  # variable, if set, or 512.
44
45
  #
45
46
  # @return a new instance of the {BatchSpanProcessor}.
46
- def initialize(exporter:,
47
+ def initialize(exporter,
47
48
  exporter_timeout: Float(ENV.fetch('OTEL_BSP_EXPORT_TIMEOUT', 30_000)),
48
49
  schedule_delay: Float(ENV.fetch('OTEL_BSP_SCHEDULE_DELAY', 5_000)),
49
50
  max_queue_size: Integer(ENV.fetch('OTEL_BSP_MAX_QUEUE_SIZE', 2048)),
@@ -51,6 +52,7 @@ module OpenTelemetry
51
52
  start_thread_on_boot: String(ENV['OTEL_RUBY_BSP_START_THREAD_ON_BOOT']) !~ /false/i,
52
53
  metrics_reporter: nil)
53
54
  raise ArgumentError if max_export_batch_size > max_queue_size
55
+ raise ArgumentError, "exporter #{exporter.inspect} does not appear to be a valid exporter" unless Common::Utilities.valid_exporter?(exporter)
54
56
 
55
57
  @exporter = exporter
56
58
  @exporter_timeout_seconds = exporter_timeout / 1000.0
@@ -99,7 +101,7 @@ module OpenTelemetry
99
101
  # @return [Integer] SUCCESS if no error occurred, FAILURE if a
100
102
  # non-specific failure occurred, TIMEOUT if a timeout occurred.
101
103
  def force_flush(timeout: nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
102
- start_time = Time.now
104
+ start_time = OpenTelemetry::Common::Utilities.timeout_timestamp
103
105
  snapshot = lock do
104
106
  reset_on_fork if @keep_running
105
107
  spans.shift(spans.size)
@@ -113,7 +115,7 @@ module OpenTelemetry
113
115
  return result_code unless result_code == SUCCESS
114
116
  end
115
117
 
116
- SUCCESS
118
+ @exporter.force_flush(timeout: OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time))
117
119
  ensure
118
120
  # Unshift the remaining spans if we timed out. We drop excess spans from
119
121
  # the snapshot because they're older than any spans in the spans buffer.
@@ -135,7 +137,7 @@ module OpenTelemetry
135
137
  # @return [Integer] SUCCESS if no error occurred, FAILURE if a
136
138
  # non-specific failure occurred, TIMEOUT if a timeout occurred.
137
139
  def shutdown(timeout: nil)
138
- start_time = Time.now
140
+ start_time = OpenTelemetry::Common::Utilities.timeout_timestamp
139
141
  thread = lock do
140
142
  @keep_running = false
141
143
  @condition.signal
@@ -176,6 +178,9 @@ module OpenTelemetry
176
178
  @pid = pid
177
179
  spans.clear
178
180
  @thread = restart_thread ? Thread.new { work } : nil
181
+ rescue ThreadError => e
182
+ @metrics_reporter.add_to_counter('otel.bsp.error', labels: { 'reason' => 'ThreadError' })
183
+ OpenTelemetry.handle_error(exception: e, message: 'unexpected error in BatchSpanProcessor#reset_on_fork')
179
184
  end
180
185
 
181
186
  def export_batch(batch, timeout: @exporter_timeout_seconds)
@@ -26,6 +26,10 @@ module OpenTelemetry
26
26
  SUCCESS
27
27
  end
28
28
 
29
+ def force_flush(timeout: nil)
30
+ SUCCESS
31
+ end
32
+
29
33
  def shutdown(timeout: nil)
30
34
  @stopped = true
31
35
  SUCCESS
@@ -10,30 +10,39 @@ module OpenTelemetry
10
10
  module Export
11
11
  # A SpanExporter implementation that can be used to test OpenTelemetry integration.
12
12
  #
13
- # Example usage:
13
+ # Example usage in a test suite:
14
14
  #
15
15
  # class MyClassTest
16
16
  # def setup
17
17
  # @tracer_provider = TracerProvider.new
18
- # @exporter = InMemorySpanExporter.new
18
+ # # The default is `recording: true`, which is appropriate in non-test environments.
19
+ # @exporter = InMemorySpanExporter.new(recording: false)
19
20
  # @tracer_provider.add_span_processor(SimpleSampledSpansProcessor.new(@exporter))
20
21
  # end
21
22
  #
22
23
  # def test_finished_spans
24
+ # @exporter.recording = true
23
25
  # @tracer_provider.tracer.in_span("span") {}
24
26
  #
25
27
  # spans = @exporter.finished_spans
26
28
  # spans.wont_be_nil
27
29
  # spans.size.must_equal(1)
28
30
  # spans[0].name.must_equal("span")
31
+ #
32
+ # @exporter.recording = false
29
33
  # end
30
34
  class InMemorySpanExporter
35
+ # Controls whether or not the exporter will record spans, or discard them.
36
+ # @return [Boolean] when true, the exporter is recording. By default, this is true.
37
+ attr_accessor :recording
38
+
31
39
  # Returns a new instance of the {InMemorySpanExporter}.
32
40
  #
33
41
  # @return a new instance of the {InMemorySpanExporter}.
34
- def initialize
42
+ def initialize(recording: true)
35
43
  @finished_spans = []
36
44
  @stopped = false
45
+ @recording = recording
37
46
  @mutex = Mutex.new
38
47
  end
39
48
 
@@ -67,11 +76,21 @@ module OpenTelemetry
67
76
  @mutex.synchronize do
68
77
  return FAILURE if @stopped
69
78
 
70
- @finished_spans.concat(span_datas.to_a)
79
+ @finished_spans.concat(span_datas.to_a) if @recording
71
80
  end
72
81
  SUCCESS
73
82
  end
74
83
 
84
+ # Called when {TracerProvider#force_flush} is called, if this exporter is
85
+ # registered to a {TracerProvider} object.
86
+ #
87
+ # @param [optional Numeric] timeout An optional timeout in seconds.
88
+ # @return [Integer] SUCCESS if no error occurred, FAILURE if a
89
+ # non-specific failure occurred, TIMEOUT if a timeout occurred.
90
+ def force_flush(timeout: nil)
91
+ SUCCESS
92
+ end
93
+
75
94
  # Called when {TracerProvider#shutdown} is called, if this exporter is
76
95
  # registered to a {TracerProvider} object.
77
96
  #
@@ -29,6 +29,8 @@ module OpenTelemetry
29
29
  # @return [SimpleSpanProcessor]
30
30
  # @raise ArgumentError if the span_exporter is nil.
31
31
  def initialize(span_exporter)
32
+ raise ArgumentError, "exporter #{span_exporter.inspect} does not appear to be a valid exporter" unless Common::Utilities.valid_exporter?(span_exporter)
33
+
32
34
  @span_exporter = span_exporter
33
35
  end
34
36
 
@@ -61,7 +63,7 @@ module OpenTelemetry
61
63
  end
62
64
 
63
65
  # Export all ended spans to the configured `Exporter` that have not yet
64
- # been exported.
66
+ # been exported, then call {Exporter#force_flush}.
65
67
  #
66
68
  # This method should only be called in cases where it is absolutely
67
69
  # necessary, such as when using some FaaS providers that may suspend
@@ -72,7 +74,7 @@ module OpenTelemetry
72
74
  # @return [Integer] SUCCESS if no error occurred, FAILURE if a
73
75
  # non-specific failure occurred, TIMEOUT if a timeout occurred.
74
76
  def force_flush(timeout: nil)
75
- SUCCESS
77
+ @span_exporter&.force_flush(timeout: timeout) || SUCCESS
76
78
  end
77
79
 
78
80
  # Called when {TracerProvider#shutdown} is called.
@@ -8,29 +8,40 @@ module OpenTelemetry
8
8
  module SDK
9
9
  module Trace
10
10
  module Export
11
- # A noop exporter that demonstrates and documents the SpanExporter
12
- # duck type. SpanExporter allows different tracing services to export
11
+ # SpanExporter describes a duck type. It is not required to subclass this
12
+ # class to provide an implementation of SpanExporter, provided the interface is
13
+ # satisfied. SpanExporter allows different tracing services to export
13
14
  # recorded data for sampled spans in their own format.
14
15
  #
15
16
  # To export data an exporter MUST be registered to the {TracerProvider} using
16
- # a {SimpleSpanProcessor} or a {BatchSpanProcessor}.
17
- class NoopSpanExporter
17
+ # a {SpanProcessor} implementation.
18
+ class SpanExporter
18
19
  def initialize
19
20
  @stopped = false
20
21
  end
21
22
 
22
- # Called to export sampled {Span}s.
23
+ # Called to export sampled {SpanData}s.
23
24
  #
24
- # @param [Enumerable<Span>] spans the list of sampled {Span}s to be
25
+ # @param [Enumerable<SpanData>] span_data the list of sampled {SpanData} to be
25
26
  # exported.
26
27
  # @param [optional Numeric] timeout An optional timeout in seconds.
27
28
  # @return [Integer] the result of the export.
28
- def export(spans, timeout: nil)
29
+ def export(span_data, timeout: nil)
29
30
  return SUCCESS unless @stopped
30
31
 
31
32
  FAILURE
32
33
  end
33
34
 
35
+ # Called when {TracerProvider#force_flush} is called, if this exporter is
36
+ # registered to a {TracerProvider} object.
37
+ #
38
+ # @param [optional Numeric] timeout An optional timeout in seconds.
39
+ # @return [Integer] SUCCESS if no error occurred, FAILURE if a
40
+ # non-specific failure occurred, TIMEOUT if a timeout occurred.
41
+ def force_flush(timeout: nil)
42
+ SUCCESS
43
+ end
44
+
34
45
  # Called when {TracerProvider#shutdown} is called, if this exporter is
35
46
  # registered to a {TracerProvider} object.
36
47
  #
@@ -10,15 +10,16 @@ module OpenTelemetry
10
10
  # Implementation of {OpenTelemetry::Trace::Span} that records trace events.
11
11
  #
12
12
  # This implementation includes reader methods intended to allow access to
13
- # internal state by SpanProcessors (see {NoopSpanProcessor} for the interface).
13
+ # internal state by {SpanProcessor}s.
14
14
  # Instrumentation should use the API provided by {OpenTelemetry::Trace::Span}
15
15
  # and should consider {Span} to be write-only.
16
16
  #
17
17
  # rubocop:disable Metrics/ClassLength
18
18
  class Span < OpenTelemetry::Trace::Span
19
- DEFAULT_STATUS = OpenTelemetry::Trace::Status.new(OpenTelemetry::Trace::Status::UNSET)
19
+ DEFAULT_STATUS = OpenTelemetry::Trace::Status.unset
20
+ EMPTY_ATTRIBUTES = {}.freeze
20
21
 
21
- private_constant(:DEFAULT_STATUS)
22
+ private_constant :DEFAULT_STATUS, :EMPTY_ATTRIBUTES
22
23
 
23
24
  # The following readers are intended for the use of SpanProcessors and
24
25
  # should not be considered part of the public interface for instrumentation.
@@ -64,10 +65,12 @@ module OpenTelemetry
64
65
  #
65
66
  # @param [String] key
66
67
  # @param [String, Boolean, Numeric, Array<String, Numeric, Boolean>] value
68
+ # Values must be non-nil and (array of) string, boolean or numeric type.
69
+ # Array values must not contain nil elements and all elements must be of
70
+ # the same basic type (string, numeric, boolean).
67
71
  #
68
72
  # @return [self] returns itself
69
73
  def set_attribute(key, value)
70
- super
71
74
  @mutex.synchronize do
72
75
  if @ended
73
76
  OpenTelemetry.logger.warn('Calling set_attribute on an ended Span.')
@@ -82,6 +85,33 @@ module OpenTelemetry
82
85
  end
83
86
  alias []= set_attribute
84
87
 
88
+ # Add attributes
89
+ #
90
+ # Note that the OpenTelemetry project
91
+ # {https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md
92
+ # documents} certain "standard attributes" that have prescribed semantic
93
+ # meanings.
94
+ #
95
+ # @param [Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}] attributes
96
+ # Values must be non-nil and (array of) string, boolean or numeric type.
97
+ # Array values must not contain nil elements and all elements must be of
98
+ # the same basic type (string, numeric, boolean).
99
+ #
100
+ # @return [self] returns itself
101
+ def add_attributes(attributes)
102
+ @mutex.synchronize do
103
+ if @ended
104
+ OpenTelemetry.logger.warn('Calling add_attributes on an ended Span.')
105
+ else
106
+ @attributes ||= {}
107
+ @attributes.merge!(attributes)
108
+ trim_span_attributes(@attributes)
109
+ @total_recorded_attributes += attributes.size
110
+ end
111
+ end
112
+ self
113
+ end
114
+
85
115
  # Add an Event to a {Span}.
86
116
  #
87
117
  # Example:
@@ -96,13 +126,12 @@ module OpenTelemetry
96
126
  # @param [String] name Name of the event.
97
127
  # @param [optional Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}] attributes
98
128
  # One or more key:value pairs, where the keys must be strings and the
99
- # values may be string, boolean or numeric type.
129
+ # values may be (array of) string, boolean or numeric type.
100
130
  # @param [optional Time] timestamp Optional timestamp for the event.
101
131
  #
102
132
  # @return [self] returns itself
103
133
  def add_event(name, attributes: nil, timestamp: nil)
104
- super
105
- event = Event.new(name: name, attributes: attributes, timestamp: timestamp || Time.now)
134
+ event = Event.new(name, truncate_attribute_values(attributes), wall_clock(timestamp))
106
135
 
107
136
  @mutex.synchronize do
108
137
  if @ended
@@ -130,7 +159,7 @@ module OpenTelemetry
130
159
  event_attributes = {
131
160
  'exception.type' => exception.class.to_s,
132
161
  'exception.message' => exception.message,
133
- 'exception.stacktrace' => exception.full_message(highlight: false, order: :top)
162
+ 'exception.stacktrace' => exception.full_message(highlight: false, order: :top).encode('UTF-8', invalid: :replace, undef: :replace, replace: '�')
134
163
  }
135
164
  event_attributes.merge!(attributes) unless attributes.nil?
136
165
  add_event('exception', attributes: event_attributes)
@@ -138,21 +167,23 @@ module OpenTelemetry
138
167
 
139
168
  # Sets the Status to the Span
140
169
  #
141
- # If used, this will override the default Span status. Default is OK.
170
+ # If used, this will override the default Span status. Default has code = Status::UNSET.
142
171
  #
143
- # Only the value of the last call will be recorded, and implementations
144
- # are free to ignore previous calls.
172
+ # An attempt to set the status with code == Status::UNSET is ignored.
173
+ # If the status is set with code == Status::OK, any further attempt to set the status
174
+ # is ignored.
145
175
  #
146
176
  # @param [Status] status The new status, which overrides the default Span
147
- # status, which is OK.
177
+ # status, which has code = Status::UNSET.
148
178
  #
149
179
  # @return [void]
150
180
  def status=(status)
151
- super
181
+ return if status.code == OpenTelemetry::Trace::Status::UNSET
182
+
152
183
  @mutex.synchronize do
153
184
  if @ended
154
185
  OpenTelemetry.logger.warn('Calling status= on an ended Span.')
155
- else
186
+ elsif @status.code != OpenTelemetry::Trace::Status::OK
156
187
  @status = status
157
188
  end
158
189
  end
@@ -168,7 +199,6 @@ module OpenTelemetry
168
199
  #
169
200
  # @return [void]
170
201
  def name=(new_name)
171
- super
172
202
  @mutex.synchronize do
173
203
  if @ended
174
204
  OpenTelemetry.logger.warn('Calling name= on an ended Span.')
@@ -203,12 +233,12 @@ module OpenTelemetry
203
233
  OpenTelemetry.logger.warn('Calling finish on an ended Span.')
204
234
  return self
205
235
  end
206
- @end_timestamp = end_timestamp || Time.now
236
+ @end_timestamp = wall_clock(end_timestamp)
207
237
  @attributes = validated_attributes(@attributes).freeze
208
238
  @events.freeze
209
239
  @ended = true
210
240
  end
211
- @span_processor.on_finish(self)
241
+ @span_processors.each { |processor| processor.on_finish(self) }
212
242
  self
213
243
  end
214
244
 
@@ -246,14 +276,14 @@ module OpenTelemetry
246
276
  end
247
277
 
248
278
  # @api private
249
- def initialize(context, parent_context, name, kind, parent_span_id, trace_config, span_processor, attributes, links, start_timestamp, resource, instrumentation_library) # rubocop:disable Metrics/AbcSize
279
+ def initialize(context, parent_context, name, kind, parent_span_id, span_limits, span_processors, attributes, links, start_timestamp, resource, instrumentation_library) # rubocop:disable Metrics/AbcSize
250
280
  super(span_context: context)
251
281
  @mutex = Mutex.new
252
282
  @name = name
253
283
  @kind = kind
254
284
  @parent_span_id = parent_span_id.freeze || OpenTelemetry::Trace::INVALID_SPAN_ID
255
- @trace_config = trace_config
256
- @span_processor = span_processor
285
+ @span_limits = span_limits
286
+ @span_processors = span_processors
257
287
  @resource = resource
258
288
  @instrumentation_library = instrumentation_library
259
289
  @ended = false
@@ -261,13 +291,13 @@ module OpenTelemetry
261
291
  @total_recorded_events = 0
262
292
  @total_recorded_links = links&.size || 0
263
293
  @total_recorded_attributes = attributes&.size || 0
264
- @start_timestamp = start_timestamp
294
+ @start_timestamp = wall_clock(start_timestamp)
265
295
  @end_timestamp = nil
266
296
  @attributes = attributes.nil? ? nil : Hash[attributes] # We need a mutable copy of attributes.
267
297
  trim_span_attributes(@attributes)
268
298
  @events = nil
269
- @links = trim_links(links, trace_config.max_links_count, trace_config.max_attributes_per_link)
270
- @span_processor.on_start(self, parent_context)
299
+ @links = trim_links(links, span_limits.link_count_limit, span_limits.attribute_per_link_count_limit)
300
+ @span_processors.each { |processor| processor.on_start(self, parent_context) }
271
301
  end
272
302
 
273
303
  # TODO: Java implementation overrides finalize to log if a span isn't finished.
@@ -275,7 +305,7 @@ module OpenTelemetry
275
305
  private
276
306
 
277
307
  def validated_attributes(attrs)
278
- return attrs if Internal.valid_attributes?(attrs)
308
+ return attrs if Internal.valid_attributes?(name, 'span', attrs)
279
309
 
280
310
  attrs.keep_if { |key, value| Internal.valid_key?(key) && Internal.valid_value?(value) }
281
311
  end
@@ -283,56 +313,70 @@ module OpenTelemetry
283
313
  def trim_span_attributes(attrs)
284
314
  return if attrs.nil?
285
315
 
286
- excess = attrs.size - @trace_config.max_attributes_count
316
+ excess = attrs.size - @span_limits.attribute_count_limit
287
317
  excess.times { attrs.shift } if excess.positive?
318
+ truncate_attribute_values(attrs)
288
319
  nil
289
320
  end
290
321
 
291
- def trim_links(links, max_links_count, max_attributes_per_link) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
322
+ def truncate_attribute_values(attrs)
323
+ return EMPTY_ATTRIBUTES if attrs.nil?
324
+
325
+ attribute_length_limit = @span_limits.attribute_length_limit
326
+ attrs.each { |key, value| attrs[key] = OpenTelemetry::Common::Utilities.truncate(value, attribute_length_limit) } if attribute_length_limit
327
+ attrs
328
+ end
329
+
330
+ def trim_links(links, link_count_limit, attribute_per_link_count_limit) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
292
331
  # Fast path (likely) common cases.
293
332
  return nil if links.nil?
294
333
 
295
- if links.size <= max_links_count &&
296
- links.all? { |link| link.attributes.size <= max_attributes_per_link && Internal.valid_attributes?(link.attributes) }
334
+ if links.size <= link_count_limit &&
335
+ links.all? { |link| link.attributes.size <= attribute_per_link_count_limit && Internal.valid_attributes?(name, 'link', link.attributes) }
297
336
  return links.frozen? ? links : links.clone.freeze
298
337
  end
299
338
 
300
339
  # Slow path: trim attributes for each Link.
301
- links.last(max_links_count).map! do |link|
340
+ links.last(link_count_limit).map! do |link|
302
341
  attrs = Hash[link.attributes] # link.attributes is frozen, so we need an unfrozen copy to adjust.
303
342
  attrs.keep_if { |key, value| Internal.valid_key?(key) && Internal.valid_value?(value) }
304
- excess = attrs.size - max_attributes_per_link
343
+ excess = attrs.size - attribute_per_link_count_limit
305
344
  excess.times { attrs.shift } if excess.positive?
306
345
  OpenTelemetry::Trace::Link.new(link.span_context, attrs)
307
346
  end.freeze
308
347
  end
309
348
 
310
349
  def append_event(events, event) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
311
- max_events_count = @trace_config.max_events_count
312
- max_attributes_per_event = @trace_config.max_attributes_per_event
313
- valid_attributes = Internal.valid_attributes?(event.attributes)
350
+ event_count_limit = @span_limits.event_count_limit
351
+ attribute_per_event_count_limit = @span_limits.attribute_per_event_count_limit
352
+ valid_attributes = Internal.valid_attributes?(name, 'event', event.attributes)
314
353
 
315
354
  # Fast path (likely) common case.
316
- if events.size < max_events_count &&
317
- event.attributes.size <= max_attributes_per_event &&
355
+ if events.size < event_count_limit &&
356
+ event.attributes.size <= attribute_per_event_count_limit &&
318
357
  valid_attributes
319
358
  return events << event
320
359
  end
321
360
 
322
361
  # Slow path.
323
- excess = events.size + 1 - max_events_count
362
+ excess = events.size + 1 - event_count_limit
324
363
  events.shift(excess) if excess.positive?
325
364
 
326
- excess = event.attributes.size - max_attributes_per_event
365
+ excess = event.attributes.size - attribute_per_event_count_limit
327
366
  if excess.positive? || !valid_attributes
328
367
  attrs = Hash[event.attributes] # event.attributes is frozen, so we need an unfrozen copy to adjust.
329
368
  attrs.keep_if { |key, value| Internal.valid_key?(key) && Internal.valid_value?(value) }
330
- excess = attrs.size - max_attributes_per_event
369
+ excess = attrs.size - attribute_per_event_count_limit
331
370
  excess.times { attrs.shift } if excess.positive?
332
- event = Event.new(name: event.name, attributes: attrs, timestamp: event.timestamp)
371
+ event = Event.new(event.name, attrs.freeze, event.timestamp)
333
372
  end
334
373
  events << event
335
374
  end
375
+
376
+ def wall_clock(timestamp)
377
+ timestamp = (timestamp.to_r * 1_000_000_000).to_i unless timestamp.nil?
378
+ timestamp || Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
379
+ end
336
380
  end
337
381
  # rubocop:enable Metrics/ClassLength
338
382
  end