solarwinds_apm 6.0.0 → 6.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/oboe_metal/extconf.rb +42 -41
- data/ext/oboe_metal/lib/liboboe-1.0-aarch64.so.sha256 +1 -1
- data/ext/oboe_metal/lib/liboboe-1.0-alpine-aarch64.so.sha256 +1 -1
- data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.sha256 +1 -1
- data/ext/oboe_metal/lib/liboboe-1.0-lambda-aarch64.so.sha256 +1 -0
- data/ext/oboe_metal/lib/liboboe-1.0-lambda-x86_64.so.sha256 +1 -0
- data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.sha256 +1 -1
- data/ext/oboe_metal/src/VERSION +1 -1
- data/ext/oboe_metal/src/oboe.h +3 -0
- data/ext/oboe_metal/src/oboe_api.cpp +1 -1
- data/lib/oboe_metal.rb +30 -27
- data/lib/rails/generators/solarwinds_apm/install_generator.rb +21 -19
- data/lib/solarwinds_apm/api/current_trace_info.rb +16 -9
- data/lib/solarwinds_apm/api/custom_metrics.rb +6 -4
- data/lib/solarwinds_apm/api/opentelemetry.rb +8 -4
- data/lib/solarwinds_apm/api/tracing.rb +6 -4
- data/lib/solarwinds_apm/api/transaction_name.rb +21 -11
- data/lib/solarwinds_apm/api.rb +7 -5
- data/lib/solarwinds_apm/config.rb +70 -46
- data/lib/solarwinds_apm/constants.rb +25 -23
- data/lib/solarwinds_apm/logger.rb +2 -0
- data/lib/solarwinds_apm/noop/api.rb +3 -1
- data/lib/solarwinds_apm/noop/context.rb +2 -0
- data/lib/solarwinds_apm/noop/metadata.rb +2 -0
- data/lib/solarwinds_apm/noop/span.rb +2 -0
- data/lib/solarwinds_apm/noop.rb +8 -6
- data/lib/solarwinds_apm/oboe_init_options.rb +47 -39
- data/lib/solarwinds_apm/opentelemetry/otlp_processor.rb +135 -0
- data/lib/solarwinds_apm/opentelemetry/solarwinds_exporter.rb +66 -41
- data/lib/solarwinds_apm/opentelemetry/solarwinds_processor.rb +49 -51
- data/lib/solarwinds_apm/opentelemetry/solarwinds_propagator.rb +30 -22
- data/lib/solarwinds_apm/opentelemetry/solarwinds_response_propagator.rb +29 -16
- data/lib/solarwinds_apm/opentelemetry/solarwinds_sampler.rb +135 -98
- data/lib/solarwinds_apm/opentelemetry.rb +8 -5
- data/lib/solarwinds_apm/otel_config.rb +32 -25
- data/lib/solarwinds_apm/otel_lambda_config.rb +53 -0
- data/lib/solarwinds_apm/patch.rb +2 -0
- data/lib/solarwinds_apm/support/logger_formatter.rb +4 -2
- data/lib/solarwinds_apm/support/logging_log_event.rb +2 -0
- data/lib/solarwinds_apm/support/lumberjack_formatter.rb +2 -0
- data/lib/solarwinds_apm/support/oboe_tracing_mode.rb +2 -0
- data/lib/solarwinds_apm/support/service_key_checker.rb +18 -6
- data/lib/solarwinds_apm/support/support_report.rb +5 -3
- data/lib/solarwinds_apm/support/swomarginalia/comment.rb +18 -17
- data/lib/solarwinds_apm/support/swomarginalia/load_swomarginalia.rb +13 -12
- data/lib/solarwinds_apm/support/swomarginalia/railtie.rb +4 -2
- data/lib/solarwinds_apm/support/swomarginalia/swomarginalia.rb +3 -1
- data/lib/solarwinds_apm/support/transaction_cache.rb +6 -4
- data/lib/solarwinds_apm/support/transaction_settings.rb +7 -3
- data/lib/solarwinds_apm/support/txn_name_manager.rb +8 -3
- data/lib/solarwinds_apm/support/utils.rb +12 -9
- data/lib/solarwinds_apm/support/x_trace_options.rb +23 -17
- data/lib/solarwinds_apm/support.rb +28 -24
- data/lib/solarwinds_apm/version.rb +3 -1
- data/lib/solarwinds_apm.rb +44 -34
- metadata +14 -23
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
8
|
+
|
9
|
+
module SolarWindsAPM
|
10
|
+
module OpenTelemetry
|
11
|
+
# reference: OpenTelemetry::SDK::Trace::SpanProcessor; inheritance: SolarWindsProcessor
|
12
|
+
class OTLPProcessor < SolarWindsProcessor
|
13
|
+
attr_accessor :description
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
super(nil)
|
17
|
+
@meters = init_meters
|
18
|
+
@metrics = init_metrics
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [Span] span the {Span} that just started.
|
22
|
+
# @param [Context] parent_context the
|
23
|
+
# started span.
|
24
|
+
def on_start(span, parent_context)
|
25
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_start span: #{span.inspect}" }
|
26
|
+
|
27
|
+
return if non_entry_span(parent_context: parent_context)
|
28
|
+
|
29
|
+
span.add_attributes(span_attributes(span))
|
30
|
+
span.add_attributes({ 'sw.is_entry_span' => true })
|
31
|
+
rescue StandardError => e
|
32
|
+
SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] processor on_start error: #{e.message}" }
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [Span] span the {Span} that just ended.
|
36
|
+
def on_finish(span)
|
37
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish span: #{span.to_span_data.inspect}" }
|
38
|
+
|
39
|
+
# return if span is non-entry span
|
40
|
+
return if non_entry_span(span: span)
|
41
|
+
|
42
|
+
record_request_metrics(span)
|
43
|
+
record_sampling_metrics
|
44
|
+
|
45
|
+
::OpenTelemetry.meter_provider.metric_readers.each(&:pull)
|
46
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish succeed" }
|
47
|
+
rescue StandardError => e
|
48
|
+
SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] error processing span on_finish: #{e.message}" }
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Create two meters for sampling and request count
|
54
|
+
def init_meters
|
55
|
+
{
|
56
|
+
'sw.apm.sampling.metrics' => ::OpenTelemetry.meter_provider.meter('sw.apm.sampling.metrics'),
|
57
|
+
'sw.apm.request.metrics' => ::OpenTelemetry.meter_provider.meter('sw.apm.request.metrics')
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def span_attributes(span)
|
62
|
+
span_attrs = { 'sw.transaction' => calculate_lambda_transaction_name(span) }
|
63
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_attrs: #{span_attrs.inspect}" }
|
64
|
+
span_attrs
|
65
|
+
end
|
66
|
+
|
67
|
+
def meter_attributes(span)
|
68
|
+
meter_attrs = {
|
69
|
+
'sw.is_error' => error?(span) == 1,
|
70
|
+
'sw.transaction' => calculate_lambda_transaction_name(span)
|
71
|
+
}
|
72
|
+
|
73
|
+
http_status_code = get_http_status_code(span)
|
74
|
+
meter_attrs['http.status_code'] = http_status_code if http_status_code != 0
|
75
|
+
meter_attrs['http.method'] = span.attributes[HTTP_METHOD] if span.attributes[HTTP_METHOD]
|
76
|
+
|
77
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] meter_attrs: #{meter_attrs.inspect}" }
|
78
|
+
meter_attrs
|
79
|
+
end
|
80
|
+
|
81
|
+
def calculate_lambda_transaction_name(span)
|
82
|
+
(ENV['SW_APM_TRANSACTION_NAME'] || ENV['AWS_LAMBDA_FUNCTION_NAME'] || span.name || 'unknown').slice(0, 255)
|
83
|
+
end
|
84
|
+
|
85
|
+
def init_metrics
|
86
|
+
request_meter = @meters['sw.apm.request.metrics']
|
87
|
+
sampling_meter = @meters['sw.apm.sampling.metrics']
|
88
|
+
|
89
|
+
metrics = {}
|
90
|
+
metrics[:response_time] = request_meter.create_histogram('trace.service.response_time', unit: 'ms', description: 'measures the duration of an inbound HTTP request')
|
91
|
+
metrics[:tracecount] = sampling_meter.create_counter('trace.service.tracecount')
|
92
|
+
metrics[:samplecount] = sampling_meter.create_counter('trace.service.samplecount')
|
93
|
+
metrics[:request_count] = sampling_meter.create_counter('trace.service.request_count')
|
94
|
+
metrics[:toex_count] = sampling_meter.create_counter('trace.service.tokenbucket_exhaustion_count')
|
95
|
+
metrics[:through_count] = sampling_meter.create_counter('trace.service.through_trace_count')
|
96
|
+
metrics[:tt_count] = sampling_meter.create_counter('trace.service.triggered_trace_count')
|
97
|
+
metrics
|
98
|
+
end
|
99
|
+
|
100
|
+
def record_request_metrics(span)
|
101
|
+
meter_attrs = meter_attributes(span)
|
102
|
+
span_time = calculate_span_time(start_time: span.start_timestamp, end_time: span.end_timestamp)
|
103
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] entry span, response_time: #{span_time}." }
|
104
|
+
@metrics[:response_time].record(span_time, attributes: meter_attrs)
|
105
|
+
end
|
106
|
+
|
107
|
+
# oboe_api will return 0 in case of failed operation, and report 0 value
|
108
|
+
def record_sampling_metrics
|
109
|
+
_, trace_count = SolarWindsAPM.oboe_api.consumeTraceCount
|
110
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] trace_count: #{trace_count}" }
|
111
|
+
@metrics[:tracecount].add(trace_count)
|
112
|
+
|
113
|
+
_, sample_count = SolarWindsAPM.oboe_api.consumeSampleCount
|
114
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] sample_count: #{sample_count}" }
|
115
|
+
@metrics[:samplecount].add(sample_count)
|
116
|
+
|
117
|
+
_, request_count = SolarWindsAPM.oboe_api.consumeRequestCount
|
118
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] request_count: #{request_count}" }
|
119
|
+
@metrics[:request_count].add(request_count)
|
120
|
+
|
121
|
+
_, token_bucket_exhaustion_count = SolarWindsAPM.oboe_api.consumeTokenBucketExhaustionCount
|
122
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] tokenbucket_exhaustion_count: #{token_bucket_exhaustion_count}" }
|
123
|
+
@metrics[:toex_count].add(token_bucket_exhaustion_count)
|
124
|
+
|
125
|
+
_, through_trace_count = SolarWindsAPM.oboe_api.consumeThroughTraceCount
|
126
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] through_trace_count: #{through_trace_count}" }
|
127
|
+
@metrics[:through_count].add(through_trace_count)
|
128
|
+
|
129
|
+
_, triggered_trace_count = SolarWindsAPM.oboe_api.consumeTriggeredTraceCount
|
130
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] triggered_trace_count: #{triggered_trace_count}" }
|
131
|
+
@metrics[:tt_count].add(triggered_trace_count)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
|
2
4
|
#
|
3
5
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
|
@@ -12,7 +14,7 @@ module SolarWindsAPM
|
|
12
14
|
FAILURE = ::OpenTelemetry::SDK::Trace::Export::FAILURE
|
13
15
|
|
14
16
|
private_constant(:SUCCESS, :FAILURE)
|
15
|
-
|
17
|
+
|
16
18
|
def initialize(txn_manager: nil)
|
17
19
|
@shutdown = false
|
18
20
|
@txn_manager = txn_manager
|
@@ -22,7 +24,7 @@ module SolarWindsAPM
|
|
22
24
|
@version_cache = {}
|
23
25
|
end
|
24
26
|
|
25
|
-
def export(span_data,
|
27
|
+
def export(span_data, timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
|
26
28
|
return FAILURE if @shutdown
|
27
29
|
|
28
30
|
status = SUCCESS
|
@@ -45,31 +47,36 @@ module SolarWindsAPM
|
|
45
47
|
private
|
46
48
|
|
47
49
|
def log_span_data(span_data)
|
48
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span_data: #{span_data.inspect}\n"}
|
50
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_data: #{span_data.inspect}\n" }
|
49
51
|
|
50
52
|
md = build_meta_data(span_data)
|
51
53
|
event = nil
|
52
|
-
if span_data.parent_span_id
|
54
|
+
if span_data.parent_span_id == ::OpenTelemetry::Trace::INVALID_SPAN_ID
|
55
|
+
event = @context.createEntry(md, (span_data.start_timestamp.to_i / 1000).to_i)
|
56
|
+
add_info_transaction_name(span_data, event)
|
57
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Start a new trace." }
|
58
|
+
else
|
53
59
|
parent_md = build_meta_data(span_data, parent: true)
|
54
60
|
event = @context.createEntry(md, (span_data.start_timestamp.to_i / 1000).to_i, parent_md)
|
55
|
-
SolarWindsAPM.logger.debug
|
56
|
-
|
57
|
-
|
58
|
-
add_info_transaction_name(span_data, event)
|
59
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Start a new trace."}
|
61
|
+
SolarWindsAPM.logger.debug do
|
62
|
+
"[#{self.class}/#{__method__}] Continue trace from parent metadata: #{parent_md.toString}."
|
63
|
+
end
|
60
64
|
end
|
61
|
-
|
65
|
+
|
62
66
|
layer_name = "#{span_data.kind}:#{span_data.name}"
|
63
67
|
event.addInfo('Layer', layer_name)
|
64
68
|
event.addInfo('sw.span_kind', span_data.kind.to_s)
|
65
69
|
event.addInfo('Language', 'Ruby')
|
66
|
-
|
70
|
+
|
67
71
|
add_instrumentation_scope(event, span_data)
|
68
72
|
add_instrumented_framework(event, span_data)
|
69
73
|
add_span_data_attributes(event, span_data.attributes) if span_data.attributes
|
70
74
|
|
71
|
-
event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_STATUS, span_data.status.ok
|
72
|
-
|
75
|
+
event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_STATUS, span_data.status.ok? ? 'OK' : 'ERROR')
|
76
|
+
unless span_data.status.description.empty?
|
77
|
+
event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_STATUS_DESCRIPTION,
|
78
|
+
span_data.status.description)
|
79
|
+
end
|
73
80
|
|
74
81
|
@reporter.send_report(event, with_system_timestamp: false)
|
75
82
|
|
@@ -81,10 +88,10 @@ module SolarWindsAPM
|
|
81
88
|
event = @context.createExit((span_data.end_timestamp.to_i / 1000).to_i)
|
82
89
|
event.addInfo('Layer', layer_name)
|
83
90
|
@reporter.send_report(event, with_system_timestamp: false)
|
84
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Exit a trace: #{event.metadataString}"}
|
91
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Exit a trace: #{event.metadataString}" }
|
85
92
|
SUCCESS
|
86
93
|
rescue StandardError => e
|
87
|
-
SolarWindsAPM.logger.info {"[#{self.class}/#{__method__}] exporter error: \n #{e.message} #{e.backtrace}\n"}
|
94
|
+
SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] exporter error: \n #{e.message} #{e.backtrace}\n" }
|
88
95
|
FAILURE
|
89
96
|
end
|
90
97
|
|
@@ -93,23 +100,23 @@ module SolarWindsAPM
|
|
93
100
|
def add_span_data_attributes(event, span_attributes)
|
94
101
|
target = 'http.target'
|
95
102
|
attributes = span_attributes.dup
|
96
|
-
attributes[target] = attributes[target].split('?').first if attributes[target] && SolarWindsAPM::Config[:log_args] == false
|
103
|
+
attributes[target] = attributes[target].split('?').first if attributes[target] && SolarWindsAPM::Config[:log_args] == false
|
97
104
|
attributes.each { |k, v| event.addInfo(k, v) }
|
98
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span_data attributes added: #{attributes.inspect}"}
|
105
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_data attributes added: #{attributes.inspect}" }
|
99
106
|
end
|
100
107
|
|
101
108
|
##
|
102
109
|
# get instrumentation scope data: scope name and version.
|
103
110
|
# the version if the opentelemetry-instrumentation-* gem version
|
104
111
|
def add_instrumentation_scope(event, span_data)
|
105
|
-
scope_name =
|
106
|
-
scope_version =
|
112
|
+
scope_name = ''
|
113
|
+
scope_version = ''
|
107
114
|
if span_data.instrumentation_scope
|
108
115
|
scope_name = span_data.instrumentation_scope.name if span_data.instrumentation_scope.name
|
109
116
|
scope_version = span_data.instrumentation_scope.version if span_data.instrumentation_scope.version
|
110
117
|
end
|
111
|
-
event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_SCOPE_NAME, scope_name)
|
112
|
-
event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_SCOPE_VERSION, scope_version)
|
118
|
+
event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_SCOPE_NAME, scope_name)
|
119
|
+
event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_SCOPE_VERSION, scope_version)
|
113
120
|
end
|
114
121
|
|
115
122
|
##
|
@@ -118,38 +125,46 @@ module SolarWindsAPM
|
|
118
125
|
def add_instrumented_framework(event, span_data)
|
119
126
|
scope_name = span_data.instrumentation_scope.name
|
120
127
|
scope_name = scope_name.downcase if scope_name
|
121
|
-
return unless scope_name&.include?
|
122
|
-
|
123
|
-
framework = scope_name.split(
|
128
|
+
return unless scope_name&.include? 'opentelemetry::instrumentation'
|
129
|
+
|
130
|
+
framework = scope_name.split('::')[2..]&.join('::')
|
124
131
|
return if framework.nil? || framework.empty?
|
125
|
-
|
132
|
+
|
126
133
|
framework = normalize_framework_name(framework)
|
127
134
|
framework_version = check_framework_version(framework)
|
128
|
-
SolarWindsAPM.logger.debug
|
129
|
-
|
135
|
+
SolarWindsAPM.logger.debug do
|
136
|
+
"[#{self.class}/#{__method__}] #{span_data.instrumentation_scope.name} with #{framework} and version #{framework_version}"
|
137
|
+
end
|
138
|
+
event.addInfo("Ruby.#{framework}.Version", framework_version) unless framework_version.nil?
|
130
139
|
end
|
131
140
|
|
132
141
|
##
|
133
142
|
# helper function that extract gem library version for func add_instrumented_framework
|
134
143
|
def check_framework_version(framework)
|
135
144
|
framework_version = nil
|
136
|
-
if @version_cache.
|
145
|
+
if @version_cache.key?(framework)
|
137
146
|
|
138
147
|
framework_version = @version_cache[framework]
|
139
148
|
else
|
140
149
|
|
141
150
|
begin
|
142
151
|
require framework
|
143
|
-
framework_version = Gem.loaded_specs[framework].version.to_s
|
152
|
+
framework_version = Gem.loaded_specs[version_framework_name(framework)].version.to_s
|
144
153
|
rescue LoadError => e
|
145
|
-
SolarWindsAPM.logger.debug
|
154
|
+
SolarWindsAPM.logger.debug do
|
155
|
+
"[#{self.class}/#{__method__}] couldn't load #{framework} with error #{e.message}; skip"
|
156
|
+
end
|
146
157
|
rescue StandardError => e
|
147
|
-
SolarWindsAPM.logger.debug
|
158
|
+
SolarWindsAPM.logger.debug do
|
159
|
+
"[#{self.class}/#{__method__}] couldn't find #{framework} with error #{e.message}; skip"
|
160
|
+
end
|
148
161
|
ensure
|
149
162
|
@version_cache[framework] = framework_version
|
150
163
|
end
|
151
164
|
end
|
152
|
-
SolarWindsAPM.logger.debug
|
165
|
+
SolarWindsAPM.logger.debug do
|
166
|
+
"[#{self.class}/#{__method__}] current framework version cached: #{@version_cache.inspect}"
|
167
|
+
end
|
153
168
|
framework_version
|
154
169
|
end
|
155
170
|
|
@@ -157,21 +172,31 @@ module SolarWindsAPM
|
|
157
172
|
# helper function that convert opentelemetry instrumentation name to gem library understandable
|
158
173
|
def normalize_framework_name(framework)
|
159
174
|
case framework
|
160
|
-
when
|
161
|
-
|
175
|
+
when 'net::http'
|
176
|
+
'net/http'
|
177
|
+
else
|
178
|
+
framework
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
##
|
183
|
+
# helper function that convert opentelemetry instrumentation name to gem library understandable
|
184
|
+
def version_framework_name(framework)
|
185
|
+
case framework
|
186
|
+
when 'net/http'
|
187
|
+
'net-http'
|
162
188
|
else
|
163
|
-
|
189
|
+
framework
|
164
190
|
end
|
165
|
-
normalized
|
166
191
|
end
|
167
192
|
|
168
193
|
##
|
169
194
|
# Add transaction name from cache to root span then removes from cache
|
170
195
|
def add_info_transaction_name(span_data, event)
|
171
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] transaction manager: #{@txn_manager.inspect}."}
|
196
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] transaction manager: #{@txn_manager.inspect}." }
|
172
197
|
trace_span_id = "#{span_data.hex_trace_id}-#{span_data.hex_span_id}"
|
173
198
|
txname = @txn_manager.get(trace_span_id) || ''
|
174
|
-
event.addInfo(
|
199
|
+
event.addInfo('TransactionName', txname)
|
175
200
|
@txn_manager.del(trace_span_id)
|
176
201
|
end
|
177
202
|
|
@@ -190,7 +215,7 @@ module SolarWindsAPM
|
|
190
215
|
attributes.each { |key, value| event.addInfo(key, value) }
|
191
216
|
end
|
192
217
|
|
193
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] exception event #{event.metadataString}"}
|
218
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] exception event #{event.metadataString}" }
|
194
219
|
@reporter.send_report(event, with_system_timestamp: false)
|
195
220
|
end
|
196
221
|
|
@@ -200,12 +225,12 @@ module SolarWindsAPM
|
|
200
225
|
event = @context.createEvent((span_event.timestamp.to_i / 1000).to_i)
|
201
226
|
event.addInfo('Label', 'info')
|
202
227
|
span_event.attributes&.each { |key, value| event.addInfo(key, value) }
|
203
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] info event #{event.metadataString}"}
|
228
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] info event #{event.metadataString}" }
|
204
229
|
@reporter.send_report(event, with_system_timestamp: false)
|
205
230
|
end
|
206
231
|
|
207
232
|
def build_meta_data(span_data, parent: false)
|
208
|
-
flag = span_data.trace_flags.sampled
|
233
|
+
flag = span_data.trace_flags.sampled? ? 1 : 0
|
209
234
|
xtr = parent == false ? "00-#{span_data.hex_trace_id}-#{span_data.hex_span_id}-0#{flag}" : "00-#{span_data.hex_trace_id}-#{span_data.hex_parent_span_id}-0#{flag}"
|
210
235
|
@metadata.fromString(xtr)
|
211
236
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
|
2
4
|
#
|
3
5
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
|
@@ -8,56 +10,46 @@ module SolarWindsAPM
|
|
8
10
|
module OpenTelemetry
|
9
11
|
# reference: OpenTelemetry::SDK::Trace::SpanProcessor
|
10
12
|
class SolarWindsProcessor
|
11
|
-
HTTP_METHOD =
|
12
|
-
HTTP_ROUTE =
|
13
|
-
HTTP_STATUS_CODE =
|
14
|
-
HTTP_URL =
|
13
|
+
HTTP_METHOD = 'http.method'
|
14
|
+
HTTP_ROUTE = 'http.route'
|
15
|
+
HTTP_STATUS_CODE = 'http.status_code'
|
16
|
+
HTTP_URL = 'http.url'
|
15
17
|
LIBOBOE_HTTP_SPAN_STATUS_UNAVAILABLE = 0
|
16
18
|
|
17
19
|
attr_reader :txn_manager
|
18
20
|
|
19
|
-
def initialize(
|
20
|
-
@exporter = exporter
|
21
|
+
def initialize(txn_manager)
|
21
22
|
@txn_manager = txn_manager
|
22
23
|
end
|
23
24
|
|
24
25
|
# Called when a {Span} is started, if the {Span#recording?}
|
25
26
|
# returns true.
|
26
27
|
#
|
27
|
-
# This method is called synchronously on the execution thread, should
|
28
|
-
# not throw or block the execution thread.
|
29
|
-
#
|
30
28
|
# @param [Span] span the {Span} that just started.
|
31
29
|
# @param [Context] parent_context the parent {Context} of the newly
|
32
30
|
# started span.
|
33
31
|
def on_start(span, parent_context)
|
32
|
+
SolarWindsAPM.logger.debug do
|
33
|
+
"[#{self.class}/#{__method__}] processor on_start span: #{span.inspect}, parent_context: #{parent_context.inspect}"
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
parent_span = ::OpenTelemetry::Trace.current_span(parent_context)
|
38
|
-
return if parent_span && parent_span.context != ::OpenTelemetry::Trace::SpanContext::INVALID && parent_span.context.remote? == false
|
36
|
+
return if non_entry_span(parent_context: parent_context)
|
39
37
|
|
40
38
|
trace_flags = span.context.trace_flags.sampled? ? '01' : '00'
|
41
|
-
@txn_manager.set_root_context_h(span.context.hex_trace_id,"#{span.context.hex_span_id}-#{trace_flags}")
|
39
|
+
@txn_manager.set_root_context_h(span.context.hex_trace_id, "#{span.context.hex_span_id}-#{trace_flags}")
|
40
|
+
span.add_attributes({ 'sw.is_entry_span' => true })
|
42
41
|
rescue StandardError => e
|
43
|
-
SolarWindsAPM.logger.info {"[#{self.class}/#{__method__}] processor on_start error: #{e.message}"}
|
42
|
+
SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] processor on_start error: #{e.message}" }
|
44
43
|
end
|
45
44
|
|
46
45
|
# Called when a {Span} is ended, if the {Span#recording?}
|
47
46
|
# returns true.
|
48
47
|
#
|
49
|
-
# This method is called synchronously on the execution thread, should
|
50
|
-
# not throw or block the execution thread.
|
51
|
-
# Only calculate inbound metrics for service root spans
|
52
|
-
#
|
53
48
|
# @param [Span] span the {Span} that just ended.
|
54
49
|
def on_finish(span)
|
55
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] processor on_finish span: #{span.inspect}"}
|
50
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish span: #{span.inspect}" }
|
56
51
|
|
57
|
-
if span
|
58
|
-
@exporter&.export([span.to_span_data]) if span.context.trace_flags.sampled?
|
59
|
-
return
|
60
|
-
end
|
52
|
+
return if non_entry_span(span: span)
|
61
53
|
|
62
54
|
span_time = calculate_span_time(start_time: span.start_timestamp, end_time: span.end_timestamp)
|
63
55
|
domain = nil
|
@@ -68,7 +60,7 @@ module SolarWindsAPM
|
|
68
60
|
request_method = span.attributes[HTTP_METHOD]
|
69
61
|
url_tran = span.attributes[HTTP_URL]
|
70
62
|
|
71
|
-
SolarWindsAPM.logger.debug do
|
63
|
+
SolarWindsAPM.logger.debug do
|
72
64
|
"[#{self.class}/#{__method__}] createHttpSpan with\n
|
73
65
|
trans_name: #{trans_name}\n
|
74
66
|
url_tran: #{url_tran}\n
|
@@ -79,11 +71,12 @@ module SolarWindsAPM
|
|
79
71
|
has_error: #{has_error}"
|
80
72
|
end
|
81
73
|
|
82
|
-
liboboe_txn_name = SolarWindsAPM::Span.createHttpSpan(trans_name,url_tran,domain,span_time,status_code,
|
83
|
-
|
74
|
+
liboboe_txn_name = SolarWindsAPM::Span.createHttpSpan(trans_name, url_tran, domain, span_time, status_code,
|
75
|
+
request_method, has_error)
|
76
|
+
|
84
77
|
else
|
85
|
-
|
86
|
-
SolarWindsAPM.logger.debug do
|
78
|
+
|
79
|
+
SolarWindsAPM.logger.debug do
|
87
80
|
"[#{self.class}/#{__method__}] createSpan with \n
|
88
81
|
trans_name: #{trans_name}\n
|
89
82
|
domain: #{domain}\n
|
@@ -94,37 +87,30 @@ module SolarWindsAPM
|
|
94
87
|
liboboe_txn_name = SolarWindsAPM::Span.createSpan(trans_name, domain, span_time, has_error)
|
95
88
|
end
|
96
89
|
|
97
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] liboboe_txn_name: #{liboboe_txn_name}"}
|
98
|
-
|
90
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] liboboe_txn_name: #{liboboe_txn_name}" }
|
91
|
+
if span.context.trace_flags.sampled?
|
92
|
+
@txn_manager["#{span.context.hex_trace_id}-#{span.context.hex_span_id}"] =
|
93
|
+
liboboe_txn_name
|
94
|
+
end
|
99
95
|
@txn_manager.delete_root_context_h(span.context.hex_trace_id)
|
100
|
-
@exporter&.export([span.to_span_data]) if span.context.trace_flags.sampled?
|
101
96
|
rescue StandardError => e
|
102
|
-
SolarWindsAPM.logger.info
|
103
|
-
|
97
|
+
SolarWindsAPM.logger.info do
|
98
|
+
"[#{self.class}/#{__method__}] solarwinds_processor on_finish error: #{e.message}"
|
99
|
+
end
|
104
100
|
end
|
105
101
|
|
106
|
-
# Export all ended spans to the configured `Exporter` that have not yet
|
107
|
-
# been exported.
|
108
|
-
#
|
109
|
-
# This method should only be called in cases where it is absolutely
|
110
|
-
# necessary, such as when using some FaaS providers that may suspend
|
111
|
-
# the process after an invocation, but before the `Processor` exports
|
112
|
-
# the completed spans.
|
113
|
-
#
|
114
102
|
# @param [optional Numeric] timeout An optional timeout in seconds.
|
115
103
|
# @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
|
116
104
|
# a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
|
117
|
-
def force_flush(timeout: nil)
|
118
|
-
|
105
|
+
def force_flush(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
|
106
|
+
::OpenTelemetry::SDK::Trace::Export::SUCCESS
|
119
107
|
end
|
120
108
|
|
121
|
-
# Called when {TracerProvider#shutdown} is called.
|
122
|
-
#
|
123
109
|
# @param [optional Numeric] timeout An optional timeout in seconds.
|
124
110
|
# @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
|
125
111
|
# a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
|
126
|
-
def shutdown(timeout: nil)
|
127
|
-
|
112
|
+
def shutdown(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
|
113
|
+
::OpenTelemetry::SDK::Trace::Export::SUCCESS
|
128
114
|
end
|
129
115
|
|
130
116
|
private
|
@@ -147,16 +133,28 @@ module SolarWindsAPM
|
|
147
133
|
span.attributes[HTTP_STATUS_CODE] || LIBOBOE_HTTP_SPAN_STATUS_UNAVAILABLE
|
148
134
|
end
|
149
135
|
|
136
|
+
# check if it's entry span based on no parent or parent is remote
|
137
|
+
def non_entry_span(span: nil, parent_context: nil)
|
138
|
+
if parent_context
|
139
|
+
parent_span = ::OpenTelemetry::Trace.current_span(parent_context)
|
140
|
+
parent_span && parent_span.context != ::OpenTelemetry::Trace::SpanContext::INVALID && parent_span.context.remote? == false
|
141
|
+
elsif span
|
142
|
+
span.attributes['sw.is_entry_span'] != true
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
150
146
|
# Get trans_name and url_tran of this span instance.
|
151
147
|
# Predecessor order: custom SDK > env var SW_APM_TRANSACTION_NAME > automatic naming
|
152
148
|
def calculate_transaction_names(span)
|
153
149
|
trace_span_id = "#{span.context.hex_trace_id}-#{span.context.hex_span_id}"
|
154
150
|
trans_name = @txn_manager.get(trace_span_id)
|
155
151
|
if trans_name
|
156
|
-
SolarWindsAPM.logger.debug
|
152
|
+
SolarWindsAPM.logger.debug do
|
153
|
+
"[#{self.class}/#{__method__}] found trans name from txn_manager: #{trans_name} by #{trace_span_id}"
|
154
|
+
end
|
157
155
|
@txn_manager.del(trace_span_id)
|
158
|
-
elsif ENV.
|
159
|
-
trans_name =
|
156
|
+
elsif ENV.key?('SW_APM_TRANSACTION_NAME') && ENV['SW_APM_TRANSACTION_NAME'] != ''
|
157
|
+
trans_name = ENV.fetch('SW_APM_TRANSACTION_NAME', nil)
|
160
158
|
else
|
161
159
|
trans_name = span.attributes[HTTP_ROUTE] || nil
|
162
160
|
trans_name = span.name if span.name && (trans_name.nil? || trans_name.empty?)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
|
2
4
|
#
|
3
5
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
|
@@ -10,14 +12,14 @@ module SolarWindsAPM
|
|
10
12
|
# TextMapPropagator
|
11
13
|
# propagator error will be rescued by OpenTelemetry::Context::Propagation::TextMapPropagator
|
12
14
|
class TextMapPropagator
|
13
|
-
TRACESTATE_HEADER_NAME =
|
14
|
-
XTRACEOPTIONS_HEADER_NAME =
|
15
|
-
XTRACEOPTIONS_SIGNATURE_HEADER_NAME =
|
16
|
-
INTL_SWO_X_OPTIONS_KEY =
|
17
|
-
INTL_SWO_SIGNATURE_KEY =
|
15
|
+
TRACESTATE_HEADER_NAME = 'tracestate'
|
16
|
+
XTRACEOPTIONS_HEADER_NAME = 'x-trace-options'
|
17
|
+
XTRACEOPTIONS_SIGNATURE_HEADER_NAME = 'x-trace-options-signature'
|
18
|
+
INTL_SWO_X_OPTIONS_KEY = 'sw_xtraceoptions'
|
19
|
+
INTL_SWO_SIGNATURE_KEY = 'sw_signature'
|
18
20
|
|
19
21
|
private_constant \
|
20
|
-
:TRACESTATE_HEADER_NAME, :XTRACEOPTIONS_HEADER_NAME,
|
22
|
+
:TRACESTATE_HEADER_NAME, :XTRACEOPTIONS_HEADER_NAME,
|
21
23
|
:XTRACEOPTIONS_SIGNATURE_HEADER_NAME, :INTL_SWO_X_OPTIONS_KEY, :INTL_SWO_SIGNATURE_KEY
|
22
24
|
|
23
25
|
# Extract trace context from the supplied carrier.
|
@@ -31,11 +33,11 @@ module SolarWindsAPM
|
|
31
33
|
#
|
32
34
|
# @return [Context] context updated with extracted baggage, or the original context
|
33
35
|
# if extraction fails
|
34
|
-
def extract(carrier, context: ::OpenTelemetry::Context.current,
|
35
|
-
|
36
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] extract context: #{context.inspect}"}
|
36
|
+
def extract(carrier, context: ::OpenTelemetry::Context.current,
|
37
|
+
getter: ::OpenTelemetry::Context::Propagation.text_map_getter)
|
38
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] extract context: #{context.inspect}" }
|
37
39
|
|
38
|
-
context =
|
40
|
+
context = ::OpenTelemetry::Context.new({}) if context.nil?
|
39
41
|
context = inject_extracted_header(carrier, context, getter, XTRACEOPTIONS_HEADER_NAME, INTL_SWO_X_OPTIONS_KEY)
|
40
42
|
inject_extracted_header(carrier, context, getter, XTRACEOPTIONS_SIGNATURE_HEADER_NAME, INTL_SWO_SIGNATURE_KEY)
|
41
43
|
end
|
@@ -47,31 +49,37 @@ module SolarWindsAPM
|
|
47
49
|
# @param [optional Setter] setter If the optional setter is provided, it
|
48
50
|
# will be used to write context into the carrier, otherwise the default
|
49
51
|
# text map setter will be used.
|
50
|
-
def inject(carrier, context: ::OpenTelemetry::Context.current,
|
52
|
+
def inject(carrier, context: ::OpenTelemetry::Context.current,
|
53
|
+
setter: ::OpenTelemetry::Context::Propagation.text_map_setter)
|
54
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] inject context: #{context.inspect}" }
|
51
55
|
|
52
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] inject context: #{context.inspect}"}
|
53
|
-
|
54
56
|
span_context = ::OpenTelemetry::Trace.current_span(context)&.context
|
55
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span_context #{span_context.inspect}"}
|
57
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_context #{span_context.inspect}" }
|
56
58
|
return unless span_context&.valid?
|
57
59
|
|
58
|
-
trace_flag = span_context.trace_flags.sampled
|
60
|
+
trace_flag = span_context.trace_flags.sampled? ? 1 : 0
|
59
61
|
sw_value = "#{span_context.hex_span_id}-0#{trace_flag}"
|
60
|
-
trace_state_header = carrier[TRACESTATE_HEADER_NAME].nil
|
61
|
-
SolarWindsAPM.logger.debug
|
62
|
+
trace_state_header = carrier[TRACESTATE_HEADER_NAME].nil? ? nil : carrier[TRACESTATE_HEADER_NAME]
|
63
|
+
SolarWindsAPM.logger.debug do
|
64
|
+
"[#{self.class}/#{__method__}] sw_value: #{sw_value}; trace_state_header: #{trace_state_header}"
|
65
|
+
end
|
62
66
|
|
63
67
|
# prepare carrier with carrier's or new tracestate
|
64
68
|
if trace_state_header.nil?
|
65
69
|
# only create new trace state if valid span_id
|
66
70
|
unless span_context.span_id == ::OpenTelemetry::Trace::INVALID_SPAN_ID
|
67
|
-
trace_state = ::OpenTelemetry::Trace::Tracestate.create({SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY => sw_value})
|
68
|
-
SolarWindsAPM.logger.debug
|
71
|
+
trace_state = ::OpenTelemetry::Trace::Tracestate.create({ SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY => sw_value })
|
72
|
+
SolarWindsAPM.logger.debug do
|
73
|
+
"[#{self.class}/#{__method__}] creating new trace state: #{trace_state.inspect}"
|
74
|
+
end
|
69
75
|
setter.set(carrier, TRACESTATE_HEADER_NAME, Utils.trace_state_header(trace_state))
|
70
76
|
end
|
71
77
|
else
|
72
78
|
trace_state_from_string = ::OpenTelemetry::Trace::Tracestate.from_string(trace_state_header)
|
73
79
|
trace_state = trace_state_from_string.set_value(SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY, sw_value)
|
74
|
-
SolarWindsAPM.logger.debug
|
80
|
+
SolarWindsAPM.logger.debug do
|
81
|
+
"[#{self.class}/#{__method__}] updating/adding trace state for injection #{trace_state.inspect}"
|
82
|
+
end
|
75
83
|
setter.set(carrier, TRACESTATE_HEADER_NAME, Utils.trace_state_header(trace_state))
|
76
84
|
end
|
77
85
|
end
|
@@ -85,11 +93,11 @@ module SolarWindsAPM
|
|
85
93
|
end
|
86
94
|
|
87
95
|
private
|
88
|
-
|
96
|
+
|
89
97
|
def inject_extracted_header(carrier, context, getter, header, inject_key)
|
90
98
|
extracted_header = getter.get(carrier, header)
|
91
99
|
context = context.set_value(inject_key, extracted_header) if extracted_header
|
92
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] #{header}: #{inject_key} = #{extracted_header}"}
|
100
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] #{header}: #{inject_key} = #{extracted_header}" }
|
93
101
|
context
|
94
102
|
end
|
95
103
|
end
|