solarwinds_apm 6.0.0.prev6 → 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 +43 -42
- 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 +46 -84
- 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 +10 -6
- data/lib/solarwinds_apm/api/tracing.rb +7 -5
- data/lib/solarwinds_apm/api/transaction_name.rb +21 -11
- data/lib/solarwinds_apm/api.rb +8 -6
- data/lib/solarwinds_apm/config.rb +72 -47
- data/lib/solarwinds_apm/constants.rb +26 -26
- data/lib/solarwinds_apm/logger.rb +2 -0
- data/lib/solarwinds_apm/noop/README.md +1 -1
- data/lib/solarwinds_apm/noop/api.rb +85 -0
- data/lib/solarwinds_apm/noop/context.rb +15 -2
- data/lib/solarwinds_apm/noop/metadata.rb +7 -2
- data/lib/solarwinds_apm/{base.rb → noop/span.rb} +16 -15
- data/lib/solarwinds_apm/noop.rb +33 -0
- data/lib/solarwinds_apm/oboe_init_options.rb +50 -111
- 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 +50 -52
- 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 +136 -99
- data/lib/solarwinds_apm/opentelemetry.rb +8 -5
- data/lib/solarwinds_apm/otel_config.rb +38 -43
- data/lib/solarwinds_apm/otel_lambda_config.rb +53 -0
- data/lib/solarwinds_apm/patch/dummy_patch.rb +12 -0
- data/lib/solarwinds_apm/{thread_local.rb → patch.rb} +6 -22
- 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 +22 -22
- data/lib/solarwinds_apm/support/service_key_checker.rb +106 -0
- data/lib/solarwinds_apm/{support_report.rb → support/support_report.rb} +15 -10
- 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 -23
- data/lib/solarwinds_apm/version.rb +4 -2
- data/lib/solarwinds_apm.rb +82 -52
- metadata +23 -28
@@ -0,0 +1,33 @@
|
|
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
|
+
require_relative 'noop/context'
|
10
|
+
require_relative 'noop/metadata'
|
11
|
+
require_relative 'noop/span'
|
12
|
+
require_relative 'noop/api'
|
13
|
+
|
14
|
+
module SolarWindsAPM
|
15
|
+
include Oboe_metal
|
16
|
+
# Reporter noop
|
17
|
+
class Reporter
|
18
|
+
##
|
19
|
+
# noop version of :send_report
|
20
|
+
#
|
21
|
+
def self.send_report(event, with_system_timestamp: false); end
|
22
|
+
|
23
|
+
##
|
24
|
+
# noop version of :send_status
|
25
|
+
#
|
26
|
+
def self.send_status(event, context = nil, with_system_timestamp: false); end
|
27
|
+
|
28
|
+
##
|
29
|
+
# noop version of :start
|
30
|
+
#
|
31
|
+
def self.start; end
|
32
|
+
end
|
33
|
+
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
|
@@ -7,14 +9,18 @@
|
|
7
9
|
require 'singleton'
|
8
10
|
require 'uri'
|
9
11
|
|
12
|
+
require_relative 'support/service_key_checker'
|
13
|
+
|
10
14
|
module SolarWindsAPM
|
11
15
|
# OboeInitOptions
|
12
16
|
class OboeInitOptions
|
13
17
|
include Singleton
|
14
18
|
|
15
|
-
attr_reader :reporter, :host, :service_name, :ec2_md_timeout, :grpc_proxy # exposing these mainly for testing
|
19
|
+
attr_reader :reporter, :host, :service_name, :ec2_md_timeout, :grpc_proxy, :lambda_env # exposing these mainly for testing
|
16
20
|
|
17
21
|
def initialize
|
22
|
+
# determining the lambda env based on env var (not used in array_for_oboe for oboe initialization)
|
23
|
+
@lambda_env = determine_lambda
|
18
24
|
# optional hostname alias
|
19
25
|
@hostname_alias = ENV['SW_APM_HOSTNAME_ALIAS'] || SolarWindsAPM::Config[:hostname_alias] || ''
|
20
26
|
# level at which log messages will be written to log file (0-6)
|
@@ -80,18 +86,18 @@ module SolarWindsAPM
|
|
80
86
|
@reporter, # 7
|
81
87
|
@host, # 8
|
82
88
|
@service_key, # 9
|
83
|
-
@certificates, #10
|
84
|
-
@buffer_size, #11
|
85
|
-
@trace_metrics, #12
|
86
|
-
@histogram_precision, #13
|
87
|
-
@token_bucket_capacity, #14
|
88
|
-
@token_bucket_rate, #15
|
89
|
-
@file_single, #16
|
90
|
-
@ec2_md_timeout, #17
|
91
|
-
@grpc_proxy, #18
|
92
|
-
0, #19 arg for lambda (no lambda for ruby yet)
|
93
|
-
@metric_format, #20
|
94
|
-
@log_type #21
|
89
|
+
@certificates, # 10
|
90
|
+
@buffer_size, # 11
|
91
|
+
@trace_metrics, # 12
|
92
|
+
@histogram_precision, # 13
|
93
|
+
@token_bucket_capacity, # 14
|
94
|
+
@token_bucket_rate, # 15
|
95
|
+
@file_single, # 16
|
96
|
+
@ec2_md_timeout, # 17
|
97
|
+
@grpc_proxy, # 18
|
98
|
+
0, # 19 arg for lambda (no lambda for ruby yet)
|
99
|
+
@metric_format, # 20
|
100
|
+
@log_type # 21
|
95
101
|
]
|
96
102
|
end
|
97
103
|
|
@@ -102,104 +108,24 @@ module SolarWindsAPM
|
|
102
108
|
private
|
103
109
|
|
104
110
|
def reporter_and_host
|
105
|
-
|
106
111
|
reporter = ENV['SW_APM_REPORTER'] || 'ssl'
|
107
112
|
|
108
|
-
host =
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
# TODO: decide what to do
|
115
|
-
# ____ SolarWindsAPM::Config[:reporter_host] and
|
116
|
-
# ____ SolarWindsAPM::Config[:reporter_port] were moved here from
|
117
|
-
# ____ oboe_metal.rb and are not documented anywhere
|
118
|
-
# ____ udp is for internal use only
|
119
|
-
when 'null'
|
120
|
-
host = ''
|
121
|
-
end
|
113
|
+
host = case reporter
|
114
|
+
when 'ssl', 'file'
|
115
|
+
ENV['SW_APM_COLLECTOR'] || ''
|
116
|
+
else
|
117
|
+
''
|
118
|
+
end
|
122
119
|
|
123
120
|
host = sanitize_collector_uri(host) unless reporter == 'file'
|
124
121
|
[reporter, host]
|
125
122
|
end
|
126
123
|
|
127
124
|
def read_and_validate_service_key
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY not configured."}
|
133
|
-
return ''
|
134
|
-
end
|
135
|
-
|
136
|
-
match = service_key.match(/([^:]+)(:{0,1})(.*)/)
|
137
|
-
token = match[1]
|
138
|
-
service_name = match[3]
|
139
|
-
|
140
|
-
return '' unless validate_token(token) # return if token is not even valid
|
141
|
-
|
142
|
-
if service_name.empty?
|
143
|
-
ENV.delete('OTEL_SERVICE_NAME')
|
144
|
-
SolarWindsAPM.logger.warn {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY format problem. Service Name is missing."}
|
145
|
-
return ''
|
146
|
-
end
|
147
|
-
|
148
|
-
# check OTEL_RESOURCE_ATTRIBUTES
|
149
|
-
otel_resource_service_name = nil
|
150
|
-
ENV['OTEL_RESOURCE_ATTRIBUTES']&.split(',')&.each do |pair|
|
151
|
-
key, value = pair.split('=')
|
152
|
-
if key == 'service.name'
|
153
|
-
otel_resource_service_name = value
|
154
|
-
break
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] provided otel_resource_service_name #{otel_resource_service_name}"} if otel_resource_service_name
|
159
|
-
service_name = otel_resource_service_name if otel_resource_service_name && validate_transform_service_name(otel_resource_service_name)
|
160
|
-
|
161
|
-
# check OTEL_SERVICE_NAME
|
162
|
-
otel_service_name = ENV['OTEL_SERVICE_NAME']
|
163
|
-
if otel_service_name && validate_transform_service_name(otel_service_name)
|
164
|
-
service_name = otel_service_name
|
165
|
-
SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] provided otel_service_name #{otel_service_name}"}
|
166
|
-
elsif ENV['OTEL_SERVICE_NAME'].nil?
|
167
|
-
ENV['OTEL_SERVICE_NAME'] = service_name
|
168
|
-
end
|
169
|
-
|
170
|
-
return '' unless validate_transform_service_name(service_name)
|
171
|
-
|
172
|
-
"#{token}:#{service_name}"
|
173
|
-
end
|
174
|
-
|
175
|
-
# In case of java-collector, please provide a dummy service key
|
176
|
-
def validate_token(token)
|
177
|
-
unless /^[0-9a-zA-Z_-]{71}$/.match?(token)
|
178
|
-
masked = "#{token[0..3]}...#{token[-4..]}"
|
179
|
-
SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY problem. API Token in wrong format. Masked token: #{masked}"}
|
180
|
-
return false
|
181
|
-
end
|
182
|
-
|
183
|
-
true
|
184
|
-
end
|
185
|
-
|
186
|
-
def validate_transform_service_name(service_name)
|
187
|
-
if service_name.empty?
|
188
|
-
SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY problem. Service Name is missing"}
|
189
|
-
return false
|
190
|
-
end
|
191
|
-
|
192
|
-
name_ = service_name.dup
|
193
|
-
name_.downcase!
|
194
|
-
name_.gsub!(/[^a-z0-9.:_-]/, '')
|
195
|
-
name_ = name_[0..254]
|
196
|
-
|
197
|
-
if name_ != service_name
|
198
|
-
SolarWindsAPM.logger.warn {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY problem. Service Name transformed from #{service_name} to #{name_}"}
|
199
|
-
service_name = name_
|
200
|
-
end
|
201
|
-
@service_name = service_name # instance variable used in testing
|
202
|
-
true
|
125
|
+
service_key_checker = SolarWindsAPM::ServiceKeyChecker.new(@reporter, @lambda_env)
|
126
|
+
service_key = service_key_checker.read_and_validate_service_key
|
127
|
+
@service_name = service_key.split(':', 2).last # instance variable used in testing
|
128
|
+
service_key
|
203
129
|
end
|
204
130
|
|
205
131
|
def read_and_validate_ec2_md_timeout
|
@@ -214,8 +140,8 @@ module SolarWindsAPM
|
|
214
140
|
proxy = ENV['SW_APM_PROXY'] || SolarWindsAPM::Config[:http_proxy] || ''
|
215
141
|
return proxy if proxy == ''
|
216
142
|
|
217
|
-
unless
|
218
|
-
SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] SW_APM_PROXY/http_proxy doesn't start with 'http://', #{proxy}"}
|
143
|
+
unless %r{http://.*:\d+$}.match?(proxy)
|
144
|
+
SolarWindsAPM.logger.error { "[#{self.class}/#{__method__}] SW_APM_PROXY/http_proxy doesn't start with 'http://', #{proxy}" }
|
219
145
|
return '' # try without proxy, it may work, shouldn't crash but may not report
|
220
146
|
end
|
221
147
|
|
@@ -225,13 +151,15 @@ module SolarWindsAPM
|
|
225
151
|
def read_certificates
|
226
152
|
certificate = ''
|
227
153
|
|
228
|
-
file = appoptics_collector
|
154
|
+
file = appoptics_collector? ? "#{__dir__}/cert/star.appoptics.com.issuer.crt" : ENV.fetch('SW_APM_TRUSTEDPATH', nil)
|
229
155
|
return certificate if file.nil? || file&.empty?
|
230
156
|
|
231
157
|
begin
|
232
|
-
certificate = File.
|
158
|
+
certificate = File.read(file)
|
233
159
|
rescue StandardError => e
|
234
|
-
SolarWindsAPM.logger.error
|
160
|
+
SolarWindsAPM.logger.error do
|
161
|
+
"[#{self.class}/#{__method__}] certificates: #{file} doesn't exist or caused by #{e.message}."
|
162
|
+
end
|
235
163
|
end
|
236
164
|
|
237
165
|
certificate
|
@@ -245,7 +173,7 @@ module SolarWindsAPM
|
|
245
173
|
allowed_uri = ['collector.appoptics.com', 'collector-stg.appoptics.com',
|
246
174
|
'collector.appoptics.com:443', 'collector-stg.appoptics.com:443']
|
247
175
|
|
248
|
-
(allowed_uri.include? ENV
|
176
|
+
(allowed_uri.include? ENV.fetch('SW_APM_COLLECTOR', nil))
|
249
177
|
end
|
250
178
|
|
251
179
|
def sanitize_collector_uri(uri)
|
@@ -255,9 +183,11 @@ module SolarWindsAPM
|
|
255
183
|
sanitized_uri = ::URI.parse("http://#{uri}").host
|
256
184
|
return sanitized_uri unless sanitized_uri.nil?
|
257
185
|
rescue StandardError => e
|
258
|
-
SolarWindsAPM.logger.error
|
186
|
+
SolarWindsAPM.logger.error do
|
187
|
+
"[#{self.class}/#{__method__}] uri for collector #{uri} is malformat. Error: #{e.message}"
|
188
|
+
end
|
259
189
|
end
|
260
|
-
|
190
|
+
''
|
261
191
|
end
|
262
192
|
|
263
193
|
def determine_oboe_log_type
|
@@ -266,5 +196,14 @@ module SolarWindsAPM
|
|
266
196
|
log_type = 4 if @debug_level == -1
|
267
197
|
log_type
|
268
198
|
end
|
199
|
+
|
200
|
+
def determine_lambda
|
201
|
+
if ENV['LAMBDA_TASK_ROOT'].to_s.empty? && ENV['AWS_LAMBDA_FUNCTION_NAME'].to_s.empty?
|
202
|
+
false
|
203
|
+
else
|
204
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] lambda environment - LAMBDA_TASK_ROOT: #{ENV.fetch('LAMBDA_TASK_ROOT', nil)}; AWS_LAMBDA_FUNCTION_NAME: #{ENV.fetch('AWS_LAMBDA_FUNCTION_NAME', nil)}" }
|
205
|
+
true
|
206
|
+
end
|
207
|
+
end
|
269
208
|
end
|
270
209
|
end
|
@@ -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
|