solarwinds_apm 5.1.8 → 6.0.0.preV1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +0 -1
  3. data/ext/oboe_metal/extconf.rb +19 -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 -1
  9. data/ext/oboe_metal/src/oboe_api.cpp +9 -7
  10. data/ext/oboe_metal/src/oboe_api.h +7 -7
  11. data/ext/oboe_metal/src/oboe_debug.h +2 -0
  12. data/ext/oboe_metal/src/oboe_swig_wrap.cc +19 -18
  13. data/lib/oboe_metal.rb +116 -80
  14. data/lib/rails/generators/solarwinds_apm/install_generator.rb +1 -2
  15. data/lib/rails/generators/solarwinds_apm/templates/solarwinds_apm_initializer.rb +44 -260
  16. data/lib/solarwinds_apm/api/current_trace_info.rb +148 -0
  17. data/lib/solarwinds_apm/api/tracing.rb +30 -0
  18. data/lib/solarwinds_apm/api/transaction_name.rb +57 -0
  19. data/lib/solarwinds_apm/api.rb +8 -15
  20. data/lib/solarwinds_apm/base.rb +4 -131
  21. data/lib/solarwinds_apm/config.rb +128 -175
  22. data/lib/solarwinds_apm/constants.rb +32 -0
  23. data/lib/solarwinds_apm/logger.rb +1 -1
  24. data/lib/solarwinds_apm/noop/context.rb +2 -5
  25. data/lib/solarwinds_apm/noop/metadata.rb +1 -2
  26. data/lib/solarwinds_apm/noop/profiling.rb +3 -7
  27. data/lib/solarwinds_apm/oboe_init_options.rb +71 -33
  28. data/lib/solarwinds_apm/opentelemetry/solarwinds_exporter.rb +204 -0
  29. data/lib/solarwinds_apm/opentelemetry/solarwinds_processor.rb +163 -0
  30. data/lib/solarwinds_apm/opentelemetry/solarwinds_propagator.rb +92 -0
  31. data/lib/solarwinds_apm/opentelemetry/solarwinds_response_propagator.rb +72 -0
  32. data/lib/solarwinds_apm/opentelemetry/solarwinds_sampler.rb +330 -0
  33. data/lib/solarwinds_apm/opentelemetry.rb +8 -0
  34. data/lib/solarwinds_apm/otel_config.rb +161 -0
  35. data/lib/solarwinds_apm/{inst → support}/logger_formatter.rb +5 -6
  36. data/lib/solarwinds_apm/{inst → support}/logging_log_event.rb +3 -6
  37. data/lib/solarwinds_apm/{inst → support}/lumberjack_formatter.rb +1 -4
  38. data/lib/solarwinds_apm/support/oboe_tracing_mode.rb +27 -0
  39. data/lib/solarwinds_apm/support/swomarginalia/LICENSE +20 -0
  40. data/lib/solarwinds_apm/support/swomarginalia/README.md +41 -0
  41. data/lib/solarwinds_apm/support/swomarginalia/comment.rb +205 -0
  42. data/lib/solarwinds_apm/support/swomarginalia/load_swomarginalia.rb +48 -0
  43. data/lib/solarwinds_apm/support/swomarginalia/railtie.rb +22 -0
  44. data/lib/solarwinds_apm/support/swomarginalia/swomarginalia.rb +86 -0
  45. data/lib/solarwinds_apm/support/transaction_cache.rb +24 -0
  46. data/lib/solarwinds_apm/support/transaction_settings.rb +26 -209
  47. data/lib/solarwinds_apm/support/transformer.rb +56 -0
  48. data/lib/solarwinds_apm/support/txn_name_manager.rb +25 -0
  49. data/lib/solarwinds_apm/support/x_trace_options.rb +42 -26
  50. data/lib/solarwinds_apm/support.rb +33 -10
  51. data/lib/solarwinds_apm/support_report.rb +10 -32
  52. data/lib/solarwinds_apm/thread_local.rb +1 -1
  53. data/lib/solarwinds_apm/version.rb +4 -4
  54. data/lib/solarwinds_apm.rb +31 -26
  55. metadata +76 -121
  56. data/.dockerignore +0 -5
  57. data/.gitignore +0 -58
  58. data/.rubocop.yml +0 -29
  59. data/.whitesource +0 -22
  60. data/.yardopts +0 -7
  61. data/CHANGELOG-appoptics.md +0 -766
  62. data/CHANGELOG.md +0 -72
  63. data/CONFIG.md +0 -31
  64. data/Gemfile +0 -15
  65. data/README.md +0 -385
  66. data/bin/solarwinds_apm_config +0 -15
  67. data/examples/prepend.rb +0 -13
  68. data/examples/sdk_examples.rb +0 -158
  69. data/ext/oboe_metal/README.md +0 -69
  70. data/ext/oboe_metal/extconf_local.rb +0 -75
  71. data/ext/oboe_metal/lib/.keep +0 -0
  72. data/ext/oboe_metal/noop/noop.c +0 -8
  73. data/ext/oboe_metal/src/README.md +0 -6
  74. data/ext/oboe_metal/src/frames.cc +0 -247
  75. data/ext/oboe_metal/src/frames.h +0 -40
  76. data/ext/oboe_metal/src/logging.cc +0 -97
  77. data/ext/oboe_metal/src/logging.h +0 -34
  78. data/ext/oboe_metal/src/profiling.cc +0 -435
  79. data/ext/oboe_metal/src/profiling.h +0 -78
  80. data/ext/oboe_metal/test/CMakeLists.txt +0 -53
  81. data/ext/oboe_metal/test/FindGMock.cmake +0 -43
  82. data/ext/oboe_metal/test/README.md +0 -56
  83. data/ext/oboe_metal/test/frames_test.cc +0 -164
  84. data/ext/oboe_metal/test/profiling_test.cc +0 -93
  85. data/ext/oboe_metal/test/ruby_inc_dir.rb +0 -8
  86. data/ext/oboe_metal/test/ruby_prefix.rb +0 -8
  87. data/ext/oboe_metal/test/ruby_test_helper.rb +0 -67
  88. data/ext/oboe_metal/test/test.h +0 -11
  89. data/ext/oboe_metal/test/test_main.cc +0 -32
  90. data/init.rb +0 -4
  91. data/lib/solarwinds_apm/api/layerinit.rb +0 -41
  92. data/lib/solarwinds_apm/api/logging.rb +0 -356
  93. data/lib/solarwinds_apm/api/memcache.rb +0 -37
  94. data/lib/solarwinds_apm/api/metrics.rb +0 -63
  95. data/lib/solarwinds_apm/api/util.rb +0 -98
  96. data/lib/solarwinds_apm/frameworks/grape.rb +0 -96
  97. data/lib/solarwinds_apm/frameworks/padrino.rb +0 -78
  98. data/lib/solarwinds_apm/frameworks/rails/inst/action_controller.rb +0 -100
  99. data/lib/solarwinds_apm/frameworks/rails/inst/action_controller5.rb +0 -50
  100. data/lib/solarwinds_apm/frameworks/rails/inst/action_controller_api.rb +0 -50
  101. data/lib/solarwinds_apm/frameworks/rails/inst/action_view.rb +0 -88
  102. data/lib/solarwinds_apm/frameworks/rails/inst/active_record.rb +0 -26
  103. data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +0 -29
  104. data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +0 -22
  105. data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +0 -103
  106. data/lib/solarwinds_apm/frameworks/rails/inst/logger_formatters.rb +0 -14
  107. data/lib/solarwinds_apm/frameworks/rails.rb +0 -100
  108. data/lib/solarwinds_apm/frameworks/sinatra.rb +0 -96
  109. data/lib/solarwinds_apm/inst/bunny-client.rb +0 -157
  110. data/lib/solarwinds_apm/inst/bunny-consumer.rb +0 -102
  111. data/lib/solarwinds_apm/inst/curb.rb +0 -289
  112. data/lib/solarwinds_apm/inst/dalli.rb +0 -89
  113. data/lib/solarwinds_apm/inst/delayed_job.rb +0 -100
  114. data/lib/solarwinds_apm/inst/excon.rb +0 -113
  115. data/lib/solarwinds_apm/inst/faraday.rb +0 -96
  116. data/lib/solarwinds_apm/inst/graphql.rb +0 -206
  117. data/lib/solarwinds_apm/inst/grpc_client.rb +0 -147
  118. data/lib/solarwinds_apm/inst/grpc_server.rb +0 -119
  119. data/lib/solarwinds_apm/inst/httpclient.rb +0 -182
  120. data/lib/solarwinds_apm/inst/memcached.rb +0 -86
  121. data/lib/solarwinds_apm/inst/mongo.rb +0 -246
  122. data/lib/solarwinds_apm/inst/mongo2.rb +0 -225
  123. data/lib/solarwinds_apm/inst/moped.rb +0 -466
  124. data/lib/solarwinds_apm/inst/net_http.rb +0 -60
  125. data/lib/solarwinds_apm/inst/rack.rb +0 -223
  126. data/lib/solarwinds_apm/inst/rack_cache.rb +0 -35
  127. data/lib/solarwinds_apm/inst/redis.rb +0 -280
  128. data/lib/solarwinds_apm/inst/redis_v4.rb +0 -273
  129. data/lib/solarwinds_apm/inst/resque.rb +0 -129
  130. data/lib/solarwinds_apm/inst/rest-client.rb +0 -43
  131. data/lib/solarwinds_apm/inst/sequel.rb +0 -241
  132. data/lib/solarwinds_apm/inst/sidekiq-client.rb +0 -63
  133. data/lib/solarwinds_apm/inst/sidekiq-worker.rb +0 -64
  134. data/lib/solarwinds_apm/inst/typhoeus.rb +0 -90
  135. data/lib/solarwinds_apm/instrumentation.rb +0 -22
  136. data/lib/solarwinds_apm/loading.rb +0 -65
  137. data/lib/solarwinds_apm/ruby.rb +0 -35
  138. data/lib/solarwinds_apm/sdk/current_trace_info.rb +0 -123
  139. data/lib/solarwinds_apm/sdk/custom_metrics.rb +0 -94
  140. data/lib/solarwinds_apm/sdk/logging.rb +0 -37
  141. data/lib/solarwinds_apm/sdk/trace_context_headers.rb +0 -69
  142. data/lib/solarwinds_apm/sdk/tracing.rb +0 -432
  143. data/lib/solarwinds_apm/support/profiling.rb +0 -25
  144. data/lib/solarwinds_apm/support/trace_context.rb +0 -53
  145. data/lib/solarwinds_apm/support/trace_state.rb +0 -69
  146. data/lib/solarwinds_apm/support/trace_string.rb +0 -89
  147. data/lib/solarwinds_apm/support/transaction_metrics.rb +0 -67
  148. data/lib/solarwinds_apm/test.rb +0 -165
  149. data/lib/solarwinds_apm/util.rb +0 -426
  150. data/log/.keep +0 -0
  151. data/log/postgresql/.keep +0 -0
  152. data/solarwinds_apm.gemspec +0 -55
  153. data/yardoc_frontpage.md +0 -24
@@ -1,10 +1,8 @@
1
1
  # Copyright (c) 2019 SolarWinds, LLC.
2
2
  # All rights reserved.
3
-
4
3
  require 'singleton'
5
-
6
4
  module SolarWindsAPM
7
-
5
+ # OboeInitOptions
8
6
  class OboeInitOptions
9
7
  include Singleton
10
8
 
@@ -46,7 +44,7 @@ module SolarWindsAPM
46
44
  # custom token bucket rate
47
45
  @token_bucket_rate = (ENV['SW_APM_TOKEN_BUCKET_RATE'] || -1).to_i
48
46
  # use single files in file reporter for each event
49
- @file_single = (ENV['SW_APM_REPORTER_FILE_SINGLE'].to_s.downcase == 'true') ? 1 : 0
47
+ @file_single = ENV['SW_APM_REPORTER_FILE_SINGLE'].to_s.downcase == 'true' ? 1 : 0
50
48
  # timeout for ec2 metadata
51
49
  @ec2_md_timeout = read_and_validate_ec2_md_timeout
52
50
  @grpc_proxy = read_and_validate_proxy
@@ -57,7 +55,8 @@ module SolarWindsAPM
57
55
  @metric_format = determine_the_metric_model
58
56
  end
59
57
 
60
- def re_init # for testing with changed ENV vars
58
+ # for testing with changed ENV vars
59
+ def re_init
61
60
  initialize
62
61
  end
63
62
 
@@ -84,12 +83,12 @@ module SolarWindsAPM
84
83
  @ec2_md_timeout, #17
85
84
  @grpc_proxy, #18
86
85
  0, #19 arg for lambda (no lambda for ruby yet)
87
- @metric_format #20
86
+ @metric_format #22
88
87
  ]
89
88
  end
90
89
 
91
90
  def service_key_ok?
92
- return !@service_key.empty? || @reporter != 'ssl'
91
+ !@service_key.empty? || @reporter != 'ssl'
93
92
  end
94
93
 
95
94
  private
@@ -106,9 +105,8 @@ module SolarWindsAPM
106
105
  when 'ssl', 'file'
107
106
  host = ENV['SW_APM_COLLECTOR'] || ''
108
107
  when 'udp'
109
- host = ENV['SW_APM_COLLECTOR'] ||
110
- "#{SolarWindsAPM::Config[:reporter_host]}:#{SolarWindsAPM::Config[:reporter_port]}"
111
- # TODO decide what to do
108
+ host = ENV['SW_APM_COLLECTOR'] || "#{SolarWindsAPM::Config[:reporter_host]}:#{SolarWindsAPM::Config[:reporter_port]}"
109
+ # TODO: decide what to do
112
110
  # ____ SolarWindsAPM::Config[:reporter_host] and
113
111
  # ____ SolarWindsAPM::Config[:reporter_port] were moved here from
114
112
  # ____ oboe_metal.rb and are not documented anywhere
@@ -117,6 +115,7 @@ module SolarWindsAPM
117
115
  host = ''
118
116
  end
119
117
 
118
+ host = sanitize_collector_uri(host) unless reporter == 'file'
120
119
  [reporter, host]
121
120
  end
122
121
 
@@ -125,24 +124,50 @@ module SolarWindsAPM
125
124
 
126
125
  service_key = ENV['SW_APM_SERVICE_KEY'] || SolarWindsAPM::Config[:service_key]
127
126
  unless service_key
128
- SolarWindsAPM.logger.error "[solarwinds_apm/oboe_options] SW_APM_SERVICE_KEY not configured."
127
+ SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY not configured."}
129
128
  return ''
130
129
  end
131
130
 
132
- match = service_key.match( /([^:]+)(:{0,1})(.*)/ )
131
+ match = service_key.match(/([^:]+)(:{0,1})(.*)/)
133
132
  token = match[1]
134
133
  service_name = match[3]
135
134
 
136
- return '' unless validate_token(token)
135
+ return '' unless validate_token(token) # return if token is not even valid
136
+
137
+ if service_name.empty?
138
+ ENV.delete('OTEL_SERVICE_NAME')
139
+ SolarWindsAPM.logger.warn {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY format problem. Service Name is missing."}
140
+ return ''
141
+ end
142
+
143
+ # check OTEL_RESOURCE_ATTRIBUTES
144
+ otel_resource_service_name = nil
145
+ ENV['OTEL_RESOURCE_ATTRIBUTES']&.split(',')&.each do |pair|
146
+ key, value = pair.split('=')
147
+ otel_resource_service_name = value; break if key == 'service.name'
148
+ end
149
+
150
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] provided otel_resource_service_name #{otel_resource_service_name}"} if otel_resource_service_name
151
+ service_name = otel_resource_service_name if otel_resource_service_name && validate_transform_service_name(otel_resource_service_name)
152
+
153
+ # check OTEL_SERVICE_NAME
154
+ otel_service_name = ENV['OTEL_SERVICE_NAME']
155
+ if otel_service_name && validate_transform_service_name(otel_service_name)
156
+ service_name = otel_service_name
157
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] provided otel_service_name #{otel_service_name}"}
158
+ elsif ENV['OTEL_SERVICE_NAME'].nil?
159
+ ENV['OTEL_SERVICE_NAME'] = service_name
160
+ end
161
+
137
162
  return '' unless validate_transform_service_name(service_name)
138
163
 
139
- return "#{token}:#{service_name}"
164
+ "#{token}:#{service_name}"
140
165
  end
141
166
 
142
167
  def validate_token(token)
143
168
  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}"
169
+ masked = "#{token[0..3]}...#{token[-4..]}"
170
+ SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY problem. API Token in wrong format. Masked token: #{masked}"}
146
171
  return false
147
172
  end
148
173
 
@@ -152,7 +177,7 @@ module SolarWindsAPM
152
177
  def validate_transform_service_name(service_name)
153
178
  service_name = 'test_ssl_collector' if ENV['SW_APM_COLLECTOR'] =~ /java-collector:1222/
154
179
  if service_name.empty?
155
- SolarWindsAPM.logger.error "[solarwinds_apm/oboe_options] SW_APM_SERVICE_KEY problem. Service Name is missing"
180
+ SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY problem. Service Name is missing"}
156
181
  return false
157
182
  end
158
183
 
@@ -162,7 +187,7 @@ module SolarWindsAPM
162
187
  name = name[0..254]
163
188
 
164
189
  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}"
190
+ SolarWindsAPM.logger.warn {"[#{self.class}/#{__method__}] SW_APM_SERVICE_KEY problem. Service Name transformed from #{service_name} to #{name}"}
166
191
  service_name = name
167
192
  end
168
193
  @service_name = service_name # instance variable used in testing
@@ -172,8 +197,9 @@ module SolarWindsAPM
172
197
  def read_and_validate_ec2_md_timeout
173
198
  timeout = ENV['SW_APM_EC2_METADATA_TIMEOUT'] || SolarWindsAPM::Config[:ec2_metadata_timeout]
174
199
  return 1000 unless timeout.is_a?(Integer) || timeout =~ /^\d+$/
200
+
175
201
  timeout = timeout.to_i
176
- return timeout.between?(0, 3000) ? timeout : 1000
202
+ timeout.between?(0, 3000) ? timeout : 1000
177
203
  end
178
204
 
179
205
  def read_and_validate_proxy
@@ -181,7 +207,7 @@ module SolarWindsAPM
181
207
  return proxy if proxy == ''
182
208
 
183
209
  unless proxy =~ /http:\/\/.*:\d+$/
184
- SolarWindsAPM.logger.error "[solarwinds_apm/oboe_options] SW_APM_PROXY/http_proxy doesn't start with 'http://', #{proxy}"
210
+ SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] SW_APM_PROXY/http_proxy doesn't start with 'http://', #{proxy}"}
185
211
  return '' # try without proxy, it may work, shouldn't crash but may not report
186
212
  end
187
213
 
@@ -189,31 +215,43 @@ module SolarWindsAPM
189
215
  end
190
216
 
191
217
  def read_certificates
192
-
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?)
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?
196
221
 
197
- return String.new if file.empty?
222
+ return file if file.empty?
198
223
 
199
224
  begin
200
225
  certificate = File.open(file,"r").read
201
226
  rescue StandardError => e
202
- SolarWindsAPM.logger.error "[solarwinds_apm/oboe_options] certificates: #{file} doesn't exist or caused by #{e.message}."
227
+ SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] certificates: #{file} doesn't exist or caused by #{e.message}."}
203
228
  certificate = String.new
204
229
  end
205
230
 
206
- return certificate
207
-
231
+ certificate
208
232
  end
209
233
 
210
234
  def determine_the_metric_model
211
- if ENV['SW_APM_COLLECTOR']&.include? "appoptics.com"
212
- return 1
213
- else
214
- return 0
235
+ appoptics_collector? ? 1 : 2
236
+ end
237
+
238
+ def appoptics_collector?
239
+ allowed_uri = ['collector.appoptics.com', 'collector-stg.appoptics.com',
240
+ 'collector.appoptics.com:443', 'collector-stg.appoptics.com:443']
241
+
242
+ (allowed_uri.include? ENV["SW_APM_COLLECTOR"])? true : false
243
+ end
244
+
245
+ def sanitize_collector_uri(uri)
246
+ return uri if uri.nil? || uri.empty?
247
+
248
+ begin
249
+ sanitized_uri = URI("http://#{uri}").host
250
+ return sanitized_uri unless sanitized_uri.nil?
251
+ rescue StandardError => e
252
+ SolarWindsAPM.logger.error {"[#{self.class}/#{__method__}] uri for collector #{uri} is malformat. Error: #{e.message}"}
215
253
  end
254
+ ""
216
255
  end
217
256
  end
218
257
  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,163 @@
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 {"[#{self.class}/#{__method__}] createHttpSpan with\n
68
+ trans_name: #{trans_name}\n
69
+ url_tran: #{url_tran}\n
70
+ domain: #{domain}\n
71
+ span_time: #{span_time}\n
72
+ status_code: #{status_code}\n
73
+ request_method: #{request_method}\n
74
+ has_error: #{has_error}"}
75
+
76
+ liboboe_txn_name = SolarWindsAPM::Span.createHttpSpan(trans_name,url_tran,domain,span_time,status_code,
77
+ request_method,has_error)
78
+
79
+ else
80
+
81
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] createSpan with \n
82
+ trans_name: #{trans_name}\n
83
+ domain: #{domain}\n
84
+ span_time: #{span_time}\n
85
+ has_error: #{has_error}"}
86
+
87
+ liboboe_txn_name = SolarWindsAPM::Span.createSpan(trans_name, domain, span_time, has_error)
88
+ end
89
+
90
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] liboboe_txn_name: #{liboboe_txn_name}"}
91
+ @txn_manager["#{span.context.hex_trace_id}-#{span.context.hex_span_id}"] = liboboe_txn_name if span.context.trace_flags.sampled?
92
+
93
+ @exporter&.export([span.to_span_data]) if span.context.trace_flags.sampled?
94
+ end
95
+
96
+ # Export all ended spans to the configured `Exporter` that have not yet
97
+ # been exported.
98
+ #
99
+ # This method should only be called in cases where it is absolutely
100
+ # necessary, such as when using some FaaS providers that may suspend
101
+ # the process after an invocation, but before the `Processor` exports
102
+ # the completed spans.
103
+ #
104
+ # @param [optional Numeric] timeout An optional timeout in seconds.
105
+ # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
106
+ # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
107
+ def force_flush(timeout: nil)
108
+ @exporter&.force_flush(timeout: timeout) || ::OpenTelemetry::SDK::Metrics::Export::SUCCESS
109
+ end
110
+
111
+ # Called when {TracerProvider#shutdown} is called.
112
+ #
113
+ # @param [optional Numeric] timeout An optional timeout in seconds.
114
+ # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
115
+ # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
116
+ def shutdown(timeout: nil)
117
+ @exporter&.shutdown(timeout: timeout) || ::OpenTelemetry::SDK::Metrics::Export::SUCCESS
118
+ end
119
+
120
+ private
121
+
122
+ # This span from inbound HTTP request if from a SERVER by some http.method
123
+ def span_http?(span)
124
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span.kind #{span.kind} span.attributes: #{span.attributes[HTTP_METHOD]}"}
125
+ (span.kind == ::OpenTelemetry::Trace::SpanKind::SERVER && !span.attributes[HTTP_METHOD].nil?)
126
+ end
127
+
128
+ # Calculate if this span instance has_error
129
+ # return [Integer]
130
+ def error?(span)
131
+ span.status.code == ::OpenTelemetry::Trace::Status::ERROR ? 1 : 0
132
+ end
133
+
134
+ # Calculate HTTP status_code from span or default to UNAVAILABLE
135
+ # Something went wrong in OTel or instrumented service crashed early
136
+ # if no status_code in attributes of HTTP span
137
+ def get_http_status_code(span)
138
+ span.attributes[HTTP_STATUS_CODE] || LIBOBOE_HTTP_SPAN_STATUS_UNAVAILABLE
139
+ end
140
+
141
+ # Get trans_name and url_tran of this span instance.
142
+ def calculate_transaction_names(span)
143
+
144
+ trace_span_id = "#{span.context.hex_trace_id}-#{span.context.hex_span_id}"
145
+ if @txn_manager.get(trace_span_id)
146
+ trans_name = @txn_manager.get(trace_span_id)
147
+ else
148
+ trans_name = span.attributes[HTTP_ROUTE] || nil
149
+ trans_name = span.name if span.name && (trans_name.nil? || trans_name.empty?)
150
+ end
151
+ trans_name
152
+ end
153
+
154
+ # Calculate span time in microseconds (us) using start and end time
155
+ # in nanoseconds (ns). OTel span start/end_time are optional.
156
+ def calculate_span_time(start_time: nil, end_time: nil)
157
+ return 0 if start_time.nil? || end_time.nil?
158
+
159
+ ((end_time.to_i - start_time.to_i) / 1e3).round
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,92 @@
1
+ module SolarWindsAPM
2
+ module OpenTelemetry
3
+ module SolarWindsPropagator
4
+ # TextMapPropagator
5
+ class TextMapPropagator
6
+ TRACESTATE_HEADER_NAME = "tracestate".freeze
7
+ XTRACEOPTIONS_HEADER_NAME = "x-trace-options".freeze
8
+ XTRACEOPTIONS_SIGNATURE_HEADER_NAME = "x-trace-options-signature".freeze
9
+ INTL_SWO_X_OPTIONS_KEY = "sw_xtraceoptions".freeze
10
+ INTL_SWO_SIGNATURE_KEY = "sw_signature".freeze
11
+
12
+ private_constant \
13
+ :TRACESTATE_HEADER_NAME, :XTRACEOPTIONS_HEADER_NAME,
14
+ :XTRACEOPTIONS_SIGNATURE_HEADER_NAME, :INTL_SWO_X_OPTIONS_KEY, :INTL_SWO_SIGNATURE_KEY
15
+
16
+ # Extract trace context from the supplied carrier.
17
+ #
18
+ # @param [Carrier] carrier The carrier to get the header from
19
+ # @param [optional Context] context Context to be updated with the trace context
20
+ # extracted from the carrier. Defaults to +Context.current+.
21
+ # @param [optional Getter] getter If the optional getter is provided, it
22
+ # will be used to read the header from the carrier, otherwise the default
23
+ # text map getter will be used.
24
+ #
25
+ # @return [Context] context updated with extracted baggage, or the original context
26
+ # if extraction fails
27
+ def extract(carrier, context: ::OpenTelemetry::Context.current, getter: ::OpenTelemetry::Context::Propagation.text_map_getter)
28
+
29
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] context(before): #{context.inspect} #{context.nil?}"}
30
+
31
+ context = ::OpenTelemetry::Context.new({}) if context.nil?
32
+
33
+ xtraceoptions_header = getter.get(carrier, XTRACEOPTIONS_HEADER_NAME)
34
+ context = context.set_value(INTL_SWO_X_OPTIONS_KEY, xtraceoptions_header) if xtraceoptions_header
35
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] xtraceoptions_header: #{xtraceoptions_header}"}
36
+
37
+ signature_header = getter.get(carrier, XTRACEOPTIONS_SIGNATURE_HEADER_NAME)
38
+ context = context.set_value(INTL_SWO_SIGNATURE_KEY, signature_header) if signature_header
39
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] signature_header: #{signature_header}; propagator extract context: #{context.inspect}"}
40
+
41
+ context
42
+ end
43
+
44
+ # Inject trace context into the supplied carrier.
45
+ #
46
+ # @param [Carrier] carrier The mutable carrier to inject trace context into
47
+ # @param [Context] context The context to read trace context from
48
+ # @param [optional Setter] setter If the optional setter is provided, it
49
+ # will be used to write context into the carrier, otherwise the default
50
+ # text map setter will be used.
51
+ def inject(carrier, context: ::OpenTelemetry::Context.current, setter: ::OpenTelemetry::Context::Propagation.text_map_setter)
52
+
53
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] inject context: #{context.inspect}"}
54
+
55
+ cspan = ::OpenTelemetry::Trace.current_span(context)
56
+ span_context = cspan&.context
57
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] cspan #{cspan.inspect}; span_context #{span_context.inspect}"}
58
+ return unless span_context&.valid?
59
+
60
+ sw_value = Transformer.sw_from_context(span_context) # sw_value is a string
61
+ trace_state_header = carrier[TRACESTATE_HEADER_NAME].nil?? nil : carrier[TRACESTATE_HEADER_NAME]
62
+
63
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] sw_value: #{sw_value}; trace_state_header: #{trace_state_header}"}
64
+
65
+ # Prepare carrier with carrier's or new tracestate
66
+ trace_state = nil
67
+ if trace_state_header.nil?
68
+ # Only create new trace state if valid span_id
69
+ return if span_context.span_id == ::OpenTelemetry::Trace::INVALID_SPAN_ID
70
+
71
+ trace_state = ::OpenTelemetry::Trace::Tracestate.create({SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY => sw_value})
72
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] creating new trace state: #{trace_state.inspect}"}
73
+ else
74
+ trace_state_from_string = ::OpenTelemetry::Trace::Tracestate.from_string(trace_state_header)
75
+ trace_state = trace_state_from_string.set_value(SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY, sw_value)
76
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Updating/Adding trace state for injection #{trace_state.inspect}"}
77
+ end
78
+
79
+ setter.set(carrier, TRACESTATE_HEADER_NAME, Transformer.trace_state_header(trace_state))
80
+ end
81
+
82
+ # Returns the predefined propagation fields. If your carrier is reused, you
83
+ # should delete the fields returned by this method before calling +inject+.
84
+ #
85
+ # @return [Array<String>] a list of fields that will be used by this propagator.
86
+ def fields
87
+ TRACESTATE_HEADER_NAME
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end