diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java index 3119ecd2fe5..77a88a46619 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java @@ -27,6 +27,7 @@ import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities; +import datadog.trace.bootstrap.instrumentation.api.InferredProxyContext; import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities; import datadog.trace.bootstrap.instrumentation.api.TagContext; @@ -128,6 +129,7 @@ public Context extract(REQUEST_CARRIER carrier) { public AgentSpan startSpan( String instrumentationName, REQUEST_CARRIER carrier, AgentSpanContext.Extracted context) { + AgentSpan span = tracer() .startSpan(instrumentationName, spanName(), callIGCallbackStart(context)) @@ -144,6 +146,12 @@ public AgentSpan startSpan( } public AgentSpan startSpan(REQUEST_CARRIER carrier, Context context) { + if (Config.get().isTraceInferredProxyServicesEnabled()){ + InferredProxyContext inferredContext = InferredProxyContext.fromContext(context); + if (Context.root() != inferredContext){ + return startInferredProxySpan("http-server", carrier, inferredContext, context); + } + } return startSpan("http-server", carrier, getExtractedSpanContext(context)); } @@ -152,6 +160,11 @@ public AgentSpanContext.Extracted getExtractedSpanContext(Context context) { return extractedSpan == null ? null : (AgentSpanContext.Extracted) extractedSpan.context(); } + public AgentSpan startInferredProxySpan(String instrumentationName, REQUEST_CARRIER carrier, InferredProxyContext inferredContext, Context context){ +// AgentSpan span = tracer().startSpan(instrumentationName, ) + return null; + } + public AgentSpan onRequest( final AgentSpan span, final CONNECTION connection, diff --git a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java index ab052ca6707..2623eb9eb11 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java @@ -84,6 +84,7 @@ public final class ConfigDefaults { new LinkedHashSet<>(asList(PropagationStyle.DATADOG)); static final int DEFAULT_TRACE_BAGGAGE_MAX_ITEMS = 64; static final int DEFAULT_TRACE_BAGGAGE_MAX_BYTES = 8192; + static final boolean DEFAULT_TRACE_INFERRED_PROXY_SERVICES_ENABLED = false; static final boolean DEFAULT_JMX_FETCH_ENABLED = true; static final boolean DEFAULT_TRACE_AGENT_V05_ENABLED = false; diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java index d817c88666e..5bc49039407 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java @@ -99,6 +99,9 @@ public final class TracerConfig { public static final String TRACE_BAGGAGE_MAX_ITEMS = "trace.baggage.max.items"; public static final String TRACE_BAGGAGE_MAX_BYTES = "trace.baggage.max.bytes"; + public static final String TRACE_INFERRED_PROXY_SERVICES_ENABLED = + "trace.inferred.proxy.services.enabled"; + public static final String ENABLE_TRACE_AGENT_V05 = "trace.agent.v0.5.enabled"; public static final String CLIENT_IP_ENABLED = "trace.client-ip.enabled"; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java index 28ba0832e30..1b8b85b5829 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java @@ -7,6 +7,7 @@ import static datadog.trace.api.TracePropagationBehaviorExtract.IGNORE; import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.BAGGAGE_CONCERN; import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.DSM_CONCERN; +import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.INFERRED_PROXY_CONCERN; import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.TRACING_CONCERN; import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.XRAY_TRACING_CONCERN; import static datadog.trace.common.metrics.MetricsAggregatorFactory.createMetricsAggregator; @@ -77,6 +78,7 @@ import datadog.trace.common.writer.WriterFactory; import datadog.trace.common.writer.ddintake.DDIntakeTraceInterceptor; import datadog.trace.context.TraceScope; +import datadog.trace.core.apigw.InferredProxyPropagator; import datadog.trace.core.baggage.BaggagePropagator; import datadog.trace.core.datastreams.DataStreamsMonitoring; import datadog.trace.core.datastreams.DefaultDataStreamsMonitoring; @@ -720,6 +722,9 @@ private CoreTracer( && config.getTracePropagationBehaviorExtract() != IGNORE) { Propagators.register(BAGGAGE_CONCERN, new BaggagePropagator(config)); } + if (config.isTraceInferredProxyServicesEnabled()) { + Propagators.register(INFERRED_PROXY_CONCERN, new InferredProxyPropagator()); + } this.tagInterceptor = null == tagInterceptor ? new TagInterceptor(new RuleFlags(config)) : tagInterceptor; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/apigw/InferredProxyPropagator.java b/dd-trace-core/src/main/java/datadog/trace/core/apigw/InferredProxyPropagator.java new file mode 100644 index 00000000000..42533abf441 --- /dev/null +++ b/dd-trace-core/src/main/java/datadog/trace/core/apigw/InferredProxyPropagator.java @@ -0,0 +1,105 @@ +package datadog.trace.core.apigw; + +import datadog.context.Context; +import datadog.context.propagation.CarrierSetter; +import datadog.context.propagation.CarrierVisitor; +import datadog.context.propagation.Propagator; +import datadog.trace.bootstrap.instrumentation.api.InferredProxyContext; +import datadog.trace.core.CoreTracer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.function.BiConsumer; + +public class InferredProxyPropagator implements Propagator { + private static final Logger log = LoggerFactory.getLogger(InferredProxyPropagator.class); + static final String INFERRED_PROXY_KEY = "x-dd-proxy"; + static final String REQUEST_TIME_KEY = "x-dd-proxy-request-time-ms"; + static final String DOMAIN_NAME_KEY = "x-dd-proxy-domain-name"; + static final String HTTP_METHOD_KEY = "x-dd-proxy-httpmethod"; + static final String PATH_KEY = "x-dd-proxy-path"; + static final String STAGE_KEY = "x-dd-proxy-stage"; + + /** + * METHOD STUB: InferredProxy is currently not meant to be injected to downstream services + * + * @param context the context containing the values to be injected. + * @param carrier the instance that will receive the key/value pairs to propagate. + * @param setter the callback to set key/value pairs into the carrier. + */ + @Override + public void inject(Context context, C carrier, CarrierSetter setter) {} + + /** + * Extracts an InferredProxyContext from un upstream service and stores it as part of the Context object + * + * @param context the base context to store the extracted values on top, use {@link + * Context#root()} for a default base context. + * @param carrier the instance to fetch the propagated key/value pairs from. + * @param visitor the callback to walk over the carrier and extract its key/value pais. + * @return A context with the extracted values on top of the given base context. + */ + @Override + public Context extract(Context context, C carrier, CarrierVisitor visitor) { + if (context == null || carrier == null || visitor == null) { + return context; + } + InferredProxyContextExtractor extractor = new InferredProxyContextExtractor(); + visitor.forEachKeyValue(carrier, extractor); + + InferredProxyContext extractedContext = extractor.extractedContext; + // Mandatory headers for APIGW + if (extractedContext == null || !extractedContext.validContext()) { + return context; + } + return context.with(extractedContext); + } + + public static class InferredProxyContextExtractor implements BiConsumer { + private InferredProxyContext extractedContext; + + InferredProxyContextExtractor() {} + + /** + * Performs this operation on the given arguments. + * + * @param key the first input argument from an http header + * @param value the second input argument from an http header + */ + @Override + public void accept(String key, String value) { + if (key == null || key.isEmpty() || !key.startsWith(INFERRED_PROXY_KEY)) { + return; + } + + if (extractedContext == null) { + extractedContext = new InferredProxyContext(); + } + + switch (key) { + case INFERRED_PROXY_KEY: + if (value.equals("aws.apigateway")){ + extractedContext.setProxyName(value); + } + break; + case REQUEST_TIME_KEY: + extractedContext.setStartTime(value); + break; + case DOMAIN_NAME_KEY: + extractedContext.setDomainName(value); + break; + case HTTP_METHOD_KEY: + extractedContext.setHttpMethod(value); + break; + case PATH_KEY: + extractedContext.setPath(value); + break; + case STAGE_KEY: + extractedContext.setStage(value); + break; + default: + log.info("Extracting Inferred Proxy header that doesn't match accepted Inferred Proxy keys"); + } + } + } +} diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index dc1b70139d1..921c4f944f3 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -154,6 +154,7 @@ import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_CLOUD_PAYLOAD_TAGGING_SERVICES; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_EXPERIMENTAL_FEATURES_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_HTTP_RESOURCE_REMOVE_TRAILING_SLASH; +import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_INFERRED_PROXY_SERVICES_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_KEEP_LATENCY_THRESHOLD_MS; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_LONG_RUNNING_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_LONG_RUNNING_FLUSH_INTERVAL; @@ -597,6 +598,7 @@ import static datadog.trace.api.config.TracerConfig.TRACE_HTTP_RESOURCE_REMOVE_TRAILING_SLASH; import static datadog.trace.api.config.TracerConfig.TRACE_HTTP_SERVER_ERROR_STATUSES; import static datadog.trace.api.config.TracerConfig.TRACE_HTTP_SERVER_PATH_RESOURCE_NAME_MAPPING; +import static datadog.trace.api.config.TracerConfig.TRACE_INFERRED_PROXY_SERVICES_ENABLED; import static datadog.trace.api.config.TracerConfig.TRACE_KEEP_LATENCY_THRESHOLD_MS; import static datadog.trace.api.config.TracerConfig.TRACE_LONG_RUNNING_ENABLED; import static datadog.trace.api.config.TracerConfig.TRACE_LONG_RUNNING_FLUSH_INTERVAL; @@ -823,6 +825,7 @@ public static String getHostName() { private final boolean tracePropagationExtractFirst; private final int traceBaggageMaxItems; private final int traceBaggageMaxBytes; + private final boolean traceInferredProxyServicesEnabled; private final int clockSyncPeriod; private final boolean logsInjectionEnabled; @@ -1717,6 +1720,8 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins configProvider.getBoolean( TRACE_PROPAGATION_EXTRACT_FIRST, DEFAULT_TRACE_PROPAGATION_EXTRACT_FIRST); + traceInferredProxyServicesEnabled = configProvider.getBoolean(TRACE_INFERRED_PROXY_SERVICES_ENABLED, DEFAULT_TRACE_INFERRED_PROXY_SERVICES_ENABLED); + clockSyncPeriod = configProvider.getInteger(CLOCK_SYNC_PERIOD, DEFAULT_CLOCK_SYNC_PERIOD); if (experimentalFeaturesEnabled.contains( @@ -3115,6 +3120,10 @@ public int getTraceBaggageMaxBytes() { return traceBaggageMaxBytes; } + public boolean isTraceInferredProxyServicesEnabled() { + return traceInferredProxyServicesEnabled; + } + public int getClockSyncPeriod() { return clockSyncPeriod; } @@ -5373,6 +5382,8 @@ public String toString() { + tracePropagationBehaviorExtract + ", tracePropagationExtractFirst=" + tracePropagationExtractFirst + + ", traceInferredProxyServicesEnabled=" + + traceInferredProxyServicesEnabled + ", clockSyncPeriod=" + clockSyncPeriod + ", jmxFetchEnabled=" diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentPropagation.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentPropagation.java index 3dea68cd5b2..a8e27b3dc44 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentPropagation.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentPropagation.java @@ -15,6 +15,7 @@ public final class AgentPropagation { public static final Concern TRACING_CONCERN = named("tracing"); public static final Concern BAGGAGE_CONCERN = named("baggage"); + public static final Concern INFERRED_PROXY_CONCERN = named("inferred_proxy"); public static final Concern XRAY_TRACING_CONCERN = named("tracing-xray"); // TODO DSM propagator should run after the other propagators as it stores the pathway context diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/InferredProxyContext.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/InferredProxyContext.java new file mode 100644 index 00000000000..11cb93e5b70 --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/InferredProxyContext.java @@ -0,0 +1,90 @@ +package datadog.trace.bootstrap.instrumentation.api; + +import datadog.context.Context; +import datadog.context.ContextKey; +import datadog.context.ImplicitContextKeyed; + +import java.util.Objects; +import java.util.stream.Stream; + +public class InferredProxyContext implements ImplicitContextKeyed { + public static final ContextKey CONTEXT_KEY = + ContextKey.named("inferred-proxy-key"); + private String proxyName; + private String startTime; + private String domainName; + private String httpMethod; + private String path; + private String stage; + + public static InferredProxyContext fromContext(Context context) { + return context.get(CONTEXT_KEY); + } + + public InferredProxyContext() {} + + public void setProxyName(String name){ + this.proxyName = name; + } + + public String getProxyName(){ + return this.proxyName; + } + + public void setStartTime(String time){ + this.startTime = time; + } + + public String getStartTime(){ + return this.startTime; + } + + public void setDomainName(String name){ + this.domainName = name; + } + + public String getDomainName(){ + return this.domainName; + } + + public void setHttpMethod(String httpMethod){ + this.httpMethod = httpMethod; + } + + public String getHttpMethod(){ + return this.httpMethod; + } + + public void setPath(String path){ + this.path = path; + } + + public String getPath(){ + return this.path; + } + + public void setStage(String stage){ + this.stage = stage; + } + + public String getStage(){ + return this.stage; + } + + public boolean validContext(){ + return Stream.of(proxyName, startTime, domainName, httpMethod, path, stage) + .allMatch(Objects::nonNull); + } + + /** + * Creates a new context with this value under its chosen key. + * + * @param context the context to copy the original values from. + * @return the new context with the implicitly keyed value. + * @see Context#with(ImplicitContextKeyed) + */ + @Override + public Context storeInto(Context context) { + return context.with(CONTEXT_KEY, this); + } +}