Skip to content

Commit 7c0523a

Browse files
committed
feat(proxy): update tags for proxy inferred spans
1 parent 21b25aa commit 7c0523a

File tree

18 files changed

+214
-71
lines changed

18 files changed

+214
-71
lines changed

ddtrace/_trace/_inferred_proxy.py

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
from dataclasses import dataclass
12
import logging
23
from typing import Dict
3-
from typing import Union
4+
from typing import Optional
45

56
from ddtrace import config
67
from ddtrace._trace.span import Span
@@ -13,14 +14,35 @@
1314

1415
log = logging.getLogger(__name__)
1516

17+
18+
@dataclass
19+
class ProxyHeaderContext:
20+
system_name: str
21+
request_time: str
22+
method: str
23+
path: str
24+
resource_path: Optional[str]
25+
domain_name: Optional[str]
26+
stage: str
27+
account_id: Optional[str]
28+
api_id: Optional[str]
29+
region: Optional[str]
30+
user: Optional[str]
31+
32+
1633
# Checking lower case and upper case versions per WSGI spec following ddtrace/propagation/http.py's
1734
# logic to extract http headers
1835
POSSIBLE_PROXY_HEADER_SYSTEM = _possible_header("x-dd-proxy")
1936
POSSIBLE_PROXY_HEADER_START_TIME_MS = _possible_header("x-dd-proxy-request-time-ms")
2037
POSSIBLE_PROXY_HEADER_PATH = _possible_header("x-dd-proxy-path")
38+
POSSIBLE_PROXY_HEADER_RESOURCE_PATH = _possible_header("x-dd-proxy-resource-path")
2139
POSSIBLE_PROXY_HEADER_HTTPMETHOD = _possible_header("x-dd-proxy-httpmethod")
2240
POSSIBLE_PROXY_HEADER_DOMAIN = _possible_header("x-dd-proxy-domain-name")
2341
POSSIBLE_PROXY_HEADER_STAGE = _possible_header("x-dd-proxy-stage")
42+
POSSIBLE_PROXY_HEADER_ACCOUNT_ID = _possible_header("x-dd-proxy-account-id")
43+
POSSIBLE_PROXY_HEADER_API_ID = _possible_header("x-dd-proxy-api-id")
44+
POSSIBLE_PROXY_HEADER_REGION = _possible_header("x-dd-proxy-region")
45+
POSSIBLE_PROXY_HEADER_USER = _possible_header("x-dd-proxy-user")
2446

