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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/ext/oboe_metal/extconf.rb +43 -42
  3. data/ext/oboe_metal/lib/liboboe-1.0-aarch64.so.sha256 +1 -1
  4. data/ext/oboe_metal/lib/liboboe-1.0-alpine-aarch64.so.sha256 +1 -1
  5. data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.sha256 +1 -1
  6. data/ext/oboe_metal/lib/liboboe-1.0-lambda-aarch64.so.sha256 +1 -0
  7. data/ext/oboe_metal/lib/liboboe-1.0-lambda-x86_64.so.sha256 +1 -0
  8. data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.sha256 +1 -1
  9. data/ext/oboe_metal/src/VERSION +1 -1
  10. data/ext/oboe_metal/src/oboe.h +3 -0
  11. data/ext/oboe_metal/src/oboe_api.cpp +1 -1
  12. data/lib/oboe_metal.rb +46 -84
  13. data/lib/rails/generators/solarwinds_apm/install_generator.rb +21 -19
  14. data/lib/solarwinds_apm/api/current_trace_info.rb +16 -9
  15. data/lib/solarwinds_apm/api/custom_metrics.rb +6 -4
  16. data/lib/solarwinds_apm/api/opentelemetry.rb +10 -6
  17. data/lib/solarwinds_apm/api/tracing.rb +7 -5
  18. data/lib/solarwinds_apm/api/transaction_name.rb +21 -11
  19. data/lib/solarwinds_apm/api.rb +8 -6
  20. data/lib/solarwinds_apm/config.rb +72 -47
  21. data/lib/solarwinds_apm/constants.rb +26 -26
  22. data/lib/solarwinds_apm/logger.rb +2 -0
  23. data/lib/solarwinds_apm/noop/README.md +1 -1
  24. data/lib/solarwinds_apm/noop/api.rb +85 -0
  25. data/lib/solarwinds_apm/noop/context.rb +15 -2
  26. data/lib/solarwinds_apm/noop/metadata.rb +7 -2
  27. data/lib/solarwinds_apm/{base.rb → noop/span.rb} +16 -15
  28. data/lib/solarwinds_apm/noop.rb +33 -0
  29. data/lib/solarwinds_apm/oboe_init_options.rb +50 -111
  30. data/lib/solarwinds_apm/opentelemetry/otlp_processor.rb +135 -0
  31. data/lib/solarwinds_apm/opentelemetry/solarwinds_exporter.rb +66 -41
  32. data/lib/solarwinds_apm/opentelemetry/solarwinds_processor.rb +50 -52
  33. data/lib/solarwinds_apm/opentelemetry/solarwinds_propagator.rb +30 -22
  34. data/lib/solarwinds_apm/opentelemetry/solarwinds_response_propagator.rb +29 -16
  35. data/lib/solarwinds_apm/opentelemetry/solarwinds_sampler.rb +136 -99
  36. data/lib/solarwinds_apm/opentelemetry.rb +8 -5
  37. data/lib/solarwinds_apm/otel_config.rb +38 -43
  38. data/lib/solarwinds_apm/otel_lambda_config.rb +53 -0
  39. data/lib/solarwinds_apm/patch/dummy_patch.rb +12 -0
  40. data/lib/solarwinds_apm/{thread_local.rb → patch.rb} +6 -22
  41. data/lib/solarwinds_apm/support/logger_formatter.rb +4 -2
  42. data/lib/solarwinds_apm/support/logging_log_event.rb +2 -0
  43. data/lib/solarwinds_apm/support/lumberjack_formatter.rb +2 -0
  44. data/lib/solarwinds_apm/support/oboe_tracing_mode.rb +22 -22
  45. data/lib/solarwinds_apm/support/service_key_checker.rb +106 -0
  46. data/lib/solarwinds_apm/{support_report.rb → support/support_report.rb} +15 -10
  47. data/lib/solarwinds_apm/support/swomarginalia/comment.rb +18 -17
  48. data/lib/solarwinds_apm/support/swomarginalia/load_swomarginalia.rb +13 -12
  49. data/lib/solarwinds_apm/support/swomarginalia/railtie.rb +4 -2
  50. data/lib/solarwinds_apm/support/swomarginalia/swomarginalia.rb +3 -1
  51. data/lib/solarwinds_apm/support/transaction_cache.rb +6 -4
  52. data/lib/solarwinds_apm/support/transaction_settings.rb +7 -3
  53. data/lib/solarwinds_apm/support/txn_name_manager.rb +8 -3
  54. data/lib/solarwinds_apm/support/utils.rb +12 -9
  55. data/lib/solarwinds_apm/support/x_trace_options.rb +23 -17
  56. data/lib/solarwinds_apm/support.rb +28 -23
  57. data/lib/solarwinds_apm/version.rb +4 -2
  58. data/lib/solarwinds_apm.rb +82 -52
  59. 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
- case reporter
110
- when 'ssl', 'file'
111
- host = ENV['SW_APM_COLLECTOR'] || ''
112
- when 'udp'
113
- host = ENV['SW_APM_COLLECTOR'] || "#{SolarWindsAPM::Config[:reporter_host]}:#{SolarWindsAPM::Config[:reporter_port]}"
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
- return '' unless @reporter == 'ssl'
129
-
130
- service_key = ENV['SW_APM_SERVICE_KEY'] || SolarWindsAPM::Config[:service_key]
131
- if service_key.nil? || service_key == ''
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 /http:\/\/.*:\d+$/.match?(proxy)
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?? "#{__dir__}/cert/star.appoptics.com.issuer.crt" : ENV['SW_APM_TRUSTEDPATH']
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.open(file,"r").read
158
+ certificate = File.read(file)
233
159
  rescue StandardError => e
234
- SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] certificates: #{file} doesn't exist or caused by #{e.message}."}
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["SW_APM_COLLECTOR"])? true : false
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 {"[#{self.class}/#{__method__}] uri for collector #{uri} is malformat. Error: #{e.message}"}
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, _timeout: nil)
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 != ::OpenTelemetry::Trace::INVALID_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 {"[#{self.class}/#{__method__}] Continue trace from parent metadata: #{parent_md.toString}."}
56
- else
57
- event = @context.createEntry(md, (span_data.start_timestamp.to_i / 1000).to_i)
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?? 'OK' : 'ERROR')
72
- event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_STATUS_DESCRIPTION, span_data.status.description) unless span_data.status.description.empty?
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 # remove url parameters
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? "opentelemetry::instrumentation"
122
-
123
- framework = scope_name.split("::")[2..]&.join("::")
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 {"[#{self.class}/#{__method__}] #{span_data.instrumentation_scope.name} with #{framework} and version #{framework_version}"}
129
- event.addInfo("Ruby.#{framework}.Version",framework_version) unless framework_version.nil?
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.has_key?(framework)
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 {"[#{self.class}/#{__method__}] couldn't load #{framework} with error #{e.message}; skip"}
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 {"[#{self.class}/#{__method__}] couldn't find #{framework} with error #{e.message}; skip"}
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 {"[#{self.class}/#{__method__}] current framework version cached: #{@version_cache.inspect}"}
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 "net::http"
161
- normalized = "net/http"
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
- normalized = framework
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("TransactionName", txname)
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?? 1 : 0
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