Content-Length: 642523 | pFad | https://github.com/googleapis/python-logging/commit/1f2b190c0d1a7125d9412c157915d0011cdd4c47

C8 fix: 16-bit hexadecimal formatting for XCTC span IDs (#946) · googleapis/python-logging@1f2b190 · GitHub
Skip to content

Commit 1f2b190

Browse files
fix: 16-bit hexadecimal formatting for XCTC span IDs (#946)
* fix: 16-bit hexadecimal formatting for XCTC span IDs * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * addressed nit * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Fixed test + docstring --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 6c753c2 commit 1f2b190

File tree

4 files changed

+67
-22
lines changed

4 files changed

+67
-22
lines changed

google/cloud/logging_v2/handlers/_helpers.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,20 +174,40 @@ def _parse_xcloud_trace(header):
174174
Args:
175175
header (str): the string extracted from the X_CLOUD_TRACE header
176176
Returns:
177-
Tuple[Optional[dict], Optional[str], bool]:
177+
Tuple[Optional[str], Optional[str], bool]:
178178
The trace_id, span_id and trace_sampled extracted from the header
179179
Each field will be None if not found.
180180
"""
181181
trace_id = span_id = None
182182
trace_sampled = False
183-
# see https://cloud.google.com/trace/docs/setup for X-Cloud-Trace_Context format
183+
184+
# As per the format described at https://cloud.google.com/trace/docs/trace-context#legacy-http-header
185+
# "X-Cloud-Trace-Context: TRACE_ID[/SPAN_ID][;o=OPTIONS]"
186+
# for example:
187+
# "X-Cloud-Trace-Context: 105445aa7843bc8bf206b12000100000/1;o=1"
188+
#
189+
# We expect:
190+
# * trace_id (optional, 128-bit hex string): "105445aa7843bc8bf206b12000100000"
191+
# * span_id (optional, 16-bit hex string): "0000000000000001" (needs to be converted into 16 bit hex string)
192+
# * trace_sampled (optional, bool): true
184193
if header:
185194
try:
186195
regex = r"([\w-]+)?(\/?([\w-]+))?(;?o=(\d))?"
187196
match = re.match(regex, header)
188197
trace_id = match.group(1)
189198
span_id = match.group(3)
190199
trace_sampled = match.group(5) == "1"
200+
201+
# Convert the span ID to 16-bit hexadecimal instead of decimal
202+
try:
203+
span_id_int = int(span_id)
204+
if span_id_int > 0 and span_id_int < 2**64:
205+
span_id = f"{span_id_int:016x}"
206+
else:
207+
span_id = None
208+
except (ValueError, TypeError):
209+
span_id = None
210+
191211
except IndexError:
192212
pass
193213
return trace_id, span_id, trace_sampled

tests/unit/handlers/test__helpers.py

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@
2525

2626
_FLASK_TRACE_ID = "flask0id"
2727
_FLASK_SPAN_ID = "span0flask"
28+
_FLASK_SPAN_ID_XCTC_DEC = "12345"
29+
_FLASK_SPAN_ID_XCTC_HEX = "3039".zfill(16)
2830
_FLASK_HTTP_REQUEST = {"requestUrl": "https://flask.palletsprojects.com/en/1.1.x/"}
2931
_DJANGO_TRACE_ID = "django0id"
3032
_DJANGO_SPAN_ID = "span0django"
33+
_DJANGO_SPAN_ID_XCTC_DEC = "54321"
34+
_DJANGO_SPAN_ID_XCTC_HEX = "d431".zfill(16)
3135
_DJANGO_HTTP_REQUEST = {"requestUrl": "https://www.djangoproject.com/"}
3236

3337

@@ -64,8 +68,9 @@ def test_no_context_header(self):
6468
def test_xcloud_header(self):
6569
flask_trace_header = "X_CLOUD_TRACE_CONTEXT"
6670
expected_trace_id = _FLASK_TRACE_ID
67-
expected_span_id = _FLASK_SPAN_ID
68-
flask_trace_id = f"{expected_trace_id}/{expected_span_id};o=1"
71+
input_span_id = _FLASK_SPAN_ID_XCTC_DEC
72+
expected_span_id = _FLASK_SPAN_ID_XCTC_HEX
73+
flask_trace_id = f"{expected_trace_id}/{input_span_id};o=1"
6974

7075
app = self.create_app()
7176
context = app.test_request_context(
@@ -173,9 +178,10 @@ def test_xcloud_header(self):
173178
from google.cloud.logging_v2.handlers.middleware import request
174179

175180
django_trace_header = "HTTP_X_CLOUD_TRACE_CONTEXT"
176-
expected_span_id = _DJANGO_SPAN_ID
181+
input_span_id = _DJANGO_SPAN_ID_XCTC_DEC
182+
expected_span_id = _DJANGO_SPAN_ID_XCTC_HEX
177183
expected_trace_id = _DJANGO_TRACE_ID
178-
django_trace_id = f"{expected_trace_id}/{expected_span_id};o=1"
184+
django_trace_id = f"{expected_trace_id}/{input_span_id};o=1"
179185

180186
django_request = RequestFactory().get(
181187
"/", **{django_trace_header: django_trace_id}
@@ -501,43 +507,60 @@ def test_no_span(self):
501507
self.assertEqual(sampled, False)
502508

503509
def test_no_trace(self):
504-
header = "/12345"
510+
input_span = "12345"
511+
expected_span = "3039".zfill(16)
512+
header = f"/{input_span}"
505513
trace_id, span_id, sampled = self._call_fut(header)
506514
self.assertIsNone(trace_id)
507-
self.assertEqual(span_id, "12345")
515+
self.assertEqual(span_id, expected_span)
508516
self.assertEqual(sampled, False)
509517

510518
def test_with_span(self):
511519
expected_trace = "12345"
512-
expected_span = "67890"
513-
header = f"{expected_trace}/{expected_span}"
520+
input_span = "67890"
521+
expected_span = "10932".zfill(16)
522+
header = f"{expected_trace}/{input_span}"
514523
trace_id, span_id, sampled = self._call_fut(header)
515524
self.assertEqual(trace_id, expected_trace)
516525
self.assertEqual(span_id, expected_span)
517526
self.assertEqual(sampled, False)
518527

528+
def test_with_span_decimal_not_in_bounds(self):
529+
input_spans = ["0", "9" * 100]
530+
531+
for input_span in input_spans:
532+
expected_trace = "12345"
533+
header = f"{expected_trace}/{input_span}"
534+
trace_id, span_id, sampled = self._call_fut(header)
535+
self.assertEqual(trace_id, expected_trace)
536+
self.assertIsNone(span_id)
537+
self.assertEqual(sampled, False)
538+
519539
def test_with_extra_characters(self):
520540
expected_trace = "12345"
521-
expected_span = "67890"
522-
header = f"{expected_trace}/{expected_span};abc"
541+
input_span = "67890"
542+
expected_span = "10932".zfill(16)
543+
header = f"{expected_trace}/{input_span};abc"
523544
trace_id, span_id, sampled = self._call_fut(header)
524545
self.assertEqual(trace_id, expected_trace)
525546
self.assertEqual(span_id, expected_span)
526547
self.assertEqual(sampled, False)
527548

528549
def test_with_explicit_no_sampled(self):
529550
expected_trace = "12345"
530-
expected_span = "67890"
531-
header = f"{expected_trace}/{expected_span};o=0"
551+
input_span = "67890"
552+
expected_span = "10932".zfill(16)
553+
header = f"{expected_trace}/{input_span};o=0"
532554
trace_id, span_id, sampled = self._call_fut(header)
533555
self.assertEqual(trace_id, expected_trace)
534556
self.assertEqual(span_id, expected_span)
535557
self.assertEqual(sampled, False)
536558

537559
def test_with__sampled(self):
538560
expected_trace = "12345"
539-
expected_span = "67890"
540-
header = f"{expected_trace}/{expected_span};o=1"
561+
input_span = "67890"
562+
expected_span = "10932".zfill(16)
563+
header = f"{expected_trace}/{input_span};o=1"
541564
trace_id, span_id, sampled = self._call_fut(header)
542565
self.assertEqual(trace_id, expected_trace)
543566
self.assertEqual(span_id, expected_span)

tests/unit/handlers/test_handlers.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def test_minimal_record(self):
140140
self.assertIsNone(record._labels)
141141
self.assertEqual(record._labels_str, "{}")
142142

143-
def test_record_with_request(self):
143+
def test_record_with_xctc_request(self):
144144
"""
145145
test filter adds http request data when available
146146
"""
@@ -161,8 +161,9 @@ def test_record_with_request(self):
161161
expected_path = "http://testserver/123"
162162
expected_agent = "Mozilla/5.0"
163163
expected_trace = "123"
164-
expected_span = "456"
165-
combined_trace = f"{expected_trace}/{expected_span};o=1"
164+
input_span = "456"
165+
expected_span = "1c8".zfill(16)
166+
combined_trace = f"{expected_trace}/{input_span};o=1"
166167
expected_request = {
167168
"requestMethod": "GET",
168169
"requestUrl": expected_path,

tests/unit/handlers/test_structured_log.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ def test_format_with_arguments(self):
382382
result = handler.format(record)
383383
self.assertIn(expected_result, result)
384384

385-
def test_format_with_request(self):
385+
def test_format_with_xctc_request(self):
386386
import logging
387387
import json
388388

@@ -393,8 +393,9 @@ def test_format_with_request(self):
393393
expected_path = "http://testserver/123"
394394
expected_agent = "Mozilla/5.0"
395395
expected_trace = "123"
396-
expected_span = "456"
397-
trace_header = f"{expected_trace}/{expected_span};o=1"
396+
input_span = "456"
397+
expected_span = "1c8".zfill(16)
398+
trace_header = f"{expected_trace}/{input_span};o=1"
398399
expected_payload = {
399400
"logging.googleapis.com/trace": expected_trace,
400401
"logging.googleapis.com/spanId": expected_span,

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://github.com/googleapis/python-logging/commit/1f2b190c0d1a7125d9412c157915d0011cdd4c47

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy