solarwinds_apm 6.0.0.preV1 → 6.0.0.preV3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +28 -54
  3. data/ext/oboe_metal/extconf.rb +23 -23
  4. data/ext/oboe_metal/lib/liboboe-1.0-aarch64.so.sha256 +1 -1
  5. data/ext/oboe_metal/lib/liboboe-1.0-alpine-aarch64.so.sha256 +1 -1
  6. data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.sha256 +1 -1
  7. data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.sha256 +1 -1
  8. data/ext/oboe_metal/src/VERSION +1 -2
  9. data/ext/oboe_metal/src/init_solarwinds_apm.cc +0 -6
  10. data/ext/oboe_metal/src/oboe.h +0 -12
  11. data/ext/oboe_metal/src/oboe_swig_wrap.cc +5 -5
  12. data/lib/oboe_metal.rb +3 -3
  13. data/lib/rails/generators/solarwinds_apm/install_generator.rb +0 -3
  14. data/lib/rails/generators/solarwinds_apm/templates/solarwinds_apm_initializer.rb +8 -25
  15. data/lib/solarwinds_apm/api/current_trace_info.rb +1 -2
  16. data/lib/solarwinds_apm/api/opentelemetry.rb +39 -0
  17. data/lib/solarwinds_apm/api/transaction_name.rb +18 -17
  18. data/lib/solarwinds_apm/api.rb +2 -0
  19. data/lib/solarwinds_apm/config.rb +21 -63
  20. data/lib/solarwinds_apm/constants.rb +2 -4
  21. data/lib/solarwinds_apm/noop/context.rb +1 -1
  22. data/lib/solarwinds_apm/oboe_init_options.rb +11 -16
  23. data/lib/solarwinds_apm/opentelemetry/solarwinds_exporter.rb +92 -87
  24. data/lib/solarwinds_apm/opentelemetry/solarwinds_processor.rb +27 -23
  25. data/lib/solarwinds_apm/opentelemetry/solarwinds_propagator.rb +28 -28
  26. data/lib/solarwinds_apm/opentelemetry/solarwinds_response_propagator.rb +9 -14
  27. data/lib/solarwinds_apm/opentelemetry/solarwinds_sampler.rb +132 -175
  28. data/lib/solarwinds_apm/otel_config.rb +55 -36
  29. data/lib/solarwinds_apm/support/lumberjack_formatter.rb +17 -2
  30. data/lib/solarwinds_apm/support/swomarginalia/comment.rb +2 -2
  31. data/lib/solarwinds_apm/support/swomarginalia/swomarginalia.rb +6 -5
  32. data/lib/solarwinds_apm/support/transaction_cache.rb +27 -2
  33. data/lib/solarwinds_apm/support/transaction_settings.rb +10 -10
  34. data/lib/solarwinds_apm/support/txn_name_manager.rb +34 -17
  35. data/lib/solarwinds_apm/support/utils.rb +24 -0
  36. data/lib/solarwinds_apm/support.rb +4 -2
  37. data/lib/solarwinds_apm/version.rb +1 -1
  38. data/lib/solarwinds_apm.rb +3 -4
  39. metadata +18 -33
  40. data/lib/oboe.rb +0 -7
  41. data/lib/solarwinds_apm/noop/profiling.rb +0 -17
  42. data/lib/solarwinds_apm/support/transformer.rb +0 -56
@@ -37,39 +37,36 @@ module SolarWindsAPM
37
37
  # parent_context: OpenTelemetry::Context
38
38
  def should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:)
39
39
 
40
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] should_sample? parameters \n
40
+ SolarWindsAPM.logger.debug do
41
+ "[#{self.class}/#{__method__}] should_sample? start parameters \n
41
42
  trace_id: #{trace_id.unpack1('H*')}\n
42
43
  parent_context: #{parent_context}\n
43
44
  parent_context.inspect: #{parent_context.inspect}\n
44
45
  links: #{links}\n
45
46
  name: #{name}\n
46
47
  kind: #{kind}\n
47
- attributes: #{attributes}"}
48
+ attributes: #{attributes}"
49
+ end
48
50
 
49
- # if the upstream has tracestate: sw=.... then it should capture it
51
+ parent_span_context = ::OpenTelemetry::Trace.current_span(parent_context).context
52
+ xtraceoptions = ::SolarWindsAPM::XTraceOptions.new(parent_context)
53
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] parent_span_context: #{parent_span_context.inspect}\n xtraceoptions: #{xtraceoptions.inspect}"}
50
54
 
51
- parent_span_context = ::OpenTelemetry::Trace.current_span(parent_context).context
52
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] should_sample? parent_span_context: #{parent_span_context.inspect}"}
53
-
54
- xtraceoptions = SolarWindsAPM::XTraceOptions.new(parent_context)
55
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] xtraceoptions: #{xtraceoptions.inspect}"}
56
-
57
55
  liboboe_decision = calculate_liboboe_decision(parent_span_context, xtraceoptions, name, kind, attributes)
58
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] liboboe_decision: #{liboboe_decision.inspect}"}
59
-
60
- otel_decision = otel_decision_from_liboboe(liboboe_decision)
61
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] otel_decision: #{otel_decision.inspect}"}
62
-
63
- new_trace_state = calculate_trace_state(liboboe_decision,parent_span_context,xtraceoptions)
64
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] new_trace_state: #{new_trace_state.inspect}"}
65
-
66
- new_attributes = calculate_attributes(attributes,liboboe_decision,new_trace_state,parent_span_context,xtraceoptions)
67
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] new_attributes: #{new_attributes.inspect}"}
56
+ otel_decision = otel_decision_from_liboboe(liboboe_decision)
57
+ new_trace_state = calculate_trace_state(liboboe_decision, parent_span_context, xtraceoptions)
58
+ new_attributes = otel_sampled?(otel_decision)? calculate_attributes(attributes, liboboe_decision, new_trace_state, parent_span_context, xtraceoptions) : nil
59
+ sampling_result = ::OpenTelemetry::SDK::Trace::Samplers::Result.new(decision: otel_decision,
60
+ attributes: new_attributes,
61
+ tracestate: new_trace_state)
68
62
 
69
- sampling_result = ::OpenTelemetry::SDK::Trace::Samplers::Result.new(decision: otel_decision, attributes: new_attributes, tracestate: new_trace_state)
70
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] sampling_result: #{sampling_result.inspect}"}
71
-
63
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] should_sample? end with sampling_result: #{sampling_result.inspect} from otel_decision: #{otel_decision.inspect} and new_attributes: #{new_attributes.inspect}"}
72
64
  sampling_result
65
+ rescue StandardError => e
66
+ SolarWindsAPM.logger.info {"[#{self.class}/#{__method__}] sampler error: #{e.message}"}
67
+ ::OpenTelemetry::SDK::Trace::Samplers::Result.new(decision: ::OpenTelemetry::SDK::Trace::Samplers::Decision::DROP,
68
+ attributes: attributes,
69
+ tracestate: ::OpenTelemetry::Trace::Tracestate::DEFAULT)
73
70
  end
74
71
 
75
72
  protected
@@ -78,49 +75,36 @@ module SolarWindsAPM
78
75
 
79
76
  private
80
77
 
81
- # return Hash
78
+ ##
79
+ # use parent_span_context and xtraceoptions object to feed to liboboe function getDecisions that get liboboe_decision
80
+ # name, kind and attributes are used for transaction filter caching (to avoid continous calculate_trace_mode calculation)
81
+ # return decision Hash
82
82
  def calculate_liboboe_decision(parent_span_context, xtraceoptions, name, kind, attributes)
83
+ tracestring = Utils.traceparent_from_context(parent_span_context) if parent_span_context.valid? && parent_span_context.remote?
84
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] tracestring: #{tracestring}"}
83
85
 
84
- tracestring = nil
85
- if parent_span_context.valid? && parent_span_context.remote?
86
- tracestring = Transformer.traceparent_from_context(parent_span_context)
87
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] calculate_liboboe_decision parent_span_context.remote? #{parent_span_context.remote?} with #{tracestring}"}
88
- end
89
-
90
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] name: #{name}, kind: #{kind}, attributes: #{attributes.inspect}"}
91
-
92
- url = attributes.nil?? '' : attributes['http.host'] || attributes['http.url'] || attributes['net.peer.name'] # otel-ruby contrib use different key to store url info
93
- transaction_naming_key = "#{url}-#{name}-#{kind}"
94
-
86
+ # otel-ruby contrib use different key to store url info, currently it's using http.target for path
87
+ url_path = attributes.nil?? '' : attributes['http.target']
88
+ transaction_naming_key = "#{url_path}-#{name}-#{kind}"
95
89
  tracing_mode = SolarWindsAPM::TransactionCache.get(transaction_naming_key)
96
-
97
- if tracing_mode.nil?
98
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] transaction cache NOT found: #{transaction_naming_key}."}
99
- trans_settings = SolarWindsAPM::TransactionSettings.new(url: url, name: name, kind: kind)
90
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] transaction cache: #{transaction_naming_key}; tracing_mode: #{tracing_mode}."}
91
+
92
+ unless tracing_mode
93
+ trans_settings = SolarWindsAPM::TransactionSettings.new(url_path: url_path, name: name, kind: kind)
100
94
  tracing_mode = trans_settings.calculate_trace_mode == 1 ? SWO_TRACING_ENABLED : SWO_TRACING_DISABLED
101
95
  SolarWindsAPM::TransactionCache.set(transaction_naming_key, tracing_mode)
102
- else
103
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] transaction cache found: #{transaction_naming_key}."}
104
96
  end
105
97
 
106
98
  sw_member_value = parent_span_context.tracestate[SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY]
107
-
108
- # need to create the config class
109
99
  trigger_trace_mode = OboeTracingMode.get_oboe_trigger_trace_mode(@config["trigger_trace"])
110
100
  sample_rate = UNSET
101
+ options = xtraceoptions&.options
102
+ trigger_trace = xtraceoptions&.intify_trigger_trace || 0
103
+ signature = xtraceoptions&.signature
104
+ timestamp = xtraceoptions&.timestamp
111
105
 
112
- options = nil
113
- trigger_trace = 0
114
- signature = nil
115
- timestamp = nil
116
- if xtraceoptions
117
- options = xtraceoptions.options
118
- trigger_trace = xtraceoptions.intify_trigger_trace
119
- signature = xtraceoptions.signature
120
- timestamp = xtraceoptions.timestamp
121
- end
122
-
123
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] decision parameters \n
106
+ SolarWindsAPM.logger.debug do
107
+ "[#{self.class}/#{__method__}] get liboboe decision parameters: \n
124
108
  tracestring: #{tracestring}\n
