opentelemetry-exporters-datadog 0.1.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.
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Unless explicitly stated otherwise all files in this repository are licensed
4
+ # under the Apache 2.0 license (see LICENSE).
5
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
6
+ # Copyright 2020 Datadog, Inc.
7
+
8
+ require 'opentelemetry/exporters/datadog'
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Unless explicitly stated otherwise all files in this repository are licensed
4
+ # under the Apache 2.0 license (see LICENSE).
5
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
6
+ # Copyright 2020 Datadog, Inc.
7
+
8
+ # require_relative './datadog/exporter.rb'
9
+ # require_relative './datadog/version.rb'
10
+ # require_relative './datadog/datadog_span_processor.rb'
11
+ # require_relative './datadog/propagator.rb'
12
+ # require_relative './datadog_probability_sampler'
13
+ require 'opentelemetry/exporters/datadog/exporter'
14
+ require 'opentelemetry/exporters/datadog/version'
15
+ require 'opentelemetry/exporters/datadog/datadog_span_processor'
16
+ require 'opentelemetry/exporters/datadog/propagator'
17
+ require 'opentelemetry/exporters/datadog/datadog_probability_sampler'
18
+
19
+ # OpenTelemetry is an open source observability framework, providing a
20
+ # general-purpose API, SDK, and related tools required for the instrumentation
21
+ # of cloud-native software, frameworks, and libraries.
22
+ #
23
+ # The OpenTelemetry module provides global accessors for telemetry objects.
24
+ # See the documentation for the `opentelemetry-api` gem for details.
25
+ module OpenTelemetry
26
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Unless explicitly stated otherwise all files in this repository are licensed
4
+ # under the Apache 2.0 license (see LICENSE).
5
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
6
+ # Copyright 2020 Datadog, Inc.
7
+
8
+ require 'opentelemetry/sdk/trace/samplers/probability_sampler'
9
+ require 'opentelemetry/sdk/trace/samplers/decision'
10
+ require 'opentelemetry/sdk/trace/samplers/result'
11
+
12
+ module OpenTelemetry
13
+ module Exporters
14
+ module Datadog
15
+ # Implements sampling based on a probability but records all spans regardless.
16
+ class DatadogProbabilitySampler < OpenTelemetry::SDK::Trace::Samplers::ProbabilitySampler
17
+ RECORD_AND_SAMPLED = OpenTelemetry::SDK::Trace::Samplers::Result.new(decision: OpenTelemetry::SDK::Trace::Samplers::Decision::RECORD_AND_SAMPLED)
18
+ RECORD = OpenTelemetry::SDK::Trace::Samplers::Result.new(decision: OpenTelemetry::SDK::Trace::Samplers::Decision::RECORD)
19
+
20
+ private_constant(:RECORD_AND_SAMPLED, :RECORD)
21
+
22
+ # @api private
23
+ #
24
+ # See {Samplers}.
25
+ def should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:)
26
+ # Ignored for sampling decision: links, name, kind, attributes.
27
+
28
+ if sample?(trace_id, parent_context)
29
+ RECORD_AND_SAMPLED
30
+ else
31
+ RECORD
32
+ end
33
+ end
34
+
35
+ # Returns a new sampler. The probability of sampling a trace is equal
36
+ # to that of the specified probability.
37
+ #
38
+ # @param [Numeric] probability The desired probability of sampling.
39
+ # Must be within [0.0, 1.0].
40
+ def self.default_with_probability(probability = 1.0)
41
+ raise ArgumentError, 'probability must be in range [0.0, 1.0]' unless (0.0..1.0).include?(probability)
42
+
43
+ new(probability,
44
+ ignore_parent: false,
45
+ apply_to_remote_parent: :root_spans_and_remote_parent,
46
+ apply_to_all_spans: :root_spans_and_remote_parent)
47
+ end
48
+
49
+ DEFAULT = new(1.0,
50
+ ignore_parent: false,
51
+ apply_to_remote_parent: :root_spans_and_remote_parent,
52
+ apply_to_all_spans: :root_spans_and_remote_parent)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Unless explicitly stated otherwise all files in this repository are licensed
4
+ # under the Apache 2.0 license (see LICENSE).
5
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
6
+ # Copyright 2020 Datadog, Inc.
7
+
8
+ require 'timeout'
9
+
10
+ module OpenTelemetry
11
+ module Exporters
12
+ module Datadog
13
+ # Implementation of the duck type SpanProcessor that batches spans
14
+ # exported by the SDK into complete traces then pushes them
15
+ # to the exporter pipeline.
16
+ #
17
+ # All spans reported by the SDK implementation are first added to a
18
+ # synchronized in memory trace storage (with a {max_queue_size}
19
+ # maximum size, of trace size {max_trace_size} after the size of
20
+ # either is reached spans are dropped). When traces are designated
21
+ # as "complete" they're added to a queue that is exported every
22
+ # schedule_delay_millis to the exporter pipeline in batches of
23
+ # completed traces. The datadog writer and transport supplied
24
+ # to the exporter handle the bulk of the timeout and retry logic.
25
+ class DatadogSpanProcessor
26
+ SCHEDULE_DELAY_MILLIS = 3_000
27
+ MAX_QUEUE_SIZE = 2048
28
+ MAX_TRACE_SIZE = 1024
29
+ private_constant(:SCHEDULE_DELAY_MILLIS, :MAX_QUEUE_SIZE, :MAX_TRACE_SIZE)
30
+
31
+ def initialize(exporter:,
32
+ schedule_delay_millis: SCHEDULE_DELAY_MILLIS,
33
+ max_queue_size: MAX_QUEUE_SIZE,
34
+ max_trace_size: MAX_TRACE_SIZE)
35
+ raise ArgumentError if max_trace_size > max_queue_size
36
+
37
+ @exporter = exporter
38
+ @mutex = Mutex.new
39
+ @condition = ConditionVariable.new
40
+ @keep_running = true
41
+ @delay_seconds = schedule_delay_millis / 1000.0
42
+ @max_queue_size = max_queue_size
43
+ @max_trace_size = max_trace_size
44
+ @spans = []
45
+ @thread = Thread.new { work }
46
+
47
+ @traces = {}
48
+ @traces_spans_count = {}
49
+ @traces_spans_ended_count = {}
50
+ @check_traces_queue = []
51
+ @_spans_dropped = false
52
+ end
53
+
54
+ # datadog trace-agent endpoint requires a complete trace to be sent
55
+ # threadsafe may block on lock
56
+ def on_start(span)
57
+ context = span.context
58
+ trace_id = context.trace_id
59
+
60
+ lock do
61
+ if all_spans_count(traces_spans_count) >= max_queue_size
62
+ # instead of just dropping all new spans, dd-trace-rb drops a random trace
63
+ # https://github.com/DataDog/dd-trace-rb/blob/c6fbf2410a60495f1b2d8912bf7ea7dc63422141/lib/ddtrace/buffer.rb#L34-L36
64
+ # It allows for a more fair usage of the queue when under stress load,
65
+ # and will create proportional representation of code paths being instrumented at stress time.
66
+ unfinished_trace_id = fetch_unfinished_trace_id
67
+
68
+ # if there are no unfinished traces able to be dropped, don't add more spans, and return early
69
+ if unfinished_trace_id.nil?
70
+ OpenTelemetry.logger.warn('Max spans for all traces, spans will be dropped')
71
+ @_spans_dropped = true
72
+ return
73
+ end
74
+
75
+ drop_unfinished_trace(unfinished_trace_id)
76
+ OpenTelemetry.logger.warn('Max spans for all traces, traces will be dropped')
77
+ end
78
+
79
+ if traces[trace_id].nil?
80
+ traces[trace_id] = [span]
81
+ traces_spans_count[trace_id] = 1
82
+ else
83
+ if traces[trace_id].size >= max_trace_size
84
+ OpenTelemetry.logger.warn('Max spans for trace, spans will be dropped')
85
+ @_spans_dropped = true
86
+ return
87
+ end
88
+
89
+ traces[trace_id] << span
90
+ traces_spans_count[trace_id] += 1
91
+ end
92
+ end
93
+ end
94
+
95
+ # adds a span to the batcher, threadsafe may block on lock
96
+ def on_finish(span)
97
+ if @keep_running == false
98
+ OpenTelemetry.logger.warn('Already shutdown, dropping span')
99
+ return
100
+ end
101
+
102
+ # TODO: determine if all "not-sampled" spans still get passed to on_finish?
103
+ # If so then we don't need to account for Probability Sampling
104
+ # and can likely incorporate Priority Sampling from DD
105
+ # If not, then we need to ensure the rate from OpenTelemetry.tracer_provider.active_trace_config.sampler
106
+ # can be expoed to the span or attached to spanData in some way
107
+ # return unless span.context.trace_flags.sampled?
108
+
109
+ context = span.context
110
+ trace_id = context.trace_id
111
+
112
+ lock do
113
+ if traces_spans_ended_count[trace_id].nil?
114
+ traces_spans_ended_count[trace_id] = 1
115
+ else
116
+ traces_spans_ended_count[trace_id] += 1
117
+ end
118
+
119
+ check_traces_queue.unshift(trace_id) if trace_exportable?(trace_id)
120
+ end
121
+ end
122
+
123
+ # TODO: test this explicitly.
124
+ # Export all ended traces to the configured `Exporter` that have not yet
125
+ # been exported.
126
+ #
127
+ # This method should only be called in cases where it is absolutely
128
+ # necessary, such as when using some FaaS providers that may suspend
129
+ # the process after an invocation, but before the `Processor` exports
130
+ # the completed spans.
131
+ def force_flush
132
+ snapshot = lock { fetch_batch }
133
+ export_batch(snapshot)
134
+ end
135
+
136
+ # shuts the consumer thread down and flushes the current accumulated buffer
137
+ # will block until the thread is finished
138
+ def shutdown
139
+ lock do
140
+ @keep_running = false
141
+ @condition.signal
142
+ end
143
+
144
+ @thread.join
145
+ force_flush
146
+ @exporter.shutdown
147
+ end
148
+
149
+ private
150
+
151
+ attr_reader :check_traces_queue, :max_queue_size, :max_trace_size, :traces, :traces_spans_count, :traces_spans_ended_count
152
+
153
+ def work
154
+ while @keep_running
155
+ trace_spans = lock do
156
+ @condition.wait(@mutex, @delay_seconds) if @keep_running
157
+ @condition.wait(@mutex, @delay_seconds) while check_traces_queue.empty? && @keep_running
158
+ return unless @keep_running
159
+
160
+ fetch_batch
161
+ end
162
+
163
+ export_batch(trace_spans)
164
+ end
165
+ end
166
+
167
+ def export_batch(trace_spans)
168
+ return if trace_spans.empty?
169
+
170
+ trace_spans.each do |spans|
171
+ @exporter.export(spans)
172
+ rescue StandardError => e
173
+ OpenTelemetry.logger.warn("Exception while exporting Span batch. #{e.message} , #{e.backtrace}")
174
+ end
175
+ end
176
+
177
+ def trace_exportable?(trace_id)
178
+ traces_spans_count[trace_id] - traces_spans_ended_count[trace_id] <= 0 if traces_spans_count.key?(trace_id) && traces_spans_ended_count.key?(trace_id)
179
+ end
180
+
181
+ def all_spans_count(traces_spans_count)
182
+ traces_spans_count.values.sum
183
+ end
184
+
185
+ def fetch_batch
186
+ export_traces = []
187
+
188
+ check_traces_queue.reverse_each do |trace_id|
189
+ next unless trace_exportable?(trace_id)
190
+
191
+ export_traces << fetch_spans(traces.delete(trace_id))
192
+ check_traces_queue.delete(trace_id)
193
+ traces_spans_count.delete(trace_id)
194
+ traces_spans_ended_count.delete(trace_id)
195
+ end
196
+
197
+ export_traces
198
+ end
199
+
200
+ def fetch_spans(spans)
201
+ spans.map!(&:to_span_data)
202
+ end
203
+
204
+ def fetch_unfinished_trace_id
205
+ # don't delete potentially finished trace awaiting export
206
+ unfinished_traces = traces.keys - check_traces_queue
207
+ unfinished_traces[rand(unfinished_traces.length)]
208
+ end
209
+
210
+ def drop_unfinished_trace(trace_id)
211
+ traces.delete(trace_id)
212
+ traces_spans_count.delete(trace_id)
213
+ traces_spans_ended_count.delete(trace_id)
214
+ end
215
+
216
+ def lock
217
+ @mutex.synchronize do
218
+ yield
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Unless explicitly stated otherwise all files in this repository are licensed
4
+ # under the Apache 2.0 license (see LICENSE).
5
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
6
+ # Copyright 2020 Datadog, Inc.
7
+
8
+ require 'uri'
9
+ require 'ddtrace'
10
+ require 'opentelemetry/sdk'
11
+ require 'opentelemetry/exporters/datadog/exporter/span_encoder'
12
+ # require_relative './exporter/span_encoder.rb'
13
+
14
+ module OpenTelemetry
15
+ module Exporters
16
+ module Datadog
17
+ # SpanExporter allows different tracing services to export
18
+ # recorded data for sampled spans in their own format.
19
+ #
20
+ # To export data an exporter MUST be registered to the {TracerProvider} using
21
+ # a {DatadogSpanProcessorr}.
22
+ class Exporter
23
+ DEFAULT_AGENT_URL = 'http://localhost:8126'
24
+ DEFAULT_SERVICE_NAME = 'my_service'
25
+ SUCCESS = begin
26
+ OpenTelemetry::SDK::Trace::Export::SUCCESS
27
+ rescue NameError
28
+ 0
29
+ end
30
+ FAILURE = begin
31
+ OpenTelemetry::SDK::Trace::Export::FAILURE
32
+ rescue NameError
33
+ 1
34
+ end
35
+ private_constant(:SUCCESS, :FAILURE)
36
+
37
+ def initialize(service_name: nil, agent_url: nil, env: nil, version: nil, tags: nil)
38
+ @shutdown = false
39
+ @agent_url = agent_url || ENV.fetch('DD_TRACE_AGENT_URL', DEFAULT_AGENT_URL)
40
+ @service = service_name || ENV.fetch('DD_SERVICE', DEFAULT_SERVICE_NAME)
41
+
42
+ @env = env || ENV.fetch('DD_ENV', nil)
43
+ @version = version || ENV.fetch('DD_VERSION', nil)
44
+ @tags = tags || ENV.fetch('DD_TAGS', nil)
45
+
46
+ @agent_writer = get_writer(@agent_url)
47
+
48
+ @span_encoder = SpanEncoder.new
49
+ end
50
+
51
+ # Called to export sampled {Span}s.
52
+ #
53
+ # @param [Enumerable<Span>] spans the list of sampled {Span}s to be
54
+ # exported.
55
+ # @return [Integer] the result of the export.
56
+ def export(spans)
57
+ return FAILURE if @shutdown
58
+
59
+ if @agent_writer
60
+ datadog_spans = @span_encoder.translate_to_datadog(spans, @service, @env, @version, @tags)
61
+ @agent_writer.write(datadog_spans)
62
+ SUCCESS
63
+ else
64
+ OpenTelemetry.logger.debug('Agent writer not set')
65
+ FAILURE
66
+ end
67
+ end
68
+
69
+ # Called when {TracerProvider#shutdown} is called, if this exporter is
70
+ # registered to a {TracerProvider} object.
71
+ def shutdown
72
+ @shutdown = true
73
+ end
74
+
75
+ private
76
+
77
+ def get_writer(uri)
78
+ uri_parsed = URI.parse(uri)
79
+
80
+ if %w[http https].include?(uri_parsed.scheme)
81
+ hostname = uri_parsed.hostname
82
+ port = uri_parsed.port
83
+
84
+ adapter = ::Datadog::Transport::HTTP::Adapters::Net.new(hostname, port)
85
+
86
+ transport = ::Datadog::Transport::HTTP.default do |t|
87
+ t.adapter adapter
88
+ end
89
+
90
+ ::Datadog::Writer.new(transport: transport)
91
+ elsif uri_parsed.to_s.index('/sock')
92
+ # handle uds path
93
+ transport = ::Datadog::Transport::HTTP.default do |t|
94
+ t.adapter :unix, uri_parsed.to_s
95
+ end
96
+
97
+ ::Datadog::Writer.new(transport: transport)
98
+ else
99
+ OpenTelemetry.logger.warn('only http/https and uds is supported at this time')
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Unless explicitly stated otherwise all files in this repository are licensed
4
+ # under the Apache 2.0 license (see LICENSE).
5
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
6
+ # Copyright 2020 Datadog, Inc.
7
+
8
+ require 'ddtrace/span'
9
+ require 'ddtrace/ext/http'
10
+ require 'ddtrace/ext/sql'
11
+ require 'ddtrace/ext/app_types'
12
+ require 'ddtrace/contrib/redis/ext'
13
+ require 'opentelemetry/trace/status'
14
+ require 'ddtrace/distributed_tracing/headers/headers'
15
+
16
+ module OpenTelemetry
17
+ module Exporters
18
+ module Datadog
19
+ class Exporter
20
+ # @api private
21
+ class SpanEncoder
22
+ ENV_KEY = 'env'
23
+ VERSION_KEY = 'version'
24
+ DD_ORIGIN = '_dd_origin'
25
+ USER_REJECT = -1
26
+ AUTO_REJECT = 0
27
+ AUTO_KEEP = 1
28
+ USER_KEEP = 2
29
+ INTERNAL_TRACE_REGEX = %r{/v\d\.\d/traces}.freeze
30
+ SAMPLE_RATE_METRIC_KEY = '_sample_rate'
31
+ SAMPLING_PRIORITY_KEY = '_sampling_priority_v1'
32
+ ORIGIN_REGEX = /#{DD_ORIGIN}\=(.*?)($|,)/.freeze
33
+ PROBABILITY_REGEX = /\d[.]\d{1,6}/.freeze
34
+ TRUNCATION_HELPER = ::Datadog::DistributedTracing::Headers::Headers.new({})
35
+
36
+ INSTRUMENTATION_SPAN_TYPES = {
37
+ 'OpenTelemetry::Instrumentation::Ethon' => ::Datadog::Ext::HTTP::TYPE_OUTBOUND,
38
+ 'OpenTelemetry::Instrumentation::Excon' => ::Datadog::Ext::HTTP::TYPE_OUTBOUND,
39
+ 'OpenTelemetry::Instrumentation::Faraday' => ::Datadog::Ext::HTTP::TYPE_OUTBOUND,
40
+ 'OpenTelemetry::Instrumentation::Mysql2' => ::Datadog::Ext::SQL::TYPE,
41
+ 'OpenTelemetry::Instrumentation::Net::HTTP' => ::Datadog::Ext::HTTP::TYPE_OUTBOUND,
42
+ 'OpenTelemetry::Instrumentation::Rack' => ::Datadog::Ext::HTTP::TYPE_INBOUND,
43
+ 'OpenTelemetry::Instrumentation::Redis' => ::Datadog::Contrib::Redis::Ext::TYPE,
44
+ 'OpenTelemetry::Instrumentation::RestClient' => ::Datadog::Ext::HTTP::TYPE_OUTBOUND,
45
+ 'OpenTelemetry::Instrumentation::Sidekiq' => ::Datadog::Ext::AppTypes::WORKER,
46
+ 'OpenTelemetry::Instrumentation::Sinatra' => ::Datadog::Ext::HTTP::TYPE_INBOUND
47
+ }.freeze
48
+
49
+ def translate_to_datadog(otel_spans, service, env = nil, version = nil, tags = nil) # rubocop:disable Metrics/AbcSize
50
+ datadog_spans = []
51
+
52
+ default_tags = get_default_tags(tags) || {}
53
+
54
+ otel_spans.each do |span|
55
+ trace_id, span_id, parent_id = get_trace_ids(span)
56
+ span_type = get_span_type(span)
57
+ span_name = get_span_name(span)
58
+
59
+ datadog_span = ::Datadog::Span.new(nil, span_name,
60
+ service: service,
61
+ trace_id: trace_id,
62
+ parent_id: parent_id,
63
+ resource: get_resource(span),
64
+ span_type: span_type)
65
+
66
+ # span_id is autogenerated so have to override
67
+ datadog_span.span_id = span_id
68
+ datadog_span.start_time = span.start_timestamp
69
+ datadog_span.end_time = span.end_timestamp
70
+
71
+ # set span.error, span tag error.msg/error.type
72
+ if span.status && span.status.canonical_code != OpenTelemetry::Trace::Status::OK
73
+ datadog_span.status = 1
74
+
75
+ exception_type, exception_msg, exception_stack = get_exception_info(span)
76
+
77
+ if exception_type && exception_msg && exception_stack
78
+ datadog_span.set_tag('error.type', exception_type)
79
+ datadog_span.set_tag('error.msg', exception_msg)
80
+ datadog_span.set_tag('error.stack', exception_stack)
81
+ end
82
+ end
83
+
84
+ # set default tags
85
+ default_tags&.keys&.each do |attribute|
86
+ datadog_span.set_tag(attribute, span.attributes[attribute])
87
+ end
88
+
89
+ origin = get_origin_string(span)
90
+ datadog_span.set_tag(DD_ORIGIN, origin) if origin && parent_id.zero?
91
+ datadog_span.set_tag(VERSION_KEY, version) if version && parent_id.zero?
92
+ datadog_span.set_tag(ENV_KEY, env) if env
93
+
94
+ # set tags - takes precedence over env vars
95
+ span.attributes&.keys&.each do |attribute|
96
+ datadog_span.set_tag(attribute, span.attributes[attribute])
97
+ end
98
+
99
+ sampling_rate = get_sampling_rate(span)
100
+
101
+ if filter_internal_request?(span)
102
+ datadog_span.set_metric(SAMPLE_RATE_METRIC_KEY, USER_REJECT)
103
+ elsif sampling_rate
104
+ datadog_span.set_metric(SAMPLE_RATE_METRIC_KEY, sampling_rate)
105
+ end
106
+
107
+ datadog_spans << datadog_span
108
+ end
109
+
110
+ datadog_spans
111
+ end
112
+
113
+ def int64(hex_string, base)
114
+ TRUNCATION_HELPER.value_to_id(hex_string, base)
115
+ end
116
+
117
+ private
118
+
119
+ def get_trace_ids(span)
120
+ trace_id = int64(span.trace_id.unpack1('H*'), 16)
121
+ span_id = int64(span.span_id.unpack1('H*'), 16)
122
+ parent_id = span.parent_span_id ? int64(span.parent_span_id.unpack1('H*'), 16) || 0 : 0
123
+
124
+ [trace_id, span_id, parent_id]
125
+ rescue StandardError => e
126
+ OpenTelemetry.logger.debug("error encoding trace_ids #{e.message}")
127
+ [0, 0, 0]
128
+ end
129
+
130
+ def get_span_type(span)
131
+ # Get Datadog span type
132
+ return unless span.instrumentation_library
133
+
134
+ instrumentation_name = span.instrumentation_library.name
135
+ INSTRUMENTATION_SPAN_TYPES[instrumentation_name]
136
+ rescue NoMethodError
137
+ span.name
138
+ end
139
+
140
+ def get_exception_info(span)
141
+ # Parse span exception type, msg, and stack from span events
142
+ error_event = span&.events&.find { |ev| ev.name == 'error' }
143
+
144
+ return unless error_event
145
+
146
+ err_type = error_event.attributes['error.type']
147
+ err_msg = error_event.attributes['error.msg']
148
+ err_stack = error_event.attributes['error.stack']
149
+
150
+ [err_type, err_msg, err_stack]
151
+ rescue StandardError => e
152
+ OpenTelemetry.logger.debug("error on exception info from span events: #{span.events} , #{e.message}")
153
+ end
154
+
155
+ def get_resource(span)
156
+ # Get resource name for http related spans
157
+ # TODO: how to handle resource naming for broader span types, ie db/cache/queue etc
158
+
159
+ if span.attributes&.key?('http.method')
160
+ route = span.attributes['http.route'] || span.attributes['http.target']
161
+
162
+ return span.attributes['http.method'] + ' ' + route if route
163
+
164
+ return span.attributes['http.method']
165
+ end
166
+
167
+ span.name
168
+ rescue StandardError => e
169
+ OpenTelemetry.logger.debug("error encoding trace_ids #{e.message}")
170
+ span.name
171
+ end
172
+
173
+ def get_sampling_rate(span)
174
+ get_rate_from_description(span) || 1 if span.trace_flags&.sampled?
175
+ end
176
+
177
+ def get_span_name(span)
178
+ # Get span name by using instrumentation and kind while backing off to
179
+ instrumentation_name = span.instrumentation_library&.name
180
+ kind = span.kind
181
+ instrumentation_name && kind ? "#{instrumentation_name.to_s.gsub(':', '_')}.#{kind}" : span.name
182
+ rescue NoMethodError
183
+ span.name
184
+ end
185
+
186
+ def get_origin_string(span)
187
+ tracestate = begin
188
+ span.tracestate
189
+ rescue NoMethodError
190
+ nil
191
+ end
192
+
193
+ return if tracestate.nil? || tracestate.index(DD_ORIGIN).nil?
194
+
195
+ # Depending on the edge cases in tracestate values this might be
196
+ # less efficient than mapping string => array => hash.
197
+ origin_value = tracestate.match(ORIGIN_REGEX)
198
+ return if origin_value.nil?
199
+
200
+ origin_value[1]
201
+ rescue StandardError => e
202
+ OpenTelemetry.logger.debug("error getting origin from trace state, #{e.message}")
203
+ end
204
+
205
+ def get_default_tags(tags)
206
+ # Parse a string of tags typically provided via environment variables.
207
+ # The expected string is of the form: "key1:value1,key2:value2"
208
+
209
+ return {} if tags.nil?
210
+
211
+ tag_map = tags.split(',').map { |kv| kv.split(':') }.to_h
212
+
213
+ if tag_map.keys&.index('') || tag_map.values&.index('') || tag_map.values&.any? { |v| v.ends_with?(':') }
214
+ OpenTelemetry.logger.debug("malformed tag in default tags: #{tags}")
215
+ {}
216
+ else
217
+ tag_map
218
+ end
219
+ end
220
+
221
+ def get_rate_from_description(span)
222
+ # format to parse of sampler description is
223
+ # "ProbabilitySampler{1.000000}" or
224
+ # "AlwaysOnSampler" / "AlwaysOffSampler"
225
+ # TODO: remove begin/rescue block if PR #282 is accepted+released
226
+ sampler = begin
227
+ span.sampler
228
+ rescue NoMethodError
229
+ nil
230
+ end
231
+
232
+ return nil unless sampler&.is_a?(ProbabilitySampler)
233
+
234
+ rate = sampler.description&.match(PROBABILITY_REGEX)
235
+
236
+ return nil unless rate
237
+
238
+ rate[0].to_f(4)
239
+ rescue StandardError => e
240
+ # rescue just in case the format changes dramatically in the future
241
+ OpenTelemetry.logger.warn("error while extracting sampling rate #{e.message} , #{e.backtrace}")
242
+ nil
243
+ end
244
+
245
+ def filter_internal_request?(span)
246
+ span.attributes['http.route'].match(INTERNAL_TRACE_REGEX) if span.attributes&.key?('http.route')
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end