solarwinds_apm 6.0.0.preV1 → 6.0.0.preV3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +28 -54
  3. data/ext/oboe_metal/extconf.rb +23 -23
  4. data/ext/oboe_metal/lib/liboboe-1.0-aarch64.so.sha256 +1 -1
  5. data/ext/oboe_metal/lib/liboboe-1.0-alpine-aarch64.so.sha256 +1 -1
  6. data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.sha256 +1 -1
  7. data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.sha256 +1 -1
  8. data/ext/oboe_metal/src/VERSION +1 -2
  9. data/ext/oboe_metal/src/init_solarwinds_apm.cc +0 -6
  10. data/ext/oboe_metal/src/oboe.h +0 -12
  11. data/ext/oboe_metal/src/oboe_swig_wrap.cc +5 -5
  12. data/lib/oboe_metal.rb +3 -3
  13. data/lib/rails/generators/solarwinds_apm/install_generator.rb +0 -3
  14. data/lib/rails/generators/solarwinds_apm/templates/solarwinds_apm_initializer.rb +8 -25
  15. data/lib/solarwinds_apm/api/current_trace_info.rb +1 -2
  16. data/lib/solarwinds_apm/api/opentelemetry.rb +39 -0
  17. data/lib/solarwinds_apm/api/transaction_name.rb +18 -17
  18. data/lib/solarwinds_apm/api.rb +2 -0
  19. data/lib/solarwinds_apm/config.rb +21 -63
  20. data/lib/solarwinds_apm/constants.rb +2 -4
  21. data/lib/solarwinds_apm/noop/context.rb +1 -1
  22. data/lib/solarwinds_apm/oboe_init_options.rb +11 -16
  23. data/lib/solarwinds_apm/opentelemetry/solarwinds_exporter.rb +92 -87
  24. data/lib/solarwinds_apm/opentelemetry/solarwinds_processor.rb +27 -23
  25. data/lib/solarwinds_apm/opentelemetry/solarwinds_propagator.rb +28 -28
  26. data/lib/solarwinds_apm/opentelemetry/solarwinds_response_propagator.rb +9 -14
  27. data/lib/solarwinds_apm/opentelemetry/solarwinds_sampler.rb +132 -175
  28. data/lib/solarwinds_apm/otel_config.rb +55 -36
  29. data/lib/solarwinds_apm/support/lumberjack_formatter.rb +17 -2
  30. data/lib/solarwinds_apm/support/swomarginalia/comment.rb +2 -2
  31. data/lib/solarwinds_apm/support/swomarginalia/swomarginalia.rb +6 -5
  32. data/lib/solarwinds_apm/support/transaction_cache.rb +27 -2
  33. data/lib/solarwinds_apm/support/transaction_settings.rb +10 -10
  34. data/lib/solarwinds_apm/support/txn_name_manager.rb +34 -17
  35. data/lib/solarwinds_apm/support/utils.rb +24 -0
  36. data/lib/solarwinds_apm/support.rb +4 -2
  37. data/lib/solarwinds_apm/version.rb +1 -1
  38. data/lib/solarwinds_apm.rb +3 -4
  39. metadata +18 -33
  40. data/lib/oboe.rb +0 -7
  41. data/lib/solarwinds_apm/noop/profiling.rb +0 -17
  42. data/lib/solarwinds_apm/support/transformer.rb +0 -56
@@ -11,13 +11,13 @@ module SolarWindsAPM
11
11
  module Config
12
12
  @@config = {}
13
13
  @@instrumentation = [:action_controller, :action_controller_api, :action_view,
14
- :active_record, :bunnyclient, :bunnyconsumer, :curb,
15
- :dalli, :delayed_jobclient, :delayed_jobworker,
16
- :excon, :faraday, :graphql, :grpc_client, :grpc_server, :grape,
17
- :httpclient, :nethttp, :memcached, :mongo, :moped, :padrino, :rack, :redis,
18
- :resqueclient, :resqueworker, :rest_client,
19
- :sequel, :sidekiqclient, :sidekiqworker, :sinatra, :typhoeus,
20
- :curb, :excon, :faraday, :httpclient, :nethttp, :rest_client, :typhoeus]
14
+ :active_record, :bunnyclient, :bunnyconsumer, :curb,
15
+ :dalli, :delayed_jobclient, :delayed_jobworker,
16
+ :excon, :faraday, :graphql, :grpc_client, :grpc_server, :grape,
17
+ :httpclient, :nethttp, :memcached, :mongo, :moped, :padrino, :rack, :redis,
18
+ :resqueclient, :resqueworker, :rest_client,
19
+ :sequel, :sidekiqclient, :sidekiqworker, :sinatra, :typhoeus,
20
+ :curb, :excon, :faraday, :httpclient, :nethttp, :rest_client, :typhoeus]
21
21
 
22
22
  ##
23
23
  # load_config_file
@@ -47,28 +47,21 @@ module SolarWindsAPM
47
47
  config_file = File.join(Dir.pwd, 'solarwinds_apm_config.rb')
48
48
  config_files << config_file if File.exist?(config_file)
49
49
 
50
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] Multiple configuration files configured, using the first one listed: #{config_files.join(', ')}"} if config_files.size > 1
50
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] Multiple configuration files configured, using the first one listed: #{config_files.join(', ')}"} if config_files.size > 1
51
51
  load(config_files[0]) if config_files.size > 0
52
52
 
53
53
  set_log_level # sets SolarWindsAPM::Config[:debug_level], SolarWindsAPM.logger.level
54
- set_verbose_level # the verbose setting is only relevant for ruby, ENV['SW_APM_GEM_VERBOSE'] overrides
55
54
  end
56
55
 
57
56
  def self.config_from_env
58
- config_files = []
59
57
  if File.exist?(ENV['SW_APM_CONFIG_RUBY']) && !File.directory?(ENV['SW_APM_CONFIG_RUBY'])
60
- config_files << ENV['SW_APM_CONFIG_RUBY']
58
+ config_file = ENV['SW_APM_CONFIG_RUBY']
61
59
  elsif File.exist?(File.join(ENV['SW_APM_CONFIG_RUBY'], 'solarwinds_apm_config.rb'))
62
- config_files << File.join(ENV['SW_APM_CONFIG_RUBY'], 'solarwinds_apm_config.rb')
60
+ config_file = File.join(ENV['SW_APM_CONFIG_RUBY'], 'solarwinds_apm_config.rb')
63
61
  else
64
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] Could not find the configuration file set by the SW_APM_CONFIG_RUBY environment variable: #{ENV['SW_APM_CONFIG_RUBY']}"}
62
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] Could not find the configuration file set by the SW_APM_CONFIG_RUBY environment variable: #{ENV['SW_APM_CONFIG_RUBY']}"}
65
63
  end
66
- config_files
67
- end
68
-
69
- def self.set_verbose_level
70
- verbose = ENV.has_key?('SW_APM_GEM_VERBOSE')? ENV['SW_APM_GEM_VERBOSE'].downcase == 'true' : nil
71
- SolarWindsAPM::Config[:verbose] = verbose
64
+ config_file
72
65
  end
73
66
 
74
67
  def self.set_log_level
@@ -86,9 +79,9 @@ module SolarWindsAPM
86
79
  # to create an output similar to the content of the config file
87
80
  #
88
81
  def self.print_config
89
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] General configurations list blow:"}
82
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] General configurations list blow:"}
90
83
  @@config.each do |k,v|
91
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] Config Key/Value: #{k}, #{v.inspect}"}
84
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] Config Key/Value: #{k}, #{v.inspect}"}
92
85
  end
93
86
  end
94
87
 
@@ -99,9 +92,6 @@ module SolarWindsAPM
99
92
  # The defaults are read from the template configuration file.
100
93
  #
101
94
  def self.initialize(_data={})
102
- @@config[:profiling] = :disabled
103
- @@config[:profiling_interval] = 5
104
-
105
95
  # for config file backward compatibility
106
96
  @@instrumentation.each {|inst| @@config[inst] = {}}
107
97
  @@config[:transaction_name] = {}
@@ -137,40 +127,24 @@ module SolarWindsAPM
137
127
 
138
128
  case key
139
129
  when :sampling_rate
140
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] sampling_rate is not a supported setting for SolarWindsAPM::Config. Please use :sample_rate."}
130
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] sampling_rate is not a supported setting for SolarWindsAPM::Config. Please use :sample_rate."}
141
131
 
142
132
  when :sample_rate
143
133
  unless value.is_a?(Integer) || value.is_a?(Float)
144
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] :sample_rate must be a number between 0 and 1000000 (1m) (provided: #{value}), corrected to 0"}
134
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] :sample_rate must be a number between 0 and 1000000 (1m) (provided: #{value}), corrected to 0"}
145
135
  value = 0
146
136
  end
147
137
 
148
138
  # Validate :sample_rate value
149
139
  unless value.between?(0, 1e6)
150
140
  new_value = value < 0 ? 0 : 1_000_000
151
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] :sample_rate must be between 0 and 1000000 (1m) (provided: #{value}), corrected to #{new_value}"}
141
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] :sample_rate must be between 0 and 1000000 (1m) (provided: #{value}), corrected to #{new_value}"}
152
142
  end
153
143
 
154
144
  # Assure value is an integer
155
145
  @@config[key.to_sym] = new_value.to_i
156
146
  SolarWindsAPM.sample_rate(new_value) if SolarWindsAPM.loaded
157
147
 
158
- when :profiling
159
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] Profiling feature is currently not available." }
160
- @@config[:profiling] = :disabled
161
-
162
- when :profiling_interval
163
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] Profiling feature is currently not available. :profiling_interval setting is not configured."}
164
- value = if value.is_a?(Integer) && value > 0
165
- [100, value].min
166
- else
167
- 10
168
- end
169
- @@config[:profiling_interval] = value
170
- # CProfiler may not be loaded yet, the profiler will send the value
171
- # after it is loaded
172
- SolarWindsAPM::CProfiler.interval_setup(value) if defined? SolarWindsAPM::CProfiler
173
-
174
148
  when :transaction_settings
175
149
  compile_settings(value)
176
150
 
@@ -202,7 +176,7 @@ module SolarWindsAPM
202
176
 
203
177
  # `tracing: disabled` is the default
204
178
  disabled = settings.select { |v| !v.has_key?(:tracing) || v[:tracing] == :disabled }
205
- enabled = settings.select { |v| v[:tracing] == :enabled }
179
+ enabled = settings.select { |v| v[:tracing] == :enabled }
206
180
 
207
181
  SolarWindsAPM::Config[:enabled_regexps] = compile_regexp(enabled)
208
182
  SolarWindsAPM::Config[:disabled_regexps] = compile_regexp(disabled)
@@ -210,11 +184,8 @@ module SolarWindsAPM
210
184
  private_class_method :compile_settings
211
185
 
212
186
  def self.compile_regexp(settings)
213
- regexp_regexp = compile_settings_regexp(settings)
214
- extensions_regexp = compile_settings_extensions(settings)
215
-
216
- regexps = [regexp_regexp, extensions_regexp].flatten.compact
217
-
187
+ regexp_regexp = compile_settings_regexp(settings)
188
+ regexps = [regexp_regexp].flatten.compact
218
189
  regexps.empty? ? nil : regexps
219
190
  end
220
191
  private_class_method :compile_regexp
@@ -230,7 +201,7 @@ module SolarWindsAPM
230
201
  begin
231
202
  v[:regexp].is_a?(String) ? Regexp.new(v[:regexp], v[:opts]) : Regexp.new(v[:regexp])
232
203
  rescue StandardError => e
233
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] Problem compiling transaction_settings item #{v}, will ignore. Error: #{e.message}"}
204
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] Problem compiling transaction_settings item #{v}, will ignore. Error: #{e.message}"}
234
205
  nil
235
206
  end
236
207
  end
@@ -239,19 +210,6 @@ module SolarWindsAPM
239
210
  end
240
211
  private_class_method :compile_settings_regexp
241
212
 
242
- def self.compile_settings_extensions(value)
243
- extensions = value.select do |v|
244
- v.has_key?(:extensions) &&
245
- v[:extensions].is_a?(Array) &&
246
- !v[:extensions].empty?
247
- end
248
- extensions = extensions.map { |v| v[:extensions] }.flatten
249
- extensions.keep_if { |v| v.is_a?(String) }
250
-
251
- extensions.empty? ? nil : Regexp.new("(#{Regexp.union(extensions).source})(\\?.+){0,1}$")
252
- end
253
- private_class_method :compile_settings_extensions
254
-
255
213
  def self.reset_regexps
256
214
  SolarWindsAPM::Config[:enabled_regexps] = nil
257
215
  SolarWindsAPM::Config[:disabled_regexps] = nil
@@ -20,12 +20,10 @@ module SolarWindsAPM
20
20
  INTL_SWO_PROPAGATOR = "solarwinds_propagator".freeze
21
21
  INTL_SWO_DEFAULT_PROPAGATORS = [INTL_SWO_TRACECONTEXT_PROPAGATOR, "baggage",INTL_SWO_PROPAGATOR].freeze
22
22
  INTL_SWO_SUPPORT_EMAIL = "SWO-support@solarwinds.com".freeze
23
- INTL_SWO_CURRENT_SPAN_ID = "sw-current-entry-span-id".freeze
24
- INTL_SWO_CURRENT_TRACE_ID = "sw-current-trace-id".freeze
25
- INTL_SWO_CURRENT_TRACE_FLAG = "sw-current-trace-flag".freeze
26
-
27
23
  INTL_SWO_OTEL_SCOPE_NAME = "otel.scope.name".freeze
28
24
  INTL_SWO_OTEL_SCOPE_VERSION = "otel.scope.version".freeze
25
+ INTL_SWO_OTEL_STATUS = "otel.status".freeze
26
+ INTL_SWO_OTEL_STATUS_DESCRIPTION = "otel.status_description".freeze
29
27
 
30
28
  INTERNAL_TRIGGERED_TRACE = "TriggeredTrace".freeze
31
29
  end
@@ -12,7 +12,7 @@ module SolarWindsAPM
12
12
  # toString would return the current trace context as string
13
13
  #
14
14
  def self.toString
15
- '00-00000000000000000000000000000000-0000000000000000-00'
15
+ '99-00000000000000000000000000000000-0000000000000000-00'
16
16
  end
17
17
 
18
18
  ##
@@ -1,6 +1,7 @@
1
1
  # Copyright (c) 2019 SolarWinds, LLC.
2
2
  # All rights reserved.
3
3
  require 'singleton'
4
+ require 'uri'
4
5
  module SolarWindsAPM
5
6
  # OboeInitOptions
6
7
  class OboeInitOptions
@@ -96,9 +97,6 @@ module SolarWindsAPM
96
97
  def reporter_and_host
97
98
 
98
99
  reporter = ENV['SW_APM_REPORTER'] || 'ssl'
99
- # override with 'file', e.g. when running tests
100
- # changed my mind => set the right reporter in the env when running tests !!!
101
- # reporter = 'file' if ENV.key?('SW_APM_GEM_TEST')
102
100
 
103
101
  host = ''
104
102
  case reporter
@@ -123,7 +121,7 @@ module SolarWindsAPM
123
121
  return '' unless @reporter == 'ssl'
124
122
 
125
123
  service_key = ENV['SW_APM_SERVICE_KEY'] || SolarWindsAPM::Config[:service_key]
