solarwinds_apm 6.0.0.preV2 → 6.0.0.preV4

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/ext/oboe_metal/lib/liboboe-1.0-aarch64.so.sha256 +1 -1
  3. data/ext/oboe_metal/lib/liboboe-1.0-alpine-aarch64.so.sha256 +1 -1
  4. data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.sha256 +1 -1
  5. data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.sha256 +1 -1
  6. data/ext/oboe_metal/src/VERSION +1 -2
  7. data/ext/oboe_metal/src/oboe.h +0 -12
  8. data/ext/oboe_metal/src/oboe_swig_wrap.cc +5 -5
  9. data/lib/rails/generators/solarwinds_apm/templates/solarwinds_apm_initializer.rb +1 -9
  10. data/lib/solarwinds_apm/api/current_trace_info.rb +0 -1
  11. data/lib/solarwinds_apm/api/opentelemetry.rb +39 -0
  12. data/lib/solarwinds_apm/api/transaction_name.rb +17 -17
  13. data/lib/solarwinds_apm/api.rb +2 -0
  14. data/lib/solarwinds_apm/config.rb +3 -19
  15. data/lib/solarwinds_apm/constants.rb +2 -4
  16. data/lib/solarwinds_apm/noop/context.rb +1 -1
  17. data/lib/solarwinds_apm/oboe_init_options.rb +2 -5
  18. data/lib/solarwinds_apm/opentelemetry/solarwinds_exporter.rb +92 -87
  19. data/lib/solarwinds_apm/opentelemetry/solarwinds_processor.rb +18 -17
  20. data/lib/solarwinds_apm/opentelemetry/solarwinds_propagator.rb +28 -28
  21. data/lib/solarwinds_apm/opentelemetry/solarwinds_response_propagator.rb +9 -14
  22. data/lib/solarwinds_apm/opentelemetry/solarwinds_sampler.rb +122 -170
  23. data/lib/solarwinds_apm/otel_config.rb +47 -28
  24. data/lib/solarwinds_apm/support/lumberjack_formatter.rb +17 -2
  25. data/lib/solarwinds_apm/support/swomarginalia/swomarginalia.rb +5 -4
  26. data/lib/solarwinds_apm/support/transaction_cache.rb +27 -2
  27. data/lib/solarwinds_apm/support/transaction_settings.rb +2 -2
  28. data/lib/solarwinds_apm/support/txn_name_manager.rb +34 -17
  29. data/lib/solarwinds_apm/support/utils.rb +24 -0
  30. data/lib/solarwinds_apm/support.rb +2 -4
  31. data/lib/solarwinds_apm/version.rb +1 -1
  32. data/lib/solarwinds_apm.rb +2 -2
  33. metadata +13 -13
  34. data/lib/oboe.rb +0 -7
  35. data/lib/solarwinds_apm/support/transformer.rb +0 -56
@@ -32,11 +32,9 @@ module SolarWindsAPM
32
32
  return if parent_span && parent_span.context != ::OpenTelemetry::Trace::SpanContext::INVALID && parent_span.context.remote? == false
33
33
 
34
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}"}
35
+ @txn_manager.set_root_context_h(span.context.hex_trace_id,"#{span.context.hex_span_id}-#{trace_flags}")
36
+ rescue StandardError => e
37
+ SolarWindsAPM.logger.info {"[#{self.class}/#{__method__}] processor on_start error: #{e.message}"}
40
38
  end
41
39
 
42
40
  # Called when a {Span} is ended, if the {Span#recording?}
@@ -47,7 +45,9 @@ module SolarWindsAPM
47
45
  # Only calculate inbound metrics for service root spans
48
46
  #
49
47
  # @param [Span] span the {Span} that just ended.
50
- def on_finish(span)
48
+ def on_finish(span)
49
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] processor on_finish span: #{span.inspect}"}
50
+
51
51
  if span.parent_span_id != ::OpenTelemetry::Trace::INVALID_SPAN_ID
52
52
  @exporter&.export([span.to_span_data]) if span.context.trace_flags.sampled?
53
53
  return
@@ -57,12 +57,10 @@ module SolarWindsAPM
57
57
  domain = nil
58
58
  has_error = error?(span)
59
59
  trans_name = calculate_transaction_names(span)
60
- url_tran = span.attributes[HTTP_URL]
61
-
62
- liboboe_txn_name = nil
63
60
  if span_http?(span)
64
- status_code = get_http_status_code(span)
61
+ status_code = get_http_status_code(span)
65
62
  request_method = span.attributes[HTTP_METHOD]
63
+ url_tran = span.attributes[HTTP_URL]
66
64
 
67
65
  SolarWindsAPM.logger.debug do
68
66
  "[#{self.class}/#{__method__}] createHttpSpan with\n
@@ -92,8 +90,11 @@ module SolarWindsAPM
92
90
 
93
91
  SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] liboboe_txn_name: #{liboboe_txn_name}"}
94
92
  @txn_manager["#{span.context.hex_trace_id}-#{span.context.hex_span_id}"] = liboboe_txn_name if span.context.trace_flags.sampled?
95
-
93
+ @txn_manager.delete_root_context_h(span.context.hex_trace_id)
96
94
  @exporter&.export([span.to_span_data]) if span.context.trace_flags.sampled?
95
+ rescue StandardError => e
96
+ SolarWindsAPM.logger.info {"[#{self.class}/#{__method__}] can't flush span to exporter; processor on_finish error: #{e.message}"}
97
+ ::OpenTelemetry::SDK::Trace::Export::FAILURE
97
98
  end
98
99
 
99
100
  # Export all ended spans to the configured `Exporter` that have not yet
@@ -108,7 +109,7 @@ module SolarWindsAPM
108
109
  # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
109
110
  # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
110
111
  def force_flush(timeout: nil)
111
- @exporter&.force_flush(timeout: timeout) || ::OpenTelemetry::SDK::Metrics::Export::SUCCESS
112
+ @exporter&.force_flush(timeout: timeout) || ::OpenTelemetry::SDK::Trace::Export::SUCCESS
112
113
  end
113
114
 
114
115
  # Called when {TracerProvider#shutdown} is called.
@@ -117,14 +118,13 @@ module SolarWindsAPM
117
118
  # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
118
119
  # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
119
120
  def shutdown(timeout: nil)
120
- @exporter&.shutdown(timeout: timeout) || ::OpenTelemetry::SDK::Metrics::Export::SUCCESS
121
+ @exporter&.shutdown(timeout: timeout) || ::OpenTelemetry::SDK::Trace::Export::SUCCESS
121
122
  end
122
123
 
123
124
  private
124
125
 
125
126
  # This span from inbound HTTP request if from a SERVER by some http.method
126
127
  def span_http?(span)
127
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span.kind #{span.kind} span.attributes: #{span.attributes[HTTP_METHOD]}"}
128
128
  (span.kind == ::OpenTelemetry::Trace::SpanKind::SERVER && !span.attributes[HTTP_METHOD].nil?)
129
129
  end
130
130
 
@@ -143,10 +143,11 @@ module SolarWindsAPM
143
143
 
144
144
  # Get trans_name and url_tran of this span instance.
145
145
  def calculate_transaction_names(span)
146
-
147
146
  trace_span_id = "#{span.context.hex_trace_id}-#{span.context.hex_span_id}"
148
- if @txn_manager.get(trace_span_id)
149
- trans_name = @txn_manager.get(trace_span_id)
147
+ trans_name = @txn_manager.get(trace_span_id)
148
+ if trans_name
149
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] found trans name from txn_manager: #{trans_name} by #{trace_span_id}"}
150
+ @txn_manager.del(trace_span_id)
150
151
  else
151
152
  trans_name = span.attributes[HTTP_ROUTE] || nil
152
153
  trans_name = span.name if span.name && (trans_name.nil? || trans_name.empty?)
@@ -2,6 +2,7 @@ module SolarWindsAPM
2
2
  module OpenTelemetry
3
3
  module SolarWindsPropagator
4
4
  # TextMapPropagator
5
+ # propagator error will be rescued by OpenTelemetry::Context::Propagation::TextMapPropagator
5
6
  class TextMapPropagator
6
7
  TRACESTATE_HEADER_NAME = "tracestate".freeze
7
8
  XTRACEOPTIONS_HEADER_NAME = "x-trace-options".freeze
@@ -26,19 +27,11 @@ module SolarWindsAPM
26
27
  # if extraction fails
27
28
  def extract(carrier, context: ::OpenTelemetry::Context.current, getter: ::OpenTelemetry::Context::Propagation.text_map_getter)