125
109
  sw_member_value: #{sw_member_value}\n
126
110
  tracing_mode: #{tracing_mode}\n
@@ -129,11 +113,15 @@ module SolarWindsAPM
129
113
  trigger_trace_mode: #{trigger_trace_mode}\n
130
114
  options: #{options}\n
131
115
  signature: #{signature}\n
132
- timestamp: #{timestamp}"}
116
+ timestamp: #{timestamp}"
117
+ end
118
+
119
+ args = [tracestring, sw_member_value, tracing_mode, sample_rate,
120
+ trigger_trace, trigger_trace_mode, options, signature,timestamp]
133
121
 
134
- args = [tracestring,sw_member_value,tracing_mode,sample_rate,trigger_trace,trigger_trace_mode,options,signature,timestamp]
135
- do_metrics, do_sample, rate, source, bucket_rate, \
136
- bucket_cap, decision_type, auth, status_msg, auth_msg, status = SolarWindsAPM::Context.getDecisions(*args)
122
+ do_metrics, do_sample, rate, source, bucket_rate,
123
+ bucket_cap, decision_type, auth, status_msg, auth_msg,
124
+ status = SolarWindsAPM::Context.getDecisions(*args)
137
125
 
138
126
  decision = {}
139
127
  decision["do_metrics"] = do_metrics > 0
@@ -158,136 +146,78 @@ module SolarWindsAPM
158
146
  elsif liboboe_decision["do_metrics"]
159
147
  decision = ::OpenTelemetry::SDK::Trace::Samplers::Decision::RECORD_ONLY
160
148
  end
161
- SolarWindsAPM.logger.debug {"OTel decision created: #{decision}"}
149
+ SolarWindsAPM.logger.debug {"otel decision: #{decision} created from liboboe_decision: #{liboboe_decision}"}
162
150
  decision
163
151
  end
164
152
 
165
- def create_xtraceoptions_response_value(decision, parent_span_context, xtraceoptions)
166
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] create_xtraceoptions_response_value decision[auth]: #{decision['auth']}; decision[auth_msg]: #{decision['auth_msg']}; xtraceoptions.trigger_trace: #{xtraceoptions.trigger_trace}"}
167
-
168
- response = []
169
- w3c_sanitized = SolarWindsAPM::Constants::INTL_SWO_EQUALS_W3C_SANITIZED
170
- response << [XTRACEOPTIONS_RESP_AUTH, decision['auth_msg']].join(w3c_sanitized) if xtraceoptions.signature && decision['auth_msg']
171
- if !decision["auth"] || decision["auth"] < 1
172
- trigger_msg = ""
173
- tracestring = nil
174
- if xtraceoptions.trigger_trace
175
- # If a traceparent header was provided then oboe does not generate the message
176
- tracestring = Transformer.traceparent_from_context(parent_span_context) if parent_span_context.valid? && parent_span_context.remote?
177
- trigger_msg = tracestring && decision['decision_type'] == 0 ? XTRACEOPTIONS_RESP_TRIGGER_IGNORED : decision['status_msg']
178
- else
179
- trigger_msg = XTRACEOPTIONS_RESP_TRIGGER_NOT_REQUESTED
180
- end
181
-
182
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] create_xtraceoptions_response_value parent_span_context: #{parent_span_context}; tracestring: #{tracestring}; trigger_msg: #{trigger_msg}"}
183
-
184
- # e.g. response << trigger-trace####ok
185
- response << [XTRACEOPTIONS_RESP_TRIGGER_TRACE, trigger_msg].join(w3c_sanitized)
186
-
187
- end
188
-
189
- # so far the x-trace-options are only used for liboboe calculate decision for x-trace feature
190
- # probably not need for remaining services since liboboe decision only calculate once
191
- unless xtraceoptions.ignored.empty?
192
- ignored_response = [XTRACEOPTIONS_RESP_IGNORED, xtraceoptions.ignored.join(SolarWindsAPM::Constants::INTL_SWO_COMMA_W3C_SANITIZED)]
193
- response << ignored_response.join(w3c_sanitized)
194
- # e.g. response << ignored####invalidkeys,invalidkeys,invalidkeys
195
- end
196
-
197
- response.join(';') # e.g. trigger-trace####ok;ignored####invalidkeys,invalidkeys,invalidkeys
198
- end
199
-
200
- def create_new_trace_state(parent_span_context, decision)
201
- decision = sw_from_span_and_decision(parent_span_context, decision)
202
- trace_state = ::OpenTelemetry::Trace::Tracestate.from_hash({SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY => decision}) # e.g. sw=3e222c863a04123a-01
203
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Created new trace_state: #{trace_state.inspect}"}
204
- trace_state
205
- end
206
-
207
- #
208
- # calculate_trace_state
209
- # This function merely just add sw=value and xtrace_options_response=value into the old/new tracestate
210
- # The return value tracestate will be used in propagating to next services
211
- #
212
- def calculate_trace_state(decision, parent_span_context, xtraceoptions)
213
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] calculate_trace_state parent_span_context: #{parent_span_context.inspect}"}
153
+ ##
154
+ # add sw=value and xtrace_options_response=value into the old/new tracestate
155
+ # the returned value tracestate will be used in propagating to next services
156
+ def calculate_trace_state(liboboe_decision, parent_span_context, xtraceoptions)
214
157
  if !parent_span_context.valid?