126
- unless service_key
124
+ if service_key.nil? || service_key == ''
127
125
  SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY not configured."}
128
126
  return ''
129
127
  end
@@ -133,7 +131,7 @@ module SolarWindsAPM
133
131
  service_name = match[3]
134
132
 
135
133
  return '' unless validate_token(token) # return if token is not even valid
136
-
134
+
137
135
  if service_name.empty?
138
136
  ENV.delete('OTEL_SERVICE_NAME')
139
137
  SolarWindsAPM.logger.warn {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY format problem. Service Name is missing."}
@@ -197,7 +195,7 @@ module SolarWindsAPM
197
195
  def read_and_validate_ec2_md_timeout
198
196
  timeout = ENV['SW_APM_EC2_METADATA_TIMEOUT'] || SolarWindsAPM::Config[:ec2_metadata_timeout]
199
197
  return 1000 unless timeout.is_a?(Integer) || timeout =~ /^\d+$/
200
-
198
+
201
199
  timeout = timeout.to_i
202
200
  timeout.between?(0, 3000) ? timeout : 1000
203
201
  end
@@ -215,19 +213,16 @@ module SolarWindsAPM
215
213
  end
216
214
 
217
215
  def read_certificates
218
- file = String.new
219
- file = "#{__dir__}/cert/star.appoptics.com.issuer.crt" if appoptics_collector?
220
- file = ENV['SW_APM_TRUSTEDPATH'] if !ENV['SW_APM_TRUSTEDPATH'].nil? && !ENV['SW_APM_TRUSTEDPATH']&.empty?
221
-
222
- return file if file.empty?
223
-
216
+ file = appoptics_collector?? "#{__dir__}/cert/star.appoptics.com.issuer.crt" : ENV['SW_APM_TRUSTEDPATH']
217
+ return String.new if file.nil? || file&.empty?
218
+
224
219
  begin
225
220
  certificate = File.open(file,"r").read
226
221
  rescue StandardError => e
227
222
  SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] certificates: #{file} doesn't exist or caused by #{e.message}."}
228
223
  certificate = String.new
229
224
  end
230
-
225
+
231
226
  certificate
232
227
  end
233
228
 
@@ -236,7 +231,7 @@ module SolarWindsAPM
236
231
  end
237
232
 
238
233
  def appoptics_collector?
239
- allowed_uri = ['collector.appoptics.com', 'collector-stg.appoptics.com',
234
+ allowed_uri = ['collector.appoptics.com', 'collector-stg.appoptics.com',
240
235
  'collector.appoptics.com:443', 'collector-stg.appoptics.com:443']
241
236
 
242
237
  (allowed_uri.include? ENV["SW_APM_COLLECTOR"])? true : false
@@ -246,12 +241,12 @@ module SolarWindsAPM
246
241
  return uri if uri.nil? || uri.empty?
247
242
 
248
243
  begin
249
- sanitized_uri = URI("http://#{uri}").host
244
+ sanitized_uri = ::URI.parse("http://#{uri}").host
250
245
  return sanitized_uri unless sanitized_uri.nil?
251
246
  rescue StandardError => e
252
247
  SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] uri for collector #{uri} is malformat. Error: #{e.message}"}
253
248
  end
254
- ""
249
+ ""
255
250
  end
256
251
  end
257
252
  end
@@ -19,12 +19,13 @@ module SolarWindsAPM
19
19
 
20
20
  def export(span_data, _timeout: nil)
21
21
  return FAILURE if @shutdown
22
-
23
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span_data: #{span_data}"}
22
+
23
+ status = SUCCESS
24
24
  span_data.each do |data|
25
- log_span_data(data)
25
+ status = log_span_data(data)
26
26
  end
27
- SUCCESS
27
+
28
+ status
28
29
  end
29
30
 