28
29
 
29
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] context(before): #{context.inspect} #{context.nil?}"}
30
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] extract context: #{context.inspect}"}
30
31
 
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
32
+ context = context.nil?? ::OpenTelemetry::Context.new({}) : context
33
+ context = inject_extracted_header(carrier, context, getter, XTRACEOPTIONS_HEADER_NAME, INTL_SWO_X_OPTIONS_KEY)
34
+ inject_extracted_header(carrier, context, getter, XTRACEOPTIONS_SIGNATURE_HEADER_NAME, INTL_SWO_SIGNATURE_KEY)
42
35
  end
43
36
 
44
37
  # Inject trace context into the supplied carrier.
@@ -51,32 +44,30 @@ module SolarWindsAPM
51
44
  def inject(carrier, context: ::OpenTelemetry::Context.current, setter: ::OpenTelemetry::Context::Propagation.text_map_setter)
52
45
 
53
46
  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}"}
47
+
48
+ span_context = ::OpenTelemetry::Trace.current_span(context)&.context
49
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] span_context #{span_context.inspect}"}
58
50
  return unless span_context&.valid?
59
51
 
60
- sw_value = Transformer.sw_from_context(span_context) # sw_value is a string
52
+ trace_flag = span_context.trace_flags.sampled?? 1 : 0
53
+ sw_value = "#{span_context.hex_span_id}-0#{trace_flag}"
61
54
  trace_state_header = carrier[TRACESTATE_HEADER_NAME].nil?? nil : carrier[TRACESTATE_HEADER_NAME]
62
-
63
55
  SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] sw_value: #{sw_value}; trace_state_header: #{trace_state_header}"}
64
56
 
65
- # Prepare carrier with carrier's or new tracestate
66
- trace_state = nil
57
+ # prepare carrier with carrier's or new tracestate
67
58
  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}"}
59
+ # only create new trace state if valid span_id
60
+ unless span_context.span_id == ::OpenTelemetry::Trace::INVALID_SPAN_ID
61
+ trace_state = ::OpenTelemetry::Trace::Tracestate.create({SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY => sw_value})
62
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] creating new trace state: #{trace_state.inspect}"}
63
+ setter.set(carrier, TRACESTATE_HEADER_NAME, Utils.trace_state_header(trace_state))
64
+ end
73
65
  else
74
66
  trace_state_from_string = ::OpenTelemetry::Trace::Tracestate.from_string(trace_state_header)
75
67
  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}"}
68
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] updating/adding trace state for injection #{trace_state.inspect}"}
69
+ setter.set(carrier, TRACESTATE_HEADER_NAME, Utils.trace_state_header(trace_state))
77
70
  end
78
-
79
- setter.set(carrier, TRACESTATE_HEADER_NAME, Transformer.trace_state_header(trace_state))
80
71
  end
81
72
 
82
73
  # Returns the predefined propagation fields. If your carrier is reused, you
@@ -86,6 +77,15 @@ module SolarWindsAPM
86
77
  def fields
87
78
  TRACESTATE_HEADER_NAME
88
79
  end
80
+
81
+ private
82
+
83
+ def inject_extracted_header(carrier, context, getter, header, inject_key)
84
+ extracted_header = getter.get(carrier, header)
85
+ context = context.set_value(inject_key, extracted_header) if extracted_header
86
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] #{header}: #{inject_key} = #{extracted_header}"}
87
+ context
88
+ end
89
89
  end
90
90
  end
91
91
  end
@@ -2,6 +2,7 @@ module SolarWindsAPM
2
2
  module OpenTelemetry
3
3
  module SolarWindsResponsePropagator
4
4
  # ResponsePropagator
5
+ # response propagator error will be rescued by OpenTelemetry::Instrumentation::Rack::Middlewares::EventHandler
5
6
  class TextMapPropagator
6
7
  HTTP_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers".freeze
7
8
  XTRACE_HEADER_NAME = "x-trace".freeze
@@ -25,26 +26,20 @@ module SolarWindsAPM
25
26
  # text map setter will be used.
26
27
  def inject(carrier, context: ::OpenTelemetry::Context.current, setter: ::OpenTelemetry::Context::Propagation.text_map_setter)
27
28
 
28
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] response propagator context: #{context.inspect}"}
29
29
  span_context = ::OpenTelemetry::Trace.current_span(context).context
