Skip to content

Commit 6b10b74

Browse files
feat: Improve source location overrides (#258)
1 parent a5c2f8e commit 6b10b74

File tree

6 files changed

+94
-35
lines changed

6 files changed

+94
-35
lines changed

google/cloud/logging_v2/handlers/handlers.py

+18-16
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,21 @@ def __init__(self, project=None):
4242

4343
def filter(self, record):
4444
# ensure record has all required fields set
45-
record.lineno = 0 if record.lineno is None else record.lineno
45+
if hasattr(record, "source_location"):
46+
record.line = int(record.source_location.get("line", 0))
47+
record.file = record.source_location.get("file", "")
48+
record.function = record.source_location.get("function", "")
49+
else:
50+
record.line = record.lineno if record.lineno else 0
51+
record.file = record.pathname if record.pathname else ""
52+
record.function = record.funcName if record.funcName else ""
53+
if any([record.line, record.file, record.function]):
54+
record.source_location = {
55+
"line": record.line,
56+
"file": record.file,
57+
"function": record.function,
58+
}
4659
record.msg = "" if record.msg is None else record.msg
47-
record.funcName = "" if record.funcName is None else record.funcName
48-
record.pathname = "" if record.pathname is None else record.pathname
4960
# find http request data
5061
inferred_http, inferred_trace = get_request_data()
5162
if inferred_trace is not None and self.project is not None:
@@ -146,25 +157,16 @@ def emit(self, record):
146157
total_labels.update(user_labels)
147158
if len(total_labels) == 0:
148159
total_labels = None
149-
# create source location object
150-
if record.lineno and record.funcName and record.pathname:
151-
source_location = {
152-
"file": record.pathname,
153-
"line": str(record.lineno),
154-
"function": record.funcName,
155-
}
156-
else:
157-
source_location = None
158160
# send off request
159161
self.transport.send(
160162
record,
161163
message,
162164
resource=getattr(record, "resource", self.resource),
163-
labels=(total_labels if total_labels else None),
164-
trace=(record.trace if record.trace else None),
165+
labels=total_labels,
166+
trace=getattr(record, "trace", None),
165167
span_id=getattr(record, "span_id", None),
166-
http_request=(record.http_request if record.http_request else None),
167-
source_location=source_location,
168+
http_request=getattr(record, "http_request", None),
169+
source_location=getattr(record, "source_location", None),
168170
)
169171

170172

google/cloud/logging_v2/handlers/structured_log.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from google.cloud.logging_v2.handlers.handlers import CloudLoggingFilter
2121

22-
GCP_FORMAT = '{"message": "%(message)s", "severity": "%(levelname)s", "logging.googleapis.com/trace": "%(trace)s", "logging.googleapis.com/sourceLocation": { "file": "%(pathname)s", "line": "%(lineno)d", "function": "%(funcName)s"}, "httpRequest": {"requestMethod": "%(request_method)s", "requestUrl": "%(request_url)s", "userAgent": "%(user_agent)s", "protocol": "%(protocol)s"} }'
22+
GCP_FORMAT = '{"message": "%(message)s", "severity": "%(levelname)s", "logging.googleapis.com/trace": "%(trace)s", "logging.googleapis.com/sourceLocation": { "file": "%(file)s", "line": "%(line)d", "function": "%(function)s"}, "httpRequest": {"requestMethod": "%(request_method)s", "requestUrl": "%(request_url)s", "userAgent": "%(user_agent)s", "protocol": "%(protocol)s"} }'
2323

2424

2525
class StructuredLogHandler(logging.StreamHandler):

tests/system/test_system.py

+2
Original file line numberDiff line numberDiff line change
@@ -333,10 +333,12 @@ def test_handlers_w_extras(self):
333333
cloud_logger = logging.getLogger(LOGGER_NAME)
334334
cloud_logger.addHandler(handler)
335335
expected_request = {"requestUrl": "localhost"}
336+
expected_source = {"file": "test.py"}
336337
extra = {
337338
"trace": "123",
338339
"span_id": "456",
339340
"http_request": expected_request,
341+
"source_location": expected_source,
340342
"resource": Resource(type="cloudiot_device", labels={}),
341343
"labels": {"test-label": "manual"},
342344
}

tests/unit/handlers/test_handlers.py

+23-17
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ def test_filter_record(self):
6767
success = filter_obj.filter(record)
6868
self.assertTrue(success)
6969

70-
self.assertEqual(record.lineno, lineno)
70+
self.assertEqual(record.line, lineno)
7171
self.assertEqual(record.msg, message)
72-
self.assertEqual(record.funcName, func)
73-
self.assertEqual(record.pathname, pathname)
72+
self.assertEqual(record.function, func)
73+
self.assertEqual(record.file, pathname)
7474
self.assertEqual(record.trace, "")
7575
self.assertEqual(record.http_request, {})
7676
self.assertEqual(record.request_method, "")
@@ -91,10 +91,10 @@ def test_minimal_record(self):
9191
success = filter_obj.filter(record)
9292
self.assertTrue(success)
9393

94-
self.assertEqual(record.lineno, 0)
94+
self.assertEqual(record.line, 0)
9595
self.assertEqual(record.msg, "")
96-
self.assertEqual(record.funcName, "")
97-
self.assertEqual(record.pathname, "")
96+
self.assertEqual(record.function, "")
97+
self.assertEqual(record.file, "")
9898
self.assertEqual(record.trace, "")
9999
self.assertEqual(record.http_request, {})
100100
self.assertEqual(record.request_method, "")
@@ -175,7 +175,16 @@ def test_user_overrides(self):
175175
"userAgent": overwritten_agent,
176176
"protocol": overwritten_protocol,
177177
}
178+
overwritten_line = 22
179+
overwritten_function = "test-func"
180+
overwritten_file = "test-file"
181+
overwritten_source_location = {
182+
"file": overwritten_file,
183+
"line": overwritten_line,
184+
"function": overwritten_function,
185+
}
178186
record.http_request = overwritten_request_object
187+
record.source_location = overwritten_source_location
179188
success = filter_obj.filter(record)
180189
self.assertTrue(success)
181190

@@ -185,6 +194,9 @@ def test_user_overrides(self):
185194
self.assertEqual(record.request_url, overwritten_url)
186195
self.assertEqual(record.user_agent, overwritten_agent)
187196
self.assertEqual(record.protocol, overwritten_protocol)
197+
self.assertEqual(record.line, overwritten_line)
198+
self.assertEqual(record.function, overwritten_function)
199+
self.assertEqual(record.file, overwritten_file)
188200

189201

190202
class TestCloudLoggingHandler(unittest.TestCase):
@@ -256,12 +268,13 @@ def test_emit(self):
256268
)
257269
logname = "loggername"
258270
message = "hello world"
271+
labels = {"test-key": "test-value"}
259272
record = logging.LogRecord(logname, logging, None, None, message, None, None)
260-
handler.filter(record)
273+
record.labels = labels
261274
handler.emit(record)
262275
self.assertEqual(
263276
handler.transport.send_called_with,
264-
(record, message, _GLOBAL_RESOURCE, None, None, None, None, None),
277+
(record, message, _GLOBAL_RESOURCE, labels, None, None, None, None),
265278
)
266279

267280
def test_emit_manual_field_override(self):
@@ -282,19 +295,12 @@ def test_emit_manual_field_override(self):
282295
setattr(record, "span_id", expected_span)
283296
expected_http = {"reuqest_url": "manual"}
284297
setattr(record, "http_request", expected_http)
298+
expected_source = {"file": "test-file"}
299+
setattr(record, "source_location", expected_source)
285300
expected_resource = Resource(type="test", labels={})
286301
setattr(record, "resource", expected_resource)
287302
expected_labels = {"test-label": "manual"}
288303
setattr(record, "labels", expected_labels)
289-
expected_source = {
290-
"file": "test-file",
291-
"line": str(1),
292-
"function": "test-func",
293-
}
294-
setattr(record, "lineno", int(expected_source["line"]))
295-
setattr(record, "funcName", expected_source["function"])
296-
setattr(record, "pathname", expected_source["file"])
297-
handler.filter(record)
298304
handler.emit(record)
299305

300306
self.assertEqual(

tests/unit/handlers/test_structured_log.py

+49
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,52 @@ def test_format_with_request(self):
149149
result = json.loads(handler.format(record))
150150
for (key, value) in expected_payload.items():
151151
self.assertEqual(value, result[key])
152+
153+
def test_format_overrides(self):
154+
"""
155+
Allow users to override log fields using `logging.info("", extra={})`
156+
157+
If supported fields were overriden by the user, those choices should
158+
take precedence.
159+
"""
160+
import logging
161+
import json
162+
163+
handler = self._make_one()
164+
logname = "loggername"
165+
message = "hello world,嗨 世界"
166+
record = logging.LogRecord(logname, logging.INFO, "", 0, message, None, None)
167+
overwrite_path = "http://overwrite"
168+
inferred_path = "http://testserver/123"
169+
overwrite_trace = "456"
170+
inferred_trace = "123"
171+
overwrite_file = "test-file"
172+
record.http_request = {"requestUrl": overwrite_path}
173+
record.source_location = {"file": overwrite_file}
174+
record.trace = overwrite_trace
175+
expected_payload = {
176+
"logging.googleapis.com/trace": overwrite_trace,
177+
"logging.googleapis.com/sourceLocation": {
178+
"file": overwrite_file,
179+
"function": "",
180+
"line": "0",
181+
},
182+
"httpRequest": {
183+
"requestMethod": "",
184+
"requestUrl": overwrite_path,
185+
"userAgent": "",
186+
"protocol": "",
187+
},
188+
}
189+
190+
app = self.create_app()
191+
with app.test_client() as c:
192+
c.put(
193+
path=inferred_path,
194+
data="body",
195+
headers={"X_CLOUD_TRACE_CONTEXT": inferred_trace},
196+
)
197+
handler.filter(record)
198+
result = json.loads(handler.format(record))
199+
for (key, value) in expected_payload.items():
200+
self.assertEqual(value, result[key])

0 commit comments

Comments
 (0)