-
Notifications
You must be signed in to change notification settings - Fork 375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add:HTTP header tagging with DD_TRACE_HEADER_TAGS for servers #2935
Changes from 9 commits
ae2ce15
e9e22d4
00c2ade
4127438
56a2624
1768690
11b6106
501bae1
534a8ef
5bedbd7
d99f097
bbb38d7
99daebc
2c817e6
0babd5f
132c4f9
f24d359
414d918
e3d6318
adc6c70
a01cde6
7119f2a
5456ac4
6d808b5
1929f83
d627531
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# frozen_string_literal: true | ||
|
||
module Datadog | ||
module Tracing | ||
module Configuration | ||
module HTTP | ||
# Datadog tracing supports capturing HTTP request and response headers as span tags. | ||
# | ||
# The provided configuration String for this feature has to be pre-processed to | ||
# allow for ease of utilization by each HTTP integration. | ||
# | ||
# This class process configuration, stores the result, and provides methods to | ||
# utilize this configuration. | ||
class HeaderTags | ||
# @param header_tags [Array<String>] The list of strings from DD_TRACE_HEADER_TAGS. | ||
def initialize(header_tags) | ||
@request_headers = {} | ||
@response_headers = {} | ||
@header_tags = header_tags || EMPTY | ||
|
||
@header_tags.each do |header_tag| | ||
header, tag = header_tag.split(':', 2) | ||
|
||
next unless header # Empty string guard | ||
|
||
if tag && !tag.empty? | ||
# When a custom tag name is provided, use that name for both | ||
# request and response tags. | ||
normalized_tag = Tracing::Metadata::Ext::HTTP::Headers.to_tag(tag, allow_nested: true) | ||
request = response = normalized_tag | ||
else | ||
# Otherwise, use our internal pattern of | ||
# "http.{request|response}.headers.{header}" as tag name. | ||
request = Tracing::Metadata::Ext::HTTP::RequestHeaders.to_tag(header) | ||
response = Tracing::Metadata::Ext::HTTP::ResponseHeaders.to_tag(header) | ||
end | ||
|
||
@request_headers[header] = request | ||
@response_headers[header] = response | ||
end | ||
end | ||
|
||
# Receives a case insensitive hash with the request headers and returns | ||
# a list of tag names and values that can be set in a span. | ||
def request_tags(headers) | ||
@request_headers.map do |header_name, span_tag| | ||
# Case-insensitive search. {RequestHeaderCollection} already ensures case-insensitiveness. | ||
header_value = headers[header_name] | ||
|
||
[span_tag, header_value] if header_value | ||
end.compact | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure, do you think the reduce easier to read? @request_headers.reduce([]) do |array, (header_name, span_tag)|
# Case-insensitive search
header_value = headers[header_name]
array << [span_tag, header_value] if header_value
array
end |
||
end | ||
|
||
# Receives a case insensitive hash with the response headers and returns | ||
# a list of tag names and values that can be set in a span. | ||
def response_tags(headers) | ||
@response_headers.map do |header_name, span_tag| | ||
# Case-insensitive search | ||
header_value = headers[header_name] | ||
|
||
[span_tag, header_value] if header_value | ||
end.compact | ||
end | ||
|
||
# Returns false if this class was explicitly configured | ||
# or left without configuration. | ||
def configured? | ||
!@header_tags.equal?(EMPTY) | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We would be able to remove this logic once we have #2970 in master There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yeah, I think so! 👍 |
||
|
||
# For easy configuration inspection, | ||
# print the original configuration setting. | ||
def to_s | ||
@header_tags.join(',').to_s | ||
end | ||
|
||
# Pin to know if we are using a fallback empty list. | ||
EMPTY = [].freeze | ||
private_constant :EMPTY | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# frozen_string_literal: true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code was moved here from Sinatra already had helpers to perform header matching for Rack-style headers. Because the |
||
|
||
module Datadog | ||
module Tracing | ||
module Contrib | ||
module Rack | ||
# Matches Rack-style headers with a matcher and sets matching headers into a span. | ||
module HeaderTagging | ||
def self.tag_request_headers(span, env, configuration) | ||
headers = Header::RequestHeaderCollection.new(env) | ||
|
||
# Use global DD_TRACE_HEADER_TAGS if integration-level configuration is not provided | ||
tags = if configuration.using_default?(:headers) && Datadog.configuration.tracing.header_tags.configured? | ||
marcotc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Datadog.configuration.tracing.header_tags.request_tags(headers) | ||
else | ||
whitelist = configuration[:headers][:request] || [] | ||
whitelist.each_with_object({}) do |header, result| | ||
header_value = headers.get(header) | ||
unless header_value.nil? | ||
header_tag = Tracing::Metadata::Ext::HTTP::RequestHeaders.to_tag(header) | ||
result[header_tag] = header_value | ||
end | ||
end | ||
end | ||
|
||
span.set_tags(tags) | ||
end | ||
|
||
def self.tag_response_headers(span, headers, configuration) | ||
# Use global DD_TRACE_HEADER_TAGS if integration-level configuration is not provided | ||
tags = if configuration.using_default?(:headers) && Datadog.configuration.tracing.header_tags.configured? | ||
Datadog.configuration.tracing.header_tags.response_tags( | ||
Core::Utils::Hash::CaseInsensitiveWrapper.new(headers) | ||
) | ||
else | ||
whitelist = configuration[:headers][:response] || [] | ||
whitelist.each_with_object({}) do |header, result| | ||
if headers.key?(header) | ||
result[Tracing::Metadata::Ext::HTTP::ResponseHeaders.to_tag(header)] = headers[header] | ||
else | ||
# Try a case-insensitive lookup | ||
uppercased_header = header.to_s.upcase | ||
matching_header = headers.keys.find { |h| h.upcase == uppercased_header } | ||
if matching_header | ||
result[Tracing::Metadata::Ext::HTTP::ResponseHeaders.to_tag(header)] = headers[matching_header] | ||
end | ||
marcotc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
end | ||
end | ||
|
||
span.set_tags(tags) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a01cde6 we can get rid of
EMPTY
now 😄There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This suggestion kind of worked, but I need to fix a small thing in
Options
for it to work: #2994There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I rebased this PR on that fix, but everything here remains unchanged.