solarwinds_apm 5.1.9 → 6.0.0.preV2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +28 -55
  3. data/ext/oboe_metal/extconf.rb +37 -41
  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 -1
  9. data/ext/oboe_metal/src/init_solarwinds_apm.cc +0 -6
  10. data/ext/oboe_metal/src/oboe_debug.h +1 -0
  11. data/lib/oboe_metal.rb +116 -80
  12. data/lib/rails/generators/solarwinds_apm/install_generator.rb +1 -5
  13. data/lib/rails/generators/solarwinds_apm/templates/solarwinds_apm_initializer.rb +42 -267
  14. data/lib/solarwinds_apm/api/current_trace_info.rb +148 -0
  15. data/lib/solarwinds_apm/api/tracing.rb +30 -0
  16. data/lib/solarwinds_apm/api/transaction_name.rb +58 -0
  17. data/lib/solarwinds_apm/api.rb +8 -15
  18. data/lib/solarwinds_apm/base.rb +4 -131
  19. data/lib/solarwinds_apm/config.rb +101 -174
  20. data/lib/solarwinds_apm/constants.rb +32 -0
  21. data/lib/solarwinds_apm/logger.rb +1 -1
  22. data/lib/solarwinds_apm/noop/context.rb +2 -5
  23. data/lib/solarwinds_apm/noop/metadata.rb +1 -2
  24. data/lib/solarwinds_apm/oboe_init_options.rb +74 -38
  25. data/lib/solarwinds_apm/opentelemetry/solarwinds_exporter.rb +204 -0
  26. data/lib/solarwinds_apm/opentelemetry/solarwinds_processor.rb +166 -0
  27. data/lib/solarwinds_apm/opentelemetry/solarwinds_propagator.rb +92 -0
  28. data/lib/solarwinds_apm/opentelemetry/solarwinds_response_propagator.rb +72 -0
  29. data/lib/solarwinds_apm/opentelemetry/solarwinds_sampler.rb +335 -0
  30. data/lib/solarwinds_apm/opentelemetry.rb +8 -0
  31. data/lib/solarwinds_apm/otel_config.rb +161 -0
  32. data/lib/solarwinds_apm/{inst → support}/logger_formatter.rb +5 -6
  33. data/lib/solarwinds_apm/{inst → support}/logging_log_event.rb +3 -6
  34. data/lib/solarwinds_apm/{inst → support}/lumberjack_formatter.rb +1 -4
  35. data/lib/solarwinds_apm/support/oboe_tracing_mode.rb +27 -0
  36. data/lib/solarwinds_apm/support/swomarginalia/LICENSE +20 -0
  37. data/lib/solarwinds_apm/support/swomarginalia/README.md +41 -0
  38. data/lib/solarwinds_apm/support/swomarginalia/comment.rb +205 -0
  39. data/lib/solarwinds_apm/support/swomarginalia/load_swomarginalia.rb +48 -0
  40. data/lib/solarwinds_apm/support/swomarginalia/railtie.rb +22 -0
  41. data/lib/solarwinds_apm/support/swomarginalia/swomarginalia.rb +86 -0
  42. data/lib/solarwinds_apm/support/transaction_cache.rb +24 -0
  43. data/lib/solarwinds_apm/support/transaction_settings.rb +26 -209
  44. data/lib/solarwinds_apm/support/transformer.rb +56 -0
  45. data/lib/solarwinds_apm/support/txn_name_manager.rb +25 -0
  46. data/lib/solarwinds_apm/support/x_trace_options.rb +42 -26
  47. data/lib/solarwinds_apm/support.rb +37 -10
  48. data/lib/solarwinds_apm/support_report.rb +10 -32
  49. data/lib/solarwinds_apm/thread_local.rb +1 -1
  50. data/lib/solarwinds_apm/version.rb +4 -4
  51. data/lib/solarwinds_apm.rb +29 -25
  52. metadata +63 -123
  53. data/.dockerignore +0 -5
  54. data/.gitignore +0 -58
  55. data/.rubocop.yml +0 -29
  56. data/.whitesource +0 -22
  57. data/.yardopts +0 -7
  58. data/CHANGELOG-appoptics.md +0 -766
  59. data/CHANGELOG.md +0 -82
  60. data/CONFIG.md +0 -31
  61. data/Gemfile +0 -15
  62. data/README.md +0 -385
  63. data/bin/solarwinds_apm_config +0 -15
  64. data/examples/prepend.rb +0 -13
  65. data/examples/sdk_examples.rb +0 -158
  66. data/ext/oboe_metal/README.md +0 -69
  67. data/ext/oboe_metal/extconf_local.rb +0 -75
  68. data/ext/oboe_metal/lib/.keep +0 -0
  69. data/ext/oboe_metal/noop/noop.c +0 -8
  70. data/ext/oboe_metal/src/README.md +0 -6
  71. data/ext/oboe_metal/src/frames.cc +0 -247
  72. data/ext/oboe_metal/src/frames.h +0 -40
  73. data/ext/oboe_metal/src/logging.cc +0 -97
  74. data/ext/oboe_metal/src/logging.h +0 -34
  75. data/ext/oboe_metal/src/profiling.cc +0 -435
  76. data/ext/oboe_metal/src/profiling.h +0 -78
  77. data/ext/oboe_metal/test/CMakeLists.txt +0 -53
  78. data/ext/oboe_metal/test/FindGMock.cmake +0 -43
  79. data/ext/oboe_metal/test/README.md +0 -56
  80. data/ext/oboe_metal/test/frames_test.cc +0 -164
  81. data/ext/oboe_metal/test/profiling_test.cc +0 -93
  82. data/ext/oboe_metal/test/ruby_inc_dir.rb +0 -8
  83. data/ext/oboe_metal/test/ruby_prefix.rb +0 -8
  84. data/ext/oboe_metal/test/ruby_test_helper.rb +0 -67
  85. data/ext/oboe_metal/test/test.h +0 -11
  86. data/ext/oboe_metal/test/test_main.cc +0 -32
  87. data/init.rb +0 -4
  88. data/lib/solarwinds_apm/api/layerinit.rb +0 -41
  89. data/lib/solarwinds_apm/api/logging.rb +0 -356
  90. data/lib/solarwinds_apm/api/memcache.rb +0 -37
  91. data/lib/solarwinds_apm/api/metrics.rb +0 -63
  92. data/lib/solarwinds_apm/api/util.rb +0 -98
  93. data/lib/solarwinds_apm/frameworks/grape.rb +0 -96
  94. data/lib/solarwinds_apm/frameworks/padrino.rb +0 -78
  95. data/lib/solarwinds_apm/frameworks/rails/inst/action_controller.rb +0 -100
  96. data/lib/solarwinds_apm/frameworks/rails/inst/action_controller5.rb +0 -50
  97. data/lib/solarwinds_apm/frameworks/rails/inst/action_controller_api.rb +0 -50
  98. data/lib/solarwinds_apm/frameworks/rails/inst/action_view.rb +0 -88
  99. data/lib/solarwinds_apm/frameworks/rails/inst/active_record.rb +0 -26
  100. data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +0 -29
  101. data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +0 -22
  102. data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +0 -103
  103. data/lib/solarwinds_apm/frameworks/rails/inst/logger_formatters.rb +0 -14
  104. data/lib/solarwinds_apm/frameworks/rails.rb +0 -100
  105. data/lib/solarwinds_apm/frameworks/sinatra.rb +0 -96
  106. data/lib/solarwinds_apm/inst/bunny-client.rb +0 -157
  107. data/lib/solarwinds_apm/inst/bunny-consumer.rb +0 -102
  108. data/lib/solarwinds_apm/inst/curb.rb +0 -289
  109. data/lib/solarwinds_apm/inst/dalli.rb +0 -89
  110. data/lib/solarwinds_apm/inst/delayed_job.rb +0 -100
  111. data/lib/solarwinds_apm/inst/excon.rb +0 -113
  112. data/lib/solarwinds_apm/inst/faraday.rb +0 -96
  113. data/lib/solarwinds_apm/inst/graphql.rb +0 -206
  114. data/lib/solarwinds_apm/inst/grpc_client.rb +0 -147
  115. data/lib/solarwinds_apm/inst/grpc_server.rb +0 -119
  116. data/lib/solarwinds_apm/inst/httpclient.rb +0 -182
  117. data/lib/solarwinds_apm/inst/memcached.rb +0 -86
  118. data/lib/solarwinds_apm/inst/mongo.rb +0 -246
  119. data/lib/solarwinds_apm/inst/mongo2.rb +0 -225
  120. data/lib/solarwinds_apm/inst/moped.rb +0 -466
  121. data/lib/solarwinds_apm/inst/net_http.rb +0 -60
  122. data/lib/solarwinds_apm/inst/rack.rb +0 -223
  123. data/lib/solarwinds_apm/inst/rack_cache.rb +0 -35
  124. data/lib/solarwinds_apm/inst/redis.rb +0 -280
  125. data/lib/solarwinds_apm/inst/redis_v4.rb +0 -273
  126. data/lib/solarwinds_apm/inst/resque.rb +0 -129
  127. data/lib/solarwinds_apm/inst/rest-client.rb +0 -43
  128. data/lib/solarwinds_apm/inst/sequel.rb +0 -241
  129. data/lib/solarwinds_apm/inst/sidekiq-client.rb +0 -63
  130. data/lib/solarwinds_apm/inst/sidekiq-worker.rb +0 -64
  131. data/lib/solarwinds_apm/inst/typhoeus.rb +0 -90
  132. data/lib/solarwinds_apm/instrumentation.rb +0 -22
  133. data/lib/solarwinds_apm/loading.rb +0 -65
  134. data/lib/solarwinds_apm/noop/profiling.rb +0 -21
  135. data/lib/solarwinds_apm/ruby.rb +0 -35
  136. data/lib/solarwinds_apm/sdk/current_trace_info.rb +0 -123
  137. data/lib/solarwinds_apm/sdk/custom_metrics.rb +0 -94
  138. data/lib/solarwinds_apm/sdk/logging.rb +0 -37
  139. data/lib/solarwinds_apm/sdk/trace_context_headers.rb +0 -69
  140. data/lib/solarwinds_apm/sdk/tracing.rb +0 -432
  141. data/lib/solarwinds_apm/support/profiling.rb +0 -25
  142. data/lib/solarwinds_apm/support/trace_context.rb +0 -53
  143. data/lib/solarwinds_apm/support/trace_state.rb +0 -69
  144. data/lib/solarwinds_apm/support/trace_string.rb +0 -89
  145. data/lib/solarwinds_apm/support/transaction_metrics.rb +0 -67
  146. data/lib/solarwinds_apm/test.rb +0 -165
  147. data/lib/solarwinds_apm/util.rb +0 -426
  148. data/log/.keep +0 -0
  149. data/log/postgresql/.keep +0 -0
  150. data/solarwinds_apm.gemspec +0 -55
  151. data/yardoc_frontpage.md +0 -24
@@ -1,10 +1,9 @@
1
1
  # Copyright (c) 2019 SolarWinds, LLC.
2
2
  # All rights reserved.
3
-
4
3
  require 'singleton'
5
-
4
+ require 'uri'
6
5
  module SolarWindsAPM
7
-
6
+ # OboeInitOptions
8
7
  class OboeInitOptions
9
8
  include Singleton
10
9
 
@@ -46,7 +45,7 @@ module SolarWindsAPM
46
45
  # custom token bucket rate
47
46
  @token_bucket_rate = (ENV['SW_APM_TOKEN_BUCKET_RATE'] || -1).to_i
48
47
  # use single files in file reporter for each event
49
- @file_single = (ENV['SW_APM_REPORTER_FILE_SINGLE'].to_s.downcase == 'true') ? 1 : 0
48
+ @file_single = ENV['SW_APM_REPORTER_FILE_SINGLE'].to_s.downcase == 'true' ? 1 : 0
50
49
  # timeout for ec2 metadata
51
50
  @ec2_md_timeout = read_and_validate_ec2_md_timeout
52
51
  @grpc_proxy = read_and_validate_proxy
@@ -57,7 +56,8 @@ module SolarWindsAPM
57
56
  @metric_format = determine_the_metric_model
58
57
  end
59
58
 
60
- def re_init # for testing with changed ENV vars
59
+ # for testing with changed ENV vars
60
+ def re_init
61
61
  initialize
62
62
  end
63
63
 
@@ -84,12 +84,12 @@ module SolarWindsAPM
84
84
  @ec2_md_timeout, #17
85
85
  @grpc_proxy, #18
86
86
  0, #19 arg for lambda (no lambda for ruby yet)
87
- @metric_format #20
87
+ @metric_format #22
88
88
  ]
89
89
  end
90
90
 
91
91
  def service_key_ok?
92
- return !@service_key.empty? || @reporter != 'ssl'
92
+ !@service_key.empty? || @reporter != 'ssl'
93
93
  end
94
94
 
95
95
  private
@@ -97,18 +97,14 @@ module SolarWindsAPM
97
97
  def reporter_and_host
98
98
 
99
99
  reporter = ENV['SW_APM_REPORTER'] || 'ssl'
100
- # override with 'file', e.g. when running tests
101
- # changed my mind => set the right reporter in the env when running tests !!!
102
- # reporter = 'file' if ENV.key?('SW_APM_GEM_TEST')
103
100
 
104
101
  host = ''
105
102
  case reporter
106
103
  when 'ssl', 'file'
107
104
  host = ENV['SW_APM_COLLECTOR'] || ''
108
105
  when 'udp'
109
- host = ENV['SW_APM_COLLECTOR'] ||
110
- "#{SolarWindsAPM::Config[:reporter_host]}:#{SolarWindsAPM::Config[:reporter_port]}"
111
- # TODO decide what to do
106
+ host = ENV['SW_APM_COLLECTOR'] || "#{SolarWindsAPM::Config[:reporter_host]}:#{SolarWindsAPM::Config[:reporter_port]}"
107
+ # TODO: decide what to do
112
108
  # ____ SolarWindsAPM::Config[:reporter_host] and
113
109
  # ____ SolarWindsAPM::Config[:reporter_port] were moved here from
114
110
  # ____ oboe_metal.rb and are not documented anywhere
@@ -117,6 +113,7 @@ module SolarWindsAPM
117
113
  host = ''
118
114
  end
119
115
 
116
+ host = sanitize_collector_uri(host) unless reporter == 'file'
120
117
  [reporter, host]
121
118
  end
122
119
 
@@ -124,25 +121,51 @@ module SolarWindsAPM
124
121
  return '' unless @reporter == 'ssl'
125
122
 
126
123
  service_key = ENV['SW_APM_SERVICE_KEY'] || SolarWindsAPM::Config[:service_key]
127
- unless service_key
128
- SolarWindsAPM.logger.error "[solarwinds_apm/oboe_options] SW_APM_SERVICE_KEY not configured."
124
+ if service_key.nil? || service_key == ''
125
+ SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY not configured."}
129
126
  return ''
130
127
  end
131
128
 
132
- match = service_key.match( /([^:]+)(:{0,1})(.*)/ )
129
+ match = service_key.match(/([^:]+)(:{0,1})(.*)/)
133
130
  token = match[1]
134
131
  service_name = match[3]
135
132
 
136
- return '' unless validate_token(token)
133
+ return '' unless validate_token(token) # return if token is not even valid
134
+
135
+ if service_name.empty?
136
+ ENV.delete('OTEL_SERVICE_NAME')
137
+ SolarWindsAPM.logger.warn {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY format problem. Service Name is missing."}
138
+ return ''
139
+ end
140
+
141
+ # check OTEL_RESOURCE_ATTRIBUTES
142
+ otel_resource_service_name = nil
143
+ ENV['OTEL_RESOURCE_ATTRIBUTES']&.split(',')&.each do |pair|
144
+ key, value = pair.split('=')
145
+ otel_resource_service_name = value; break if key == 'service.name'
146
+ end
147
+
148
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] provided otel_resource_service_name #{otel_resource_service_name}"} if otel_resource_service_name
149
+ service_name = otel_resource_service_name if otel_resource_service_name && validate_transform_service_name(otel_resource_service_name)
150
+
151
+ # check OTEL_SERVICE_NAME
152
+ otel_service_name = ENV['OTEL_SERVICE_NAME']
153
+ if otel_service_name && validate_transform_service_name(otel_service_name)
154
+ service_name = otel_service_name
155
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] provided otel_service_name #{otel_service_name}"}
156
+ elsif ENV['OTEL_SERVICE_NAME'].nil?
157
+ ENV['OTEL_SERVICE_NAME'] = service_name
158
+ end
159
+
137
160
  return '' unless validate_transform_service_name(service_name)
138
161
 
139
- return "#{token}:#{service_name}"
162
+ "#{token}:#{service_name}"
140
163
  end
141
164
 
142
165
  def validate_token(token)
143
166
  if (token !~ /^[0-9a-zA-Z_-]{71}$/) && ENV['SW_APM_COLLECTOR'] !~ /java-collector:1222/
144
- masked = "#{token[0..3]}...#{token[-4..-1]}"
145
- SolarWindsAPM.logger.error "[solarwinds_apm/oboe_options] SW_APM_SERVICE_KEY problem. API Token in wrong format. Masked token: #{masked}"
167
+ masked = "#{token[0..3]}...#{token[-4..]}"
168
+ SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY problem. API Token in wrong format. Masked token: #{masked}"}
146
169
  return false
147
170
  end
148
171
 
@@ -152,7 +175,7 @@ module SolarWindsAPM
152
175
  def validate_transform_service_name(service_name)
153
176
  service_name = 'test_ssl_collector' if ENV['SW_APM_COLLECTOR'] =~ /java-collector:1222/
154
177
  if service_name.empty?
155
- SolarWindsAPM.logger.error "[solarwinds_apm/oboe_options] SW_APM_SERVICE_KEY problem. Service Name is missing"
178
+ SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY problem. Service Name is missing"}
156
179
  return false
157
180
  end
158
181
 
@@ -162,7 +185,7 @@ module SolarWindsAPM
162
185
  name = name[0..254]
163
186
 
164
187
  if name != service_name
165
- SolarWindsAPM.logger.warn "[solarwinds_apm/oboe_options] SW_APM_SERVICE_KEY problem. Service Name transformed from #{service_name} to #{name}"
188
+ SolarWindsAPM.logger.warn {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY problem. Service Name transformed from #{service_name} to #{name}"}
166
189
  service_name = name
167
190
  end
168
191
  @service_name = service_name # instance variable used in testing
@@ -172,8 +195,9 @@ module SolarWindsAPM
172
195
  def read_and_validate_ec2_md_timeout
173
196
  timeout = ENV['SW_APM_EC2_METADATA_TIMEOUT'] || SolarWindsAPM::Config[:ec2_metadata_timeout]
174
197
  return 1000 unless timeout.is_a?(Integer) || timeout =~ /^\d+$/
198
+
175
199
  timeout = timeout.to_i
176
- return timeout.between?(0, 3000) ? timeout : 1000
200
+ timeout.between?(0, 3000) ? timeout : 1000
177
201
  end
178
202
 
179
203
  def read_and_validate_proxy
@@ -181,7 +205,7 @@ module SolarWindsAPM
181
205
  return proxy if proxy == ''
182
206
 
183
207
  unless proxy =~ /http:\/\/.*:\d+$/
184
- SolarWindsAPM.logger.error "[solarwinds_apm/oboe_options] SW_APM_PROXY/http_proxy doesn't start with 'http://', #{proxy}"
208
+ SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] SW_APM_PROXY/http_proxy doesn't start with 'http://', #{proxy}"}
185
209
  return '' # try without proxy, it may work, shouldn't crash but may not report
186
210
  end
187
211
 
@@ -189,31 +213,43 @@ module SolarWindsAPM
189
213
  end
190
214
 
191
215
  def read_certificates
216
+ file = String.new
217
+ file = "#{__dir__}/cert/star.appoptics.com.issuer.crt" if appoptics_collector?
218
+ file = ENV['SW_APM_TRUSTEDPATH'] if !ENV['SW_APM_TRUSTEDPATH'].nil? && !ENV['SW_APM_TRUSTEDPATH']&.empty?
219
+
220
+ return file if file.empty?
192
221
 
193
- file = ''
194
- file = "#{File.expand_path File.dirname(__FILE__)}/cert/star.appoptics.com.issuer.crt" if ENV["SW_APM_COLLECTOR"]&.include? "appoptics.com"
195
- file = ENV['SW_APM_TRUSTEDPATH'] if (!ENV['SW_APM_TRUSTEDPATH'].nil? && !ENV['SW_APM_TRUSTEDPATH']&.empty?)
196
-
197
- return String.new if file.empty?
198
-
199
222
  begin
200
223
  certificate = File.open(file,"r").read
201
224
  rescue StandardError => e
202
- SolarWindsAPM.logger.error "[solarwinds_apm/oboe_options] certificates: #{file} doesn't exist or caused by #{e.message}."
225
+ SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] certificates: #{file} doesn't exist or caused by #{e.message}."}
203
226
  certificate = String.new
204
227
  end
205
-
206
- return certificate
207
228
 
229
+ certificate
208
230
  end
209
231
 
210
232
  def determine_the_metric_model
211
- if ENV['SW_APM_COLLECTOR']&.include? "appoptics.com"
212
- return 1
213
- else
214
- return 0
233
+ appoptics_collector? ? 1 : 2
234
+ end
235
+
236
+ def appoptics_collector?
237
+ allowed_uri = ['collector.appoptics.com', 'collector-stg.appoptics.com',
238
+ 'collector.appoptics.com:443', 'collector-stg.appoptics.com:443']
239
+
240
+ (allowed_uri.include? ENV["SW_APM_COLLECTOR"])? true : false
241
+ end
242
+
243
+ def sanitize_collector_uri(uri)
244
+ return uri if uri.nil? || uri.empty?
245
+
246
+ begin
247
+ sanitized_uri = ::URI.parse("http://#{uri}").host
248
+ return sanitized_uri unless sanitized_uri.nil?
249
+ rescue StandardError => e
250
+ SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] uri for collector #{uri} is malformat. Error: #{e.message}"}
215
251
  end
252
+ ""
216
253
  end
217
254
  end
218
255
  end
219
-
@@ -0,0 +1,204 @@
1
+ # solarwinds_apm will use liboboe to export data to solarwinds swo
2
+ module SolarWindsAPM
3
+ module OpenTelemetry
4
+ # SolarWindsExporter
5
+ class SolarWindsExporter
6
+ SUCCESS = ::OpenTelemetry::SDK::Trace::Export::SUCCESS # ::OpenTelemetry #=> the OpenTelemetry at top level (to ignore SolarWindsAPM)
7
+ FAILURE = ::OpenTelemetry::SDK::Trace::Export::FAILURE
8
+
9
+ private_constant(:SUCCESS, :FAILURE)
10
+
11
+ def initialize(txn_manager: nil)
12
+ @shutdown = false
13
+ @txn_manager = txn_manager
14
+ @reporter = SolarWindsAPM::Reporter
15
+ @context = SolarWindsAPM::Context
16
+ @metadata = SolarWindsAPM::Metadata
17
+ @version_cache = {}
18
+ end
19
+
20
+ def export(span_data, _timeout: nil)
21
+ return FAILURE if @shutdown
22
+
23
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span_data: #{span_data}"}
24
+ span_data.each do |data|
25
+ log_span_data(data)
26
+ end
27
+ SUCCESS
28
+ end
29
+
30
+ def force_flush(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
31
+ SUCCESS
32
+ end
33
+
34
+ def shutdown(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
35
+ @shutdown = true
36
+ SUCCESS
37
+ end
38
+
39
+ private
40
+
41
+ def log_span_data(span_data)
42
+
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)
82
+
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
87
+
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
95
+ end
96
+
97
+ end
98
+
99
+ def add_info_instrumentation_scope(event, span_data)
100
+ scope_name = ""
101
+ scope_version = ""
102
+ if span_data.instrumentation_scope
103
+ scope_name = span_data.instrumentation_scope.name if span_data.instrumentation_scope.name
104
+ scope_version = span_data.instrumentation_scope.version if span_data.instrumentation_scope.version
105
+ end
106
+ event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_SCOPE_NAME, scope_name)
107
+ event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_SCOPE_VERSION, scope_version)
108
+ end
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}"}
115
+ scope_name = span_data.instrumentation_scope.name
116
+ scope_name = scope_name.downcase if scope_name
117
+ return unless scope_name&.include? "opentelemetry::instrumentation"
118
+
119
+ framework = scope_name.split("::")[2..]&.join("::")
120
+ return if framework.nil? || framework.empty?
121
+
122
+ framework = normalize_framework_name(framework)
123
+ framework_version = check_framework_version(framework)
124
+ event.addInfo("Ruby.#{framework}.Version",framework_version) unless framework_version.nil?
125
+ end
126
+
127
+ def check_framework_version(framework)
128
+ framework_version = nil
129
+ if @version_cache.keys.include? framework
130
+
131
+ framework_version = @version_cache[framework]
132
+ else
133
+
134
+ begin
135
+ require framework
136
+ framework_version = Gem.loaded_specs[framework].version.to_s
137
+ rescue LoadError => e
138
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] couldn't load #{framework} with error #{e.message}; skip"}
139
+ rescue StandardError => e
140
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] couldn't find #{framework} with error #{e.message}; skip"}
141
+ ensure
142
+ @version_cache[framework] = framework_version
143
+ end
144
+ end
145
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Current framework version cached: #{@version_cache.inspect}"}
146
+ framework_version
147
+ end
148
+
149
+ def normalize_framework_name(framework)
150
+ case framework
151
+ when "net::http"
152
+ normalized = "net/http"
153
+ else
154
+ normalized = framework
155
+ end
156
+ normalized
157
+ end
158
+
159
+ # Add transaction name from cache to root span then removes from cache
160
+ def add_info_transaction_name(span_data, evt)
161
+ 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)
166
+ @txn_manager.del(trace_span_id)
167
+ end
168
+
169
+ 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')
173
+
174
+ 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
+ end
182
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] exception event #{evt.metadataString}"}
183
+ @reporter.send_report(evt, with_system_timestamp: false)
184
+ end
185
+
186
+ 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)
194
+ end
195
+
196
+ def build_meta_data(span_data, parent: false)
197
+ 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}"
200
+ @metadata.fromString(xtr)
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,166 @@
1
+ module SolarWindsAPM
2
+ module OpenTelemetry
3
+ # reference: OpenTelemetry::SDK::Trace::SpanProcessor
4
+ class SolarWindsProcessor
5
+ HTTP_METHOD = "http.method".freeze
6
+ HTTP_ROUTE = "http.route".freeze
7
+ HTTP_STATUS_CODE = "http.status_code".freeze
8
+ HTTP_URL = "http.url".freeze
9
+ LIBOBOE_HTTP_SPAN_STATUS_UNAVAILABLE = 0
10
+
11
+ attr_reader :txn_manager
12
+
13
+ def initialize(exporter, txn_manager)
14
+ @exporter = exporter
15
+ @txn_manager = txn_manager
16
+ end
17
+
18
+ # Called when a {Span} is started, if the {Span#recording?}
19
+ # returns true.
20
+ #
21
+ # This method is called synchronously on the execution thread, should
22
+ # not throw or block the execution thread.
23
+ #
24
+ # @param [Span] span the {Span} that just started.
25
+ # @param [Context] parent_context the parent {Context} of the newly
26
+ # started span.
27
+ def on_start(span, parent_context)
28
+
29
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] processor on_start span: #{span.inspect}, parent_context: #{parent_context.inspect}"}
30
+
31
+ parent_span = ::OpenTelemetry::Trace.current_span(parent_context)
32
+ return if parent_span && parent_span.context != ::OpenTelemetry::Trace::SpanContext::INVALID && parent_span.context.remote? == false
33
+
34
+ trace_flags = span.context.trace_flags.sampled? ? '01' : '00'
35
+ ::OpenTelemetry::Context.attach(::OpenTelemetry::Baggage.set_value(::SolarWindsAPM::Constants::INTL_SWO_CURRENT_TRACE_ID, span.context.hex_trace_id))
36
+ ::OpenTelemetry::Context.attach(::OpenTelemetry::Baggage.set_value(::SolarWindsAPM::Constants::INTL_SWO_CURRENT_SPAN_ID, span.context.hex_span_id))
37
+ ::OpenTelemetry::Context.attach(::OpenTelemetry::Baggage.set_value(::SolarWindsAPM::Constants::INTL_SWO_CURRENT_TRACE_FLAG, trace_flags))
38
+
39
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] current baggage values: #{::OpenTelemetry::Baggage.values}"}
40
+ end
41
+
42
+ # Called when a {Span} is ended, if the {Span#recording?}
43
+ # returns true.
44
+ #
45
+ # This method is called synchronously on the execution thread, should
46
+ # not throw or block the execution thread.
47
+ # Only calculate inbound metrics for service root spans
48
+ #
49
+ # @param [Span] span the {Span} that just ended.
50
+ def on_finish(span)
51
+ if span.parent_span_id != ::OpenTelemetry::Trace::INVALID_SPAN_ID
52
+ @exporter&.export([span.to_span_data]) if span.context.trace_flags.sampled?
53
+ return
54
+ end
55
+
56
+ span_time = calculate_span_time(start_time: span.start_timestamp, end_time: span.end_timestamp)
57
+ domain = nil
58
+ has_error = error?(span)
59
+ trans_name = calculate_transaction_names(span)
60
+ url_tran = span.attributes[HTTP_URL]
61
+
62
+ liboboe_txn_name = nil
63
+ if span_http?(span)
64
+ status_code = get_http_status_code(span)
65
+ request_method = span.attributes[HTTP_METHOD]
66
+
67
+ SolarWindsAPM.logger.debug do
68
+ "[#{self.class}/#{__method__}] createHttpSpan with\n
69
+ trans_name: #{trans_name}\n
70
+ url_tran: #{url_tran}\n
71
+ domain: #{domain}\n
72
+ span_time: #{span_time}\n
73
+ status_code: #{status_code}\n
74
+ request_method: #{request_method}\n
75
+ has_error: #{has_error}"
76
+ end
77
+
78
+ liboboe_txn_name = SolarWindsAPM::Span.createHttpSpan(trans_name,url_tran,domain,span_time,status_code,request_method,has_error)
79
+
80
+ else
81
+
82
+ SolarWindsAPM.logger.debug do
83
+ "[#{self.class}/#{__method__}] createSpan with \n
84
+ trans_name: #{trans_name}\n
85
+ domain: #{domain}\n
86
+ span_time: #{span_time}\n
87
+ has_error: #{has_error}"
88
+ end
89
+
90
+ liboboe_txn_name = SolarWindsAPM::Span.createSpan(trans_name, domain, span_time, has_error)
91
+ end
92
+
93
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] liboboe_txn_name: #{liboboe_txn_name}"}
94
+ @txn_manager["#{span.context.hex_trace_id}-#{span.context.hex_span_id}"] = liboboe_txn_name if span.context.trace_flags.sampled?
95
+
96
+ @exporter&.export([span.to_span_data]) if span.context.trace_flags.sampled?
97
+ end
98
+
99
+ # Export all ended spans to the configured `Exporter` that have not yet
100
+ # been exported.
101
+ #
102
+ # This method should only be called in cases where it is absolutely
103
+ # necessary, such as when using some FaaS providers that may suspend
104
+ # the process after an invocation, but before the `Processor` exports
105
+ # the completed spans.
106
+ #
107
+ # @param [optional Numeric] timeout An optional timeout in seconds.
108
+ # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
109
+ # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
110
+ def force_flush(timeout: nil)
111
+ @exporter&.force_flush(timeout: timeout) || ::OpenTelemetry::SDK::Metrics::Export::SUCCESS
112
+ end
113
+
114
+ # Called when {TracerProvider#shutdown} is called.
115
+ #
116
+ # @param [optional Numeric] timeout An optional timeout in seconds.
117
+ # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
118
+ # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
119
+ def shutdown(timeout: nil)
120
+ @exporter&.shutdown(timeout: timeout) || ::OpenTelemetry::SDK::Metrics::Export::SUCCESS
121
+ end
122
+
123
+ private
124
+
125
+ # This span from inbound HTTP request if from a SERVER by some http.method
126
+ def span_http?(span)
127
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span.kind #{span.kind} span.attributes: #{span.attributes[HTTP_METHOD]}"}
128
+ (span.kind == ::OpenTelemetry::Trace::SpanKind::SERVER && !span.attributes[HTTP_METHOD].nil?)
129
+ end
130
+
131
+ # Calculate if this span instance has_error
132
+ # return [Integer]
133
+ def error?(span)
134
+ span.status.code == ::OpenTelemetry::Trace::Status::ERROR ? 1 : 0
135
+ end
136
+
137
+ # Calculate HTTP status_code from span or default to UNAVAILABLE
138
+ # Something went wrong in OTel or instrumented service crashed early
139
+ # if no status_code in attributes of HTTP span
140
+ def get_http_status_code(span)
141
+ span.attributes[HTTP_STATUS_CODE] || LIBOBOE_HTTP_SPAN_STATUS_UNAVAILABLE
142
+ end
143
+
144
+ # Get trans_name and url_tran of this span instance.
145
+ def calculate_transaction_names(span)
146
+
147
+ trace_span_id = "#{span.context.hex_trace_id}-#{span.context.hex_span_id}"
148
+ if @txn_manager.get(trace_span_id)
149
+ trans_name = @txn_manager.get(trace_span_id)
150
+ else
151
+ trans_name = span.attributes[HTTP_ROUTE] || nil
152
+ trans_name = span.name if span.name && (trans_name.nil? || trans_name.empty?)
153
+ end
154
+ trans_name
155
+ end
156
+
157
+ # Calculate span time in microseconds (us) using start and end time
158
+ # in nanoseconds (ns). OTel span start/end_time are optional.
159
+ def calculate_span_time(start_time: nil, end_time: nil)
160
+ return 0 if start_time.nil? || end_time.nil?
161
+
162
+ ((end_time.to_i - start_time.to_i) / 1e3).round
163
+ end
164
+ end
165
+ end
166
+ end