Skip to content

Commit fcd26eb

Browse files
feat: support span inference (#267)
1 parent fe4de39 commit fcd26eb

File tree

8 files changed

+166
-61
lines changed

8 files changed

+166
-61
lines changed

google/cloud/logging_v2/handlers/_helpers.py

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import math
1818
import json
19+
import re
1920

2021
try:
2122
import flask
@@ -55,12 +56,13 @@ def get_request_data_from_flask():
5556
"""Get http_request and trace data from flask request headers.
5657
5758
Returns:
58-
Tuple[Optional[dict], Optional[str]]:
59-
Data related to the current http request and the trace_id for the
60-
request. Both fields will be None if a flask request isn't found.
59+
Tuple[Optional[dict], Optional[str], Optional[str]]:
60+
Data related to the current http request, trace_id, and span_id for
61+
the request. All fields will be None if a django request isn't
62+
found.
6163
"""
6264
if flask is None or not flask.request:
63-
return None, None
65+
return None, None, None
6466

6567
# build http_request
6668
http_request = {
@@ -73,27 +75,26 @@ def get_request_data_from_flask():
7375
"protocol": flask.request.environ.get(_PROTOCOL_HEADER),
7476
}
7577

76-
# find trace id
77-
trace_id = None
78+
# find trace id and span id
7879
header = flask.request.headers.get(_FLASK_TRACE_HEADER)
79-
if header:
80-
trace_id = header.split("/", 1)[0]
80+
trace_id, span_id = _parse_trace_span(header)
8181

82-
return http_request, trace_id
82+
return http_request, trace_id, span_id
8383

8484

8585
def get_request_data_from_django():
8686
"""Get http_request and trace data from django request headers.
8787
8888
Returns:
89-
Tuple[Optional[dict], Optional[str]]:
90-
Data related to the current http request and the trace_id for the
91-
request. Both fields will be None if a django request isn't found.
89+
Tuple[Optional[dict], Optional[str], Optional[str]]:
90+
Data related to the current http request, trace_id, and span_id for
91+
the request. All fields will be None if a django request isn't
92+
found.
9293
"""
9394
request = _get_django_request()
9495

9596
if request is None:
96-
return None, None
97+
return None, None, None
9798

9899
# convert content_length to int if it exists
99100
content_length = None
@@ -112,32 +113,55 @@ def get_request_data_from_django():
112113
"protocol": request.META.get(_PROTOCOL_HEADER),
113114
}
114115

115-
# find trace id
116-
trace_id = None
116+
# find trace id and span id
117117
header = request.META.get(_DJANGO_TRACE_HEADER)
118-
if header:
119-
trace_id = header.split("/", 1)[0]
118+
trace_id, span_id = _parse_trace_span(header)
120119

121-
return http_request, trace_id
120+
return http_request, trace_id, span_id
121+
122+
123+
def _parse_trace_span(header):
124+
"""Given an X_CLOUD_TRACE header, extract the trace and span ids.
125+
126+
Args:
127+
header (str): the string extracted from the X_CLOUD_TRACE header
128+
Returns:
129+
Tuple[Optional[dict], Optional[str]]:
130+
The trace_id and span_id extracted from the header
131+
Each field will be None if not found.
132+
"""
133+
trace_id = None
134+
span_id = None
135+
if header:
136+
try:
137+
split_header = header.split("/", 1)
138+
trace_id = split_header[0]
139+
header_suffix = split_header[1]
140+
# the span is the set of alphanumeric characters after the /
141+
span_id = re.findall(r"^\w+", header_suffix)[0]
142+
except IndexError:
143+
pass
144+
return trace_id, span_id
122145

123146

124147
def get_request_data():
125148
"""Helper to get http_request and trace data from supported web
126149
frameworks (currently supported: Flask and Django).
127150
128151
Returns:
129-
Tuple[Optional[dict], Optional[str]]:
130-
Data related to the current http request and the trace_id for the
131-
request. Both fields will be None if a supported web request isn't found.
152+
Tuple[Optional[dict], Optional[str], Optional[str]]:
153+
Data related to the current http request, trace_id, and span_id for
154+
the request. All fields will be None if a django request isn't
155+
found.
132156
"""
133157
checkers = (
134158
get_request_data_from_django,
135159
get_request_data_from_flask,
136160
)
137161

138162
for checker in checkers:
139-
http_request, trace_id = checker()
163+
http_request, trace_id, span_id = checker()
140164
if http_request is not None:
141-
return http_request, trace_id
165+
return http_request, trace_id, span_id
142166

143-
return None, None
167+
return None, None, None

google/cloud/logging_v2/handlers/app_engine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def get_gae_labels(self):
9090
"""
9191
gae_labels = {}
9292

93-
_, trace_id = get_request_data()
93+
_, trace_id, _ = get_request_data()
9494
if trace_id is not None:
9595
gae_labels[_TRACE_ID_LABEL] = trace_id
9696

@@ -107,7 +107,7 @@ def emit(self, record):
107107
record (logging.LogRecord): The record to be logged.
108108
"""
109109
message = super(AppEngineHandler, self).format(record)
110-
inferred_http, inferred_trace = get_request_data()
110+
inferred_http, inferred_trace, _ = get_request_data()
111111
if inferred_trace is not None:
112112
inferred_trace = f"projects/{self.project_id}/traces/{inferred_trace}"
113113
# allow user overrides

google/cloud/logging_v2/handlers/handlers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def filter(self, record):
5959
}
6060
record.msg = "" if record.msg is None else record.msg
6161
# find http request data
62-
inferred_http, inferred_trace = get_request_data()
62+
inferred_http, inferred_trace, inferred_span = get_request_data()
6363
if inferred_trace is not None and self.project is not None:
6464
inferred_trace = f"projects/{self.project}/traces/{inferred_trace}"
6565
# set labels
@@ -70,6 +70,7 @@ def filter(self, record):
7070
)
7171

7272
record.trace = getattr(record, "trace", inferred_trace) or ""
73+
record.span_id = getattr(record, "span_id", inferred_span) or ""
7374
record.http_request = getattr(record, "http_request", inferred_http) or {}
7475
record.request_method = record.http_request.get("requestMethod", "")
7576
record.request_url = record.http_request.get("requestUrl", "")

google/cloud/logging_v2/handlers/structured_log.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
'"severity": "%(levelname)s", '
2525
'"logging.googleapis.com/labels": { %(total_labels_str)s }, '
2626
'"logging.googleapis.com/trace": "%(trace)s", '
27+
'"logging.googleapis.com/spanId": "%(span_id)s", '
2728
'"logging.googleapis.com/sourceLocation": { "file": "%(file)s", "line": "%(line)d", "function": "%(function)s"}, '
2829
'"httpRequest": {"requestMethod": "%(request_method)s", "requestUrl": "%(request_url)s", "userAgent": "%(user_agent)s", "protocol": "%(protocol)s"} }'
2930
)

0 commit comments

Comments
 (0)