1+ from dataclasses import dataclass
12import logging
3+ from typing import Callable
24from typing import Dict
3- from typing import Union
5+ from typing import Optional
46
57from ddtrace import config
68from ddtrace ._trace .span import Span
9+ from ddtrace .ext import SpanKind
710from ddtrace .ext import SpanTypes
811from ddtrace .ext import http
912from ddtrace .internal .constants import COMPONENT
1316
1417log = logging .getLogger (__name__ )
1518
19+
20+ @dataclass
21+ class ProxyHeaderContext :
22+ system_name : str
23+ request_time : str
24+ method : Optional [str ]
25+ path : Optional [str ]
26+ resource_path : Optional [str ]
27+ domain_name : Optional [str ]
28+ stage : Optional [str ]
29+ account_id : Optional [str ]
30+ api_id : Optional [str ]
31+ region : Optional [str ]
32+ user : Optional [str ]
33+ useragent : Optional [str ]
34+
35+
36+ @dataclass
37+ class ProxyInfo :
38+ span_name : str
39+ component : str
40+ resource_arn_builder : Optional [Callable [[ProxyHeaderContext ], Optional [str ]]] = None
41+
42+
43+ def _api_gateway_rest_api_arn (proxy_context : ProxyHeaderContext ) -> Optional [str ]:
44+ if proxy_context .region and proxy_context .api_id :
45+ return f"arn:aws:apigateway:{ proxy_context .region } ::/restapis/{ proxy_context .api_id } "
46+ return None
47+
48+
49+ def _api_gateway_http_api_arn (proxy_context : ProxyHeaderContext ) -> Optional [str ]:
50+ if proxy_context .region and proxy_context .api_id :
51+ return f"arn:aws:apigateway:{ proxy_context .region } ::/apis/{ proxy_context .api_id } "
52+ return None
53+
54+
55+ supported_proxies : Dict [str , ProxyInfo ] = {
56+ "aws-apigateway" : ProxyInfo ("aws.apigateway" , "aws-apigateway" , _api_gateway_rest_api_arn ),
57+ "aws-httpapi" : ProxyInfo ("aws.httpapi" , "aws-httpapi" , _api_gateway_http_api_arn ),
58+ }
59+
60+ SUPPORTED_PROXY_SPAN_NAMES = {info .span_name for info in supported_proxies .values ()}
61+
1662# Checking lower case and upper case versions per WSGI spec following ddtrace/propagation/http.py's
1763# logic to extract http headers
1864POSSIBLE_PROXY_HEADER_SYSTEM = _possible_header ("x-dd-proxy" )
1965POSSIBLE_PROXY_HEADER_START_TIME_MS = _possible_header ("x-dd-proxy-request-time-ms" )
2066POSSIBLE_PROXY_HEADER_PATH = _possible_header ("x-dd-proxy-path" )
67+ POSSIBLE_PROXY_HEADER_RESOURCE_PATH = _possible_header ("x-dd-proxy-resource-path" )
2168POSSIBLE_PROXY_HEADER_HTTPMETHOD = _possible_header ("x-dd-proxy-httpmethod" )
2269POSSIBLE_PROXY_HEADER_DOMAIN = _possible_header ("x-dd-proxy-domain-name" )
2370POSSIBLE_PROXY_HEADER_STAGE = _possible_header ("x-dd-proxy-stage" )
71+ POSSIBLE_PROXY_HEADER_ACCOUNT_ID = _possible_header ("x-dd-proxy-account-id" )
72+ POSSIBLE_PROXY_HEADER_API_ID = _possible_header ("x-dd-proxy-api-id" )
73+ POSSIBLE_PROXY_HEADER_REGION = _possible_header ("x-dd-proxy-region" )
74+ POSSIBLE_PROXY_HEADER_USER = _possible_header ("x-dd-proxy-user" )
2475
25- supported_proxies : Dict [str , Dict [str , str ]] = {
26- "aws-apigateway" : {"span_name" : "aws.apigateway" , "component" : "aws-apigateway" }
27- }
76+ HEADER_USERAGENT = _possible_header ("user-agent" )
2877
2978
3079def create_inferred_proxy_span_if_headers_exist (ctx , headers , child_of , tracer ) -> None :
@@ -38,19 +87,23 @@ def create_inferred_proxy_span_if_headers_exist(ctx, headers, child_of, tracer)
3887 if not proxy_context :
3988 return None
4089
41- proxy_span_info = supported_proxies [proxy_context ["proxy_system_name" ]]
90+ proxy_info = supported_proxies [proxy_context .system_name ]
91+
92+ method = proxy_context .method
93+ route_or_path = proxy_context .resource_path or proxy_context .path
94+ resource = f"{ method or '' } { route_or_path or '' } "
4295
4396 span = tracer .start_span (
44- proxy_span_info [ " span_name" ] ,
45- service = proxy_context .get ( " domain_name" , config ._get_service () ),
46- resource = proxy_context [ "method" ] + " " + proxy_context [ "path" ] ,
97+ proxy_info . span_name ,
98+ service = proxy_context .domain_name or config ._get_service (),
99+ resource = resource ,
47100 span_type = SpanTypes .WEB ,
48101 activate = True ,
49102 child_of = child_of ,
50103 )
51- span .start_ns = int (proxy_context [ " request_time" ] ) * 1000000
104+ span .start_ns = int (proxy_context . request_time ) * 1000000
52105
53- set_inferred_proxy_span_tags (span , proxy_context )
106+ set_inferred_proxy_span_tags (span , proxy_context , proxy_info )
54107
55108 # we need a callback to finish the api gateway span, this callback will be added to the child spans finish callbacks
56109 def finish_callback (_ ):
@@ -62,24 +115,61 @@ def finish_callback(_):
62115 ctx .set_item ("headers" , headers )
63116
64117
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" ])
118+ def set_inferred_proxy_span_tags (span : Span , proxy_context : ProxyHeaderContext , proxy_info : ProxyInfo ) -> Span :
119+ span ._set_tag_str (COMPONENT , proxy_info .component )
120+ span ._set_tag_str ("span.kind" , SpanKind .SERVER )
67121
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" ])
122+ span ._set_tag_str (http .URL , f"https://{ proxy_context .domain_name or '' } { proxy_context .path or '' } " )
123+
124+ if proxy_context .method :
125+ span ._set_tag_str (http .METHOD , proxy_context .method )
126+
127+ if proxy_context .resource_path :
128+ span ._set_tag_str (http .ROUTE , proxy_context .resource_path )
129+
130+ if proxy_context .useragent :
131+ span ._set_tag_str (http .USER_AGENT , proxy_context .useragent )
132+
133+ if proxy_context .stage :
134+ span ._set_tag_str ("stage" , proxy_context .stage )
135+
136+ if proxy_context .account_id :
137+ span ._set_tag_str ("account_id" , proxy_context .account_id )
138+
139+ if proxy_context .api_id :
140+ span ._set_tag_str ("apiid" , proxy_context .api_id )
141+
142+ if proxy_context .region :
143+ span ._set_tag_str ("region" , proxy_context .region )
144+
145+ if proxy_context .user :
146+ span ._set_tag_str ("user" , proxy_context .user )
147+
148+ if proxy_info .resource_arn_builder :
149+ resource_arn = proxy_info .resource_arn_builder (proxy_context )
150+ if resource_arn :
151+ span ._set_tag_str ("dd_resource_key" , resource_arn )
71152
72153 span .set_metric ("_dd.inferred_span" , 1 )
73154 return span
74155
75156
76- def extract_inferred_proxy_context (headers ) -> Union [None , Dict [str , str ]]:
77- proxy_header_system = str (_extract_header_value (POSSIBLE_PROXY_HEADER_SYSTEM , headers ))
78- proxy_header_start_time_ms = str (_extract_header_value (POSSIBLE_PROXY_HEADER_START_TIME_MS , headers ))
79- proxy_header_path = str (_extract_header_value (POSSIBLE_PROXY_HEADER_PATH , headers ))
80- proxy_header_httpmethod = str (_extract_header_value (POSSIBLE_PROXY_HEADER_HTTPMETHOD , headers ))
81- proxy_header_domain = str (_extract_header_value (POSSIBLE_PROXY_HEADER_DOMAIN , headers ))
82- proxy_header_stage = str (_extract_header_value (POSSIBLE_PROXY_HEADER_STAGE , headers ))
157+ def extract_inferred_proxy_context (headers ) -> Optional [ProxyHeaderContext ]:
158+ proxy_header_system = _extract_header_value (POSSIBLE_PROXY_HEADER_SYSTEM , headers )
159+ proxy_header_start_time_ms = _extract_header_value (POSSIBLE_PROXY_HEADER_START_TIME_MS , headers )
160+ proxy_header_path = _extract_header_value (POSSIBLE_PROXY_HEADER_PATH , headers )
161+ proxy_header_resource_path = _extract_header_value (POSSIBLE_PROXY_HEADER_RESOURCE_PATH , headers )
162+
163+ proxy_header_httpmethod = _extract_header_value (POSSIBLE_PROXY_HEADER_HTTPMETHOD , headers )
164+ proxy_header_domain = _extract_header_value (POSSIBLE_PROXY_HEADER_DOMAIN , headers )
165+ proxy_header_stage = _extract_header_value (POSSIBLE_PROXY_HEADER_STAGE , headers )
166+
167+ proxy_header_account_id = _extract_header_value (POSSIBLE_PROXY_HEADER_ACCOUNT_ID , headers )
168+ proxy_header_api_id = _extract_header_value (POSSIBLE_PROXY_HEADER_API_ID , headers )
169+ proxy_header_region = _extract_header_value (POSSIBLE_PROXY_HEADER_REGION , headers )
170+ proxy_header_user = _extract_header_value (POSSIBLE_PROXY_HEADER_USER , headers )
171+
172+ header_user_agent = _extract_header_value (HEADER_USERAGENT , headers )
83173
84174 # Exit if start time header is not present
85175 if proxy_header_start_time_ms is None :
@@ -92,14 +182,20 @@ def extract_inferred_proxy_context(headers) -> Union[None, Dict[str, str]]:
92182 )
93183 return None
94184
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- }
185+ return ProxyHeaderContext (
186+ proxy_header_system ,
187+ proxy_header_start_time_ms ,
188+ proxy_header_httpmethod ,
189+ proxy_header_path ,
190+ proxy_header_resource_path ,
191+ proxy_header_domain ,
192+ proxy_header_stage ,
193+ proxy_header_account_id ,
194+ proxy_header_api_id ,
195+ proxy_header_region ,
196+ proxy_header_user ,
197+ header_user_agent ,
198+ )
103199
104200
105201def normalize_headers (headers ) -> Dict [str , str ]:
0 commit comments