30
31
  def force_flush(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
@@ -39,64 +40,63 @@ module SolarWindsAPM
39
40
  private
40
41
 
41
42
  def log_span_data(span_data)
43
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span_data: #{span_data.inspect}\n"}
44
+
45
+ md = build_meta_data(span_data)
46
+ event = nil
47
+ if span_data.parent_span_id != ::OpenTelemetry::Trace::INVALID_SPAN_ID
48
+ parent_md = build_meta_data(span_data, parent: true)
49
+ event = @context.createEntry(md, (span_data.start_timestamp.to_i / 1000).to_i, parent_md)
50
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Continue trace from parent metadata: #{parent_md.toString}."}
51
+ else
52
+ event = @context.createEntry(md, (span_data.start_timestamp.to_i / 1000).to_i)
53
+ add_info_transaction_name(span_data, event)
54
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Start a new trace."}
55
+ end
56
+
57
+ layer_name = "#{span_data.kind}:#{span_data.name}"
58
+ event.addInfo('Layer', layer_name)
59
+ event.addInfo('sw.span_kind', span_data.kind.to_s)
60
+ event.addInfo('Language', 'Ruby')
61
+
62
+ add_instrumentation_scope(event, span_data)
63
+ add_instrumented_framework(event, span_data)
64
+ add_span_data_attributes(event, span_data.attributes) if span_data.attributes
42
65
 
43
- begin
44
- md = build_meta_data(span_data)
45
- event = nil
46
-
47
- if span_data.parent_span_id != ::OpenTelemetry::Trace::INVALID_SPAN_ID
48
-
49
- parent_md = build_meta_data(span_data, parent: true)
50
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Continue trace from parent. parent_md: #{parent_md}, span_data: #{span_data.inspect}"}
51
- event = @context.createEntry(md, (span_data.start_timestamp.to_i / 1000).to_i, parent_md)
52
- else
53
-
54
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Start a new trace."}
55
- event = @context.createEntry(md, (span_data.start_timestamp.to_i / 1000).to_i)
56
- add_info_transaction_name(span_data, event)
57
- end
58
-
59
- event.addInfo('Layer', span_data.name)
60
- event.addInfo('sw.span_kind', span_data.kind.to_s)
61
- event.addInfo('Language', 'Ruby')
62
-
63
- add_info_instrumentation_scope(event, span_data)
64
- add_info_instrumented_framework(event, span_data)
65
-
66
- if span_data.attributes
67
- if span_data.attributes['http.target']
68
- http_target = span_data.attributes['http.target']
69
- http_target = http_target.split('?').first if SolarWindsAPM::Config[:log_args] == false # remove url parameters
70
- event.addInfo('http.target', http_target)
71
- span_data.attributes.each do |k, v|
72
- next if k == 'http.target'
73
-
74
- event.addInfo(k, v)
75
- end
76
- else
77
- span_data.attributes.each { |k, v| event.addInfo(k, v) }
78
- end
79
- end
80
-
81
- @reporter.send_report(event, with_system_timestamp: false)
66
+ event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_STATUS, span_data.status.ok?? 'OK' : 'ERROR')
67
+ event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_STATUS_DESCRIPTION, span_data.status.description) unless span_data.status.description.empty?
82
68
 
83
- # info / exception event
84
- span_data.events&.each do |span_data_event|
85
- span_data_event.name == 'exception' ? report_exception_event(span_data_event) : report_info_event(span_data_event)
86
- end
69
+ @reporter.send_report(event, with_system_timestamp: false)
87
70
 
88
- event = @context.createExit((span_data.end_timestamp.to_i / 1000).to_i)
89
- event.addInfo('Layer', span_data.name)
90
- @reporter.send_report(event, with_system_timestamp: false)
91
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Exit a trace: #{event.metadataString}"}
92
- rescue StandardError => e
93
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] \n #{e.message} #{e.backtrace}\n"}
94
- raise
71
+ # info / exception event
72
+ span_data.events&.each do |span_data_event|
73
+ span_data_event.name == 'exception' ? report_exception_event(span_data_event) : report_info_event(span_data_event)
95
74
  end
96
75
 
76
+ event = @context.createExit((span_data.end_timestamp.to_i / 1000).to_i)
77
+ event.addInfo('Layer', layer_name)
78
+ @reporter.send_report(event, with_system_timestamp: false)
79
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Exit a trace: #{event.metadataString}"}
80
+ SUCCESS
81
+ rescue StandardError => e
82
+ SolarWindsAPM.logger.info {"[#{self.class}/#{__method__}] exporter error: \n #{e.message} #{e.backtrace}\n"}
83
+ FAILURE
97
84
  end
98
85
 
99
- def add_info_instrumentation_scope(event, span_data)
86
+ ##
87
+ # extract the span_data.attributes (OpenTelemetry::SDK::Trace::SpanData.attributes)
88
+ def add_span_data_attributes(event, span_attributes)
89
+ target = 'http.target'
90
+ attributes = span_attributes.dup
91
+ attributes[target] = attributes[target].split('?').first if attributes[target] && SolarWindsAPM::Config[:log_args] == false # remove url parameters
92
+ attributes.each { |k, v| event.addInfo(k, v) }
93
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span_data attributes added: #{attributes.inspect}"}
94
+ end
95
+
96
+ ##
97
+ # get instrumentation scope data: scope name and version.
98
+ # the version if the opentelemetry-instrumentation-* gem version
99
+ def add_instrumentation_scope(event, span_data)
100
100
  scope_name = ""
101
101
  scope_version = ""
102
102
  if span_data.instrumentation_scope
@@ -104,14 +104,13 @@ module SolarWindsAPM
104
104
  scope_version = span_data.instrumentation_scope.version if span_data.instrumentation_scope.version
105
105
  end
106
106
  event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_SCOPE_NAME, scope_name)
107
- event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_SCOPE_VERSION, scope_version)
107
+ event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_SCOPE_VERSION, scope_version)
108
108
  end
109
109
 
110
- #
111
- # get the framework version (the real version not the sdk instrumentation version)
112
- #
113
- def add_info_instrumented_framework(event, span_data)
114
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] add_info_instrumented_framework: #{span_data.instrumentation_scope.name}"}
110
+ ##
111
+ # add the gem library version to event
112
+ # p.s. gem library version is not same as the opentelemetry-instrumentation-* gem version
113
+ def add_instrumented_framework(event, span_data)
115
114
  scope_name = span_data.instrumentation_scope.name
116
115
  scope_name = scope_name.downcase if scope_name
117
116
  return unless scope_name&.include? "opentelemetry::instrumentation"
@@ -121,9 +120,12 @@ module SolarWindsAPM
121
120
 
122
121
  framework = normalize_framework_name(framework)
123
122
  framework_version = check_framework_version(framework)
123
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] #{span_data.instrumentation_scope.name} with #{framework} and version #{framework_version}"}
124
124
  event.addInfo("Ruby.#{framework}.Version",framework_version) unless framework_version.nil?
125
125
  end
126
126
 
127
+ ##
128
+ # helper function that extract gem library version for func add_instrumented_framework
127
129
  def check_framework_version(framework)
128
130
  framework_version = nil
129
131
  if @version_cache.keys.include? framework
@@ -142,10 +144,12 @@ module SolarWindsAPM
142
144
  @version_cache[framework] = framework_version
143
145
  end
144
146
  end
145
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Current framework version cached: #{@version_cache.inspect}"}
147
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] current framework version cached: #{@version_cache.inspect}"}
146
148
  framework_version
147
149
  end
148
150
 
151
+ ##
152
+ # helper function that convert opentelemetry instrumentation name to gem library understandable
149
153
  def normalize_framework_name(framework)
150
154
  case framework
151
155
  when "net::http"
@@ -156,47 +160,48 @@ module SolarWindsAPM
156
160
  normalized
157
161
  end
158
162
 
