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+
34+
35+ @dataclass
36+ class ProxyInfo :
37+ span_name : str
38+ component : str
39+ resource_arn_builder : Optional [Callable [[ProxyHeaderContext ], Optional [str ]]] = None
40+
41+
42+ def _api_gateway_rest_api_arn (proxy_context : ProxyHeaderContext ) -> Optional [str ]:
43+ if proxy_context .region and proxy_context .api_id :
44+ return f"arn:aws:apigateway:{ proxy_context .region } ::/restapis/{ proxy_context .api_id } "
45+ return None
46+
47+
48+ def _api_gateway_http_api_arn (proxy_context : ProxyHeaderContext ) -> Optional [str ]:
49+ if proxy_context .region and proxy_context .api_id :
50+ return f"arn:aws:apigateway:{ proxy_context .region } ::/apis/{ proxy_context .api_id } "
51+ return None
52+
53+
54+ supported_proxies : Dict [str , ProxyInfo ] = {
55+ "aws-apigateway" : ProxyInfo ("aws.apigateway" , "aws-apigateway" , _api_gateway_rest_api_arn ),
56+ "aws-httpapi" : ProxyInfo ("aws.httpapi" , "aws-httpapi" , _api_gateway_http_api_arn ),
57+ }
58+
59+ SUPPORTED_PROXY_SPAN_NAMES = {info .span_name for info in supported_proxies .values ()}
60+
1661# Checking lower case and upper case versions per WSGI spec following ddtrace/propagation/http.py's
1762# logic to extract http headers
1863POSSIBLE_PROXY_HEADER_SYSTEM = _possible_header ("x-dd-proxy" )
1964POSSIBLE_PROXY_HEADER_START_TIME_MS = _possible_header ("x-dd-proxy-request-time-ms" )
2065POSSIBLE_PROXY_HEADER_PATH = _possible_header ("x-dd-proxy-path" )
66+ POSSIBLE_PROXY_HEADER_RESOURCE_PATH = _possible_header ("x-dd-proxy-resource-path" )
2167POSSIBLE_PROXY_HEADER_HTTPMETHOD = _possible_header ("x-dd-proxy-httpmethod" )
2268POSSIBLE_PROXY_HEADER_DOMAIN = _possible_header ("x-dd-proxy-domain-name" )
2369POSSIBLE_PROXY_HEADER_STAGE = _possible_header ("x-dd-proxy-stage" )
24-
25- supported_proxies : Dict [ str , Dict [ str , str ]] = {
26- "aws-apigateway" : { "span_name" : "aws.apigateway" , "component" : "aws-apigateway" }
27- }
70+ POSSIBLE_PROXY_HEADER_ACCOUNT_ID = _possible_header ( "x-dd-proxy-account-id" )
71+ POSSIBLE_PROXY_HEADER_API_ID = _possible_header ( "x-dd-proxy-api-id" )
72+ POSSIBLE_PROXY_HEADER_REGION = _possible_header ( "x-dd-proxy-region" )
73+ POSSIBLE_PROXY_HEADER_USER = _possible_header ( "x-dd-proxy-user" )
2874
2975
3076def create_inferred_proxy_span_if_headers_exist (ctx , headers , child_of , tracer ) -> None :
@@ -38,19 +84,23 @@ def create_inferred_proxy_span_if_headers_exist(ctx, headers, child_of, tracer)
3884 if not proxy_context :
3985 return None
4086
41- proxy_span_info = supported_proxies [proxy_context ["proxy_system_name" ]]
87+ proxy_info = supported_proxies [proxy_context .system_name ]
88+
89+ method = proxy_context .method
90+ route_or_path = proxy_context .resource_path or proxy_context .path
91+ resource = f"{ method or '' } { route_or_path or '' } "
4292
4393 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" ] ,
94+ proxy_info . span_name ,
95+ service = proxy_context .domain_name or config ._get_service (),
96+ resource = resource ,
4797 span_type = SpanTypes .WEB ,
4898 activate = True ,
4999 child_of = child_of ,
50100 )
51- span .start_ns = int (proxy_context [ " request_time" ] ) * 1000000
101+ span .start_ns = int (proxy_context . request_time ) * 1000000
52102
53- set_inferred_proxy_span_tags (span , proxy_context )
103+ set_inferred_proxy_span_tags (span , proxy_context , proxy_info )
54104
55105 # we need a callback to finish the api gateway span, this callback will be added to the child spans finish callbacks
56106 def finish_callback (_ ):
@@ -62,24 +112,56 @@ def finish_callback(_):
62112 ctx .set_item ("headers" , headers )
63113
64114
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" ])
115+ def set_inferred_proxy_span_tags (span : Span , proxy_context : ProxyHeaderContext , proxy_info : ProxyInfo ) -> Span :
116+ span ._set_tag_str (COMPONENT , proxy_info .component )
117+ span ._set_tag_str ("span.kind" , SpanKind .SERVER )
118+
119+ span ._set_tag_str (http .URL , f"https://{ proxy_context .domain_name or '' } { proxy_context .path or '' } " )
120+
121+ if proxy_context .method :
122+ span ._set_tag_str (http .METHOD , proxy_context .method )
123+
124+ if proxy_context .resource_path :
125+ span ._set_tag_str (http .ROUTE , proxy_context .resource_path )
126+
127+ if proxy_context .stage :
128+ span ._set_tag_str ("stage" , proxy_context .stage )
129+
130+ if proxy_context .account_id :
131+ span ._set_tag_str ("account_id" , proxy_context .account_id )
67132
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" ])
133+ if proxy_context .api_id :
134+ span ._set_tag_str ("apiid" , proxy_context .api_id )
135+
136+ if proxy_context .region :
137+ span ._set_tag_str ("region" , proxy_context .region )
138+
139+ if proxy_context .user :
140+ span ._set_tag_str ("user" , proxy_context .user )
141+
142+ if proxy_info .resource_arn_builder :
143+ resource_arn = proxy_info .resource_arn_builder (proxy_context )
144+ if resource_arn :
145+ span ._set_tag_str ("dd_resource_key" , resource_arn )
71146
72147 span .set_metric ("_dd.inferred_span" , 1 )
73148 return span
74149
75150
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 ))
151+ def extract_inferred_proxy_context (headers ) -> Optional [ProxyHeaderContext ]:
152+ proxy_header_system = _extract_header_value (POSSIBLE_PROXY_HEADER_SYSTEM , headers )
153+ proxy_header_start_time_ms = _extract_header_value (POSSIBLE_PROXY_HEADER_START_TIME_MS , headers )
154+ proxy_header_path = _extract_header_value (POSSIBLE_PROXY_HEADER_PATH , headers )
155+ proxy_header_resource_path = _extract_header_value (POSSIBLE_PROXY_HEADER_RESOURCE_PATH , headers )
156+
157+ proxy_header_httpmethod = _extract_header_value (POSSIBLE_PROXY_HEADER_HTTPMETHOD , headers )
158+ proxy_header_domain = _extract_header_value (POSSIBLE_PROXY_HEADER_DOMAIN , headers )
159+ proxy_header_stage = _extract_header_value (POSSIBLE_PROXY_HEADER_STAGE , headers )
160+
161+ proxy_header_account_id = _extract_header_value (POSSIBLE_PROXY_HEADER_ACCOUNT_ID , headers )
162+ proxy_header_api_id = _extract_header_value (POSSIBLE_PROXY_HEADER_API_ID , headers )
163+ proxy_header_region = _extract_header_value (POSSIBLE_PROXY_HEADER_REGION , headers )
164+ proxy_header_user = _extract_header_value (POSSIBLE_PROXY_HEADER_USER , headers )
83165
84166 # Exit if start time header is not present
85167 if proxy_header_start_time_ms is None :
@@ -92,14 +174,19 @@ def extract_inferred_proxy_context(headers) -> Union[None, Dict[str, str]]:
92174 )
93175 return None
94176
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- }
177+ return ProxyHeaderContext (
178+ proxy_header_system ,
179+ proxy_header_start_time_ms ,
180+ proxy_header_httpmethod ,
181+ proxy_header_path ,
182+ proxy_header_resource_path ,
183+ proxy_header_domain ,
184+ proxy_header_stage ,
185+ proxy_header_account_id ,
186+ proxy_header_api_id ,
187+ proxy_header_region ,
188+ proxy_header_user ,
189+ )
103190
104191
105192def normalize_headers (headers ) -> Dict [str , str ]:
0 commit comments