instana 2.10.0 → 2.11.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85a74f9236a4a35af2f74850bb0c5230a921f37b23bdf80af6b578375fe620d3
4
- data.tar.gz: eaaa01ae5c3a67c7d374fff6ccdf58be1bff45e8ba36186e2617b3084abb2e90
3
+ metadata.gz: 651f82ecb4bf26f63225bb880fff4028fca72f641eeac492b3bbb85d4c0be0d2
4
+ data.tar.gz: b311f0c13d53cf688247b580f018a57a62569eb0a1fa5c25d8fac1a8939ffdf0
5
5
  SHA512:
6
- metadata.gz: 1338789ea5e37aa238bc08815cdbabf801ead7b17f28a9fc49e1efefecfa103ed2d0eb2958be6a50efa821169c2b4551e2b521b6f518585fa75e30a84cc92b0e
7
- data.tar.gz: f1290dc8888e9e570bc2ee6a1dab5f11ac2f241726a0bf809aaa4c8fa5066d2bc142a268be3e85183052e9f26eff5a9a3377cf48b06a91acf9861f1c490f4da5
6
+ metadata.gz: 29c82de02af9e09ebaa84da9429d70867f1f8eb3c397836d50a78eaddecf8339a6b20e809bbdadc22dbd0f5f8672e766d96aff1c0802f9a08edefd7bccb0a1ae
7
+ data.tar.gz: e099c624a54ce5871010efad8ad469d8aebf0672cc7011a49bd26c9b448fcc6883eb58122cc9424f69757432b418b1a38fe332d78b1718c65ba60cae8958754e
data/lib/instana/base.rb CHANGED
@@ -6,6 +6,11 @@ require "instana/version"
6
6
  require "instana/util"
7
7
 
8
8
  module Instana
9
+ # Instana trace context header names
10
+ INSTANA_TRACE_ID_HEADER = 'X-Instana-T'.freeze
11
+ INSTANA_SPAN_ID_HEADER = 'X-Instana-S'.freeze
12
+ INSTANA_LEVEL_HEADER = 'X-Instana-L'.freeze
13
+
9
14
  class << self
10
15
  attr_accessor :agent
11
16
  attr_accessor :tracer
@@ -83,6 +83,10 @@ module Instana
83
83
  # W3C Trace Context Support
84
84
  @config[:w3c_trace_correlation] = ENV.fetch('INSTANA_DISABLE_W3C_TRACE_CORRELATION', nil).nil?
85
85
 
86
+ # Server-Timing response header injection (default: injected)
87
+ # Priority: YAML > env var > agent config > default (false)
88
+ read_disable_server_timing_config
89
+
86
90
  # HTTP exit span 4xx error classification (opt-in, default: off)
87
91
  # Priority: env vars > agent config > default (false / [])
88
92
  read_http_exit_config
@@ -151,6 +155,8 @@ module Instana
151
155
  read_otlp_config_from_agent(tracing_config) if should_read_from_agent?(:otlp)
152
156
  # Read HTTP exit 4xx classification from agent if env vars did not already set it
153
157
  read_http_exit_classification_from_agent(tracing_config) unless env_http_exit_classification_set?
158
+ # Read Server-Timing disable flag from agent if not already set from YAML or env
159
+ read_disable_server_timing_from_agent(tracing_config) if @config[:disable_server_timing_source] == 'default'
154
160
  # Read span filtering configuration from agent
155
161
  ::Instana.span_filtering_config&.read_config_from_agent(discovery)
156
162
  rescue => e
@@ -268,6 +274,49 @@ module Instana
268
274
 
269
275
  private
270
276
 
