solarwinds_apm 6.0.0 → 6.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/ext/oboe_metal/extconf.rb +42 -41
  3. data/ext/oboe_metal/lib/liboboe-1.0-aarch64.so.sha256 +1 -1
  4. data/ext/oboe_metal/lib/liboboe-1.0-alpine-aarch64.so.sha256 +1 -1
  5. data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.sha256 +1 -1
  6. data/ext/oboe_metal/lib/liboboe-1.0-lambda-aarch64.so.sha256 +1 -0
  7. data/ext/oboe_metal/lib/liboboe-1.0-lambda-x86_64.so.sha256 +1 -0
  8. data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.sha256 +1 -1
  9. data/ext/oboe_metal/src/VERSION +1 -1
  10. data/ext/oboe_metal/src/oboe.h +3 -0
  11. data/ext/oboe_metal/src/oboe_api.cpp +1 -1
  12. data/lib/oboe_metal.rb +30 -27
  13. data/lib/rails/generators/solarwinds_apm/install_generator.rb +21 -19
  14. data/lib/solarwinds_apm/api/current_trace_info.rb +16 -9
  15. data/lib/solarwinds_apm/api/custom_metrics.rb +6 -4
  16. data/lib/solarwinds_apm/api/opentelemetry.rb +8 -4
  17. data/lib/solarwinds_apm/api/tracing.rb +6 -4
  18. data/lib/solarwinds_apm/api/transaction_name.rb +21 -11
  19. data/lib/solarwinds_apm/api.rb +7 -5
  20. data/lib/solarwinds_apm/config.rb +70 -46
  21. data/lib/solarwinds_apm/constants.rb +25 -23
  22. data/lib/solarwinds_apm/logger.rb +2 -0
  23. data/lib/solarwinds_apm/noop/api.rb +3 -1
  24. data/lib/solarwinds_apm/noop/context.rb +2 -0
  25. data/lib/solarwinds_apm/noop/metadata.rb +2 -0
  26. data/lib/solarwinds_apm/noop/span.rb +2 -0
  27. data/lib/solarwinds_apm/noop.rb +8 -6
  28. data/lib/solarwinds_apm/oboe_init_options.rb +47 -39
  29. data/lib/solarwinds_apm/opentelemetry/otlp_processor.rb +135 -0
  30. data/lib/solarwinds_apm/opentelemetry/solarwinds_exporter.rb +66 -41
  31. data/lib/solarwinds_apm/opentelemetry/solarwinds_processor.rb +49 -51
  32. data/lib/solarwinds_apm/opentelemetry/solarwinds_propagator.rb +30 -22
  33. data/lib/solarwinds_apm/opentelemetry/solarwinds_response_propagator.rb +29 -16
  34. data/lib/solarwinds_apm/opentelemetry/solarwinds_sampler.rb +135 -98
  35. data/lib/solarwinds_apm/opentelemetry.rb +8 -5
  36. data/lib/solarwinds_apm/otel_config.rb +32 -25
  37. data/lib/solarwinds_apm/otel_lambda_config.rb +53 -0
  38. data/lib/solarwinds_apm/patch.rb +2 -0
  39. data/lib/solarwinds_apm/support/logger_formatter.rb +4 -2
  40. data/lib/solarwinds_apm/support/logging_log_event.rb +2 -0
  41. data/lib/solarwinds_apm/support/lumberjack_formatter.rb +2 -0
  42. data/lib/solarwinds_apm/support/oboe_tracing_mode.rb +2 -0
  43. data/lib/solarwinds_apm/support/service_key_checker.rb +18 -6
  44. data/lib/solarwinds_apm/support/support_report.rb +5 -3
  45. data/lib/solarwinds_apm/support/swomarginalia/comment.rb +18 -17
  46. data/lib/solarwinds_apm/support/swomarginalia/load_swomarginalia.rb +13 -12
  47. data/lib/solarwinds_apm/support/swomarginalia/railtie.rb +4 -2
  48. data/lib/solarwinds_apm/support/swomarginalia/swomarginalia.rb +3 -1
  49. data/lib/solarwinds_apm/support/transaction_cache.rb +6 -4
  50. data/lib/solarwinds_apm/support/transaction_settings.rb +7 -3
  51. data/lib/solarwinds_apm/support/txn_name_manager.rb +8 -3
  52. data/lib/solarwinds_apm/support/utils.rb +12 -9
  53. data/lib/solarwinds_apm/support/x_trace_options.rb +23 -17
  54. data/lib/solarwinds_apm/support.rb +28 -24
  55. data/lib/solarwinds_apm/version.rb +3 -1
  56. data/lib/solarwinds_apm.rb +44 -34
  57. metadata +14 -23
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ # © 2023 SolarWinds Worldwide, LLC. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8
+
9
+ module SolarWindsAPM
10
+ module OpenTelemetry
11
+ # reference: OpenTelemetry::SDK::Trace::SpanProcessor; inheritance: SolarWindsProcessor
12
+ class OTLPProcessor < SolarWindsProcessor
13
+ attr_accessor :description
14
+
15
+ def initialize
16
+ super(nil)
17
+ @meters = init_meters
18
+ @metrics = init_metrics
19
+ end
20
+
21
+ # @param [Span] span the {Span} that just started.
22
+ # @param [Context] parent_context the
23
+ # started span.
24
+ def on_start(span, parent_context)
25
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_start span: #{span.inspect}" }
26
+
27
+ return if non_entry_span(parent_context: parent_context)
28
+
29
+ span.add_attributes(span_attributes(span))
30
+ span.add_attributes({ 'sw.is_entry_span' => true })
31
+ rescue StandardError => e
32
+ SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] processor on_start error: #{e.message}" }
33
+ end
34
+
35
+ # @param [Span] span the {Span} that just ended.
36
+ def on_finish(span)
37
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish span: #{span.to_span_data.inspect}" }
38
+
39
+ # return if span is non-entry span
40
+ return if non_entry_span(span: span)
41
+
42
+ record_request_metrics(span)
43
+ record_sampling_metrics
44
+
45
+ ::OpenTelemetry.meter_provider.metric_readers.each(&:pull)
46
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish succeed" }
47
+ rescue StandardError => e
48
+ SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] error processing span on_finish: #{e.message}" }
49
+ end
50
+
51
+ private
52
+
53
+ # Create two meters for sampling and request count
54
+ def init_meters
55
+ {
56
+ 'sw.apm.sampling.metrics' => ::OpenTelemetry.meter_provider.meter('sw.apm.sampling.metrics'),
57
+ 'sw.apm.request.metrics' => ::OpenTelemetry.meter_provider.meter('sw.apm.request.metrics')
58
+ }
59
+ end
60
+
61
+ def span_attributes(span)
62
+ span_attrs = { 'sw.transaction' => calculate_lambda_transaction_name(span) }
63
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_attrs: #{span_attrs.inspect}" }
64
+ span_attrs
65
+ end
66
+
67
+ def meter_attributes(span)
68
+ meter_attrs = {
69
+ 'sw.is_error' => error?(span) == 1,
70
+ 'sw.transaction' => calculate_lambda_transaction_name(span)
71
+ }
72
+
73
+ http_status_code = get_http_status_code(span)
74
+ meter_attrs['http.status_code'] = http_status_code if http_status_code != 0
75
+ meter_attrs['http.method'] = span.attributes[HTTP_METHOD] if span.attributes[HTTP_METHOD]
76
+
77
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] meter_attrs: #{meter_attrs.inspect}" }
78
+ meter_attrs
79
+ end
80
+
81
+ def calculate_lambda_transaction_name(span)
82
+ (ENV['SW_APM_TRANSACTION_NAME'] || ENV['AWS_LAMBDA_FUNCTION_NAME'] || span.name || 'unknown').slice(0, 255)
83
+ end
84
+
85
+ def init_metrics
86
+ request_meter = @meters['sw.apm.request.metrics']
87
+ sampling_meter = @meters['sw.apm.sampling.metrics']
88
+
89
+ metrics = {}
90
+ metrics[:response_time] = request_meter.create_histogram('trace.service.response_time', unit: 'ms', description: 'measures the duration of an inbound HTTP request')
91
+ metrics[:tracecount] = sampling_meter.create_counter('trace.service.tracecount')
92
+ metrics[:samplecount] = sampling_meter.create_counter('trace.service.samplecount')
93
+ metrics[:request_count] = sampling_meter.create_counter('trace.service.request_count')
94
+ metrics[:toex_count] = sampling_meter.create_counter('trace.service.tokenbucket_exhaustion_count')
95
+ metrics[:through_count] = sampling_meter.create_counter('trace.service.through_trace_count')
96
+ metrics[:tt_count] = sampling_meter.create_counter('trace.service.triggered_trace_count')
97
+ metrics
98
+ end
99
+
100
+ def record_request_metrics(span)
101
+ meter_attrs = meter_attributes(span)
102
+ span_time = calculate_span_time(start_time: span.start_timestamp, end_time: span.end_timestamp)
103
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] entry span, response_time: #{span_time}." }
104
+ @metrics[:response_time].record(span_time, attributes: meter_attrs)
105
+ end
106
+
107
+ # oboe_api will return 0 in case of failed operation, and report 0 value
108
+ def record_sampling_metrics
109
+ _, trace_count = SolarWindsAPM.oboe_api.consumeTraceCount
110
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] trace_count: #{trace_count}" }
111
+ @metrics[:tracecount].add(trace_count)
112
+
113
+ _, sample_count = SolarWindsAPM.oboe_api.consumeSampleCount
114
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] sample_count: #{sample_count}" }
115
+ @metrics[:samplecount].add(sample_count)
116
+
117
+ _, request_count = SolarWindsAPM.oboe_api.consumeRequestCount
118
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] request_count: #{request_count}" }
119
+ @metrics[:request_count].add(request_count)
120
+
121
+ _, token_bucket_exhaustion_count = SolarWindsAPM.oboe_api.consumeTokenBucketExhaustionCount
122
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] tokenbucket_exhaustion_count: #{token_bucket_exhaustion_count}" }
123
+ @metrics[:toex_count].add(token_bucket_exhaustion_count)
124
+
125
+ _, through_trace_count = SolarWindsAPM.oboe_api.consumeThroughTraceCount
126
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] through_trace_count: #{through_trace_count}" }
127
+ @metrics[:through_count].add(through_trace_count)
128
+
129
+ _, triggered_trace_count = SolarWindsAPM.oboe_api.consumeTriggeredTraceCount
130
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] triggered_trace_count: #{triggered_trace_count}" }
131
+ @metrics[:tt_count].add(triggered_trace_count)
132
+ end
133
+ end
134
+ end
135
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # © 2023 SolarWinds Worldwide, LLC. All rights reserved.
2
4
  #
3
5
  # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
@@ -12,7 +14,7 @@ module SolarWindsAPM
12
14
  FAILURE = ::OpenTelemetry::SDK::Trace::Export::FAILURE
13
15
 
14
16
  private_constant(:SUCCESS, :FAILURE)
15
-
17
+
16
18
  def initialize(txn_manager: nil)
17
19
  @shutdown = false
18
20
  @txn_manager = txn_manager
@@ -22,7 +24,7 @@ module SolarWindsAPM
22
24
  @version_cache = {}
23
25
  end
24
26
 
25
- def export(span_data, _timeout: nil)
27
+ def export(span_data, timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
26
28
  return FAILURE if @shutdown
27
29
 
28
30
  status = SUCCESS
@@ -45,31 +47,36 @@ module SolarWindsAPM
45
47
  private
46
48
 
47
49
  def log_span_data(span_data)
48
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span_data: #{span_data.inspect}\n"}
50
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_data: #{span_data.inspect}\n" }
49
51
 
50
52
  md = build_meta_data(span_data)
51
53
  event = nil
52
- if span_data.parent_span_id != ::OpenTelemetry::Trace::INVALID_SPAN_ID
54
+ if span_data.parent_span_id == ::OpenTelemetry::Trace::INVALID_SPAN_ID
55
+ event = @context.createEntry(md, (span_data.start_timestamp.to_i / 1000).to_i)
56
+ add_info_transaction_name(span_data, event)
57
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Start a new trace." }
58
+ else
53
59
  parent_md = build_meta_data(span_data, parent: true)
54
60
  event = @context.createEntry(md, (span_data.start_timestamp.to_i / 1000).to_i, parent_md)
55
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Continue trace from parent metadata: #{parent_md.toString}."}
56
- else
57
- event = @context.createEntry(md, (span_data.start_timestamp.to_i / 1000).to_i)
58
- add_info_transaction_name(span_data, event)
59
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Start a new trace."}
61
+ SolarWindsAPM.logger.debug do
62
+ "[#{self.class}/#{__method__}] Continue trace from parent metadata: #{parent_md.toString}."
63
+ end
60
64
  end
61
-
65
+
62
66
  layer_name = "#{span_data.kind}:#{span_data.name}"
63
67
  event.addInfo('Layer', layer_name)
64
68
  event.addInfo('sw.span_kind', span_data.kind.to_s)
65
69
  event.addInfo('Language', 'Ruby')
66
-
70
+
67
71
  add_instrumentation_scope(event, span_data)
68
72
  add_instrumented_framework(event, span_data)
69
73
  add_span_data_attributes(event, span_data.attributes) if span_data.attributes
70
74
 
71
- event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_STATUS, span_data.status.ok?? 'OK' : 'ERROR')
72
- event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_STATUS_DESCRIPTION, span_data.status.description) unless span_data.status.description.empty?
75
+ event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_STATUS, span_data.status.ok? ? 'OK' : 'ERROR')
76
+ unless span_data.status.description.empty?
77
+ event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_STATUS_DESCRIPTION,
78
+ span_data.status.description)
79
+ end
73
80
 
74
81
  @reporter.send_report(event, with_system_timestamp: false)
75
82
 
@@ -81,10 +88,10 @@ module SolarWindsAPM
81
88
  event = @context.createExit((span_data.end_timestamp.to_i / 1000).to_i)
82
89
  event.addInfo('Layer', layer_name)
83
90
  @reporter.send_report(event, with_system_timestamp: false)
84
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Exit a trace: #{event.metadataString}"}
91
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Exit a trace: #{event.metadataString}" }
85
92
  SUCCESS
86
93
  rescue StandardError => e
87
- SolarWindsAPM.logger.info {"[#{self.class}/#{__method__}] exporter error: \n #{e.message} #{e.backtrace}\n"}
94
+ SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] exporter error: \n #{e.message} #{e.backtrace}\n" }
88
95
  FAILURE
89
96
  end
90
97
 
@@ -93,23 +100,23 @@ module SolarWindsAPM
93
100
  def add_span_data_attributes(event, span_attributes)
94
101
  target = 'http.target'
95
102
  attributes = span_attributes.dup
96
- attributes[target] = attributes[target].split('?').first if attributes[target] && SolarWindsAPM::Config[:log_args] == false # remove url parameters
103
+ attributes[target] = attributes[target].split('?').first if attributes[target] && SolarWindsAPM::Config[:log_args] == false
97
104
  attributes.each { |k, v| event.addInfo(k, v) }
98
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span_data attributes added: #{attributes.inspect}"}
105
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_data attributes added: #{attributes.inspect}" }
99
106
  end
100
107
 
101
108
  ##
102
109
  # get instrumentation scope data: scope name and version.
103
110
  # the version if the opentelemetry-instrumentation-* gem version
104
111
  def add_instrumentation_scope(event, span_data)
105
- scope_name = ""
106
- scope_version = ""
112
+ scope_name = ''
113
+ scope_version = ''
107
114
  if span_data.instrumentation_scope
108
115
  scope_name = span_data.instrumentation_scope.name if span_data.instrumentation_scope.name
109
116
  scope_version = span_data.instrumentation_scope.version if span_data.instrumentation_scope.version
110
117
  end
111
- event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_SCOPE_NAME, scope_name)
112
- event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_SCOPE_VERSION, scope_version)
118
+ event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_SCOPE_NAME, scope_name)
119
+ event.addInfo(SolarWindsAPM::Constants::INTL_SWO_OTEL_SCOPE_VERSION, scope_version)
113
120
  end
114
121
 
115
122
  ##
@@ -118,38 +125,46 @@ module SolarWindsAPM
118
125
  def add_instrumented_framework(event, span_data)
119
126
  scope_name = span_data.instrumentation_scope.name
120
127
  scope_name = scope_name.downcase if scope_name
121
- return unless scope_name&.include? "opentelemetry::instrumentation"
122
-
123
- framework = scope_name.split("::")[2..]&.join("::")
128
+ return unless scope_name&.include? 'opentelemetry::instrumentation'
129
+
130
+ framework = scope_name.split('::')[2..]&.join('::')
124
131
  return if framework.nil? || framework.empty?
125
-
132
+
126
133
  framework = normalize_framework_name(framework)
127
134
  framework_version = check_framework_version(framework)
128
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] #{span_data.instrumentation_scope.name} with #{framework} and version #{framework_version}"}
129
- event.addInfo("Ruby.#{framework}.Version",framework_version) unless framework_version.nil?
135
+ SolarWindsAPM.logger.debug do
136
+ "[#{self.class}/#{__method__}] #{span_data.instrumentation_scope.name} with #{framework} and version #{framework_version}"
137
+ end
138
+ event.addInfo("Ruby.#{framework}.Version", framework_version) unless framework_version.nil?
130
139
  end
131
140
 
132
141
  ##
133
142
  # helper function that extract gem library version for func add_instrumented_framework
134
143
  def check_framework_version(framework)
135
144
  framework_version = nil
136
- if @version_cache.has_key?(framework)
145
+ if @version_cache.key?(framework)
137
146
 
138
147
  framework_version = @version_cache[framework]
139
148
  else
140
149
 
141
150
  begin
142
151
  require framework
143
- framework_version = Gem.loaded_specs[framework].version.to_s
152
+ framework_version = Gem.loaded_specs[version_framework_name(framework)].version.to_s
144
153
  rescue LoadError => e
145
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] couldn't load #{framework} with error #{e.message}; skip"}
154
+ SolarWindsAPM.logger.debug do
155
+ "[#{self.class}/#{__method__}] couldn't load #{framework} with error #{e.message}; skip"
156
+ end
146
157
  rescue StandardError => e
147
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] couldn't find #{framework} with error #{e.message}; skip"}
158
+ SolarWindsAPM.logger.debug do
159
+ "[#{self.class}/#{__method__}] couldn't find #{framework} with error #{e.message}; skip"
160
+ end
148
161
  ensure
149
162
  @version_cache[framework] = framework_version
150
163
  end
151
164
  end
152
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] current framework version cached: #{@version_cache.inspect}"}
165
+ SolarWindsAPM.logger.debug do
166
+ "[#{self.class}/#{__method__}] current framework version cached: #{@version_cache.inspect}"
167
+ end
153
168
  framework_version
154
169
  end
155
170
 
@@ -157,21 +172,31 @@ module SolarWindsAPM
157
172
  # helper function that convert opentelemetry instrumentation name to gem library understandable