2547
supported_proxies: Dict[str, Dict[str, str]] = {
2648
"aws-apigateway": {"span_name": "aws.apigateway", "component": "aws-apigateway"}
@@ -38,17 +60,25 @@ def create_inferred_proxy_span_if_headers_exist(ctx, headers, child_of, tracer)
3860
if not proxy_context:
3961
return None
4062

41-
proxy_span_info = supported_proxies[proxy_context["proxy_system_name"]]
63+
proxy_span_info = supported_proxies[proxy_context.system_name]
64+
65+
method = proxy_context.method
66+
route = proxy_context.resource_path
67+
if route:
68+
resource = f"{method} {route}"
69+
else:
70+
path = proxy_context.path
71+
resource = f"{method} {path}"
4272

4373
span = tracer.start_span(
4474
proxy_span_info["span_name"],
45-
service=proxy_context.get("domain_name", config._get_service()),
46-
resource=proxy_context["method"] + " " + proxy_context["path"],
75+
service=proxy_context.domain_name or config._get_service(),
76+
resource=resource,
4777
span_type=SpanTypes.WEB,
4878
activate=True,
4979
child_of=child_of,
5080
)
51-
span.start_ns = int(proxy_context["request_time"]) * 1000000
81+
span.start_ns = int(proxy_context.request_time) * 1000000
5282

5383
set_inferred_proxy_span_tags(span, proxy_context)
5484

@@ -62,25 +92,43 @@ def finish_callback(_):
6292
ctx.set_item("headers", headers)
6393

6494

65-
def set_inferred_proxy_span_tags(span, proxy_context) -> Span:
66-
span._set_tag_str(COMPONENT, supported_proxies[proxy_context["proxy_system_name"]]["component"])
95+
def set_inferred_proxy_span_tags(span: Span, proxy_context: ProxyHeaderContext) -> Span:
96+
span._set_tag_str(COMPONENT, supported_proxies[proxy_context.system_name]["component"])
6797

68-
span._set_tag_str(http.METHOD, proxy_context["method"])
69-
span._set_tag_str(http.URL, f"{proxy_context['domain_name']}{proxy_context['path']}")
70-
span._set_tag_str("stage", proxy_context["stage"])
98+
span._set_tag_str(http.METHOD, proxy_context.method)
99+
span._set_tag_str(http.URL, f"https://{proxy_context.domain_name}{proxy_context.path}")
100+
if proxy_context.resource_path:
101+
span._set_tag_str(http.ROUTE, proxy_context.resource_path)
102+
103+
span._set_tag_str("stage", proxy_context.stage)
104+
if proxy_context.account_id and proxy_context.api_id and proxy_context.region and proxy_context.user:
105+
span._set_tag_str("account_id", proxy_context.account_id)
106+
span._set_tag_str("apiid", proxy_context.api_id)
107+
span._set_tag_str("region", proxy_context.region)
108+
span._set_tag_str("user", proxy_context.user)
109+
span._set_tag_str(
110+
"dd_resource_key", f"arn:aws:apigateway:{proxy_context.region}::/restapis/{proxy_context.api_id}"
111+
)
71112

72113
span.set_metric("_dd.inferred_span", 1)
73114
return span
74115

75116

76-
def extract_inferred_proxy_context(headers) -> Union[None, Dict[str, str]]:
117+
def extract_inferred_proxy_context(headers) -> Optional[ProxyHeaderContext]:
77118
proxy_header_system = str(_extract_header_value(POSSIBLE_PROXY_HEADER_SYSTEM, headers))
78119
proxy_header_start_time_ms = str(_extract_header_value(POSSIBLE_PROXY_HEADER_START_TIME_MS, headers))
79120
proxy_header_path = str(_extract_header_value(POSSIBLE_PROXY_HEADER_PATH, headers))
121+
proxy_header_resource_path = str(_extract_header_value(POSSIBLE_PROXY_HEADER_RESOURCE_PATH, headers))
122+
80123
proxy_header_httpmethod = str(_extract_header_value(POSSIBLE_PROXY_HEADER_HTTPMETHOD, headers))
81124
proxy_header_domain = str(_extract_header_value(POSSIBLE_PROXY_HEADER_DOMAIN, headers))
82125
proxy_header_stage = str(_extract_header_value(POSSIBLE_PROXY_HEADER_STAGE, headers))
83126

127+
proxy_header_account_id = str(_extract_header_value(POSSIBLE_PROXY_HEADER_ACCOUNT_ID, headers))
128+
proxy_header_api_id = str(_extract_header_value(POSSIBLE_PROXY_HEADER_API_ID, headers))
129+
proxy_header_region = str(_extract_header_value(POSSIBLE_PROXY_HEADER_REGION, headers))
130+
proxy_header_user = str(_extract_header_value(POSSIBLE_PROXY_HEADER_USER, headers))
131+
84132
# Exit if start time header is not present
85133
if proxy_header_start_time_ms is None:
86134
return None
@@ -92,14 +140,19 @@ def extract_inferred_proxy_context(headers) -> Union[None, Dict[str, str]]:
92140
)
93141
return None
94142