277
+ # Read disable-server-timing from YAML (tracing.global) or INSTANA_TRACING_DISABLE_SERVER_TIMING.
278
+ # Agent value is applied later via read_config_from_agent if neither is set.
279
+ def read_disable_server_timing_config
280
+ @config[:disable_server_timing] = false
281
+ @config[:disable_server_timing_source] = 'default'
282
+
283
+ yaml_value = parse_disable_server_timing_from_yaml
284
+ unless yaml_value.nil?
285
+ @config[:disable_server_timing] = truthy?(yaml_value)
286
+ @config[:disable_server_timing_source] = 'yaml'
287
+ return
288
+ end
289
+
290
+ env_value = ENV.fetch('INSTANA_TRACING_DISABLE_SERVER_TIMING', nil)
291
+ return if env_value.nil? || env_value.strip.empty?
292
+
293
+ @config[:disable_server_timing] = truthy?(env_value.strip)
294
+ @config[:disable_server_timing_source] = 'env'
295
+ end
296
+
297
+ # @return [Object, nil] raw YAML value of tracing.global.disable-server-timing, or nil if not set
298
+ def parse_disable_server_timing_from_yaml
299
+ config_path = ENV.fetch('INSTANA_CONFIG_PATH', nil)
300
+ return nil unless config_path && File.exist?(config_path)
301
+
302
+ yaml_content = YAML.safe_load(File.read(config_path))
303
+ tracing_config = yaml_content[TRACING_KEY] || yaml_content[LEGACY_TRACING_KEY]
304
+ global_config = tracing_config && tracing_config['global']
305
+ global_config.is_a?(Hash) ? global_config['disable-server-timing'] : nil
306
+ rescue => e
307
+ ::Instana.logger.warn("Failed to load disable-server-timing configuration from YAML: #{e.message}")
308
+ nil
309
+ end
310
+
311
+ # @param tracing_config [Hash] the tracing sub-hash from the discovery payload
312
+ def read_disable_server_timing_from_agent(tracing_config)
313
+ global_config = tracing_config['global']
314
+ return unless global_config.is_a?(Hash) && !global_config['disable-server-timing'].nil?
315
+
316
+ @config[:disable_server_timing] = truthy?(global_config['disable-server-timing'])
317
+ @config[:disable_server_timing_source] = 'agent'
318
+ end
319
+
271
320
  # Read HTTP exit 4xx error classification config (wrapper — same pattern as read_span_stack_config / read_otlp_config)
272
321
  def read_http_exit_config
273
322
  @config[:http_exit_classify_all_4xx_as_errors] = false
@@ -1,19 +1,5 @@
1
1
  # (c) Copyright IBM Corp. 2021
2
2
  # (c) Copyright Instana Inc. 2021
3
- module OpenTelemetry
4
- module Trace
5
- module Propagation
6
- module TraceContext
7
- # A TraceParent is an implementation of the W3C trace context specification
8
- # https://www.w3.org/TR/trace-context/
9
- # {Trace::SpanContext}
10
- class TraceParent
11
- REGEXP = /^(?<version>[A-Fa-f0-9]{2})-(?<trace_id>[A-Fa-f0-9]{32})-(?<span_id>[A-Fa-f0-9]{32})-(?<flags>[A-Fa-f0-9]{2})(?<ignored>-.*)?$/
12
- end
13
- end
14
- end
15
- end
16
- end
17
3
 
18
4
  module Instana
19
5
  module Instrumentation
@@ -48,11 +34,8 @@ module Instana
48
34
  job_id: job.job_id
49
35
  }
50
36
  }
51
- incoming_context = if job.arguments.is_a?(Array) && job.arguments.last.is_a?(Hash) && job.arguments.last.key?(:instana_context)
52
- instana_context = job.arguments.last[:instana_context]
53
- job.arguments.pop
54
- instana_context ? ::Instana::SpanContext.new(trace_id: instana_context[:trace_id], span_id: instana_context[:span_id]) : nil
55
- end
37
+ incoming_context = ::Instana::Instrumentation::ActiveJob.extract_context(job)
38
+
56
39
  Trace.with_span(OpenTelemetry::Trace.non_recording_span(incoming_context)) do
57
40
  ::Instana.tracer.in_span(:activejob, attributes: tags) do
58
41
  block.call
@@ -60,6 +43,38 @@ module Instana
60
43
  end
61
44
  end
62
45
  end
46
+
47
+ def self.extract_context(job)
48
+ # 1. From job arguments (Instana payload)
49
+ if job.arguments.is_a?(Array) && job.arguments.last.is_a?(Hash) && job.arguments.last.key?(:instana_context)
50
+ instana_context = job.arguments.last[:instana_context]
51
+ job.arguments.pop
52
+ return instana_context ? ::Instana::SpanContext.new(
53
+ trace_id: instana_context[:trace_id],
54
+ span_id: instana_context[:span_id],
55
+ level: instana_context[:level] || 1
56
+ ) : nil
57
+ end
58
+
59
+ # 2. From job.__otel_headers (if opentelemetry-instrumentation-active_job is active)
60
+ if job.respond_to?(:__otel_headers) && job.__otel_headers.is_a?(Hash)
61
+ headers = job.__otel_headers
62
+ tp = headers['traceparent'] || headers[:traceparent]
63
+ if tp
64
+ ctx = parse_w3c_traceparent(tp, headers['tracestate'] || headers[:tracestate])
65
+ return ctx if ctx
66
+ end
67
+ end
68
+
69
+ nil
70
+ end
71
+
72
+ def self.parse_w3c_traceparent(traceparent, tracestate)
73
+ ctx = ::Instana::Util.parse_w3c_trace_context(traceparent, tracestate)
74
+ return nil unless ctx
75
+
76
+ ::Instana::SpanContext.new(trace_id: ctx[:trace_id], span_id: ctx[:span_id], level: ctx[:level])
77
+ end
63
78
  end
64
79
  end
65
80
  end
@@ -9,15 +9,24 @@ module Instana
9
9
  def call(context)
10
10
  return @handler.call(context) unless [:invoke_async, :invoke].include?(context.operation_name)
11
11
 
12
- if context.params[:client_context].nil? && ::Instana.tracer.tracing? && context.operation_name == :invoke
12
+ if ::Instana.tracer.tracing? && context.operation_name == :invoke
13
13
  span_context = ::Instana.tracer.context
14
- payload = {
15
- 'X-INSTANA-T' => span_context.trace_id,
16
- 'X-INSTANA-S' => span_context.span_id,
17
- 'X-INSTANA-L' => span_context.level.to_s
18
- }
14
+ if span_context
15
+ trace_payload = {
16
+ 'X-INSTANA-T' => span_context.trace_id,
17
+ 'X-INSTANA-S' => span_context.span_id,
18
+ 'X-INSTANA-L' => span_context.level.to_s,
19
+ 'traceparent' => span_context.trace_parent_header,
20
+ 'tracestate' => span_context.trace_state_header
21
+ }.reject { |_, v| v.nil? || v.empty? }
19
22
 
20
- context.params[:client_context] = Base64.strict_encode64(JSON.dump(payload))
23
+ merged = if context.params[:client_context]
24
+ JSON.parse(Base64.strict_decode64(context.params[:client_context])).merge(trace_payload)
25
+ else
26
+ trace_payload
27
+ end
28
+ context.params[:client_context] = Base64.strict_encode64(JSON.dump(merged))
29
+ end
21
30
  end
22
31
 
23
32
  tags = {
@@ -14,11 +14,41 @@ module Instana
14
14
  }.reject { |_, v| v.nil? }
15
15
 
16
16
  if context.operation_name == :publish
17
- ::Instana.tracer.in_span(:sns, attributes: {sns: sns_tags}) { @handler.call(context) }
17
+ ::Instana.tracer.in_span(:sns, attributes: { sns: sns_tags }) do |_span|
18
+ if ::Instana.tracer.tracing?
19
+ inject_trace_attributes(context.params)
20
+ end
21
+ @handler.call(context)
22
+ end
23
+ elsif context.operation_name == :publish_batch
24
+ ::Instana.tracer.in_span(:sns, attributes: { sns: sns_tags }) do |_span|
25
+ if ::Instana.tracer.tracing?
26
+ context.params[:publish_batch_request_entries]&.each do |entry|
27
+ inject_trace_attributes(entry)
28
+ end
29
+ end
30
+ @handler.call(context)
31
+ end
18
32
  else
19
33
  @handler.call(context)
20
34
  end
21
35
  end
36
+
37
+ private
38
+
39
+ def inject_trace_attributes(params)
40
+ span_context = ::Instana.tracer.context
41
+ return unless span_context
42
+
43
+ params[:message_attributes] ||= {}
44
+ available_slots = 10 - params[:message_attributes].size
45
+
46
+ extra = ::Instana::Util.aws_message_attributes_w3c(span_context)
47
+ extra.merge!(::Instana::Util.aws_message_attributes_instana(span_context)) if available_slots >= 5
48
+
49
+ extra.reject! { |_, v| v[:string_value].nil? || v[:string_value].empty? }
50
+ params[:message_attributes].merge!(extra)
51
+ end
22
52
  end
23
53
 
24
54
  def add_handlers(handlers, _config)
@@ -36,11 +36,16 @@ module Instana
36
36
 
37
37
  def inject_instana_headers(span, params)
38
38
  params[:message_attributes] ||= {}
39
- params[:message_attributes].merge!({
40
- "X_INSTANA_T" => {data_type: 'String', string_value: span.context.trace_id},
41
- "X_INSTANA_S" => {data_type: 'String', string_value: span.context.span_id},
42
- "X_INSTANA_L" => {data_type: 'String', string_value: span.context.level.to_s}
43
- })
39
+ span_context = span.context
40
+ return unless span_context
41
+
42
+ available_slots = 10 - params[:message_attributes].size
43
+
44
+ extra = ::Instana::Util.aws_message_attributes_w3c(span_context)
45
+ extra.merge!(::Instana::Util.aws_message_attributes_instana(span_context)) if available_slots >= 5
46
+
47
+ extra.reject! { |_, v| v[:string_value].nil? || v[:string_value].empty? }
48
+ params[:message_attributes].merge!(extra)
44
49
  end
45
50
 
46
51
  def tags_for(operation_name, params)
@@ -20,9 +20,11 @@ module Instana
20
20
  ::Instana.tracer.in_span(:rabbitmq, attributes: kvs) do |span|
21
21
  # Inject trace context into message headers
22
22
  options[:headers] ||= {}
23
- options[:headers]['X-Instana-T'] = span.context.trace_id
24
- options[:headers]['X-Instana-S'] = span.context.span_id
25
- options[:headers]['X-Instana-L'] = span.context.level.to_s
23
+ options[:headers][INSTANA_TRACE_ID_HEADER] = span.context.trace_id
24
+ options[:headers][INSTANA_SPAN_ID_HEADER] = span.context.span_id
25
+ options[:headers][INSTANA_LEVEL_HEADER] = span.context.level.to_s
26
+ options[:headers]['traceparent'] = span.context.trace_parent_header
27
+ options[:headers]['tracestate'] = span.context.trace_state_header
26
28
 
27
29
  super(payload, options)
28
30
  end
@@ -113,13 +115,28 @@ module Instana
113
115
  return {} unless properties && properties.headers
114
116
 
115
117
  headers = properties.headers
118
+
119
+ if headers[INSTANA_TRACE_ID_HEADER]
120
+ extract_instana_context(headers)
121
+ elsif headers['traceparent']
122
+ extract_w3c_context(headers)
123
+ else
124
+ {}
125
+ end
126
+ end
127
+
128
+ def extract_instana_context(headers)
116
129
  {
117
- trace_id: headers['X-Instana-T'],
118
- span_id: headers['X-Instana-S'],
119
- level: headers['X-Instana-L']&.to_i
130
+ trace_id: headers[INSTANA_TRACE_ID_HEADER],
131
+ span_id: headers[INSTANA_SPAN_ID_HEADER],
132
+ level: headers[INSTANA_LEVEL_HEADER]&.to_i || 1
120
133
  }.reject { |_, v| v.nil? }
121
134
  end
122
135
 
136
+ def extract_w3c_context(headers)
137
+ ::Instana::Util.parse_w3c_trace_context(headers['traceparent'], headers['tracestate']) || {}
138
+ end
139
+
123
140
  def log_error(error)
124
141
  # Log errors on to console if INSTANA_DEBUG is enabled
125
142
  ::Instana.logger.debug { "#{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{error.message}" }
@@ -8,8 +8,8 @@ module Instana
8
8
  module GRPCCientInstrumentation
9
9
  CALL_TYPES = [:request_response, :client_streamer, :server_streamer, :bidi_streamer]
10
10
 
11
- CALL_TYPES.each do |call_type|
12
- define_method(call_type) do |method, *others, **options|
11
+ CALL_TYPES.each do |call_type| # rubocop:disable Metrics/BlockLength
12
+ define_method(call_type) do |method, *others, **options| # rubocop:disable Metrics/BlockLength
13
13
  begin
14
14
  kvs = { rpc: {} }
15
15
 
@@ -26,10 +26,15 @@ module Instana
26
26
 
27
27
  context = ::Instana.tracer.context
28
28
  if context
29
- options[:metadata] = (options[:metadata] || {}).merge(
29
+ extra = {
30
30
  'x-instana-t' => context.trace_id_header,
31
- 'x-instana-s' => context.span_id_header
32
- )
31
+ 'x-instana-s' => context.span_id_header,
32
+ 'x-instana-l' => context.level.to_s,
33
+ 'traceparent' => context.trace_parent_header,
34
+ 'tracestate' => context.trace_state_header
35
+ }
36
+ extra.reject! { |_, v| v.nil? || v.empty? }
37
+ options[:metadata] = (options[:metadata] || {}).merge(extra)
33
38
  end
34
39
 
35
40
  super(method, *others, **options)
@@ -58,12 +63,7 @@ module Instana
58
63
  kvs = { rpc: {} }
59
64
  metadata = active_call.metadata
60
65
 
61
- incoming_context = nil
62
- if metadata.key?('x-instana-t')
63
- incoming_context = SpanContext.new(trace_id: ::Instana::Util.header_to_id(metadata['x-instana-t']),
64
- span_id: metadata.key?('x-instana-s') ? ::Instana::Util.header_to_id(metadata['x-instana-s']) : nil,
65
- level: metadata.key?('x-instana-l') ? metadata['x-instana-l'] : nil)
66
- end
66
+ incoming_context = extract_grpc_context(metadata)
67
67
 
68
68
  kvs[:rpc][:flavor] = :grpc
69
69
  kvs[:rpc][:host] = Socket.gethostname
@@ -87,6 +87,34 @@ module Instana
87
87
  end
88
88
  end
89
89
  end
90
+
91
+ private
92
+
93
+ def extract_grpc_context(metadata)
94
+ if metadata.key?('x-instana-t')
95
+ extract_instana_grpc_context(metadata)
96
+ elsif metadata.key?('traceparent')
97
+ extract_w3c_grpc_context(metadata)
98
+ end
99
+ end
100
+
101
+ def extract_instana_grpc_context(metadata)
102
+ SpanContext.new(
103
+ trace_id: ::Instana::Util.header_to_id(metadata['x-instana-t']),
104
+ span_id: metadata.key?('x-instana-s') ? ::Instana::Util.header_to_id(metadata['x-instana-s']) : nil,
105
+ level: metadata['x-instana-l'] || 1
106
+ )
107
+ end
108
+
109
+ def extract_w3c_grpc_context(metadata)
110
+ ctx = ::Instana::Util.parse_w3c_trace_context(metadata['traceparent'], metadata['tracestate'])
111
+ return nil unless ctx
112
+
113
+ SpanContext.new(trace_id: ctx[:trace_id],
114
+ span_id: ctx[:span_id],
115
+ level: ctx[:level],
116
+ baggage: ctx.slice(:external_trace_id, :external_trace_flags, :external_state))
117
+ end
90
118
  end
91
119
  end
92
120
  end
@@ -32,19 +32,14 @@ module Instana
32
32
 
33
33
  context[:level] = @env['HTTP_X_INSTANA_L'][0] if @env['HTTP_X_INSTANA_L']
34
34
 
35
- unless ::Instana.config[:w3c_trace_correlation]
36
- trace_state = parse_trace_state
37
-
38
- if context[:from_w3c] && trace_state.empty?
39
- context.delete(:span_id)
40
- context[:from_w3c] = false
41
- elsif context[:from_w3c] && !trace_state.empty?
42
- context[:trace_id] = trace_state[:t]
43
- context[:span_id] = trace_state[:p]
44
- context[:from_w3c] = false
45
- end
35
+ # Honour W3C sampled bit when no X-Instana-L is present.
36
+ if context[:level].nil? && context[:external_trace_flags]
37
+ w3c_sampled = (context[:external_trace_flags].to_i(16) & 0x01) == 1
38
+ context[:level] = w3c_sampled ? '1' : '0'
46
39
  end
47
40
 
41
+ apply_legacy_w3c_override(context) unless ::Instana.config[:w3c_trace_correlation]
42
+
48
43
  context
49
44
  end
50
45
 
@@ -107,6 +102,21 @@ module Instana
107
102
 
108
103
  private
109
104
 
105
+ def apply_legacy_w3c_override(context)
106
+ return unless context[:from_w3c]
107
+
108
+ trace_state = parse_trace_state
109
+
110
+ if trace_state.empty?
111
+ context.delete(:span_id)
112
+ else
113
+ context[:trace_id] = trace_state[:t]
114
+ context[:span_id] = trace_state[:p]
115
+ end
116
+
117
+ context[:from_w3c] = false
118
+ end
119
+
110
120
  def context_from_instana_headers
111
121
  sanitized_t = ::Instana::Util.header_to_id(@env['HTTP_X_INSTANA_T'])
112
122
  sanitized_s = ::Instana::Util.header_to_id(@env['HTTP_X_INSTANA_S'])
@@ -139,6 +139,8 @@ module Instana
139
139
  end
140
140
 
141
141
  headers['Traceparent'] = trace_context.trace_parent_header
142
+ return if ::Instana.config[:disable_server_timing]
143
+
142
144
  headers['Server-Timing'] = "intid;desc=#{trace_context.trace_id_header}"
143
145
  end
144
146
  end
@@ -79,7 +79,8 @@ module Instana
79
79
  context_from_wire = job.payload['args'].pop
80
80
  ::Instana::SpanContext.new(
81
81
  trace_id: context_from_wire['trace_id'],
82
- span_id: context_from_wire['span_id']
82
+ span_id: context_from_wire['span_id'],
83
+ level: context_from_wire['level'] || 1
83
84
  )
84
85
  end
85
86
  span = OpenTelemetry::Trace.non_recording_span(trace_context) if trace_context
@@ -15,24 +15,45 @@ module Instana
15
15
  }
16
16
 
17
17
  context = incomming_context_from(sqs_message.message_attributes)
18
- instana_context = Instana::SpanContext.new(trace_id: context[:trace_id], span_id: context[:span_id], level: context[:level])
19
- Trace.with_span(OpenTelemetry::Trace.non_recording_span(instana_context)) do
20
- ::Instana.tracer.in_span(:sqs, attributes: {sqs: sqs_tags}, &block)
18
+ if context[:trace_id]
19
+ instana_context = Instana::SpanContext.new(
20
+ trace_id: context[:trace_id],
21
+ span_id: context[:span_id],
22
+ level: context[:level] || 1
23
+ )
24
+ Trace.with_span(OpenTelemetry::Trace.non_recording_span(instana_context)) do
25
+ ::Instana.tracer.in_span(:sqs, attributes: { sqs: sqs_tags }, &block)
26
+ end
27
+ else
28
+ ::Instana.tracer.in_span(:sqs, attributes: { sqs: sqs_tags }, &block)
21
29
  end
22
30
  end
23
31
 
24
32
  private
25
33
 
26
34
  def incomming_context_from(attributes)
27
- trace_id = read_message_header(attributes, 'X_INSTANA_T')
28
- span_id = read_message_header(attributes, 'X_INSTANA_S')
29
- level = read_message_header(attributes, 'X_INSTANA_L')
30
-
31
- {
32
- trace_id: trace_id,
33
- span_id: span_id,
34
- level: level
35
- }.reject { |_, v| v.nil? }
35
+ return {} unless attributes
36
+
37
+ if read_message_header(attributes, 'X_INSTANA_T')
38
+ trace_id = read_message_header(attributes, 'X_INSTANA_T')
39
+ span_id = read_message_header(attributes, 'X_INSTANA_S')
40
+ level = read_message_header(attributes, 'X_INSTANA_L')
41
+
42
+ {
43
+ trace_id: trace_id,
44
+ span_id: span_id,
45
+ level: level ? level.to_i : 1
46
+ }.reject { |_, v| v.nil? }
47
+ else
48
+ w3c_context_from(attributes)
49
+ end
50
+ end
51
+
52
+ def w3c_context_from(attributes)
53
+ ::Instana::Util.parse_w3c_trace_context(
54
+ read_message_header(attributes, 'traceparent'),
55
+ read_message_header(attributes, 'tracestate')
56
+ ) || {}
36
57
  end
37
58
 
38
59
  def read_message_header(attributes, key)
@@ -31,6 +31,7 @@ module Instana
31
31
  if context
32
32
  msg['X-Instana-T'] = context.trace_id_header
33
33
  msg['X-Instana-S'] = context.span_id_header
34
+ msg['X-Instana-L'] = context.level.to_s
34
35
  end
35
36
 
36
37
  result = yield
@@ -30,10 +30,12 @@ module Instana
30
30
  trace_context = nil
31
31
  if msg.key?('X-Instana-T')
32
32
  trace_id = msg.delete('X-Instana-T')
33
- span_id = msg.delete('X-Instana-S')
33
+ span_id = msg.delete('X-Instana-S')
34
+ level = msg.delete('X-Instana-L')
34
35
  trace_context = ::Instana::SpanContext.new(
35
36
  trace_id: ::Instana::Util.header_to_id(trace_id),
36
- span_id: span_id ? ::Instana::Util.header_to_id(span_id) : nil
37
+ span_id: span_id ? ::Instana::Util.header_to_id(span_id) : nil,
38
+ level: level || 1
37
39
  )
38
40
  end
39
41
 
@@ -68,7 +68,7 @@ module Instana
68
68
  end
69
69
 
70
70
  def to_hash
71
- { :trace_id => @trace_id, :span_id => @span_id }
71
+ { :trace_id => @trace_id, :span_id => @span_id, :level => @level }
72
72
  end
73
73
 
74
74
  def valid?
data/lib/instana/util.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  # (c) Copyright Instana Inc. 2016
3
3
 
4
4
  module Instana
5
- module Util
5
+ module Util # rubocop:disable Metrics/ModuleLength
6
6
  class << self
7
7
  ID_RANGE = -2**63..2**63-1
8
8
  # Retrieves and returns the source code for any ruby
@@ -171,6 +171,44 @@ module Instana
171
171
  given
172
172
  end
173
173
 
174
+ # Parse W3C +traceparent+ / +tracestate+ values using the OpenTelemetry
175
+ # trace-context propagator.
176
+ #
177
+ # When the tracestate carries an Instana entry (+in=<trace>;<span>+) it
178
+ # takes precedence over the ids in the traceparent.
179
+ #
180
+ # @param traceparent [String] the traceparent header value
181
+ # @param tracestate [String, nil] the tracestate header value
182
+ #
183
+ # @return [Hash, nil] +nil+ when the traceparent is missing or invalid,
184
+ # otherwise a hash with:
185
+ # - +:trace_id+, +:span_id+, +:level+ for continuing the trace
186
+ # - +:external_trace_id+, +:external_trace_flags+, +:external_state+
187
+ # for re-emitting the W3C context downstream
188
+ #
189
+ def parse_w3c_trace_context(traceparent, tracestate = nil)
190
+ return nil unless traceparent.is_a?(String)
191
+
192
+ carrier = { 'traceparent' => traceparent, 'tracestate' => tracestate.is_a?(String) ? tracestate : nil }
193
+ extracted = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator.extract(
194
+ carrier, context: OpenTelemetry::Context.empty
195
+ )
196
+ span_context = OpenTelemetry::Trace.current_span(extracted).context
197
+ return nil unless span_context.valid?
198
+
199
+ sampled = span_context.trace_flags.sampled?
200
+ instana_state = span_context.tracestate.value('in')&.match(/\A(?<trace>[0-9a-f]+);(?<span>[0-9a-f]+)\z/)
201
+
202
+ {
203
+ trace_id: header_to_id(instana_state ? instana_state[:trace] : span_context.hex_trace_id[16..]),
204
+ span_id: header_to_id(instana_state ? instana_state[:span] : span_context.hex_span_id),
205
+ level: sampled ? 1 : 0,
206
+ external_trace_id: span_context.hex_trace_id,
207
+ external_trace_flags: sampled ? '01' : '00',
208
+ external_state: tracestate
209
+ }
210
+ end
211
+
174
212
  def timeout_timestamp
175
213
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
176
214
  end
@@ -229,6 +267,35 @@ module Instana
229
267
 
230
268
  klass.name.sub('Net::HTTP', '').gsub(/([A-Z])/, ' \1').strip
231
269
  end
270
+
271
+ # Build the W3C trace-context message attributes for an AWS message
272
+ # (SQS or SNS). Returns a hash suitable for merging into
273
+ # +params[:message_attributes]+.
274
+ #
275
+ # @param span_context [Instana::SpanContext]
276
+ # @return [Hash]
277
+ #
278
+ def aws_message_attributes_w3c(span_context)
279
+ {
280
+ 'traceparent' => { data_type: 'String', string_value: span_context.trace_parent_header },
281
+ 'tracestate' => { data_type: 'String', string_value: span_context.trace_state_header }
282
+ }
283
+ end
284
+
285
+ # Build the Instana proprietary message attributes for an AWS message
286
+ # (SQS or SNS). Returns a hash suitable for merging into
287
+ # +params[:message_attributes]+.
288
+ #
289
+ # @param span_context [Instana::SpanContext]
290
+ # @return [Hash]
291
+ #
292
+ def aws_message_attributes_instana(span_context)
293
+ {
294
+ 'X_INSTANA_T' => { data_type: 'String', string_value: span_context.trace_id },
295
+ 'X_INSTANA_S' => { data_type: 'String', string_value: span_context.span_id },
296
+ 'X_INSTANA_L' => { data_type: 'String', string_value: span_context.level.to_s }
297
+ }
298
+ end
232
299
  end
233
300
  end
234
301
  end
@@ -2,6 +2,6 @@
2
2
  # (c) Copyright Instana Inc. 2016
3
3
 
4
4
  module Instana
5
- VERSION = "2.10.0"
5
+ VERSION = "2.11.0"
6
6
  VERSION_FULL = "instana-#{VERSION}"
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: instana
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.10.0
4
+ version: 2.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Giacomo Lombardo
@@ -416,7 +416,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
416
416
  - !ruby/object:Gem::Version
417
417
  version: '0'
418
418
  requirements: []
419
- rubygems_version: 4.0.22
419
+ rubygems_version: 4.0.23
420
420
  specification_version: 4
421
421
  summary: Ruby Distributed Tracing & Metrics Sensor for Instana
422
422
  test_files: []