Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/instana/instrumentation/instrumented_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ def context_from_instana_headers
long_instana_id: long_instana_id? ? sanitized_t : nil,
external_trace_id: external_trace_id,
external_state: @env['HTTP_TRACESTATE'],
external_trace_flags: context_from_trace_parent[:external_trace_flags],
from_w3c: false
}.reject { |_, v| v.nil? }
end
Expand All @@ -140,6 +141,7 @@ def context_from_trace_parent
external_state: @env['HTTP_TRACESTATE'],
trace_id: trace_id,
span_id: span_id,
external_trace_flags: matches['flags'],
from_w3c: true
}
end
Expand Down
3 changes: 2 additions & 1 deletion lib/instana/instrumentation/net-http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ def request(*args, &block)
# without a backtrace (no exception)
current_span.record_exception(nil)
end

extra_headers = ::Instana::Util.extra_header_tags(response)&.merge(::Instana::Util.extra_header_tags(request))
kv_payload[:http][:header] = extra_headers unless extra_headers&.empty?
response
rescue => e
current_span&.record_exception(e)
Expand Down
152 changes: 86 additions & 66 deletions lib/instana/instrumentation/rack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def initialize(app)
@app = app
end

def call(env) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
def call(env)
req = InstrumentedRequest.new(env)
kvs = {
http: req.request_tags
Expand All @@ -26,75 +26,14 @@ def call(env) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLengt
@trace_token = OpenTelemetry::Context.attach(trace_ctx)
status, headers, response = @app.call(env)

if ::Instana.tracer.tracing?
unless req.correlation_data.empty?
current_span[:crid] = req.correlation_data[:id]
current_span[:crtp] = req.correlation_data[:type]
end

if !req.instana_ancestor.empty? && req.continuing_from_trace_parent?
current_span[:ia] = req.instana_ancestor
end

if req.continuing_from_trace_parent?
current_span[:tp] = true
end

if req.external_trace_id?
current_span[:lt] = req.external_trace_id
end

if req.synthetic?
current_span[:sy] = true
end

# In case some previous middleware returned a string status, make sure that we're dealing with
# an integer. In Ruby nil.to_i, "asdfasdf".to_i will always return 0 from Ruby versions 1.8.7 and newer.
# So if an 0 status is reported here, it indicates some other issue (e.g. no status from previous middleware)
# See Rack Spec: https://www.rubydoc.info/github/rack/rack/file/SPEC#label-The+Status
kvs[:http][:status] = status.to_i

if status.to_i >= 500
# Because of the 5xx response, we flag this span as errored but
# without a backtrace (no exception)
::Instana.tracer.log_error(nil)
end

# If the framework instrumentation provides a path template,
# pass it into the span here.
# See: https://www.instana.com/docs/tracing/custom-best-practices/#path-templates-visual-grouping-of-http-endpoints
kvs[:http][:path_tpl] = env['INSTANA_HTTP_PATH_TEMPLATE'] if env['INSTANA_HTTP_PATH_TEMPLATE']

# Save the span context before the trace ends so we can place
# them in the response headers in the ensure block
trace_context = ::Instana.tracer.current_span.context
end

trace_context = process_span_tags(req, current_span, kvs, status, env) if ::Instana.tracer.tracing?
merge_response_headers(kvs, headers)
[status, headers, response]
rescue Exception => e
current_span.record_exception(e) if ::Instana.tracer.tracing?
raise
ensure
if ::Instana.tracer.tracing?
if headers
# Set response headers; encode as hex string
if trace_context.active?
headers['X-Instana-T'] = trace_context.trace_id_header
headers['X-Instana-S'] = trace_context.span_id_header
headers['X-Instana-L'] = '1'

headers['Tracestate'] = trace_context.trace_state_header
else
headers['X-Instana-L'] = '0'
end

headers['Traceparent'] = trace_context.trace_parent_header
headers['Server-Timing'] = "intid;desc=#{trace_context.trace_id_header}"
end
current_span.set_tags(kvs)
OpenTelemetry::Context.detach(@trace_token) if @trace_token
current_span.finish
end
finalize_trace(current_span, kvs, headers, trace_context) if ::Instana.tracer.tracing?
end

private
Expand All @@ -112,7 +51,8 @@ def extract_trace_context(incoming_context)
level: incoming_context[:level],
baggage: {
external_trace_id: incoming_context[:external_trace_id],
external_state: incoming_context[:external_state]
external_state: incoming_context[:external_state],
external_trace_flags: incoming_context[:external_trace_flags]
}
)
end
Expand All @@ -121,5 +61,85 @@ def extract_trace_context(incoming_context)
end
parent_context
end

def process_span_tags(req, current_span, kvs, status, env)
add_correlation_data(req, current_span)
add_trace_parent_data(req, current_span)
add_status_and_error(kvs, status)
add_path_template(kvs, env)

# Save the span context before the trace ends so we can place
# them in the response headers in the ensure block
::Instana.tracer.current_span.context
end

def add_correlation_data(req, current_span)
return if req.correlation_data.empty?

current_span[:crid] = req.correlation_data[:id]
current_span[:crtp] = req.correlation_data[:type]
end

def add_trace_parent_data(req, current_span)
if !req.instana_ancestor.empty? && req.continuing_from_trace_parent?
current_span[:ia] = req.instana_ancestor
end

current_span[:tp] = true if req.continuing_from_trace_parent?
current_span[:lt] = req.external_trace_id if req.external_trace_id?
current_span[:sy] = true if req.synthetic?
end

def add_status_and_error(kvs, status)
# In case some previous middleware returned a string status, make sure that we're dealing with
# an integer. In Ruby nil.to_i, "asdfasdf".to_i will always return 0 from Ruby versions 1.8.7 and newer.
# So if an 0 status is reported here, it indicates some other issue (e.g. no status from previous middleware)
# See Rack Spec: https://www.rubydoc.info/github/rack/rack/file/SPEC#label-The+Status
kvs[:http][:status] = status.to_i

return unless status.to_i >= 500

# Because of the 5xx response, we flag this span as errored but
# without a backtrace (no exception)
::Instana.tracer.log_error(nil)
end

def add_path_template(kvs, env)
# If the framework instrumentation provides a path template,
# pass it into the span here.
# See: https://www.instana.com/docs/tracing/custom-best-practices/#path-templates-visual-grouping-of-http-endpoints
kvs[:http][:path_tpl] = env['INSTANA_HTTP_PATH_TEMPLATE'] if env['INSTANA_HTTP_PATH_TEMPLATE']
end

def merge_response_headers(kvs, headers)
extra_response_headers = ::Instana::Util.extra_header_tags(headers)
if kvs[:http][:header].nil?
kvs[:http][:header] = extra_response_headers
else
kvs[:http][:header].merge!(extra_response_headers)
end
end

def finalize_trace(current_span, kvs, headers, trace_context)
set_response_headers(headers, trace_context) if headers
current_span.set_tags(kvs)
OpenTelemetry::Context.detach(@trace_token) if @trace_token
current_span.finish
end

def set_response_headers(headers, trace_context)
# Set response headers; encode as hex string
if trace_context.active?
headers['X-Instana-T'] = trace_context.trace_id_header
headers['X-Instana-S'] = trace_context.span_id_header
headers['X-Instana-L'] = '1'
headers['Tracestate'] = trace_context.trace_state_header
else
headers['X-Instana-L'] = '0'
end

headers['Traceparent'] = trace_context.trace_parent_header
headers['Server-Timing'] = "intid;desc=#{trace_context.trace_id_header}"
end
end
end
12 changes: 10 additions & 2 deletions lib/instana/trace/span_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,16 @@ def span_id_header
def trace_parent_header
trace = (@baggage[:external_trace_id] || trace_id_header).rjust(32, '0')
parent = span_id_header.rjust(16, '0')
flags = @level == 1 ? "03" : "02"

flags = if @baggage[:external_trace_flags]
# Parse external flags as 8-bit hex, clear LSB, then set LSB based on level
external_flags = @baggage[:external_trace_flags].to_i(16) & 0xFE # Clear LSB
combined_flags = external_flags | (@level == 1 ? 1 : 0) # Set LSB based on level
combined_flags = [combined_flags, 0xFF].min # Cap at 8-bit max
format('%02x', combined_flags)
else
@level == 1 ? "03" : "02"
end
flags = "03" if flags > "03"
"00-#{trace}-#{parent}-#{flags}"
end

Expand Down
13 changes: 13 additions & 0 deletions lib/instana/util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,19 @@ def maybe_timeout(timeout, start_time)
timeout -= (timeout_timestamp - start_time)
timeout.positive? ? timeout : 0
end

def extra_header_tags(req_res_headers)
return {} if req_res_headers.nil?
return nil unless ::Instana.agent.extra_headers

headers = {}

::Instana.agent.extra_headers.each do |custom_header|
headers[custom_header.to_sym] = req_res_headers[custom_header] if req_res_headers.key?(custom_header)
end

headers
end
end
end
end
3 changes: 3 additions & 0 deletions test/instrumentation/rack_instrumented_request_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def test_incoming_w3c_context
external_state: nil,
trace_id: 'a3ce929d0e0e4736',
span_id: '00f067aa0ba902b7',
external_trace_flags: "01",
from_w3c: true
}

Expand All @@ -86,6 +87,7 @@ def test_incoming_w3c_context_newer_version_additional_fields
external_state: nil,
trace_id: 'a3ce929d0e0e4736',
span_id: '00f067aa0ba902b7',
external_trace_flags: "01",
from_w3c: true
}

Expand All @@ -103,6 +105,7 @@ def test_incoming_w3c_context_unknown_flags
external_state: nil,
trace_id: 'a3ce929d0e0e4736',
span_id: '00f067aa0ba902b7',
external_trace_flags: "ff",
from_w3c: true
}

Expand Down
Loading