95-
return {
96-
"request_time": proxy_header_start_time_ms,
97-
"method": proxy_header_httpmethod,
98-
"path": proxy_header_path,
99-
"stage": proxy_header_stage,
100-
"domain_name": proxy_header_domain,
101-
"proxy_system_name": proxy_header_system,
102-
}
143+
return ProxyHeaderContext(
144+
proxy_header_system,
145+
proxy_header_start_time_ms,
146+
proxy_header_httpmethod,
147+
proxy_header_path,
148+
proxy_header_resource_path,
149+
proxy_header_domain,
150+
proxy_header_stage,
151+
proxy_header_account_id,
152+
proxy_header_api_id,
153+
proxy_header_region,
154+
proxy_header_user,
155+
)
103156

104157

105158
def normalize_headers(headers) -> Dict[str, str]:

tests/contrib/aiohttp/test_middleware.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ async def test_parenting_200_dd(app_tracer, aiohttp_client):
558558
"x-dd-proxy": "aws-apigateway",
559559
"x-dd-proxy-request-time-ms": "1736973768000",
560560
"x-dd-proxy-path": "/",
561+
"x-dd-proxy-resource-path": "/",
561562
"x-dd-proxy-httpmethod": "GET",
562563
"x-dd-proxy-domain-name": "local",
563564
"x-dd-proxy-stage": "stage",
@@ -569,6 +570,7 @@ async def test_parenting_200_dd(app_tracer, aiohttp_client):
569570
"x-dd-proxy": "aws-apigateway",
570571
"x-dd-proxy-request-time-ms": "1736973768000",
571572
"x-dd-proxy-path": "/",
573+
"x-dd-proxy-resource-path": "/",
572574
"x-dd-proxy-httpmethod": "GET",
573575
"x-dd-proxy-domain-name": "local",
574576
"x-dd-proxy-stage": "stage",
@@ -616,10 +618,11 @@ async def test_inferred_spans_api_gateway(app_tracer, aiohttp_client, test_app,
616618
web_span_service_name="aiohttp-web",
617619
web_span_resource=test_app["http_method"] + " " + test_app["path"],
618620
api_gateway_service_name="local",
619-
api_gateway_resource="GET /",
620621
method="GET",
621622
status_code=str(test_app["status_code"]),
622-
url="local/",
623+
url="https://local/",
624+
path="/",
625+
stage="stage",
623626
start=1736973768,
624627
is_distributed=test_headers["type"] == "distributed",
625628
distributed_trace_id=1,

tests/contrib/asgi/test_asgi.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,7 @@ async def test_inferred_spans_api_gateway_default(scope, tracer, test_spans, app
747747
("x-dd-proxy", "aws-apigateway"),
748748
("x-dd-proxy-request-time-ms", "1736973768000"),
749749
("x-dd-proxy-path", "/"),
750+
("x-dd-proxy-resource-path", "/"),
750751
("x-dd-proxy-httpmethod", "GET"),
751752
("x-dd-proxy-domain-name", "local"),
752753
("x-dd-proxy-stage", "stage"),
@@ -756,6 +757,7 @@ async def test_inferred_spans_api_gateway_default(scope, tracer, test_spans, app
756757
("x-dd-proxy", "aws-apigateway"),
757758
("x-dd-proxy-request-time-ms", "1736973768000"),
758759
("x-dd-proxy-path", "/"),
760+
("x-dd-proxy-resource-path", "/"),
759761
("x-dd-proxy-httpmethod", "GET"),
760762
("x-dd-proxy-domain-name", "local"),
761763
("x-dd-proxy-stage", "stage"),
@@ -805,10 +807,12 @@ async def test_inferred_spans_api_gateway_default(scope, tracer, test_spans, app
805807
web_span_service_name="tests.contrib.asgi",
806808
web_span_resource="GET /",
807809
api_gateway_service_name="local",
808-
api_gateway_resource="GET /",
809810
method="GET",
810811
status_code=app_type["status_code"],
811-
url="local/",
812+
url="https://local/",
813+
route="/",
814+
path="/",
815+
stage="stage",
812816
start=1736973768,
813817
is_distributed=headers == distributed_headers,
814818
distributed_trace_id=1,

tests/contrib/bottle/test.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ def handled_error_endpoint():
524524
"x-dd-proxy": "aws-apigateway",
525525
"x-dd-proxy-request-time-ms": "1736973768000",
526526
"x-dd-proxy-path": "/",
527+
"x-dd-proxy-resource-path": "/",
527528
"x-dd-proxy-httpmethod": "GET",
528529
"x-dd-proxy-domain-name": "local",
529530
"x-dd-proxy-stage": "stage",
@@ -555,10 +556,11 @@ def handled_error_endpoint():
555556
web_span_service_name=SERVICE,
556557
web_span_resource="GET " + test_endpoint["endpoint"],
557558
api_gateway_service_name="local",
558-
api_gateway_resource="GET /",
559559
method="GET",
560560
status_code=str(test_endpoint["status"]),
561-
url="local/",
561+
url="https://local/",
562+
path="/",
563+
stage="stage",
562564
start=1736973768,
563565
is_distributed=False,
564566
distributed_trace_id=1,

tests/contrib/bottle/test_distributed.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ def default_endpoint():
142142
"x-dd-proxy": "aws-apigateway",
143143
"x-dd-proxy-request-time-ms": "1736973768000",
144144
"x-dd-proxy-path": "/",
145+
"x-dd-proxy-resource-path": "/",
145146
"x-dd-proxy-httpmethod": "GET",
146147
"x-dd-proxy-domain-name": "local",
147148
"x-dd-proxy-stage": "stage",
@@ -166,10 +167,11 @@ def default_endpoint():
166167
web_span_service_name=SERVICE,
167168
web_span_resource="GET " + "/",
168169
api_gateway_service_name="local",
169-
api_gateway_resource="GET /",
170170
method="GET",
171171
status_code=200,
172-
url="local/",
172+
url="https://local/",
173+
path="/",
174+
stage="stage",
173175
start=1736973768,
174176
is_distributed=True,
175177
distributed_trace_id=1,
@@ -190,6 +192,7 @@ def default_endpoint():
190192
"x-dd-proxy": "aws-apigateway",
191193
"x-dd-proxy-request-time-ms": "1736973768000",
192194
"x-dd-proxy-path": "/",
195+
"x-dd-proxy-resource-path": "/",
193196
"x-dd-proxy-httpmethod": "GET",
194197
"x-dd-proxy-domain-name": "local",
195198
"x-dd-proxy-stage": "stage",

tests/contrib/cherrypy/test_middleware.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def test_and_emit_get_version(self):
6161
from ddtrace.contrib.internal.cherrypy.patch import get_version
6262

6363
version = get_version()
64-
assert type(version) == str
64+
assert isinstance(version, str)
6565
assert version != ""
6666

6767
emit_integration_and_version_to_test_agent("cherrypy", version)
@@ -487,6 +487,7 @@ def test_inferred_spans_api_gateway_default(self):
487487
("x-dd-proxy", "aws-apigateway"),
488488
("x-dd-proxy-request-time-ms", "1736973768000"),
489489
("x-dd-proxy-path", "/"),
490+
("x-dd-proxy-resource-path", "/"),
490491
("x-dd-proxy-httpmethod", "GET"),
491492
("x-dd-proxy-domain-name", "local"),
492493
("x-dd-proxy-stage", "stage"),
@@ -496,6 +497,7 @@ def test_inferred_spans_api_gateway_default(self):
496497
("x-dd-proxy", "aws-apigateway"),
497498
("x-dd-proxy-request-time-ms", "1736973768000"),
498499
("x-dd-proxy-path", "/"),
500+
("x-dd-proxy-resource-path", "/"),
499501
("x-dd-proxy-httpmethod", "GET"),
500502
("x-dd-proxy-domain-name", "local"),
501503
("x-dd-proxy-stage", "stage"),
@@ -539,10 +541,11 @@ def test_inferred_spans_api_gateway_default(self):
539541
web_span_service_name="test.cherrypy.service",
540542
web_span_resource="GET " + test_endpoint["endpoint"],
541543
api_gateway_service_name="local",
542-
api_gateway_resource="GET /",
543544
method="GET",
544545
status_code=test_endpoint["status"],
545-
url="local/",
546+
url="https://local/",
547+
path="/",
548+
stage="stage",
546549
start=1736973768,
547550
is_distributed=test_headers == distributed_headers,
548551
distributed_trace_id=1,

tests/contrib/django/test_django.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,7 +1600,8 @@ def test_cached_view():
16001600
"component": "django",
16011601
"django.cache.backend": "django.core.cache.backends.locmem.LocMemCache",
16021602
"django.cache.key": (
1603-
"views.decorators.cache.cache_page..GET.03cdc1cc4aab71b038a6764e5fcabb82.d41d8cd98f00b204e9800998ecf8..."
1603+
"views.decorators.cache.cache_page..GET.03cdc1cc4aab71b038a6764e5fcabb82."
1604+
"d41d8cd98f00b204e9800998ecf8..."
16041605
),
16051606
"_dd.base_service": "ddtrace_subprocess_dir",
16061607
}
@@ -1921,7 +1922,7 @@ def test_inferred_spans_api_gateway_default(client, test_spans):
19211922
headers = {
19221923
"HTTP_X_DD_PROXY": "aws-apigateway",
19231924
"HTTP_X_DD_PROXY_REQUEST_TIME_MS": "1736973768000",
1924-
"HTTP_X_DD_PROXY_PATH": "/",
1925+
"HTTP_X_DD_PROXY_RESOURCE_PATH": "/",
19251926
"HTTP_X_DD_PROXY_HTTPMETHOD": "GET",
19261927
"HTTP_X_DD_PROXY_DOMAIN_NAME": "local",
19271928
"HTTP_X_DD_PROXY_STAGE": "stage",
@@ -1949,10 +1950,12 @@ def test_inferred_spans_api_gateway_default(client, test_spans):
19491950
web_span_service_name="django",
19501951
web_span_resource="GET ^$",
19511952
api_gateway_service_name="local",
1952-
api_gateway_resource="GET /",
19531953
method="GET",
19541954
status_code="200",
1955-
url="local/",
1955+
url="https://local/",
1956+
route="/",
1957+
path="/",
1958+
stage="stage",
19561959
start=1736973768.0,
19571960
)
19581961

@@ -1972,10 +1975,12 @@ def test_inferred_spans_api_gateway_default(client, test_spans):
19721975
web_span_service_name="django",
19731976
web_span_resource="GET ^error-500/$",
19741977
api_gateway_service_name="local",
1975-
api_gateway_resource="GET /",
19761978
method="GET",
19771979
status_code="500",
1978-
url="local/",
1980+
url="https://local/",
1981+
route="/",
1982+
path="/",
1983+
stage="stage",
19791984
start=1736973768.0,
19801985
)
19811986

@@ -2032,10 +2037,11 @@ def test_inferred_spans_api_gateway_distributed_tracing(client, test_spans):
20322037
web_span_service_name="django",
20332038
web_span_resource="GET ^$",
20342039
api_gateway_service_name="local",
2035-
api_gateway_resource="GET /",
20362040
method="GET",
20372041
status_code="200",
2038-
url="local/",
2042+
url="https://local/",
2043+
path="/",
2044+
stage="stage",
20392045
start=1736973768.0,
20402046
is_distributed=True,
20412047
distributed_trace_id=1,

tests/contrib/djangorestframework/test_djangorestframework.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,11 @@ def test_inferred_spans_api_gateway_default(client, test_spans, test_endpoint, i
104104
web_span_service_name="django",
105105
web_span_resource=test_endpoint["resource_name"],
106106
api_gateway_service_name="local",
107-
api_gateway_resource="GET /",
108107
method="GET",
109108
status_code=test_endpoint["status_code"],
110-
url="local/",
109+
url="https://local/",
110+
path="/",
111+
stage="stage",
111112
start=1736973768,
112113
is_distributed=headers == distributed_headers,
113114
distributed_trace_id=1,

0 commit comments

Comments
 (0)