163
+ ##
159
164
  # Add transaction name from cache to root span then removes from cache
160
- def add_info_transaction_name(span_data, evt)
165
+ def add_info_transaction_name(span_data, event)
166
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] transaction manager: #{@txn_manager.inspect}."}
161
167
  trace_span_id = "#{span_data.hex_trace_id}-#{span_data.hex_span_id}"
162
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] #{@txn_manager.inspect},\n span_data: #{span_data.inspect}"}
163
- txname = @txn_manager.get(trace_span_id).nil?? "" : @txn_manager.get(trace_span_id)
164
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] txname: #{txname}"}
165
- evt.addInfo("TransactionName", txname)
168
+ txname = @txn_manager.get(trace_span_id) || ''
169
+ event.addInfo("TransactionName", txname)
166
170
  @txn_manager.del(trace_span_id)
167
171
  end
168
172
 
173
+ ##
174
+ # report exception event
169
175
  def report_exception_event(span_event)
170
- evt = @context.createEvent((span_event.timestamp.to_i / 1000).to_i)
171
- evt.addInfo('Label', 'error')
172
- evt.addInfo('Spec', 'error')
176
+ event = @context.createEvent((span_event.timestamp.to_i / 1000).to_i)
177
+ event.addInfo('Label', 'error')
178
+ event.addInfo('Spec', 'error')
173
179
 
174
180
  unless span_event.attributes.nil?
175
- evt.addInfo('ErrorClass', span_event.attributes['exception.type'])
176
- evt.addInfo('ErrorMsg', span_event.attributes['exception.message'])
177
- evt.addInfo('Backtrace', span_event.attributes['exception.stacktrace'])
178
- span_event.attributes.each do |key, value|
179
- evt.addInfo(key, value) unless ['exception.type', 'exception.message','exception.stacktrace'].include? key
180
- end
181
+ attributes = span_event.attributes.dup
182
+ attributes.delete('exception.type') if event.addInfo('ErrorClass', attributes['exception.type'])
183
+ attributes.delete('exception.message') if event.addInfo('ErrorMsg', attributes['exception.message'])
184
+ attributes.delete('exception.stacktrace') if event.addInfo('Backtrace', attributes['exception.stacktrace'])
185
+ attributes.each { |key, value| event.addInfo(key, value) }
181
186
  end
182
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] exception event #{evt.metadataString}"}
183
- @reporter.send_report(evt, with_system_timestamp: false)
187
+
188
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] exception event #{event.metadataString}"}
189
+ @reporter.send_report(event, with_system_timestamp: false)
184
190
  end
185
191
 
192
+ ##
193
+ # report non-exception/info event
186
194
  def report_info_event(span_event)
187
- evt = @context.createEvent((span_event.timestamp.to_i / 1000).to_i)
188
- evt.addInfo('Label', 'info')
189
- span_event.attributes&.each do |key, value|
190
- evt.addInfo(key, value)
191
- end
192
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] info event #{evt.metadataString}"}
193
- @reporter.send_report(evt, with_system_timestamp: false)
195
+ event = @context.createEvent((span_event.timestamp.to_i / 1000).to_i)
196
+ event.addInfo('Label', 'info')
197
+ span_event.attributes&.each { |key, value| event.addInfo(key, value) }
198
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] info event #{event.metadataString}"}
199
+ @reporter.send_report(event, with_system_timestamp: false)
194
200
  end
195
201
 
196
202
  def build_meta_data(span_data, parent: false)
197
203
  flag = span_data.trace_flags.sampled?? 1 : 0
198
- version = "00"
199
- xtr = parent == false ? "#{version}-#{span_data.hex_trace_id}-#{span_data.hex_span_id}-0#{flag}" : "#{version}-#{span_data.hex_trace_id}-#{span_data.hex_parent_span_id}-0#{flag}"
204
+ 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}"
200
205
  @metadata.fromString(xtr)
201
206
  end
202
207
  end