158
173
  def normalize_framework_name(framework)
159
174
  case framework
160
- when "net::http"
161
- normalized = "net/http"
175
+ when 'net::http'
176
+ 'net/http'
177
+ else
178
+ framework
179
+ end
180
+ end
181
+
182
+ ##
183
+ # helper function that convert opentelemetry instrumentation name to gem library understandable
184
+ def version_framework_name(framework)
185
+ case framework
186
+ when 'net/http'
187
+ 'net-http'
162
188
  else
163
- normalized = framework
189
+ framework
164
190
  end
165
- normalized
166
191
  end
167
192
 
168
193
  ##
169
194
  # Add transaction name from cache to root span then removes from cache
170
195
  def add_info_transaction_name(span_data, event)
171
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] transaction manager: #{@txn_manager.inspect}."}
196
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] transaction manager: #{@txn_manager.inspect}." }
172
197
  trace_span_id = "#{span_data.hex_trace_id}-#{span_data.hex_span_id}"
173
198
  txname = @txn_manager.get(trace_span_id) || ''
174
- event.addInfo("TransactionName", txname)
199
+ event.addInfo('TransactionName', txname)
175
200
  @txn_manager.del(trace_span_id)
176
201
  end
177
202
 
@@ -190,7 +215,7 @@ module SolarWindsAPM
190
215
  attributes.each { |key, value| event.addInfo(key, value) }
191
216
  end
192
217
 
193
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] exception event #{event.metadataString}"}
218
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] exception event #{event.metadataString}" }
194
219
  @reporter.send_report(event, with_system_timestamp: false)
195
220
  end
196
221
 
@@ -200,12 +225,12 @@ module SolarWindsAPM
200
225
  event = @context.createEvent((span_event.timestamp.to_i / 1000).to_i)
201
226
  event.addInfo('Label', 'info')
202
227
  span_event.attributes&.each { |key, value| event.addInfo(key, value) }
203
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] info event #{event.metadataString}"}
228
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] info event #{event.metadataString}" }
204
229
  @reporter.send_report(event, with_system_timestamp: false)
205
230
  end
206
231
 
207
232
  def build_meta_data(span_data, parent: false)
208
- flag = span_data.trace_flags.sampled?? 1 : 0
233
+ flag = span_data.trace_flags.sampled? ? 1 : 0
209
234
  xtr = parent == false ? "00-#{span_data.hex_trace_id}-#{span_data.hex_span_id}-0#{flag}" : "00-#{span_data.hex_trace_id}-#{span_data.hex_parent_span_id}-0#{flag}"
210
235
  @metadata.fromString(xtr)
211
236
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # © 2023 SolarWinds Worldwide, LLC. All rights reserved.
2
4
  #
3
5
  # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
@@ -8,56 +10,46 @@ module SolarWindsAPM
8
10
  module OpenTelemetry
9
11
  # reference: OpenTelemetry::SDK::Trace::SpanProcessor
10
12
  class SolarWindsProcessor
11
- HTTP_METHOD = "http.method".freeze
12
- HTTP_ROUTE = "http.route".freeze
13
- HTTP_STATUS_CODE = "http.status_code".freeze
14
- HTTP_URL = "http.url".freeze
13
+ HTTP_METHOD = 'http.method'
14
+ HTTP_ROUTE = 'http.route'
15
+ HTTP_STATUS_CODE = 'http.status_code'
16
+ HTTP_URL = 'http.url'
15
17
  LIBOBOE_HTTP_SPAN_STATUS_UNAVAILABLE = 0
16
18
 
17
19
  attr_reader :txn_manager
18
20
 
19
- def initialize(exporter, txn_manager)
20
- @exporter = exporter
21
+ def initialize(txn_manager)
21
22
  @txn_manager = txn_manager
22
23
  end
23
24
 
24
25
  # Called when a {Span} is started, if the {Span#recording?}
25
26
  # returns true.
26
27
  #
27
- # This method is called synchronously on the execution thread, should
28
- # not throw or block the execution thread.
29
- #
30
28
  # @param [Span] span the {Span} that just started.
31
29
  # @param [Context] parent_context the parent {Context} of the newly
32
30
  # started span.
33
31
  def on_start(span, parent_context)
32
+ SolarWindsAPM.logger.debug do
33
+ "[#{self.class}/#{__method__}] processor on_start span: #{span.inspect}, parent_context: #{parent_context.inspect}"
34
+ end
34
35
 
35
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] processor on_start span: #{span.inspect}, parent_context: #{parent_context.inspect}"}
36
-
37
- parent_span = ::OpenTelemetry::Trace.current_span(parent_context)
38
- return if parent_span && parent_span.context != ::OpenTelemetry::Trace::SpanContext::INVALID && parent_span.context.remote? == false
36
+ return if non_entry_span(parent_context: parent_context)
39
37
 
40
38
  trace_flags = span.context.trace_flags.sampled? ? '01' : '00'
41
- @txn_manager.set_root_context_h(span.context.hex_trace_id,"#{span.context.hex_span_id}-#{trace_flags}")
39
+ @txn_manager.set_root_context_h(span.context.hex_trace_id, "#{span.context.hex_span_id}-#{trace_flags}")
40
+ span.add_attributes({ 'sw.is_entry_span' => true })
42
41
  rescue StandardError => e
43
- SolarWindsAPM.logger.info {"[#{self.class}/#{__method__}] processor on_start error: #{e.message}"}
42
+ SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] processor on_start error: #{e.message}" }
44
43
  end
45
44
 
46
45
  # Called when a {Span} is ended, if the {Span#recording?}
47
46
  # returns true.
48
47
  #
49
- # This method is called synchronously on the execution thread, should
50
- # not throw or block the execution thread.
51
- # Only calculate inbound metrics for service root spans
52
- #
53
48
  # @param [Span] span the {Span} that just ended.
54
49
  def on_finish(span)
55
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] processor on_finish span: #{span.inspect}"}
50
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish span: #{span.inspect}" }
56
51
 
57
- if span.parent_span_id != ::OpenTelemetry::Trace::INVALID_SPAN_ID
58
- @exporter&.export([span.to_span_data]) if span.context.trace_flags.sampled?
59
- return
60
- end
52
+ return if non_entry_span(span: span)
61
53
 
62
54
  span_time = calculate_span_time(start_time: span.start_timestamp, end_time: span.end_timestamp)
63
55
  domain = nil
@@ -68,7 +60,7 @@ module SolarWindsAPM
68
60
  request_method = span.attributes[HTTP_METHOD]
69
61
  url_tran = span.attributes[HTTP_URL]
70
62
 
71
- SolarWindsAPM.logger.debug do
63
+ SolarWindsAPM.logger.debug do
72
64
  "[#{self.class}/#{__method__}] createHttpSpan with\n
73
65
  trans_name: #{trans_name}\n
74
66
  url_tran: #{url_tran}\n
@@ -79,11 +71,12 @@ module SolarWindsAPM
79
71
  has_error: #{has_error}"
80
72
  end
81
73
 
82
- liboboe_txn_name = SolarWindsAPM::Span.createHttpSpan(trans_name,url_tran,domain,span_time,status_code,request_method,has_error)
83
-
74
+ liboboe_txn_name = SolarWindsAPM::Span.createHttpSpan(trans_name, url_tran, domain, span_time, status_code,
75
+ request_method, has_error)
76
+
84
77
  else
85
-
86
- SolarWindsAPM.logger.debug do
78
+
79
+ SolarWindsAPM.logger.debug do
87
80
  "[#{self.class}/#{__method__}] createSpan with \n
88
81
  trans_name: #{trans_name}\n
89
82
  domain: #{domain}\n
@@ -94,37 +87,30 @@ module SolarWindsAPM
94
87
  liboboe_txn_name = SolarWindsAPM::Span.createSpan(trans_name, domain, span_time, has_error)
95
88
  end
96
89
 
97
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] liboboe_txn_name: #{liboboe_txn_name}"}
98
- @txn_manager["#{span.context.hex_trace_id}-#{span.context.hex_span_id}"] = liboboe_txn_name if span.context.trace_flags.sampled?
90
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] liboboe_txn_name: #{liboboe_txn_name}" }
91
+ if span.context.trace_flags.sampled?
92
+ @txn_manager["#{span.context.hex_trace_id}-#{span.context.hex_span_id}"] =
93
+ liboboe_txn_name
94
+ end
99
95
  @txn_manager.delete_root_context_h(span.context.hex_trace_id)
100
- @exporter&.export([span.to_span_data]) if span.context.trace_flags.sampled?
101
96
  rescue StandardError => e
102
- SolarWindsAPM.logger.info {"[#{self.class}/#{__method__}] can't flush span to exporter; processor on_finish error: #{e.message}"}
103
- ::OpenTelemetry::SDK::Trace::Export::FAILURE
97
+ SolarWindsAPM.logger.info do
98
+ "[#{self.class}/#{__method__}] solarwinds_processor on_finish error: #{e.message}"
99
+ end
104
100
  end
105
101
 
106
- # Export all ended spans to the configured `Exporter` that have not yet
107
- # been exported.
108
- #
109
- # This method should only be called in cases where it is absolutely
110
- # necessary, such as when using some FaaS providers that may suspend
111
- # the process after an invocation, but before the `Processor` exports
112
- # the completed spans.
113
- #
114
102
  # @param [optional Numeric] timeout An optional timeout in seconds.
115
103
  # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
116
104
  # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
