solarwinds_apm 6.0.0.preV2 → 6.0.0.preV4

Sign up to get free protection for your applications and to get access to all the features.
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