215
-
216
- trace_state = create_new_trace_state(parent_span_context, decision)
158
+ trace_state = create_new_trace_state(parent_span_context, liboboe_decision)
159
+ elsif parent_span_context.tracestate.nil?
160
+ trace_state = create_new_trace_state(parent_span_context, liboboe_decision)
217
161
  else
218
-
219
- parent_trace_state = parent_span_context.tracestate
220
- if parent_trace_state.nil?
221
- trace_state = create_new_trace_state(parent_span_context, decision)
222
- else
223
- trace_state = parent_trace_state.set_value(SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY, sw_from_span_and_decision(parent_span_context, decision))
224
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Updated trace_state with span_id and sw trace_flags: #{trace_state.inspect}"}
225
- end
162
+ trace_state = parent_span_context.tracestate.set_value(SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY,
163
+ sw_from_span_and_decision(parent_span_context, liboboe_decision))
164
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] updated trace_state: #{trace_state.inspect}"}
226
165
  end
227
166
 
228
167
  # for setting up the xtrace_options_response
229
168
  if xtraceoptions&.options
230
- trace_state = trace_state.set_value(
231
- XTraceOptions.sw_xtraceoptions_response_key.to_s,
232
- create_xtraceoptions_response_value(decision,parent_span_context,xtraceoptions))
169
+ trace_state = trace_state.set_value(XTraceOptions.sw_xtraceoptions_response_key.to_s,
170
+ create_xtraceoptions_response_value(liboboe_decision, parent_span_context, xtraceoptions))
233
171
  end
172
+ trace_state
173
+ end
234
174
 
175
+ ##
176
+ #
177
+ def create_new_trace_state(parent_span_context, liboboe_decision)
178
+ decision = sw_from_span_and_decision(parent_span_context, liboboe_decision)
179
+ trace_state = ::OpenTelemetry::Trace::Tracestate.from_hash({SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY => decision}) # e.g. sw=3e222c863a04123a-01
180
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] created new trace_state: #{trace_state.inspect}"}
235
181
  trace_state
236
182
  end
237
183
 
184
+ ##
238
185
  #
239
- # SW_TRACESTATE_CAPTURE_KEY = "sw.w3c.tracestate"
240
- # sw_xtraceoptions_response_key = "xtrace_options_response"
241
- # trace_state is the new trace_state from existing span information
242
- # parent_span_context.trace_state is from its parent
243
- #
244
- def add_tracestate_capture_to_attributes_dict(attributes_dict, decision, trace_state, parent_span_context)
245
-
246
- tracestate_capture = attributes_dict[SW_TRACESTATE_CAPTURE_KEY]
247
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] tracestate_capture #{tracestate_capture.inspect}; attributes_dict #{attributes_dict.inspect}; trace_state #{trace_state.inspect}; parent_span_context: #{parent_span_context.inspect}" }
186
+ def create_xtraceoptions_response_value(liboboe_decision, parent_span_context, xtraceoptions)
248
187
 
249
- # tracestate_capture seems always nil because attributes_dict never have the SW_TRACESTATE_CAPTURE_KEY (sw.w3c.tracestate)
250
- # since tracestate_capture is always nil, so the sw always have new value for sw=key
251
- if tracestate_capture.nil?
252
- trace_state_no_response = trace_state.delete(XTraceOptions.sw_xtraceoptions_response_key)
188
+ response = []
189
+ w3c_sanitized = SolarWindsAPM::Constants::INTL_SWO_EQUALS_W3C_SANITIZED
190
+ w3c_sanitized_comma = SolarWindsAPM::Constants::INTL_SWO_COMMA_W3C_SANITIZED
191
+ response << [XTRACEOPTIONS_RESP_AUTH, liboboe_decision['auth_msg']].join(w3c_sanitized) if xtraceoptions.signature && liboboe_decision['auth_msg']
253
192
 
254
- else
255
- # Must retain all potential tracestate pairs for attributes
256
- attr_trace_state = ::OpenTelemetry::Trace::Tracestate.from_string(tracestate_capture)
193
+ if !liboboe_decision["auth"] || liboboe_decision["auth"] < 1
194
+ if xtraceoptions.trigger_trace
195
+ # If a traceparent header was provided then oboe does not generate the message
196
+ tracestring = Utils.traceparent_from_context(parent_span_context) if parent_span_context.valid? && parent_span_context.remote?
197
+ trigger_msg = tracestring && liboboe_decision['decision_type'] == 0 ? XTRACEOPTIONS_RESP_TRIGGER_IGNORED : liboboe_decision['status_msg']
198
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] tracestring: #{tracestring}; trigger_msg: #{trigger_msg}"}
199
+ else
200
+ trigger_msg = XTRACEOPTIONS_RESP_TRIGGER_NOT_REQUESTED
201
+ end
257
202
 
258
- # This step generated the new sw=key for tracestate based on root parent_span_id
259
- new_attr_trace_state = attr_trace_state.set_value(SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY, sw_from_span_and_decision(parent_span_context,decision))
260
-
261
- trace_state_no_response = new_attr_trace_state.delete(XTraceOptions.sw_xtraceoptions_response_key)
203
+ response << [XTRACEOPTIONS_RESP_TRIGGER_TRACE, trigger_msg].join(w3c_sanitized) # e.g. response << trigger-trace####ok
262
204
  end
263
205
 
264
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] trace_state_no_response #{trace_state_no_response.inspect}"}
265
-
266
- # other approach
267
- trace_state_no_response = parent_span_context.tracestate.delete(XTraceOptions.sw_xtraceoptions_response_key)
268
- no_sw_count = trace_state_no_response.to_h.reject { |k, _v| k == "sw" }.count
269
- attributes_dict[SW_TRACESTATE_CAPTURE_KEY] = Transformer.trace_state_header(trace_state_no_response) if no_sw_count > 0
270
-
271
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] attributes_dict #{attributes_dict.inspect}"}
272
- attributes_dict
206
+ # appending ignored value from xtraceoptions to response. e.g. response << ignored####invalidkeys,invalidkeys,invalidkeys
207
+ response << [XTRACEOPTIONS_RESP_IGNORED, xtraceoptions.ignored.join(w3c_sanitized_comma)].join(w3c_sanitized) unless xtraceoptions.ignored.empty?
208
+ joined_response = response.join(';')
209
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] final response_value: #{joined_response}"}
210
+ joined_response
273
211
  end
274
212
 
275
213
  ##
276
- # calculate_attributes is used for getting the otel Result class in last step of sampler should_sample? e.g. result = Result.new(decision: otel_decision, attributes: new_attributes, tracestate: new_trace_state)
214
+ # calculate_attributes is used for getting the otel Result class in last step of sampler should_sample?
215
+ # e.g. result = Result.new(decision: otel_decision, attributes: new_attributes, tracestate: new_trace_state)
277
216
  # calculate_attributes use new_trace_state that is derived from current span information and old tracestate from parent_span_context.tracestate
278
217
  # the sw.w3c.tracestate should perserve the old tracestate value for debugging purpose
279
- ##
280
- def calculate_attributes(attributes, decision, trace_state, parent_span_context, xtraceoptions)
281
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Received attributes: #{attributes.inspect}; decision:#{decision.inspect}; trace_state:#{trace_state.inspect}; parent_span_context:#{parent_span_context.inspect}; xtraceoptions:#{xtraceoptions.inspect}"}
282
-
283
- otel_decision = otel_decision_from_liboboe(decision)
284
- return nil if Transformer.sampled?(otel_decision) == false
285
-
286
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Trace decision is_sampled - setting attributes #{otel_decision.inspect}"}
287
-
288
- new_attributes = {}
289
- # Copy existing MappingProxyType KV into new_attributes for modification.
290
- attributes&.each {|k,v| new_attributes[k] = v}
218
+ def calculate_attributes(attributes, liboboe_decision, trace_state, parent_span_context, xtraceoptions)
219
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] new_trace_state: #{trace_state.inspect}"}
220
+ new_attributes = attributes.dup || {}
291
221
 
292
222
  # Always (root or is_remote) set _INTERNAL_SW_KEYS if injected
293
223
  new_attributes[INTERNAL_SW_KEYS] = xtraceoptions.sw_keys if xtraceoptions.sw_keys
@@ -296,16 +226,14 @@ module SolarWindsAPM
296
226
  xtraceoptions.custom_kvs&.each {|k,v| new_attributes[k] = v}
297
227
 
298
228
  # Always (root or is_remote) set service entry internal KVs
299
- new_attributes[INTERNAL_BUCKET_CAPACITY] = decision["bucket_cap"].to_s
300
- new_attributes[INTERNAL_BUCKET_RATE] = decision["bucket_rate"].to_s
301
- new_attributes[INTERNAL_SAMPLE_RATE] = decision["rate"]
302
- new_attributes[INTERNAL_SAMPLE_SOURCE] = decision["source"]
303
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Set attributes with service entry internal KVs: #{new_attributes}"}
229
+ new_attributes[INTERNAL_BUCKET_CAPACITY] = liboboe_decision["bucket_cap"].to_s
230
+ new_attributes[INTERNAL_BUCKET_RATE] = liboboe_decision["bucket_rate"].to_s
231
+ new_attributes[INTERNAL_SAMPLE_RATE] = liboboe_decision["rate"]
232
+ new_attributes[INTERNAL_SAMPLE_SOURCE] = liboboe_decision["source"]
304
233
 
305
234
  # set sw.tracestate_parent_id if its tracestate contains "sw"
306
235
  sw_value = parent_span_context.tracestate.value(SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY)
307
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] calculate_attributes sw_value: #{sw_value.inspect} parent_span_context.tracestate #{parent_span_context.tracestate.inspect}"}
308
- new_attributes[SW_TRACESTATE_ROOT_KEY]= Transformer.span_id_from_sw(sw_value) if sw_value && parent_span_context.remote?
236
+ new_attributes[SW_TRACESTATE_ROOT_KEY] = sw_value.split("-")[0] if sw_value && parent_span_context.remote?
309
237
 
310
238
  # If unsigned or signed TT (root or is_remote), set TriggeredTrace