117
- def force_flush(timeout: nil)
118
- @exporter&.force_flush(timeout: timeout) || ::OpenTelemetry::SDK::Trace::Export::SUCCESS
105
+ def force_flush(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
106
+ ::OpenTelemetry::SDK::Trace::Export::SUCCESS
119
107
  end
120
108
 
121
- # Called when {TracerProvider#shutdown} is called.
122
- #
123
109
  # @param [optional Numeric] timeout An optional timeout in seconds.
124
110
  # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
125
111
  # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
126
- def shutdown(timeout: nil)
127
- @exporter&.shutdown(timeout: timeout) || ::OpenTelemetry::SDK::Trace::Export::SUCCESS
112
+ def shutdown(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
113
+ ::OpenTelemetry::SDK::Trace::Export::SUCCESS
128
114
  end
129
115
 
130
116
  private
@@ -147,16 +133,28 @@ module SolarWindsAPM
147
133
  span.attributes[HTTP_STATUS_CODE] || LIBOBOE_HTTP_SPAN_STATUS_UNAVAILABLE
148
134
  end
149
135
 
136
+ # check if it's entry span based on no parent or parent is remote
137
+ def non_entry_span(span: nil, parent_context: nil)
138
+ if parent_context
139
+ parent_span = ::OpenTelemetry::Trace.current_span(parent_context)
140
+ parent_span && parent_span.context != ::OpenTelemetry::Trace::SpanContext::INVALID && parent_span.context.remote? == false
141
+ elsif span
142
+ span.attributes['sw.is_entry_span'] != true
143
+ end
144
+ end
145
+
150
146
  # Get trans_name and url_tran of this span instance.
151
147
  # Predecessor order: custom SDK > env var SW_APM_TRANSACTION_NAME > automatic naming
152
148
  def calculate_transaction_names(span)
153
149
  trace_span_id = "#{span.context.hex_trace_id}-#{span.context.hex_span_id}"
154
150
  trans_name = @txn_manager.get(trace_span_id)
155
151
  if trans_name
156
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] found trans name from txn_manager: #{trans_name} by #{trace_span_id}"}
152
+ SolarWindsAPM.logger.debug do
153
+ "[#{self.class}/#{__method__}] found trans name from txn_manager: #{trans_name} by #{trace_span_id}"
154
+ end
157
155
  @txn_manager.del(trace_span_id)
158
- elsif ENV.has_key?('SW_APM_TRANSACTION_NAME') && ENV['SW_APM_TRANSACTION_NAME'] != ''
159
- trans_name = ENV['SW_APM_TRANSACTION_NAME']
156
+ elsif ENV.key?('SW_APM_TRANSACTION_NAME') && ENV['SW_APM_TRANSACTION_NAME'] != ''
157
+ trans_name = ENV.fetch('SW_APM_TRANSACTION_NAME', nil)
160
158
  else
161
159
  trans_name = span.attributes[HTTP_ROUTE] || nil
162
160
  trans_name = span.name if span.name && (trans_name.nil? || trans_name.empty?)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # © 2023 SolarWinds Worldwide, LLC. All rights reserved.
2
4
  #
3
5
  # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
@@ -10,14 +12,14 @@ module SolarWindsAPM
10
12
  # TextMapPropagator
11
13
  # propagator error will be rescued by OpenTelemetry::Context::Propagation::TextMapPropagator
12
14
  class TextMapPropagator
13
- TRACESTATE_HEADER_NAME = "tracestate".freeze
14
- XTRACEOPTIONS_HEADER_NAME = "x-trace-options".freeze
15
- XTRACEOPTIONS_SIGNATURE_HEADER_NAME = "x-trace-options-signature".freeze
16
- INTL_SWO_X_OPTIONS_KEY = "sw_xtraceoptions".freeze
17
- INTL_SWO_SIGNATURE_KEY = "sw_signature".freeze
15
+ TRACESTATE_HEADER_NAME = 'tracestate'
16
+ XTRACEOPTIONS_HEADER_NAME = 'x-trace-options'
17
+ XTRACEOPTIONS_SIGNATURE_HEADER_NAME = 'x-trace-options-signature'
18
+ INTL_SWO_X_OPTIONS_KEY = 'sw_xtraceoptions'
19
+ INTL_SWO_SIGNATURE_KEY = 'sw_signature'
18
20
 
19
21
  private_constant \
20
- :TRACESTATE_HEADER_NAME, :XTRACEOPTIONS_HEADER_NAME,
22
+ :TRACESTATE_HEADER_NAME, :XTRACEOPTIONS_HEADER_NAME,
21
23
  :XTRACEOPTIONS_SIGNATURE_HEADER_NAME, :INTL_SWO_X_OPTIONS_KEY, :INTL_SWO_SIGNATURE_KEY
22
24
 
23
25
  # Extract trace context from the supplied carrier.
@@ -31,11 +33,11 @@ module SolarWindsAPM
31
33
  #
32
34
  # @return [Context] context updated with extracted baggage, or the original context
33
35
  # if extraction fails
34
- def extract(carrier, context: ::OpenTelemetry::Context.current, getter: ::OpenTelemetry::Context::Propagation.text_map_getter)
35
-
36
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] extract context: #{context.inspect}"}
36
+ def extract(carrier, context: ::OpenTelemetry::Context.current,
37
+ getter: ::OpenTelemetry::Context::Propagation.text_map_getter)
38
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] extract context: #{context.inspect}" }
37
39
 
38
- context = context.nil?? ::OpenTelemetry::Context.new({}) : context
40
+ context = ::OpenTelemetry::Context.new({}) if context.nil?
39
41
  context = inject_extracted_header(carrier, context, getter, XTRACEOPTIONS_HEADER_NAME, INTL_SWO_X_OPTIONS_KEY)
40
42
  inject_extracted_header(carrier, context, getter, XTRACEOPTIONS_SIGNATURE_HEADER_NAME, INTL_SWO_SIGNATURE_KEY)
41
43
  end
@@ -47,31 +49,37 @@ module SolarWindsAPM
47
49
  # @param [optional Setter] setter If the optional setter is provided, it
48
50
  # will be used to write context into the carrier, otherwise the default
49
51
  # text map setter will be used.
50
- def inject(carrier, context: ::OpenTelemetry::Context.current, setter: ::OpenTelemetry::Context::Propagation.text_map_setter)
52
+ def inject(carrier, context: ::OpenTelemetry::Context.current,
53
+ setter: ::OpenTelemetry::Context::Propagation.text_map_setter)
54
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] inject context: #{context.inspect}" }
51
55
 
52
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] inject context: #{context.inspect}"}
53
-
54
56
  span_context = ::OpenTelemetry::Trace.current_span(context)&.context
55
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span_context #{span_context.inspect}"}
57
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_context #{span_context.inspect}" }
56
58
  return unless span_context&.valid?
57
59
 
58
- trace_flag = span_context.trace_flags.sampled?? 1 : 0
60
+ trace_flag = span_context.trace_flags.sampled? ? 1 : 0
59
61
  sw_value = "#{span_context.hex_span_id}-0#{trace_flag}"
60
- trace_state_header = carrier[TRACESTATE_HEADER_NAME].nil?? nil : carrier[TRACESTATE_HEADER_NAME]
61
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] sw_value: #{sw_value}; trace_state_header: #{trace_state_header}"}
62
+ trace_state_header = carrier[TRACESTATE_HEADER_NAME].nil? ? nil : carrier[TRACESTATE_HEADER_NAME]
63
+ SolarWindsAPM.logger.debug do
64
+ "[#{self.class}/#{__method__}] sw_value: #{sw_value}; trace_state_header: #{trace_state_header}"
65
+ end
62
66
 
63
67
  # prepare carrier with carrier's or new tracestate
64
68
  if trace_state_header.nil?
65
69
  # only create new trace state if valid span_id
66
70
  unless span_context.span_id == ::OpenTelemetry::Trace::INVALID_SPAN_ID
67
- trace_state = ::OpenTelemetry::Trace::Tracestate.create({SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY => sw_value})
68
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] creating new trace state: #{trace_state.inspect}"}
71
+ trace_state = ::OpenTelemetry::Trace::Tracestate.create({ SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY => sw_value })
72
+ SolarWindsAPM.logger.debug do
73
+ "[#{self.class}/#{__method__}] creating new trace state: #{trace_state.inspect}"
74
+ end
69
75
  setter.set(carrier, TRACESTATE_HEADER_NAME, Utils.trace_state_header(trace_state))
70
76
  end
71
77
  else
72
78
  trace_state_from_string = ::OpenTelemetry::Trace::Tracestate.from_string(trace_state_header)
73
79
  trace_state = trace_state_from_string.set_value(SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY, sw_value)
74
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] updating/adding trace state for injection #{trace_state.inspect}"}
80
+ SolarWindsAPM.logger.debug do
81
+ "[#{self.class}/#{__method__}] updating/adding trace state for injection #{trace_state.inspect}"
82
+ end
75
83
  setter.set(carrier, TRACESTATE_HEADER_NAME, Utils.trace_state_header(trace_state))
76
84
  end
77
85
  end
@@ -85,11 +93,11 @@ module SolarWindsAPM
85
93
  end
86
94
 
87
95
  private
88
-
96
+
89
97
  def inject_extracted_header(carrier, context, getter, header, inject_key)
90
98
  extracted_header = getter.get(carrier, header)
91
99
  context = context.set_value(inject_key, extracted_header) if extracted_header
92
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] #{header}: #{inject_key} = #{extracted_header}"}
100
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] #{header}: #{inject_key} = #{extracted_header}" }
93
101
  context
94
102
  end
95
103
  end