30
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] response propagator span_context: #{span_context.inspect}"}
31
- return unless span_context.valid?
30
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] context: #{context.inspect}; span_context: #{span_context.inspect}"}
31
+ return unless span_context&.valid?
32
32
 
33
- x_trace = Transformer.traceparent_from_context(span_context)
34
- setter.set(carrier, XTRACE_HEADER_NAME, x_trace)
35
- exposed_headers = [XTRACE_HEADER_NAME]
36
-
37
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] response propagator span_context.tracestate: #{span_context.tracestate.inspect}"}
33
+ x_trace = Utils.traceparent_from_context(span_context)
34
+ exposed_headers = [XTRACE_HEADER_NAME]
38
35
  xtraceoptions_response = recover_response_from_tracestate(span_context.tracestate)
39
36
 
40
- unless xtraceoptions_response.empty?
41
- exposed_headers << XTRACEOPTIONS_RESPONSE_HEADER_NAME
42
- setter.set(carrier, XTRACEOPTIONS_RESPONSE_HEADER_NAME, xtraceoptions_response)
43
- end
37
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] x-trace: #{x_trace}; exposed headers: #{exposed_headers.inspect}; x-trace-options-response: #{xtraceoptions_response}"}
38
+ exposed_headers.append(XTRACEOPTIONS_RESPONSE_HEADER_NAME) unless xtraceoptions_response.empty?
44
39
 
45
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] response propagator exposed_headers: #{exposed_headers.inspect}"}
40
+ setter.set(carrier, XTRACE_HEADER_NAME, x_trace)
41
+ setter.set(carrier, XTRACEOPTIONS_RESPONSE_HEADER_NAME, xtraceoptions_response) unless xtraceoptions_response.empty?
46
42
  setter.set(carrier, HTTP_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, exposed_headers.join(","))
47
-
48
43
  end
49
44
 
50
45
  # Returns the predefined propagation fields. If your carrier is reused, you
@@ -38,7 +38,7 @@ module SolarWindsAPM
38
38
  def should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:)
39
39
 
40
40
  SolarWindsAPM.logger.debug do
41
- "[#{self.class}/#{__method__}] should_sample? parameters \n
41
+ "[#{self.class}/#{__method__}] should_sample? start parameters \n
42
42
  trace_id: #{trace_id.unpack1('H*')}\n
43
43
  parent_context: #{parent_context}\n
44
44
  parent_context.inspect: #{parent_context.inspect}\n
@@ -48,30 +48,25 @@ module SolarWindsAPM
48
48
  attributes: #{attributes}"
49
49
  end
50
50
 
51
- # 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}"}
52
54
 
53
- parent_span_context = ::OpenTelemetry::Trace.current_span(parent_context).context
54
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] should_sample? parent_span_context: #{parent_span_context.inspect}"}
55
-
56
- xtraceoptions = SolarWindsAPM::XTraceOptions.new(parent_context)
57
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] xtraceoptions: #{xtraceoptions.inspect}"}
58
-
59
55
  liboboe_decision = calculate_liboboe_decision(parent_span_context, xtraceoptions, name, kind, attributes)
60
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] liboboe_decision: #{liboboe_decision.inspect}"}
61
-
62
- otel_decision = otel_decision_from_liboboe(liboboe_decision)
63
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] otel_decision: #{otel_decision.inspect}"}
64
-
65
- new_trace_state = calculate_trace_state(liboboe_decision,parent_span_context,xtraceoptions)
66
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] new_trace_state: #{new_trace_state.inspect}"}
67
-
68
- new_attributes = calculate_attributes(attributes,liboboe_decision,new_trace_state,parent_span_context,xtraceoptions)
69
- 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)
70
62
 
71
- sampling_result = ::OpenTelemetry::SDK::Trace::Samplers::Result.new(decision: otel_decision, attributes: new_attributes, tracestate: new_trace_state)
72
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] sampling_result: #{sampling_result.inspect}"}
73
-
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}"}
74
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)
75
70
  end
76
71
 
77
72
  protected
@@ -80,51 +75,36 @@ module SolarWindsAPM
80
75
 
81
76
  private
82
77
 
83
- # 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
84
82
  def calculate_liboboe_decision(parent_span_context, xtraceoptions, name, kind, attributes)
85
-
86
- tracestring = nil
87
- if parent_span_context.valid? && parent_span_context.remote?
88
- tracestring = Transformer.traceparent_from_context(parent_span_context)
89
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] calculate_liboboe_decision parent_span_context.remote? #{parent_span_context.remote?} with #{tracestring}"}
90
- end
91
-
92
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] name: #{name}, kind: #{kind}, attributes: #{attributes.inspect}"}
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}"}
93
85
 
94
86
  # otel-ruby contrib use different key to store url info, currently it's using http.target for path
95
87
  url_path = attributes.nil?? '' : attributes['http.target']
96
88
  transaction_naming_key = "#{url_path}-#{name}-#{kind}"
97
-
98
89
  tracing_mode = SolarWindsAPM::TransactionCache.get(transaction_naming_key)
99
-
100
- if tracing_mode.nil?
101
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] transaction cache NOT found: #{transaction_naming_key}."}
90
+ SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] transaction cache: #{transaction_naming_key}; tracing_mode: #{tracing_mode}."}
91
+
92
+ unless tracing_mode
102
93
  trans_settings = SolarWindsAPM::TransactionSettings.new(url_path: url_path, name: name, kind: kind)
103
94
  tracing_mode = trans_settings.calculate_trace_mode == 1 ? SWO_TRACING_ENABLED : SWO_TRACING_DISABLED
104
95
  SolarWindsAPM::TransactionCache.set(transaction_naming_key, tracing_mode)
105
- else
106
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] transaction cache found: #{transaction_naming_key}."}
107
96
  end
108
97
 
109
98
  sw_member_value = parent_span_context.tracestate[SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY]
110
-
111
- # need to create the config class
112
99
  trigger_trace_mode = OboeTracingMode.get_oboe_trigger_trace_mode(@config["trigger_trace"])
113
100
  sample_rate = UNSET
114
-
115
- options = nil
116
- trigger_trace = 0
117
- signature = nil
118
- timestamp = nil
119
- if xtraceoptions
120
- options = xtraceoptions.options
121
- trigger_trace = xtraceoptions.intify_trigger_trace
122
- signature = xtraceoptions.signature
123
- timestamp = xtraceoptions.timestamp
124
- end
101
+ options = xtraceoptions&.options
102
+ trigger_trace = xtraceoptions&.intify_trigger_trace || 0
103
+ signature = xtraceoptions&.signature
104
+ timestamp = xtraceoptions&.timestamp
125
105
 
126
106
  SolarWindsAPM.logger.debug do
127
- "[#{self.class}/#{__method__}] decision parameters \n
107
+ "[#{self.class}/#{__method__}] get liboboe decision parameters: \n
128
108
  tracestring: #{tracestring}\n
129
109
  sw_member_value: #{sw_member_value}\n
130
110
  tracing_mode: #{tracing_mode}\n
@@ -136,9 +116,12 @@ module SolarWindsAPM
136
116
  timestamp: #{timestamp}"
137
117
  end
138
118
 
139
- args = [tracestring,sw_member_value,tracing_mode,sample_rate,trigger_trace,trigger_trace_mode,options,signature,timestamp]
140
- do_metrics, do_sample, rate, source, bucket_rate, \
141
- bucket_cap, decision_type, auth, status_msg, auth_msg, status = SolarWindsAPM::Context.getDecisions(*args)
119
+ args = [tracestring, sw_member_value, tracing_mode, sample_rate,
120
+ trigger_trace, trigger_trace_mode, options, signature,timestamp]
121
+
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)
142
125
 
143
126
  decision = {}
144
127
  decision["do_metrics"] = do_metrics > 0
@@ -163,136 +146,78 @@ module SolarWindsAPM
163
146
  elsif liboboe_decision["do_metrics"]
164
147
  decision = ::OpenTelemetry::SDK::Trace::Samplers::Decision::RECORD_ONLY
165
148
  end
166
- SolarWindsAPM.logger.debug {"OTel decision created: #{decision}"}
149
+ SolarWindsAPM.logger.debug {"otel decision: #{decision} created from liboboe_decision: #{liboboe_decision}"}
167
150
  decision
168
151
  end
169
152
 
170
- def create_xtraceoptions_response_value(decision, parent_span_context, xtraceoptions)
171
- 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}"}
172
-
173
- response = []
174
- w3c_sanitized = SolarWindsAPM::Constants::INTL_SWO_EQUALS_W3C_SANITIZED
175
- response << [XTRACEOPTIONS_RESP_AUTH, decision['auth_msg']].join(w3c_sanitized) if xtraceoptions.signature && decision['auth_msg']
176
- if !decision["auth"] || decision["auth"] < 1
177
- trigger_msg = ""
178
- tracestring = nil
179
- if xtraceoptions.trigger_trace
180
- # If a traceparent header was provided then oboe does not generate the message
181
- tracestring = Transformer.traceparent_from_context(parent_span_context) if parent_span_context.valid? && parent_span_context.remote?
182
- trigger_msg = tracestring && decision['decision_type'] == 0 ? XTRACEOPTIONS_RESP_TRIGGER_IGNORED : decision['status_msg']
183
- else
184
- trigger_msg = XTRACEOPTIONS_RESP_TRIGGER_NOT_REQUESTED
185
- end
186
-
187
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] create_xtraceoptions_response_value parent_span_context: #{parent_span_context}; tracestring: #{tracestring}; trigger_msg: #{trigger_msg}"}
188
-
189
- # e.g. response << trigger-trace####ok
190
- response << [XTRACEOPTIONS_RESP_TRIGGER_TRACE, trigger_msg].join(w3c_sanitized)
191
-
192
- end
193
-
194
- # so far the x-trace-options are only used for liboboe calculate decision for x-trace feature
195
- # probably not need for remaining services since liboboe decision only calculate once
196
- unless xtraceoptions.ignored.empty?
197
- ignored_response = [XTRACEOPTIONS_RESP_IGNORED, xtraceoptions.ignored.join(SolarWindsAPM::Constants::INTL_SWO_COMMA_W3C_SANITIZED)]
198
- response << ignored_response.join(w3c_sanitized)
199
- # e.g. response << ignored####invalidkeys,invalidkeys,invalidkeys
200
- end
201
-
202
- response.join(';') # e.g. trigger-trace####ok;ignored####invalidkeys,invalidkeys,invalidkeys
203
- end
204
-
205
- def create_new_trace_state(parent_span_context, decision)
206
- decision = sw_from_span_and_decision(parent_span_context, decision)
207
- trace_state = ::OpenTelemetry::Trace::Tracestate.from_hash({SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY => decision}) # e.g. sw=3e222c863a04123a-01
208
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Created new trace_state: #{trace_state.inspect}"}
209
- trace_state
210
- end
211
-
212
- #
213
- # calculate_trace_state
214
- # This function merely just add sw=value and xtrace_options_response=value into the old/new tracestate
215
- # The return value tracestate will be used in propagating to next services
216
- #
217
- def calculate_trace_state(decision, parent_span_context, xtraceoptions)
218
- 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)
219
157
  if !parent_span_context.valid?
220
-
221
- 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)
222
161
  else
223
-
224
- parent_trace_state = parent_span_context.tracestate
225
- if parent_trace_state.nil?
226
- trace_state = create_new_trace_state(parent_span_context, decision)
227
- else
228
- trace_state = parent_trace_state.set_value(SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY, sw_from_span_and_decision(parent_span_context, decision))
229
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Updated trace_state with span_id and sw trace_flags: #{trace_state.inspect}"}
230
- 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}"}
231
165
  end
232
166
 
233
167
  # for setting up the xtrace_options_response
234
168
  if xtraceoptions&.options
235
- trace_state = trace_state.set_value(
236
- XTraceOptions.sw_xtraceoptions_response_key.to_s,
237
- 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))
238
171
  end
172
+ trace_state
173
+ end
239
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}"}
240
181
  trace_state
241
182
  end
242
183
 
184
+ ##
243
185
  #
244
- # SW_TRACESTATE_CAPTURE_KEY = "sw.w3c.tracestate"
245
- # sw_xtraceoptions_response_key = "xtrace_options_response"
246
- # trace_state is the new trace_state from existing span information
247
- # parent_span_context.trace_state is from its parent
248
- #
249
- def add_tracestate_capture_to_attributes_dict(attributes_dict, decision, trace_state, parent_span_context)
250
-
251
- tracestate_capture = attributes_dict[SW_TRACESTATE_CAPTURE_KEY]
252
- 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)
253
187
 
