-
Notifications
You must be signed in to change notification settings - Fork 57
fix: 16-bit hexadecimal formatting for XCTC span IDs #946
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
162af3e
b5b6265
18d029e
f94d288
3491f48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -174,20 +174,41 @@ def _parse_xcloud_trace(header): | |
Args: | ||
header (str): the string extracted from the X_CLOUD_TRACE header | ||
Returns: | ||
Tuple[Optional[dict], Optional[str], bool]: | ||
Tuple[Optional[str], Optional[str], bool]: | ||
The trace_id, span_id and trace_sampled extracted from the header | ||
Each field will be None if not found. | ||
""" | ||
trace_id = span_id = None | ||
trace_sampled = False | ||
# see https://cloud.google.com/trace/docs/setup for X-Cloud-Trace_Context format | ||
|
||
# As per the format described at https://cloud.google.com/trace/docs/trace-context#legacy-http-header | ||
# "X-Cloud-Trace-Context: TRACE_ID[/SPAN_ID][;o=OPTIONS]" | ||
# for example: | ||
# "X-Cloud-Trace-Context: 105445aa7843bc8bf206b12000100000/1;o=1" | ||
# | ||
# We expect: | ||
# * trace_id (optional, 32-bit hex string): "105445aa7843bc8bf206b12000100000" | ||
# * span_id (optional, 16-bit hex string): "0000000000000001" (needs to be converted into 16 bit hex string) | ||
# * trace_sampled (optional, bool): true | ||
if header: | ||
try: | ||
regex = r"([\w-]+)?(\/?([\w-]+))?(;?o=(\d))?" | ||
match = re.match(regex, header) | ||
trace_id = match.group(1) | ||
span_id = match.group(3) | ||
trace_sampled = match.group(5) == "1" | ||
|
||
# Convert the span ID to 16-bit hexadecimal instead of decimal | ||
if span_id is not None: | ||
try: | ||
span_id_int = int(span_id) | ||
if span_id_int > 0 and span_id_int < 2**64: | ||
span_id = f"{span_id_int:016x}" | ||
else: | ||
span_id = None | ||
|
||
except ValueError: | ||
span_id = None | ||
|
||
except IndexError: | ||
pass | ||
return trace_id, span_id, trace_sampled | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,9 +25,13 @@ | |
|
||
_FLASK_TRACE_ID = "flask0id" | ||
_FLASK_SPAN_ID = "span0flask" | ||
_FLASK_SPAN_ID_XCTC_DEC = "12345" | ||
_FLASK_SPAN_ID_XCTC_HEX = "3039".zfill(16) | ||
_FLASK_HTTP_REQUEST = {"requestUrl": "https://flask.palletsprojects.com/en/1.1.x/"} | ||
_DJANGO_TRACE_ID = "django0id" | ||
_DJANGO_SPAN_ID = "span0django" | ||
_DJANGO_SPAN_ID_XCTC_DEC = "54321" | ||
_DJANGO_SPAN_ID_XCTC_HEX = "d431".zfill(16) | ||
_DJANGO_HTTP_REQUEST = {"requestUrl": "https://www.djangoproject.com/"} | ||
|
||
|
||
|
@@ -64,8 +68,9 @@ def test_no_context_header(self): | |
def test_xcloud_header(self): | ||
flask_trace_header = "X_CLOUD_TRACE_CONTEXT" | ||
expected_trace_id = _FLASK_TRACE_ID | ||
expected_span_id = _FLASK_SPAN_ID | ||
flask_trace_id = f"{expected_trace_id}/{expected_span_id};o=1" | ||
input_span_id = _FLASK_SPAN_ID_XCTC_DEC | ||
expected_span_id = _FLASK_SPAN_ID_XCTC_HEX | ||
flask_trace_id = f"{expected_trace_id}/{input_span_id};o=1" | ||
|
||
app = self.create_app() | ||
context = app.test_request_context( | ||
|
@@ -173,9 +178,10 @@ def test_xcloud_header(self): | |
from google.cloud.logging_v2.handlers.middleware import request | ||
|
||
django_trace_header = "HTTP_X_CLOUD_TRACE_CONTEXT" | ||
expected_span_id = _DJANGO_SPAN_ID | ||
input_span_id = _DJANGO_SPAN_ID_XCTC_DEC | ||
expected_span_id = _DJANGO_SPAN_ID_XCTC_HEX | ||
expected_trace_id = _DJANGO_TRACE_ID | ||
django_trace_id = f"{expected_trace_id}/{expected_span_id};o=1" | ||
django_trace_id = f"{expected_trace_id}/{input_span_id};o=1" | ||
|
||
django_request = RequestFactory().get( | ||
"/", **{django_trace_header: django_trace_id} | ||
|
@@ -501,43 +507,62 @@ def test_no_span(self): | |
self.assertEqual(sampled, False) | ||
|
||
def test_no_trace(self): | ||
header = "/12345" | ||
input_span = "12345" | ||
expected_span = "3039".zfill(16) | ||
header = f"/{input_span}" | ||
trace_id, span_id, sampled = self._call_fut(header) | ||
self.assertIsNone(trace_id) | ||
self.assertEqual(span_id, "12345") | ||
self.assertEqual(span_id, expected_span) | ||
self.assertEqual(sampled, False) | ||
|
||
def test_with_span(self): | ||
expected_trace = "12345" | ||
expected_span = "67890" | ||
header = f"{expected_trace}/{expected_span}" | ||
input_span = "67890" | ||
expected_span = "10932".zfill(16) | ||
header = f"{expected_trace}/{input_span}" | ||
trace_id, span_id, sampled = self._call_fut(header) | ||
self.assertEqual(trace_id, expected_trace) | ||
self.assertEqual(span_id, expected_span) | ||
self.assertEqual(sampled, False) | ||
|
||
def test_with_span_decimal_not_in_bounds(self): | ||
input_spans = ["0", "9" * 100] | ||
|
||
for input_span in input_spans: | ||
expected_trace = "12345" | ||
input_span = "67890" | ||
expected_span = "10932".zfill(16) | ||
|
||
header = f"{expected_trace}/{input_span}" | ||
trace_id, span_id, sampled = self._call_fut(header) | ||
self.assertEqual(trace_id, expected_trace) | ||
self.assertEqual(span_id, expected_span) | ||
self.assertEqual(sampled, False) | ||
|
||
def test_with_extra_characters(self): | ||
expected_trace = "12345" | ||
expected_span = "67890" | ||
header = f"{expected_trace}/{expected_span};abc" | ||
input_span = "67890" | ||
expected_span = "10932".zfill(16) | ||
header = f"{expected_trace}/{input_span};abc" | ||
trace_id, span_id, sampled = self._call_fut(header) | ||
self.assertEqual(trace_id, expected_trace) | ||
self.assertEqual(span_id, expected_span) | ||
self.assertEqual(sampled, False) | ||
|
||
def test_with_explicit_no_sampled(self): | ||
expected_trace = "12345" | ||
expected_span = "67890" | ||
header = f"{expected_trace}/{expected_span};o=0" | ||
input_span = "67890" | ||
expected_span = "10932".zfill(16) | ||
header = f"{expected_trace}/{input_span};o=0" | ||
trace_id, span_id, sampled = self._call_fut(header) | ||
self.assertEqual(trace_id, expected_trace) | ||
self.assertEqual(span_id, expected_span) | ||
self.assertEqual(sampled, False) | ||
|
||
def test_with__sampled(self): | ||
expected_trace = "12345" | ||
expected_span = "67890" | ||
header = f"{expected_trace}/{expected_span};o=1" | ||
input_span = "67890" | ||
expected_span = "10932".zfill(16) | ||
header = f"{expected_trace}/{input_span};o=1" | ||
trace_id, span_id, sampled = self._call_fut(header) | ||
self.assertEqual(trace_id, expected_trace) | ||
self.assertEqual(span_id, expected_span) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have a lot of layers of nesting here... consider splitting this up into multiple smaller helper functions. For example: