instana 2.9.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.
@@ -11,15 +11,36 @@ module Instana
11
11
  module Otlp
12
12
  # Converter for messaging spans to OTLP format
13
13
  class MessagingConverter < BaseConverter
14
- # Build OTel-compliant span name for messaging (RabbitMQ) spans
14
+ # Build OTel-compliant span name for messaging spans
15
15
  #
16
- # Formula per SPAN_NAME_PATTERNS.txt Section 3 (bunny/AMQP):
17
- # publish → "{exchange} publish" (or "{queue} publish" when no exchange)
18
- # receive → "{queue} receive"
16
+ # Formula per SPAN_NAME_PATTERNS.txt Section 3 (bunny/AMQP / Kafka):
17
+ # RabbitMQ publish → "{exchange} publish" (or "{queue} publish" when no exchange)
18
+ # RabbitMQ receive → "{queue} receive"
19
+ # Kafka → "{service} {access}" (e.g., "orders send")
19
20
  #
20
21
  # @return [String] The span name
21
22
  def span_name
22
- rabbitmq_data = span[:data]&.[](:rabbitmq) || {}
23
+ if (rabbitmq_data = span[:data]&.[](:rabbitmq))
24
+ rabbitmq_span_name(rabbitmq_data)
25
+ elsif (kafka_data = span[:data]&.[](:kafka))
26
+ kafka_span_name(kafka_data)
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def convert_attributes
33
+ attributes = {}
34
+
35
+ convert_rabbitmq_attributes(attributes, span[:data]&.[](:rabbitmq))
36
+ convert_kafka_attributes(attributes, span[:data]&.[](:kafka))
37
+
38
+ attributes
39
+ end
40
+
41
+ private
42
+
43
+ def rabbitmq_span_name(rabbitmq_data)
23
44
  sort = rabbitmq_data[:sort].to_s
24
45
 
25
46
  if sort == 'publish'
@@ -32,26 +53,51 @@ module Instana
32
53
  end
33
54
  end
34
55
 
35
- def convert_attributes
36
- attributes = {}
56
+ def kafka_span_name(kafka_data)
57
+ service = kafka_data[:service].to_s.strip
58
+ access = kafka_data[:access].to_s.strip
59
+ parts = [service, access].reject(&:empty?)
60
+ parts.empty? ? 'kafka' : parts.join(' ')
61
+ end
37
62
 
38
- rabbitmq_data = span[:data]&.[](:rabbitmq)
39
- if rabbitmq_data
40
- add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_SYSTEM, 'rabbitmq')
41
- add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_DESTINATION_NAME,
42
- rabbitmq_destination_name(rabbitmq_data))
43
- add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY, rabbitmq_data[:key])
44
- add_attribute(attributes, 'messaging.rabbitmq.queue', rabbitmq_data[:queue])
45
- add_attribute(attributes, OpenTelemetry::SemConv::SERVER::SERVER_ADDRESS, rabbitmq_data[:address])
46
-
47
- operation = rabbitmq_data[:sort] == 'publish' ? 'send' : 'receive'
48
- add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_OPERATION_TYPE, operation)
49
- end
63
+ def convert_rabbitmq_attributes(attributes, rabbitmq_data)
64
+ return unless rabbitmq_data
50
65
 
51
- attributes
66
+ add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_SYSTEM, 'rabbitmq')
67
+ add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_DESTINATION_NAME,
68
+ rabbitmq_destination_name(rabbitmq_data))
69
+ add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY, rabbitmq_data[:key])
70
+ add_attribute(attributes, 'messaging.rabbitmq.queue', rabbitmq_data[:queue])
71
+ add_attribute(attributes, OpenTelemetry::SemConv::SERVER::SERVER_ADDRESS, extract_host(rabbitmq_data[:address]))
72
+ add_attribute(attributes, OpenTelemetry::SemConv::SERVER::SERVER_PORT, extract_port(rabbitmq_data[:address]))
73
+ add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_MESSAGE_BODY_SIZE, rabbitmq_data[:size])
74
+
75
+ operation = rabbitmq_data[:sort] == 'publish' ? 'send' : 'receive'
76
+ add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_OPERATION_TYPE, operation)
77
+ add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_OPERATION_NAME, rabbitmq_data[:sort])
52
78
  end
53
79
 
54
- private
80
+ def convert_kafka_attributes(attributes, kafka_data)
81
+ return unless kafka_data
82
+
83
+ add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_SYSTEM, 'kafka')
84
+ add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_DESTINATION_NAME, kafka_data[:service])
85
+ add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_OPERATION_NAME, kafka_data[:access])
86
+ add_attribute(attributes, OpenTelemetry::SemConv::Incubating::MESSAGING::MESSAGING_OPERATION_TYPE, kafka_data[:access])
87
+ end
88
+
89
+ def extract_host(address)
90
+ return nil unless address
91
+
92
+ address.to_s.split(':').first
93
+ end
94
+
95
+ def extract_port(address)
96
+ return nil unless address
97
+
98
+ port = address.to_s.split(':').last
99
+ port.to_i if port =~ /^\d+$/
100
+ end
55
101
 
56
102
  # Build the composite destination name per spec:
57
103
  # Producer (publish): "{exchange}:{key}" — omit absent parts
@@ -6,6 +6,7 @@ require_relative 'base_converter'
6
6
  require 'opentelemetry/semconv/incubating/rpc'
7
7
  require 'opentelemetry/semconv/incubating/code'
8
8
  require 'opentelemetry/semconv/server'
9
+ require 'opentelemetry/semconv/network'
9
10
 
10
11
  module Instana
11
12
  module Exporter
@@ -48,10 +49,13 @@ module Instana
48
49
 
49
50
  private
50
51
 
52
+ # gRPC status code mapping: 0 = OK for success, otherwise derive from error
53
+ GRPC_STATUS_CODE_OK = 0
54
+
51
55
  # Convert gRPC span attributes
52
56
  def convert_grpc_attributes(attributes, rpc_data)
53
57
  # RPC system
54
- add_attribute(attributes, OpenTelemetry::SemConv::Incubating::RPC::RPC_SYSTEM, 'grpc')
58
+ add_attribute(attributes, Instana::Exporter::Otlp::RPC_SYSTEM_ATTR, 'grpc')
55
59
 
56
60
  # RPC service and method
57
61
  if rpc_data[:call]
@@ -60,9 +64,20 @@ module Instana
60
64
  add_attribute(attributes, OpenTelemetry::SemConv::Incubating::RPC::RPC_METHOD, method)
61
65
  end
62
66
 
63
- # Network peer
67
+ # Server host and port
64
68
  add_attribute(attributes, OpenTelemetry::SemConv::SERVER::SERVER_ADDRESS, rpc_data[:host])
65
- add_attribute(attributes, OpenTelemetry::SemConv::SERVER::SERVER_ADDRESS, rpc_data.dig(:peer, :address))
69
+ add_attribute(attributes, OpenTelemetry::SemConv::SERVER::SERVER_PORT, parse_port(rpc_data[:port]))
70
+
71
+ # Network peer address (peer.address → network.peer.address)
72
+ peer = rpc_data[:peer]
73
+ if peer
74
+ add_attribute(attributes, OpenTelemetry::SemConv::NETWORK::NETWORK_PEER_ADDRESS, peer[:address])
75
+ add_attribute(attributes, OpenTelemetry::SemConv::NETWORK::NETWORK_PEER_PORT, parse_port(peer[:port]))
76
+ end
77
+
78
+ # gRPC status code (integer): 0 = OK for success spans
79
+ grpc_status = grpc_status_code(rpc_data)
80
+ add_attribute(attributes, OpenTelemetry::SemConv::Incubating::RPC::RPC_GRPC_STATUS_CODE, grpc_status)
66
81
 
67
82
  # gRPC-specific attributes
68
83
  add_attribute(attributes, 'rpc.grpc.call_type', rpc_data[:call_type])
@@ -71,7 +86,7 @@ module Instana
71
86
  # Convert ActionCable span attributes
72
87
  def convert_action_cable_attributes(attributes, rpc_data)
73
88
  # RPC system
74
- add_attribute(attributes, OpenTelemetry::SemConv::Incubating::RPC::RPC_SYSTEM, 'actioncable')
89
+ add_attribute(attributes, Instana::Exporter::Otlp::RPC_SYSTEM_ATTR, 'actioncable')
75
90
 
76
91
  # ActionCable-specific attributes
77
92
  add_attribute(attributes, 'rails.actioncable.channel', rpc_data[:call])
@@ -96,6 +111,51 @@ module Instana
96
111
 
97
112
  [parts[1], parts[2]]
98
113
  end
114
+
115
+ # Parse port value to integer, returns nil if invalid or out of range
116
+ def parse_port(port)
117
+ return nil unless port
118
+
119
+ int_port = port.to_i
120
+ int_port if int_port >= 0 && int_port <= 65_535
121
+ end
122
+
123
+ # Map to gRPC status code integer.
124
+ # For successful spans (ec = 0) → 0 (OK).
125
+ # For error spans with a recognized gRPC status string in the error → map it.
126
+ # Otherwise → nil (omit the attribute).
127
+ GRPC_STATUS_CODES = {
128
+ 'OK' => 0,
129
+ 'CANCELLED' => 1,
130
+ 'UNKNOWN' => 2,
131
+ 'INVALID_ARGUMENT' => 3,
132
+ 'DEADLINE_EXCEEDED' => 4,
133
+ 'NOT_FOUND' => 5,
134
+ 'ALREADY_EXISTS' => 6,
135
+ 'PERMISSION_DENIED' => 7,
136
+ 'RESOURCE_EXHAUSTED' => 8,
137
+ 'FAILED_PRECONDITION' => 9,
138
+ 'ABORTED' => 10,
139
+ 'OUT_OF_RANGE' => 11,
140
+ 'UNIMPLEMENTED' => 12,
141
+ 'INTERNAL' => 13,
142
+ 'UNAVAILABLE' => 14,
143
+ 'DATA_LOSS' => 15,
144
+ 'UNAUTHENTICATED' => 16
145
+ }.freeze
146
+
147
+ def grpc_status_code(rpc_data)
148
+ error_count = span[:ec].to_i
149
+ return GRPC_STATUS_CODE_OK if error_count.zero?
150
+
151
+ # Attempt to extract gRPC status string from error message
152
+ error_msg = rpc_data[:error].to_s
153
+ GRPC_STATUS_CODES.each do |name, code|
154
+ return code if error_msg.include?(name)
155
+ end
156
+
157
+ GRPC_STATUS_CODES['UNKNOWN']
158
+ end
99
159
  end
100
160
  end
101
161
  end
@@ -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