254
- # tracestate_capture seems always nil because attributes_dict never have the SW_TRACESTATE_CAPTURE_KEY (sw.w3c.tracestate)
255
- # since tracestate_capture is always nil, so the sw always have new value for sw=key
256
- if tracestate_capture.nil?
257
- 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']
258
192
 
259
- else
260
- # Must retain all potential tracestate pairs for attributes
261
- 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
262
202
 
263
- # This step generated the new sw=key for tracestate based on root parent_span_id
264
- new_attr_trace_state = attr_trace_state.set_value(SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY, sw_from_span_and_decision(parent_span_context,decision))
265
-
266
- 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
267
204
  end
268
205
 
269
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] trace_state_no_response #{trace_state_no_response.inspect}"}
270
-
271
- # other approach
272
- trace_state_no_response = parent_span_context.tracestate.delete(XTraceOptions.sw_xtraceoptions_response_key)
273
- no_sw_count = trace_state_no_response.to_h.reject { |k, _v| k == "sw" }.count
274
- attributes_dict[SW_TRACESTATE_CAPTURE_KEY] = Transformer.trace_state_header(trace_state_no_response) if no_sw_count > 0
275
-
276
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] attributes_dict #{attributes_dict.inspect}"}
277
- 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
278
211
  end
279
212
 
280
213
  ##
281
- # 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)
282
216
  # calculate_attributes use new_trace_state that is derived from current span information and old tracestate from parent_span_context.tracestate
283
217
  # the sw.w3c.tracestate should perserve the old tracestate value for debugging purpose
284
- ##
285
- def calculate_attributes(attributes, decision, trace_state, parent_span_context, xtraceoptions)
286
- 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}"}
287
-
288
- otel_decision = otel_decision_from_liboboe(decision)
289
- return nil if Transformer.sampled?(otel_decision) == false
290
-
291
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] Trace decision is_sampled - setting attributes #{otel_decision.inspect}"}
292
-
293
- new_attributes = {}
294
- # Copy existing MappingProxyType KV into new_attributes for modification.
295
- 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 || {}
296
221
 
297
222
  # Always (root or is_remote) set _INTERNAL_SW_KEYS if injected
298
223
  new_attributes[INTERNAL_SW_KEYS] = xtraceoptions.sw_keys if xtraceoptions.sw_keys
@@ -301,16 +226,14 @@ module SolarWindsAPM
301
226
  xtraceoptions.custom_kvs&.each {|k,v| new_attributes[k] = v}
302
227
 
303
228
  # Always (root or is_remote) set service entry internal KVs
304
- new_attributes[INTERNAL_BUCKET_CAPACITY] = decision["bucket_cap"].to_s
305
- new_attributes[INTERNAL_BUCKET_RATE] = decision["bucket_rate"].to_s
306
- new_attributes[INTERNAL_SAMPLE_RATE] = decision["rate"]
307
- new_attributes[INTERNAL_SAMPLE_SOURCE] = decision["source"]
308
- 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"]
309
233
 
310
234
  # set sw.tracestate_parent_id if its tracestate contains "sw"
311
235
  sw_value = parent_span_context.tracestate.value(SolarWindsAPM::Constants::INTL_SWO_TRACESTATE_KEY)
312
- SolarWindsAPM.logger.debug {"[#{self.class}/#{__method__}] calculate_attributes sw_value: #{sw_value.inspect} parent_span_context.tracestate #{parent_span_context.tracestate.inspect}"}
313
- 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?
314
237
 
315
238
  # If unsigned or signed TT (root or is_remote), set TriggeredTrace
316
239
  new_attributes[SolarWindsAPM::Constants::INTERNAL_TRIGGERED_TRACE] = true if xtraceoptions.trigger_trace
@@ -321,14 +244,43 @@ module SolarWindsAPM
321
244
  return new_attributes.freeze || nil
322
245
  end
323
246
 
324
- new_attributes = add_tracestate_capture_to_attributes_dict(new_attributes,decision,trace_state,parent_span_context)
325
- # 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)
326
248
  new_attributes.freeze
327
249
  end
328
250
 
329
- def sw_from_span_and_decision(parent_span_context, decision)
330
- trace_flag = Transformer.trace_flags_from_boolean(decision["do_sample"])
331
- 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
332
284
  end
333
285
  end
334
286
  end