|
9 | 9 |
|
10 | 10 | from sentry_sdk.utils import (
|
11 | 11 | capture_internal_exceptions,
|
| 12 | + Dsn, |
12 | 13 | logger,
|
| 14 | + to_base64, |
13 | 15 | to_string,
|
14 | 16 | )
|
15 | 17 | from sentry_sdk._compat import PY2
|
|
26 | 28 | from typing import Generator
|
27 | 29 | from typing import Optional
|
28 | 30 | from typing import Any
|
| 31 | + from typing import Union |
29 | 32 |
|
30 |
| - from sentry_sdk.tracing import Span |
| 33 | + from sentry_sdk.tracing import Span, Transaction |
31 | 34 |
|
32 | 35 |
|
33 | 36 | SENTRY_TRACE_REGEX = re.compile(
|
|
38 | 41 | "[ \t]*$" # whitespace
|
39 | 42 | )
|
40 | 43 |
|
| 44 | +# This is a normal base64 regex, modified to reflect that fact that we strip the |
| 45 | +# trailing = or == off |
| 46 | +b64 = base64_stripped = ( |
| 47 | + # any of the characters in the base64 "alphabet", in multiples of 4 |
| 48 | + "([a-zA-Z0-9+/]{4})*" |
| 49 | + # either nothing or 2 or 3 base64-alphabet characters (see |
| 50 | + # https://en.wikipedia.org/wiki/Base64#Decoding_Base64_without_padding for |
| 51 | + # why there's never only 1 extra character) |
| 52 | + "([a-zA-Z0-9+/]{2,3})?" |
| 53 | +) |
| 54 | +ov = outside_vendor_entry = r"\w+=\w+" |
| 55 | + |
| 56 | +TRACESTATE_HEADER_REGEX = re.compile( |
| 57 | + # zero or more other vendors' entries, each followed by a comma, |
| 58 | + # captured together as one group |
| 59 | + "^((?:{ov},)*)" |
| 60 | + # sentry's entry, with only the value captured |
| 61 | + "(?:sentry=({b64}))?" |
| 62 | + # zero or more other vendors' entries, each preceded by a comma, |
| 63 | + # captured together as one group |
| 64 | + "((?:,{ov})*)$".format(ov=ov, b64=b64) |
| 65 | +) |
| 66 | + |
| 67 | +TRACESTATE_SENTRY_VALUE_REGEX = re.compile( |
| 68 | + "sentry=({b64})^[a-zA-Z0-9+/]?".format(b64=b64) |
| 69 | +) |
| 70 | + |
41 | 71 |
|
42 | 72 | class EnvironHeaders(Mapping): # type: ignore
|
43 | 73 | def __init__(
|
@@ -161,6 +191,67 @@ def maybe_create_breadcrumbs_from_span(hub, span):
|
161 | 191 | )
|
162 | 192 |
|
163 | 193 |
|
| 194 | +def extract_sentrytrace_data(header): |
| 195 | + # type: (Optional[str]) -> typing.Mapping[str, Union[Optional[str], Optional[bool]]] |
| 196 | + |
| 197 | + """ |
| 198 | + Given a `sentry-trace` header string, return a dictionary of data. |
| 199 | + """ |
| 200 | + trace_id = parent_span_id = parent_sampled = None |
| 201 | + |
| 202 | + if header: |
| 203 | + if header.startswith("00-") and header.endswith("-00"): |
| 204 | + header = header[3:-3] |
| 205 | + |
| 206 | + match = SENTRY_TRACE_REGEX.match(header) |
| 207 | + |
| 208 | + if match: |
| 209 | + trace_id, parent_span_id, sampled_str = match.groups() |
| 210 | + |
| 211 | + if trace_id: |
| 212 | + trace_id = "{:032x}".format(int(trace_id, 16)) |
| 213 | + if parent_span_id: |
| 214 | + parent_span_id = "{:016x}".format(int(parent_span_id, 16)) |
| 215 | + if sampled_str: |
| 216 | + parent_sampled = sampled_str != "0" |
| 217 | + |
| 218 | + return { |
| 219 | + "trace_id": trace_id, |
| 220 | + "parent_span_id": parent_span_id, |
| 221 | + "parent_sampled": parent_sampled, |
| 222 | + } |
| 223 | + |
| 224 | + |
| 225 | +def extract_tracestate_data(header): |
| 226 | + # type: (Optional[str]) -> typing.Mapping[str, Optional[str]] |
| 227 | + """ |
| 228 | + Extracts the sentry tracestate value and any third-party data from the given |
| 229 | + tracestate header, returning a dictionary of data. |
| 230 | + """ |
| 231 | + sentry_value = third_party = None |
| 232 | + |
| 233 | + if header: |
| 234 | + |
| 235 | + match = TRACESTATE_HEADER_REGEX.match(header) |
| 236 | + |
| 237 | + if match: |
| 238 | + before, sentry_value, after = match.groups() |
| 239 | + if before or after: |
| 240 | + # filter out empty strings, and make sure there aren't too many |
| 241 | + # commas between them |
| 242 | + third_party = ",".join( |
| 243 | + [value.strip(",") for value in [before, after] if value != ""] |
| 244 | + ) |
| 245 | + |
| 246 | + else: |
| 247 | + # if the header is malformed, at least try to grab sentry's part, if any |
| 248 | + sentry_value_match = TRACESTATE_SENTRY_VALUE_REGEX.search(header) |
| 249 | + if sentry_value_match: |
| 250 | + sentry_value = sentry_value_match.group(1) |
| 251 | + |
| 252 | + return {"sentry_tracestate": sentry_value, "third_party_tracestate": third_party} |
| 253 | + |
| 254 | + |
164 | 255 | def compute_new_tracestate(transaction):
|
165 | 256 | # type: (Transaction) -> str
|
166 | 257 | """
|
|
0 commit comments