solarwinds_apm 6.0.0.preV1 → 6.0.0.preV3

Sign up to get free protection for your applications and to get access to all the features.
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