Skip to content
Draft
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 @@ -6,6 +6,9 @@
import static datadog.trace.bootstrap.instrumentation.decorator.http.HttpResourceDecorator.HTTP_RESOURCE_DECORATOR;

import datadog.appsec.api.blocking.BlockingException;
import datadog.context.Context;
import datadog.context.ContextKey;
import datadog.context.ContextScope;
import datadog.trace.api.Config;
import datadog.trace.api.DDTags;
import datadog.trace.api.InstrumenterConfig;
Expand All @@ -23,6 +26,7 @@
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes;
import datadog.trace.bootstrap.instrumentation.api.OtelHttpSemantics;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import datadog.trace.bootstrap.instrumentation.api.URIUtils;
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
Expand All @@ -38,17 +42,31 @@ public abstract class HttpClientDecorator<REQUEST, RESPONSE> extends UriBasedCli

private static final Logger log = LoggerFactory.getLogger(HttpClientDecorator.class);

private static final ContextKey<AgentSpan> CLIENT_SPAN_KEY =
ContextKey.named("dd-http-client-span");

private static final String DATADOG_META_LANG_HEADER_NAME = "Datadog-Meta-Lang";
private static final String DD_CLIENT_LIBRARY_LANGUAGE_HEADER_NAME = "DD-Client-Library-Language";

private static final BitSet CLIENT_ERROR_STATUSES = Config.get().getHttpClientErrorStatuses();
private static final boolean OTEL_SEMANTICS = Config.get().isTraceOtelSemanticsEnabled();

private static final UTF8BytesString DEFAULT_RESOURCE_NAME = UTF8BytesString.create("/");

private static final boolean CLIENT_TAG_HEADERS = Config.get().isHttpClientTagHeaders();

private static final boolean APPSEC_RASP_ENABLED = Config.get().isAppSecRaspEnabled();

/** Marks the active HTTP client span so lower-level transports can avoid duplicating it. */
public static ContextScope suppressNestedClientSpans(final AgentSpan span) {
return Context.current().with(CLIENT_SPAN_KEY, span).attach();
}

/** Returns whether {@code span} owns the HTTP request represented by {@code context}. */
public static boolean isNestedClientSpanSuppressed(final Context context, final AgentSpan span) {
return context != null && span != null && context.get(CLIENT_SPAN_KEY) == span;
}

protected abstract String method(REQUEST request);

protected abstract URI url(REQUEST request) throws URISyntaxException;
Expand Down Expand Up @@ -118,7 +136,14 @@ protected void doOnRequest(final AgentSpan span, final REQUEST request) {
DSM_TRANSACTION_SOURCE_READER);

String method = method(request);
span.setTag(Tags.HTTP_METHOD, method);
if (OTEL_SEMANTICS) {
OtelHttpSemantics.setRequestMethod(span, method);
if (shouldSetResourceName()) {
OtelHttpSemantics.setMethodResourceName(span, method);
}
} else {
span.setTag(Tags.HTTP_METHOD, method);
}

if (CLIENT_TAG_HEADERS) {
for (Map.Entry<String, String> headerTag :
Expand All @@ -135,19 +160,28 @@ protected void doOnRequest(final AgentSpan span, final REQUEST request) {
final URI url = url(request);
if (url != null) {
onURI(span, url);
span.setTag(
Tags.HTTP_URL,
URIUtils.lazyValidURL(url.getScheme(), url.getHost(), url.getPort(), url.getPath()));
if (Config.get().isHttpClientTagQueryString()) {
span.setTag(DDTags.HTTP_QUERY, url.getQuery());
span.setTag(DDTags.HTTP_FRAGMENT, url.getFragment());
if (OTEL_SEMANTICS) {
OtelHttpSemantics.setClientUrl(span, url);
// url.full may trigger URLAsResourceNameRule; restore the OTel span name afterwards.
if (shouldSetResourceName()) {
OtelHttpSemantics.setMethodResourceName(span, method);
}
} else {
span.setTag(
Tags.HTTP_URL,
URIUtils.lazyValidURL(
url.getScheme(), url.getHost(), url.getPort(), url.getPath()));
if (Config.get().isHttpClientTagQueryString()) {
span.setTag(DDTags.HTTP_QUERY, url.getQuery());
span.setTag(DDTags.HTTP_FRAGMENT, url.getFragment());
}
}
if (shouldSetResourceName()) {
if (!OTEL_SEMANTICS && shouldSetResourceName()) {
HTTP_RESOURCE_DECORATOR.withClientPath(span, method, url.getPath());
}
// SSRF exploit prevention check
onHttpClientRequest(span, url.toString());
} else if (shouldSetResourceName()) {
} else if (!OTEL_SEMANTICS && shouldSetResourceName()) {
span.setResourceName(DEFAULT_RESOURCE_NAME);
}
} catch (final BlockingException e) {
Expand Down Expand Up @@ -177,6 +211,9 @@ protected void doOnResponse(final AgentSpan span, final RESPONSE response) {
span.setHttpStatusCode(status);
if (CLIENT_ERROR_STATUSES.get(status)) {
span.setError(true);
if (OTEL_SEMANTICS) {
OtelHttpSemantics.setErrorType(span, status);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import datadog.context.propagation.Propagators;
import datadog.trace.api.Config;
import datadog.trace.api.DDTags;
import datadog.trace.api.KnownTags;
import datadog.trace.api.datastreams.DataStreamsTransactionExtractor;
import datadog.trace.api.datastreams.DataStreamsTransactionTracker;
import datadog.trace.api.function.TriConsumer;
Expand All @@ -34,6 +35,7 @@
import datadog.trace.bootstrap.instrumentation.api.ClientIpAddressData;
import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities;
import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes;
import datadog.trace.bootstrap.instrumentation.api.OtelHttpSemantics;
import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities;
import datadog.trace.bootstrap.instrumentation.api.TagContext;
import datadog.trace.bootstrap.instrumentation.api.Tags;
Expand Down Expand Up @@ -68,13 +70,15 @@ public abstract class HttpServerDecorator<REQUEST, CONNECTION, RESPONSE, REQUEST
public static final String DD_IGNORE_COMMIT_ATTRIBUTE = "datadog.commit.ignore";

private static final UTF8BytesString DEFAULT_RESOURCE_NAME = UTF8BytesString.create("/");
private static final boolean OTEL_SEMANTICS = Config.get().isTraceOtelSemanticsEnabled();
protected static final UTF8BytesString NOT_FOUND_RESOURCE_NAME = UTF8BytesString.create("404");
protected static final boolean SHOULD_SET_404_RESOURCE_NAME =
Config.get().isRuleEnabled("URLAsResourceNameRule")
!OTEL_SEMANTICS
&& Config.get().isRuleEnabled("URLAsResourceNameRule")
&& Config.get().isRuleEnabled("Status404Rule")
&& Config.get().isRuleEnabled("Status404Decorator");
private static final boolean SHOULD_SET_URL_RESOURCE_NAME =
Config.get().isRuleEnabled("URLAsResourceNameRule");
!OTEL_SEMANTICS && Config.get().isRuleEnabled("URLAsResourceNameRule");

private static final BitSet SERVER_ERROR_STATUSES = Config.get().getHttpServerErrorStatuses();
private static final String DEFAULT_INSTRUMENTATION_NAME = "http-server";
Expand Down Expand Up @@ -287,7 +291,9 @@ protected void doOnRequest(
// - peer.ipv4
// - peer.ipv6
final boolean shouldTagIps =
config.isClientIpEnabled() || traceClientIpResolverEnabled && APPSEC_ACTIVE;
OTEL_SEMANTICS
|| config.isClientIpEnabled()
|| traceClientIpResolverEnabled && APPSEC_ACTIVE;
// Whether to stash IP data for later tagging or not.
// AI Guard requires client IP tags on the local root span when an ai_guard span is created.
// Resolve the IPs eagerly but do not tag the span yet; stash them on the request context so
Expand Down Expand Up @@ -329,7 +335,12 @@ protected void doOnRequest(

if (request != null) {
String method = method(request);
span.setTag(Tags.HTTP_METHOD, method);
if (OTEL_SEMANTICS) {
OtelHttpSemantics.setRequestMethod(span, method);
OtelHttpSemantics.setMethodResourceName(span, method);
} else {
span.setTag(Tags.HTTP_METHOD, method);
}

// Copy of HttpClientDecorator url handling
try {
Expand All @@ -339,23 +350,59 @@ protected void doOnRequest(
boolean encoded = supportsRaw && config.isHttpServerRawResource();
boolean valid = url.isValid();
String path = encoded ? url.rawPath() : url.path();
if (valid) {
span.setTag(
Tags.HTTP_URL, URIUtils.lazyValidURL(url.scheme(), url.host(), url.port(), path));
} else if (supportsRaw) {
span.setTag(Tags.HTTP_URL, URIUtils.lazyInvalidUrl(url.raw()));
}
if (extracted != null && extracted.getXForwardedHost() != null) {
span.setTag(Tags.HTTP_HOSTNAME, extracted.getXForwardedHost());
} else if (url.host() != null) {
span.setTag(Tags.HTTP_HOSTNAME, url.host());
if (OTEL_SEMANTICS) {
String scheme =
OtelHttpSemantics.forwardedValue(
extracted == null ? null : extracted.getXForwardedProto());
if (scheme == null) {
scheme = url.scheme();
}
if (valid) {
if (scheme != null) {
span.setTag(KnownTags.URL_SCHEME_NAME, scheme);
}
// Ensure that path components have a leading '/'
if (path == null || path.isEmpty()) {
path = "/";
} else if (path.charAt(0) != '/') {
path = "/" + path;
}
span.setTag(KnownTags.URL_PATH_NAME, path);
} else if (supportsRaw) {
span.setTag(KnownTags.URL_PATH_NAME, url.raw());
}
OtelHttpSemantics.setServerAddressAndPort(
span,
extracted == null ? null : extracted.getXForwardedHost(),
extracted == null ? null : extracted.getXForwardedPort(),
scheme,
url.host(),
url.port());
} else {
if (valid) {
span.setTag(
Tags.HTTP_URL, URIUtils.lazyValidURL(url.scheme(), url.host(), url.port(), path));
} else if (supportsRaw) {
span.setTag(Tags.HTTP_URL, URIUtils.lazyInvalidUrl(url.raw()));
}
if (extracted != null && extracted.getXForwardedHost() != null) {
span.setTag(Tags.HTTP_HOSTNAME, extracted.getXForwardedHost());
} else if (url.host() != null) {
span.setTag(Tags.HTTP_HOSTNAME, url.host());
}
}

if (valid && config.isHttpServerTagQueryString()) {
if (valid && (OTEL_SEMANTICS || config.isHttpServerTagQueryString())) {
String query =
supportsRaw && config.isHttpServerRawQueryString() ? url.rawQuery() : url.query();
span.setTag(DDTags.HTTP_QUERY, query);
span.setTag(DDTags.HTTP_FRAGMENT, url.fragment());
if (OTEL_SEMANTICS) {
if (query != null && !query.isEmpty()) {
span.setTag(KnownTags.URL_QUERY_NAME, query);
}
} else {
span.setTag(DDTags.HTTP_QUERY, query);
span.setTag(DDTags.HTTP_FRAGMENT, url.fragment());
}
}
Flow<Void> flow = callIGCallbackURI(span, url, method);
if (flow.getAction() instanceof RequestBlockingAction) {
Expand Down Expand Up @@ -422,6 +469,9 @@ protected void doOnRequest(
}
if (shouldTagIps) {
span.setTag(Tags.NETWORK_CLIENT_IP, peerIp);
if (OTEL_SEMANTICS && span.getTag(KnownTags.HTTP_CLIENT_IP_OTEL_NAME) == null) {
span.setTag(KnownTags.HTTP_CLIENT_IP_OTEL_NAME, peerIp);
}
}
}
if (shouldStashIps && (peerIp != null || inferredAddressStr != null)) {
Expand Down Expand Up @@ -484,6 +534,9 @@ protected void doOnResponseStatus(final AgentSpan span, final int status) {
if (!BlockingException.class.getName().equals(span.getTag("error.type"))) {
span.setError(SERVER_ERROR_STATUSES.get(status), ErrorPriorities.HTTP_SERVER_DECORATOR);
}
if (OTEL_SEMANTICS && SERVER_ERROR_STATUSES.get(status)) {
OtelHttpSemantics.setErrorType(span, status);
}
}

if (SHOULD_SET_404_RESOURCE_NAME && status == 404) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,36 @@
import datadog.context.Context;
import datadog.context.propagation.CarrierSetter;
import datadog.trace.api.Config;
import datadog.trace.api.KnownTags;
import datadog.trace.api.datastreams.DataStreamsContext;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.OtelHttpSemantics;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import java.net.URI;
import javax.annotation.Nonnull;

public abstract class UriBasedClientDecorator extends ClientDecorator {
private static final boolean OTEL_SEMANTICS = Config.get().isTraceOtelSemanticsEnabled();

public void onURI(@Nonnull final AgentSpan span, @Nonnull final URI uri) {
String host = uri.getHost();
int port = uri.getPort();
if (null != host && !host.isEmpty()) {
span.setTag(Tags.PEER_HOSTNAME, host);
if (OTEL_SEMANTICS) {
span.setTag(KnownTags.SERVER_ADDRESS_NAME, host);
int serverPort = OtelHttpSemantics.serverPort(uri);
if (serverPort > 0) {
span.setTag(KnownTags.SERVER_PORT_NAME, serverPort);
}
} else {
span.setTag(Tags.PEER_HOSTNAME, host);
int port = uri.getPort();
if (port > 0) {
setPeerPort(span, port);
}
}
if (Config.get().isHttpClientSplitByDomain() && host.charAt(0) >= 'A') {
span.setServiceName(host, component());
}
if (port > 0) {
setPeerPort(span, port);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package datadog.trace.bootstrap.instrumentation.decorator;

import datadog.trace.api.Config;
import datadog.trace.api.cache.DDCache;
import datadog.trace.api.cache.DDCaches;
import datadog.trace.api.naming.SpanNaming;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes;
import datadog.trace.bootstrap.instrumentation.api.OtelHttpSemantics;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
import java.net.URI;
Expand All @@ -16,6 +18,7 @@
import org.slf4j.LoggerFactory;

public class UrlConnectionDecorator extends UriBasedClientDecorator {
private static final boolean OTEL_SEMANTICS = Config.get().isTraceOtelSemanticsEnabled();
private static final DDCache<String, CharSequence> CACHE = DDCaches.newFixedSizeCache(16);

private static final Function<String, CharSequence> ADDER =
Expand Down Expand Up @@ -51,7 +54,11 @@ protected String service() {
@Override
public void onURI(@Nonnull AgentSpan span, @Nonnull URI uri) {
super.onURI(span, uri);
span.setTag(Tags.HTTP_URL, uri.toString());
if (OTEL_SEMANTICS) {
OtelHttpSemantics.setClientUrl(span, uri);
} else {
span.setTag(Tags.HTTP_URL, uri.toString());
}
}

public void onURL(@Nonnull final AgentSpan span, @Nonnull final URL url) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import datadog.trace.api.Config;
import datadog.trace.api.normalize.HttpResourceNames;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.OtelHttpSemantics;
import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import datadog.trace.bootstrap.instrumentation.api.URIUtils;
Expand All @@ -12,18 +13,27 @@ public class HttpResourceDecorator {
public static final HttpResourceDecorator HTTP_RESOURCE_DECORATOR = new HttpResourceDecorator();

private static final UTF8BytesString DEFAULT_RESOURCE_NAME = UTF8BytesString.create("/");
private static final boolean OTEL_SEMANTICS = Config.get().isTraceOtelSemanticsEnabled();

private final boolean shouldSetUrlResourceName =
Config.get().isRuleEnabled("URLAsResourceNameRule");

private HttpResourceDecorator() {}

public final void withClientPath(AgentSpan span, CharSequence method, CharSequence path) {
HttpResourceNames.setForClient(span, method, path, false);
if (OTEL_SEMANTICS) {
OtelHttpSemantics.setMethodResourceName(span, method);
} else {
HttpResourceNames.setForClient(span, method, path, false);
}
}

public final void withServerPath(
AgentSpan span, CharSequence method, CharSequence path, boolean encoded) {
if (OTEL_SEMANTICS) {
OtelHttpSemantics.setMethodResourceName(span, method);
return;
}
if (!shouldSetUrlResourceName) {
span.setResourceName(DEFAULT_RESOURCE_NAME);
return;
Expand All @@ -44,7 +54,11 @@ public final void withRoute(
routeTag = URIUtils.decode(route.toString());
}
span.setTag(Tags.HTTP_ROUTE, routeTag);
if (Config.get().isHttpServerRouteBasedNaming()) {
if (OTEL_SEMANTICS) {
final CharSequence resourceName =
HttpResourceNames.join(OtelHttpSemantics.spanNameMethod(method), routeTag);
span.setResourceName(resourceName, ResourceNamePriorities.HTTP_FRAMEWORK_ROUTE);
} else if (Config.get().isHttpServerRouteBasedNaming()) {
final CharSequence resourceName = HttpResourceNames.join(method, route);
span.setResourceName(resourceName, ResourceNamePriorities.HTTP_FRAMEWORK_ROUTE);
}
Expand Down
Loading
Loading