311
239
  new_attributes[SolarWindsAPM::Constants::INTERNAL_TRIGGERED_TRACE] = true if xtraceoptions.trigger_trace
@@ -316,14 +244,43 @@ module SolarWindsAPM
316
244
  return new_attributes.freeze || nil
317
245
  end
318
246
 
319
- new_attributes = add_tracestate_capture_to_attributes_dict(new_attributes,decision,trace_state,parent_span_context)
320
- # e.g. {"SWKeys"=>"check-id:check-1013,website-id:booking-demo", "BucketCapacity"=>"6.0", "BucketRate"=>"0.1", "SampleRate"=>-1, "SampleSource"=>-1, "http.method"=>"GET", "http.host"=>"0.0.0.0:8002", "http.scheme"=>"http", "http.target"=>"/call_second_rails/", "http.user_agent"=>"curl/7.81.0", "sw.w3c.tracestate"=>"sw=aaaa1111bbbb2222-01"}
247
+ new_attributes = add_tracestate_capture_to_new_attributes(new_attributes, liboboe_decision, trace_state, parent_span_context)
321
248
  new_attributes.freeze
322
249
  end
323
250
 
324
- def sw_from_span_and_decision(parent_span_context, decision)
325
- trace_flag = Transformer.trace_flags_from_boolean(decision["do_sample"])
326
- Transformer.sw_from_span_and_decision(parent_span_context.hex_span_id, trace_flag)
251
+ ##
252
+ #
253
+ def add_tracestate_capture_to_new_attributes(new_attributes, liboboe_decision, trace_state, parent_span_context)
254
+
255
+ tracestate_capture = new_attributes[SW_TRACESTATE_CAPTURE_KEY]
256
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] tracestate_capture #{tracestate_capture.inspect}; new_attributes #{new_attributes.inspect}"}
257
+
258
+ if tracestate_capture.nil?
259
+ trace_state_no_response = trace_state.delete(XTraceOptions.sw_xtraceoptions_response_key)
260
+ else
261
+ # retain all potential tracestate pairs for attributes and generate new sw=key for tracestate based on root parent_span_id
262
+ attr_trace_state = ::OpenTelemetry::Trace::Tracestate.from_string(tracestate_capture)
263
+ new_attr_trace_state = attr_trace_state.set_value(SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY, sw_from_span_and_decision(parent_span_context,liboboe_decision))
264
+ trace_state_no_response = new_attr_trace_state.delete(XTraceOptions.sw_xtraceoptions_response_key)
265
+ end
266
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] trace_state_no_response #{trace_state_no_response.inspect}"}
267
+
268
+ trace_state_no_response = parent_span_context.tracestate.delete(XTraceOptions.sw_xtraceoptions_response_key)
269
+ no_sw_count = trace_state_no_response.to_h.reject { |k, _v| k == "sw" }.count
270
+ new_attributes[SW_TRACESTATE_CAPTURE_KEY] = Utils.trace_state_header(trace_state_no_response) if no_sw_count > 0
271
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] new_attributes after add_tracestate_capture_to_new_attributes: #{new_attributes.inspect}"}
272
+
273
+ new_attributes
274
+ end
275
+
276
+ # formats tracestate sw value from span_id and liboboe decision as 16-byte span_id with 8-bit trace_flags e.g. 1a2b3c4d5e6f7g8h-01
277
+ def sw_from_span_and_decision(parent_span_context, liboboe_decision)
278
+ trace_flag = (liboboe_decision["do_sample"] == true) ? "01" : "00"
279
+ [parent_span_context.hex_span_id, trace_flag].join("-")
280
+ end
281
+
282
+ def otel_sampled?(otel_decision)
283
+ otel_decision == ::OpenTelemetry::SDK::Trace::Samplers::Decision::RECORD_AND_SAMPLE
327
284
  end
328
285
  end
329
286
  end
@@ -6,14 +6,14 @@ module SolarWindsAPM
6
6
  module OTelConfig
7
7
  @@config = {}
8
8
  @@config_map = {}
9
-
9
+
10
10
  @@agent_enabled = true
11
11
 
12
12
  def self.disable_agent
13
13
  return unless @@agent_enabled # only show the msg once
14
-
14
+
15
15
  @@agent_enabled = false
16
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] Agent disabled. No Trace exported."}
16
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] Agent disabled. No Trace exported."}
17
17
  end
18
18
 
19
19
  def self.validate_service_key
@@ -25,51 +25,69 @@ module SolarWindsAPM
25
25
  def self.resolve_sampler
26
26
 
27
27
  resolve_sampler_config
28
- @@config[:sampler] =
28
+ @@config[:sampler] =
29
29
  ::OpenTelemetry::SDK::Trace::Samplers.parent_based(
30
30
  root: SolarWindsAPM::OpenTelemetry::SolarWindsSampler.new(@@config[:sampler_config]),
31
31
  remote_parent_sampled: SolarWindsAPM::OpenTelemetry::SolarWindsSampler.new(@@config[:sampler_config]),
32
32
  remote_parent_not_sampled: SolarWindsAPM::OpenTelemetry::SolarWindsSampler.new(@@config[:sampler_config]))
33
33
  end
34
34
 
35
- def self.resolve_sampler_config
35
+ def self.resolve_sampler_config
36
36
  sampler_config = {}
37
- sampler_config["trigger_trace"] = "enabled"
37
+ sampler_config["trigger_trace"] = "enabled"
38
38
  sampler_config["trigger_trace"] = nil if ENV["SW_APM_TRIGGER_TRACING_MODE"] == 'disabled'
39
39
  @@config[:sampler_config] = sampler_config
40
40
  end
41
41
 
42
42
  #
43
43
  # append/add solarwinds_response_propagator into rack instrumentation
44
- #
45
- def self.resolve_for_response_propagator
46
- response_propagator = SolarWindsAPM::OpenTelemetry::SolarWindsResponsePropagator::TextMapPropagator.new
47
- if @@config_map["OpenTelemetry::Instrumentation::Rack"]
48
- if @@config_map["OpenTelemetry::Instrumentation::Rack"][:response_propagators].instance_of?(Array)
49
- @@config_map["OpenTelemetry::Instrumentation::Rack"][:response_propagators].append(response_propagator)
44
+ #
45
+ def self.resolve_response_propagator
46
+ response_propagator = SolarWindsAPM::OpenTelemetry::SolarWindsResponsePropagator::TextMapPropagator.new
47
+ rack_setting = @@config_map["OpenTelemetry::Instrumentation::Rack"]
48
+
49
+ if rack_setting
50
+ if rack_setting[:response_propagators].instance_of?(Array)
51
+ rack_setting[:response_propagators].append(response_propagator)
52
+ elsif rack_setting[:response_propagators].nil?
53
+ rack_setting[:response_propagators] = [response_propagator]
50
54
  else
51
- @@config_map["OpenTelemetry::Instrumentation::Rack"][:response_propagators] = [response_propagator]
55
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] Rack response propagator resolve failed. Provided type #{rack_setting[:response_propagators].class}, please provide Array e.g. [#{rack_setting[:response_propagators]}]"}
52
56
  end
53
57
  else
54
58
  @@config_map["OpenTelemetry::Instrumentation::Rack"] = {response_propagators: [response_propagator]}
55
59
  end
56
60
  end
57
61
 
62
+ def self.obfuscate_helper(instrumentation)
63
+ if @@config_map[instrumentation] # user provided the option
64
+ @@config_map[instrumentation][:db_statement] = :obfuscate unless @@config_map[instrumentation][:db_statement] # user provided the db_statement, ignore our default setting
65
+ else
66
+ @@config_map[instrumentation] = {db_statement: :obfuscate}
67
+ end
68
+ end
69
+
70
+ def self.obfuscate_query
71
+ obfuscate_helper("OpenTelemetry::Instrumentation::Dalli")
72
+ obfuscate_helper("OpenTelemetry::Instrumentation::Mysql2")
73
+ obfuscate_helper("OpenTelemetry::Instrumentation::PG")
74
+ end
75
+
58
76
  def self.[](key)
59
77
  @@config[key.to_sym]
60
78
  end
61
79
 
62
80
  def self.print_config
63
81
  @@config.each do |config, value|
64
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] config: #{config} = #{value}"}
82
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] config: #{config} = #{value}"}
65
83
  end
66
84
  @@config_map.each do |config, value|
67
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] config_map: #{config} = #{value}"}
85
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] config_map: #{config} = #{value}"}
68
86
  end
69
87
  end
70
88
 
71
89
  def self.resolve_solarwinds_processor
72
- txn_manager = SolarWindsAPM::OpenTelemetry::TxnNameManager.new
90
+ txn_manager = SolarWindsAPM::TxnNameManager.new
73
91
  exporter = SolarWindsAPM::OpenTelemetry::SolarWindsExporter.new(txn_manager: txn_manager)
74
92
  @@config[:span_processor] = SolarWindsAPM::OpenTelemetry::SolarWindsProcessor.new(exporter, txn_manager)
75
93
  end
@@ -84,16 +102,16 @@ module SolarWindsAPM
84
102
  return
85
103
  end
86
104
 
87
- SolarWindsAPM.logger.debug {"[#{self.name}/#{__method__}] propagators: #{propagators.map(&:class)}"}
105
+ SolarWindsAPM.logger.debug {"[#{name}/#{__method__}] propagators: #{propagators.map(&:class)}"}
88
106
  unless ([::OpenTelemetry::Trace::Propagation::TraceContext::TextMapPropagator, ::OpenTelemetry::Baggage::Propagation::TextMapPropagator] - propagators.map(&:class)).empty? # rubocop:disable Style/GuardClause
89
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] Missing tracecontext propagator."}
107
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] Missing tracecontext propagator."}
90
108
  disable_agent
91
109
  end
92
110
  end
93
111
 
94
112
  def self.initialize
95
113
  unless defined?(::OpenTelemetry::SDK::Configurator)
96
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] missing OpenTelemetry::SDK::Configurator; opentelemetry seems not loaded."}
114
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] missing OpenTelemetry::SDK::Configurator; opentelemetry seems not loaded."}
97
115
  disable_agent
98
116
  return
99
117
  end
@@ -103,39 +121,40 @@ module SolarWindsAPM
103
121
  return unless @@agent_enabled
104
122
 
105
123
  resolve_sampler
106
-
124
+
107
125
  resolve_solarwinds_propagator
108
126
  resolve_solarwinds_processor
109
- resolve_for_response_propagator
127
+ resolve_response_propagator
128
+
129
+ obfuscate_query
110
130
 
111
131
  print_config if SolarWindsAPM.logger.level.zero?
112
132
 
113
- ENV['OTEL_TRACES_EXPORTER'] = 'none' if ENV['OTEL_TRACES_EXPORTER'].nil?
114
- ::OpenTelemetry::SDK.configure do |c|
115
- c.use_all(@@config_map)
116
- end
117
-
133
+ ENV['OTEL_TRACES_EXPORTER'] = 'none' if ENV['OTEL_TRACES_EXPORTER'].to_s.empty?
134
+
135
+ ::OpenTelemetry::SDK.configure { |c| c.use_all(@@config_map) }
136
+
118
137
  validate_propagator(::OpenTelemetry.propagation.instance_variable_get(:@propagators))
119
138
 
120
139
  return unless @@agent_enabled
121
140
 
122
141
  # append our propagators
123
- ::OpenTelemetry.propagation.instance_variable_get(:@propagators).append(@@config[:propagators])
124
-
125
- # append our processors (with our exporter)
142
+ ::OpenTelemetry.propagation.instance_variable_get(:@propagators).append(@@config[:propagators])
143
+
144
+ # append our processors (with our exporter)
126
145
  ::OpenTelemetry.tracer_provider.add_span_processor(@@config[:span_processor])
127
-
146
+
128
147
  # configure sampler afterwards
129
148
  ::OpenTelemetry.tracer_provider.sampler = @@config[:sampler]
130
149
  nil
131
150
  end
132
151
 
133
- #
152
+ #
134
153
  # Allow initialize after set new value to SolarWindsAPM::Config[:key]=value
135
- #
154
+ #
136
155
  # Usage:
137
- #
138
- # Default using the use_all to load all instrumentation
156
+ #
157
+ # Default using the use_all to load all instrumentation
139
158
  # But with specific instrumentation disabled, use {:enabled: false} in config
140
159
  # SolarWindsAPM::OTelConfig.initialize_with_config do |config|
141
160
  # config["OpenTelemetry::Instrumentation::Rack"] = {"a" => "b"}
@@ -144,14 +163,14 @@ module SolarWindsAPM
144
163
  #
145
164
  def self.initialize_with_config
146
165
  unless block_given?
147
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] Block not given while doing in-code configuration. Agent disabled."}
166
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] Block not given while doing in-code configuration. Agent disabled."}
148
167
  return
149
168
  end
150
169
 
151
170
  yield @@config_map
152
171
 
153
172
  if @@config_map.empty?
154
- SolarWindsAPM.logger.warn {"[#{self.name}/#{__method__}] No configuration given for in-code configuration. Agent disabled."}
173
+ SolarWindsAPM.logger.warn {"[#{name}/#{__method__}] No configuration given for in-code configuration. Agent disabled."}
155
174
  return
156
175
  end
157
176
 
@@ -1,6 +1,21 @@
1
- # Copyright (c) 2019 SolarWinds, LLC.
1
+ # Copyright (c) 2023 SolarWinds, LLC.
2
2
  # All rights reserved.
3
3
 
4
4
  require_relative 'logger_formatter'
5
5
 
6
- Lumberjack::Formatter.prepend(SolarWindsAPM::Logger::Formatter) if SolarWindsAPM.loaded && defined?(Lumberjack::Formatter)
6
+ module SolarWindsAPM
7
+ module Lumberjack
8
+ module LogEntry
9
+ include SolarWindsAPM::Logger::Formatter # provides #insert_trace_id
10
+
11
+ def initialize(time, severity, message, progname, pid, tags)
12
+ super if SolarWindsAPM::Config[:log_traceId] == :never
13
+
14
+ message = insert_trace_id(message)
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Lumberjack::LogEntry.prepend(SolarWindsAPM::Lumberjack::LogEntry) if SolarWindsAPM.loaded && defined?(Lumberjack::LogEntry)
@@ -107,7 +107,7 @@ module SolarWindsAPM
107
107
  marginalia_job["class"] if marginalia_job.respond_to?(:[])
108
108
  end
109
109
 
110
- DEFAULT_LINES_TO_IGNORE_REGEX = %r{\.rvm|/ruby/gems/|vendor/|marginalia|rbenv|monitor\.rb.*mon_synchronize}.freeze
110
+ DEFAULT_LINES_TO_IGNORE_REGEX = %r{\.rvm|/ruby/gems/|vendor/|marginalia|rbenv|monitor\.rb.*mon_synchronize}
111
111
 
112
112
  def self.line
113
113
  SWOMarginalia::Comment.lines_to_ignore ||= DEFAULT_LINES_TO_IGNORE_REGEX
@@ -179,7 +179,7 @@ module SolarWindsAPM
179
179
  span_id: span_context.hex_span_id,
180
180
  trace_flags: trace_flag)
181
181
  rescue NameError => e
182
- SolarWindsAPM.logger.error {"[#{self.name}/#{__method__}] Couldn't find OpenTelemetry. Error: #{e.message}"}
182
+ SolarWindsAPM.logger.error {"[#{name}/#{__method__}] Couldn't find OpenTelemetry. Error: #{e.message}"}
183
183
  end
184
184
 
185
185
  if Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new('6.1')