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
Original file line number Diff line number Diff line change
Expand Up @@ -642,14 +642,19 @@ static ResponseHeaderTagClassifier create(AgentSpan span, Map<String, String> he

private final AgentSpan span;
private final Map<String, String> headerTags;
private final String wildcardHeaderPrefix;

public ResponseHeaderTagClassifier(AgentSpan span, Map<String, String> headerTags) {
this.span = span;
this.headerTags = headerTags;
this.wildcardHeaderPrefix = this.headerTags.get("*");
}

@Override
public boolean accept(String key, String value) {
if (wildcardHeaderPrefix != null) {
span.setTag((wildcardHeaderPrefix + key).toLowerCase(Locale.ROOT), value);
}
String mappedKey = headerTags.get(key.toLowerCase(Locale.ROOT));
if (mappedKey != null) {
span.setTag(mappedKey, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import datadog.trace.api.gateway.Flow
import datadog.trace.api.gateway.InstrumentationGateway
import datadog.trace.api.gateway.RequestContext
import datadog.trace.api.gateway.RequestContextSlot
import datadog.trace.api.TraceConfig
import datadog.trace.bootstrap.ActiveSubsystems
import datadog.trace.bootstrap.instrumentation.api.AgentPropagation
import datadog.trace.bootstrap.instrumentation.api.AgentSpan
Expand Down Expand Up @@ -34,6 +35,17 @@ class HttpServerDecoratorTest extends ServerDecoratorTest {

def span = Mock(AgentSpan)

static class MapCarrierVisitor
implements AgentPropagation.ContextVisitor<Map> {
@Override
void forEachKey(Map carrier, AgentPropagation.KeyClassifier classifier) {
Map<String, String> headers = carrier.headers
headers?.each {
classifier.accept(it.key, it.value)
}
}
}

boolean origAppSecActive

void setup() {
Expand Down Expand Up @@ -350,12 +362,45 @@ class HttpServerDecoratorTest extends ServerDecoratorTest {
null | null | false
}

def "test response headers with trace.header.tags"() {
setup:
def traceConfig = Mock(TraceConfig)
traceConfig.getResponseHeaderTags() >> headerTags

def tags = [:]

def responseSpan = Mock(AgentSpan)
responseSpan.traceConfig() >> traceConfig
responseSpan.setTag(_, _) >> { String k, String v ->
tags[k] = v
return responseSpan
}

def decorator = newDecorator(null, new MapCarrierVisitor())

when:
decorator.onResponse(responseSpan, resp)

then:
if (expectedTag){
expectedTag.each {
assert tags[it.key] == it.value
}
}

where:
headerTags | resp | expectedTag
[:] | [status: 200, headers: ['X-Custom-Header': 'custom-value', 'Content-Type': 'application/json']] | [:]
["x-custom-header": "abc"] | [status: 200, headers: ['X-Custom-Header': 'custom-value', 'Content-Type': 'application/json']] | [abc:"custom-value"]
["*": "datadog.response.headers."] | [status: 200, headers: ['X-Custom-Header': 'custom-value', 'Content-Type': 'application/json']] | ["datadog.response.headers.x-custom-header":"custom-value", "datadog.response.headers.content-type":"application/json"]
}

@Override
def newDecorator() {
return newDecorator(null)
return newDecorator(null, null)
}

def newDecorator(TracerAPI tracer) {
def newDecorator(TracerAPI tracer, AgentPropagation.ContextVisitor<Map> contextVisitor) {
if (!tracer) {
tracer = AgentTracer.NOOP_TRACER
}
Expand Down Expand Up @@ -383,7 +428,7 @@ class HttpServerDecoratorTest extends ServerDecoratorTest {

@Override
protected AgentPropagation.ContextVisitor<Map> responseGetter() {
return null
return contextVisitor
}

@Override
Expand Down Expand Up @@ -449,7 +494,7 @@ class HttpServerDecoratorTest extends ServerDecoratorTest {
getUniversalCallbackProvider() >> cbpAppSec // no iast callbacks, so this is equivalent
getDataStreamsMonitoring() >> Mock(DataStreamsMonitoring)
}
def decorator = newDecorator(mTracer)
def decorator = newDecorator(mTracer, null)

when:
decorator.startSpan("test", headers, null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,12 @@ private static void loadMapWithOptionalMapping(
"Illegal tag starting with non letter for key '" + key + "'");
}
} else {
// If wildcard exists, we do not allow other header mappings
if (key.charAt(0) == '*') {
map.clear();
map.put(key, defaultPrefix);
return;
}
if (Character.isLetter(key.charAt(0))) {
value = defaultPrefix + Strings.normalizedHeaderTag(key);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ class ConfigTest extends DDSpecification {
private static final DD_JMXFETCH_METRICS_CONFIGS_ENV = "DD_JMXFETCH_METRICS_CONFIGS"
private static final DD_TRACE_AGENT_PORT_ENV = "DD_TRACE_AGENT_PORT"
private static final DD_AGENT_PORT_LEGACY_ENV = "DD_AGENT_PORT"
private static final DD_TRACE_HEADER_TAGS = "DD_TRACE_HEADER_TAGS"
private static final DD_TRACE_REPORT_HOSTNAME = "DD_TRACE_REPORT_HOSTNAME"
private static final DD_RUNTIME_METRICS_ENABLED_ENV = "DD_RUNTIME_METRICS_ENABLED"
private static final DD_TRACE_LONG_RUNNING_ENABLED = "DD_TRACE_EXPERIMENTAL_LONG_RUNNING_ENABLED"
Expand Down Expand Up @@ -568,6 +569,7 @@ class ConfigTest extends DDSpecification {
environmentVariables.set(DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH, "42")
environmentVariables.set(DD_TRACE_LONG_RUNNING_ENABLED, "true")
environmentVariables.set(DD_TRACE_LONG_RUNNING_FLUSH_INTERVAL, "81")
environmentVariables.set(DD_TRACE_HEADER_TAGS, "*")

when:
def config = new Config()
Expand All @@ -587,6 +589,8 @@ class ConfigTest extends DDSpecification {
config.xDatadogTagsMaxLength == 42
config.isLongRunningTraceEnabled()
config.getLongRunningTraceFlushInterval() == 81
config.requestHeaderTags == ["*":"http.request.headers."]
config.responseHeaderTags == ["*":"http.response.headers."]
}

def "sys props override env vars"() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,32 +135,36 @@ class ConfigConverterTest extends DDSpecification {

def "test parseMapWithOptionalMappings"() {
when:
def result = ConfigConverter.parseMapWithOptionalMappings(mapString, "test", "", lowercaseKeys)
def result = ConfigConverter.parseMapWithOptionalMappings(mapString, "test", defaultPrefix, lowercaseKeys)

then:
result == expected

where:
mapString | expected | lowercaseKeys
"header1:one,header2:two" | [header1: "one", header2: "two"] | false
"header1:one, header2:two" | [header1: "one", header2: "two"] | false
"header1,header2:two" | [header1: "header1", header2: "two"] | false
"Header1:one,header2:two" | [header1: "one", header2: "two"] | true
"\"header1:one,header2:two\"" | ["\"header1": "one", header2: "two\""] | true
"header1" | [header1: "header1"] | true
",header1:tag" | [header1: "tag"] | true
"header1:tag," | [header1: "tag"] | true
"header:tag:value" | [header: "tag:value"] | true
"" | [:] | true
null | [:] | true

mapString | expected | lowercaseKeys | defaultPrefix
"header1:one,header2:two" | [header1: "one", header2: "two"] | false | ""
"header1:one, header2:two" | [header1: "one", header2: "two"] | false | ""
"header1,header2:two" | [header1: "header1", header2: "two"] | false | ""
"Header1:one,header2:two" | [header1: "one", header2: "two"] | true | ""
"\"header1:one,header2:two\"" | ["\"header1": "one", header2: "two\""] | true | ""
"header1" | [header1: "header1"] | true | ""
",header1:tag" | [header1: "tag"] | true | ""
"header1:tag," | [header1: "tag"] | true | ""
"header:tag:value" | [header: "tag:value"] | true | ""
"" | [:] | true | ""
null | [:] | true | ""
// Test for wildcard header tags
"*" | ["*":"datadog.response.headers."] | true | "datadog.response.headers"
"*:" | [:] | true | "datadog.response.headers"
"*,header1:tag" | ["*":"datadog.response.headers."] | true | "datadog.response.headers"
"header1:tag,*" | ["*":"datadog.response.headers."] | true | "datadog.response.headers"
// logs warning: Illegal key only tag starting with non letter '1header'
"1header,header2:two" | [:] | true
"1header,header2:two" | [:] | true | ""
// logs warning: Illegal tag starting with non letter for key 'header'
"header::tag" | [:] | true
"header::tag" | [:] | true | ""
// logs warning: Illegal empty key at position 0
":tag" | [:] | true
":tag" | [:] | true | ""
// logs warning: Illegal empty key at position 11
"header:tag,:tag" | [:] | true
"header:tag,:tag" | [:] | true | ""
}
}