Skip to content

Commit 3c5d6fc

Browse files
committed
WIP
1 parent f55ea06 commit 3c5d6fc

1 file changed

Lines changed: 74 additions & 12 deletions

File tree

internal-api/src/main/java/datadog/trace/api/gateway/InferredProxySpan.java

Lines changed: 74 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import static datadog.context.ContextKey.named;
44
import static datadog.trace.api.DDTags.SPAN_TYPE;
5+
import static datadog.trace.bootstrap.instrumentation.api.ErrorPriorities.HTTP_SERVER_DECORATOR;
56
import static datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities.MANUAL_INSTRUMENTATION;
67
import static datadog.trace.bootstrap.instrumentation.api.Tags.COMPONENT;
78
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_METHOD;
89
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_ROUTE;
910
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_URL;
11+
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_USER_AGENT;
1012
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND;
1113
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_SERVER;
1214

@@ -46,6 +48,10 @@ public class InferredProxySpan implements ImplicitContextKeyed {
4648

4749
private final Map<String, String> headers;
4850
private AgentSpan span;
51+
// Service-entry span registered at startSpan() time; used to guard against premature finishing
52+
// by child spans (e.g., Spring MVC handler spans) before the response status is known.
53+
private AgentSpan registeredServiceEntrySpan;
54+
private boolean phasedFinished;
4955

5056
public static InferredProxySpan fromHeaders(Map<String, String> values) {
5157
return new InferredProxySpan(values);
@@ -191,31 +197,75 @@ private String computeArn(String proxySystem, String region, String apiId) {
191197
return String.format("arn:%s:apigateway:%s::/%s/%s", partition, region, resourceType, apiId);
192198
}
193199

200+
/**
201+
* Registers the service-entry span for this inferred proxy span. This allows {@link
202+
* #finish(AgentSpan)} to distinguish between premature finish calls from child handler spans
203+
* (e.g., Spring MVC) and the final finish call from the service-entry span after the response is
204+
* written.
205+
*/
206+
public void registerServiceEntrySpan(AgentSpan serviceEntrySpan) {
207+
this.registeredServiceEntrySpan = serviceEntrySpan;
208+
}
209+
194210
public void finish() {
195211
finish(null);
196212
}
197213

198214
/**
199-
* Finishes this inferred proxy span and copies AppSec tags from the service-entry span to this
200-
* span as required by RFC-1081. AppSec detection occurs in the service-entry span context, so its
201-
* tags must be propagated to the inferred proxy span for endpoint correlation.
215+
* Finishes this inferred proxy span, copying relevant tags from the given span.
202216
*
203-
* @param serviceEntrySpan the service-entry child span, or null if not available
217+
* <p>When a service-entry span is registered (via {@link #registerServiceEntrySpan}), this method
218+
* distinguishes between two callers:
219+
*
220+
* <ul>
221+
* <li><b>Non-service-entry caller</b> (e.g., Spring MVC handler span): copies available tags
222+
* (AppSec) and calls {@link AgentSpan#phasedFinish()} to record duration without
223+
* publishing. The span stays alive so HTTP tags can be added later.
224+
* <li><b>Service-entry caller</b>: copies all tags including HTTP status/error/useragent, then
225+
* publishes the span (via {@link AgentSpan#publish()} if phasedFinished, or {@link
226+
* AgentSpan#finish()} otherwise).
227+
* </ul>
228+
*
229+
* @param callerSpan the span calling finish, used to copy tags and determine caller type
204230
*/
205-
public void finish(AgentSpan serviceEntrySpan) {
206-
if (this.span != null) {
207-
copyAppSecTagsFromServiceEntry(serviceEntrySpan);
208-
this.span.finish();
231+
public void finish(AgentSpan callerSpan) {
232+
if (this.span == null) {
233+
return;
234+
}
235+
236+
boolean isServiceEntryOrFallback =
237+
registeredServiceEntrySpan == null
238+
|| callerSpan == null
239+
|| callerSpan == registeredServiceEntrySpan;
240+
241+
if (isServiceEntryOrFallback) {
242+
// Final call: copy all tags (AppSec + HTTP status/error/useragent) and close the span
243+
copyTagsFromServiceEntry(callerSpan);
244+
if (phasedFinished) {
245+
this.span.publish();
246+
} else {
247+
this.span.finish();
248+
}
209249
this.span = null;
250+
this.registeredServiceEntrySpan = null;
251+
this.phasedFinished = false;
252+
} else if (!phasedFinished) {
253+
// First non-service-entry call (e.g., Spring MVC handler span fires beforeFinish() before
254+
// the response is written): copy available tags (AppSec) and phase-finish to record
255+
// duration, but keep the span alive so the service-entry call can add HTTP tags later.
256+
copyTagsFromServiceEntry(callerSpan);
257+
this.span.phasedFinish();
258+
this.phasedFinished = true;
210259
}
260+
// If already phasedFinished and caller is not service-entry: ignore duplicate calls
211261
}
212262

213263
/**
214-
* Copies AppSec tags from the service-entry span to this inferred proxy span as required by
215-
* RFC-1081: the inferred span must carry {@code _dd.appsec.enabled} and {@code _dd.appsec.json}
216-
* so that security activity can be correlated with the API Gateway endpoint.
264+
* Copies relevant tags from the service-entry span to this inferred proxy span. This includes
265+
* AppSec tags required by RFC-1081, plus HTTP tags that are only known after the request
266+
* completes ({@code http.status_code}, {@code error}, {@code http.useragent}).
217267
*/
218-
private void copyAppSecTagsFromServiceEntry(AgentSpan serviceEntrySpan) {
268+
private void copyTagsFromServiceEntry(AgentSpan serviceEntrySpan) {
219269
if (serviceEntrySpan == null || serviceEntrySpan == this.span) {
220270
return;
221271
}
@@ -229,6 +279,18 @@ private void copyAppSecTagsFromServiceEntry(AgentSpan serviceEntrySpan) {
229279
if (appsecJson != null) {
230280
this.span.setTag("_dd.appsec.json", appsecJson.toString());
231281
}
282+
283+
short statusCode = serviceEntrySpan.getHttpStatusCode();
284+
if (statusCode > 0) {
285+
this.span.setHttpStatusCode(statusCode);
286+
boolean isError = Config.get().getHttpServerErrorStatuses().get(statusCode);
287+
this.span.setError(isError, HTTP_SERVER_DECORATOR);
288+
}
289+
290+
Object userAgent = serviceEntrySpan.getTag(HTTP_USER_AGENT);
291+
if (userAgent != null) {
292+
this.span.setTag(HTTP_USER_AGENT, userAgent.toString());
293+
}
232294
}
233295

234296
@Override

0 commit comments

Comments
 (0)