2727from google .cloud .logging_v2 .handlers .middleware .request import _get_django_request
2828
2929_DJANGO_CONTENT_LENGTH = "CONTENT_LENGTH"
30- _DJANGO_TRACE_HEADER = "HTTP_X_CLOUD_TRACE_CONTEXT"
30+ _DJANGO_XCLOUD_TRACE_HEADER = "HTTP_X_CLOUD_TRACE_CONTEXT"
31+ _DJANGO_TRACEPARENT = "HTTP_TRACEPARENT"
3132_DJANGO_USERAGENT_HEADER = "HTTP_USER_AGENT"
3233_DJANGO_REMOTE_ADDR_HEADER = "REMOTE_ADDR"
3334_DJANGO_REFERER_HEADER = "HTTP_REFERER"
34- _FLASK_TRACE_HEADER = "X_CLOUD_TRACE_CONTEXT"
35+ _FLASK_XCLOUD_TRACE_HEADER = "X_CLOUD_TRACE_CONTEXT"
36+ _FLASK_TRACEPARENT = "TRACEPARENT"
3537_PROTOCOL_HEADER = "SERVER_PROTOCOL"
3638
3739
@@ -62,13 +64,12 @@ def get_request_data_from_flask():
6264 """Get http_request and trace data from flask request headers.
6365
6466 Returns:
65- Tuple[Optional[dict], Optional[str], Optional[str]]:
66- Data related to the current http request, trace_id, and span_id for
67- the request. All fields will be None if a django request isn't
68- found.
67+ Tuple[Optional[dict], Optional[str], Optional[str], bool]:
68+ Data related to the current http request, trace_id, span_id and trace_sampled
69+ for the request. All fields will be None if a django request isn't found.
6970 """
7071 if flask is None or not flask .request :
71- return None , None , None
72+ return None , None , None , False
7273
7374 # build http_request
7475 http_request = {
@@ -79,25 +80,29 @@ def get_request_data_from_flask():
7980 }
8081
8182 # find trace id and span id
82- header = flask .request .headers .get (_FLASK_TRACE_HEADER )
83- trace_id , span_id = _parse_trace_span (header )
83+ # first check for w3c traceparent header
84+ header = flask .request .headers .get (_FLASK_TRACEPARENT )
85+ trace_id , span_id , trace_sampled = _parse_trace_parent (header )
86+ if trace_id is None :
87+ # traceparent not found. look for xcloud_trace_context header
88+ header = flask .request .headers .get (_FLASK_XCLOUD_TRACE_HEADER )
89+ trace_id , span_id , trace_sampled = _parse_xcloud_trace (header )
8490
85- return http_request , trace_id , span_id
91+ return http_request , trace_id , span_id , trace_sampled
8692
8793
8894def get_request_data_from_django ():
8995 """Get http_request and trace data from django request headers.
9096
9197 Returns:
92- Tuple[Optional[dict], Optional[str], Optional[str]]:
93- Data related to the current http request, trace_id, and span_id for
94- the request. All fields will be None if a django request isn't
95- found.
98+ Tuple[Optional[dict], Optional[str], Optional[str], bool]:
99+ Data related to the current http request, trace_id, span_id, and trace_sampled
100+ for the request. All fields will be None if a django request isn't found.
96101 """
97102 request = _get_django_request ()
98103
99104 if request is None :
100- return None , None , None
105+ return None , None , None , False
101106
102107 # build http_request
103108 http_request = {
@@ -108,54 +113,94 @@ def get_request_data_from_django():
108113 }
109114
110115 # find trace id and span id
111- header = request .META .get (_DJANGO_TRACE_HEADER )
112- trace_id , span_id = _parse_trace_span (header )
116+ # first check for w3c traceparent header
117+ header = request .META .get (_DJANGO_TRACEPARENT )
118+ trace_id , span_id , trace_sampled = _parse_trace_parent (header )
119+ if trace_id is None :
120+ # traceparent not found. look for xcloud_trace_context header
121+ header = request .META .get (_DJANGO_XCLOUD_TRACE_HEADER )
122+ trace_id , span_id , trace_sampled = _parse_xcloud_trace (header )
113123
114- return http_request , trace_id , span_id
124+ return http_request , trace_id , span_id , trace_sampled
115125
116126
117- def _parse_trace_span (header ):
127+ def _parse_trace_parent (header ):
128+ """Given a w3 traceparent header, extract the trace and span ids.
129+ For more information see https://www.w3.org/TR/trace-context/
130+
131+ Args:
132+ header (str): the string extracted from the traceparent header
133+ example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
134+ Returns:
135+ Tuple[Optional[dict], Optional[str], bool]:
136+ The trace_id, span_id and trace_sampled extracted from the header
137+ Each field will be None if header can't be parsed in expected format.
138+ """
139+ trace_id = span_id = None
140+ trace_sampled = False
141+ # see https://www.w3.org/TR/trace-context/ for W3C traceparent format
142+ if header :
143+ try :
144+ VERSION_PART = r"(?!ff)[a-f\d]{2}"
145+ TRACE_ID_PART = r"(?![0]{32})[a-f\d]{32}"
146+ PARENT_ID_PART = r"(?![0]{16})[a-f\d]{16}"
147+ FLAGS_PART = r"[a-f\d]{2}"
148+ regex = f"^\\ s?({ VERSION_PART } )-({ TRACE_ID_PART } )-({ PARENT_ID_PART } )-({ FLAGS_PART } )(-.*)?\\ s?$"
149+ match = re .match (regex , header )
150+ trace_id = match .group (2 )
151+ span_id = match .group (3 )
152+ # trace-flag component is an 8-bit bit field. Read as an int
153+ int_flag = int (match .group (4 ), 16 )
154+ # trace sampled is set if the right-most bit in flag component is set
155+ trace_sampled = bool (int_flag & 1 )
156+ except (IndexError , AttributeError ):
157+ # could not parse header as expected. Return None
158+ pass
159+ return trace_id , span_id , trace_sampled
160+
161+
162+ def _parse_xcloud_trace (header ):
118163 """Given an X_CLOUD_TRACE header, extract the trace and span ids.
119164
120165 Args:
121166 header (str): the string extracted from the X_CLOUD_TRACE header
122167 Returns:
123- Tuple[Optional[dict], Optional[str]]:
124- The trace_id and span_id extracted from the header
168+ Tuple[Optional[dict], Optional[str], bool ]:
169+ The trace_id, span_id and trace_sampled extracted from the header
125170 Each field will be None if not found.
126171 """
127- trace_id = None
128- span_id = None
172+ trace_id = span_id = None
173+ trace_sampled = False
174+ # see https://cloud.google.com/trace/docs/setup for X-Cloud-Trace_Context format
129175 if header :
130176 try :
131- split_header = header . split ( "/" , 1 )
132- trace_id = split_header [ 0 ]
133- header_suffix = split_header [ 1 ]
134- # the span is the set of alphanumeric characters after the /
135- span_id = re . findall ( r"^\w+" , header_suffix )[ 0 ]
177+ regex = r"([\w-]+)?(\/?([\w-]+))?(;?o=(\d))?"
178+ match = re . match ( regex , header )
179+ trace_id = match . group ( 1 )
180+ span_id = match . group ( 3 )
181+ trace_sampled = match . group ( 5 ) == "1"
136182 except IndexError :
137183 pass
138- return trace_id , span_id
184+ return trace_id , span_id , trace_sampled
139185
140186
141187def get_request_data ():
142188 """Helper to get http_request and trace data from supported web
143189 frameworks (currently supported: Flask and Django).
144190
145191 Returns:
146- Tuple[Optional[dict], Optional[str], Optional[str]]:
147- Data related to the current http request, trace_id, and span_id for
148- the request. All fields will be None if a django request isn't
149- found.
192+ Tuple[Optional[dict], Optional[str], Optional[str], bool]:
193+ Data related to the current http request, trace_id, span_id, and trace_sampled
194+ for the request. All fields will be None if a http request isn't found.
150195 """
151196 checkers = (
152197 get_request_data_from_django ,
153198 get_request_data_from_flask ,
154199 )
155200
156201 for checker in checkers :
157- http_request , trace_id , span_id = checker ()
202+ http_request , trace_id , span_id , trace_sampled = checker ()
158203 if http_request is not None :
159- return http_request , trace_id , span_id
204+ return http_request , trace_id , span_id , trace_sampled
160205
161- return None , None , None
206+ return None , None , None , False
0 commit comments