Skip to content

Commit 1e77860

Browse files
refactor: clean up CloudLoggingFilter (#281)
1 parent fcd26eb commit 1e77860

File tree

6 files changed

+124
-133
lines changed

6 files changed

+124
-133
lines changed

google/cloud/logging_v2/handlers/_helpers.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,7 @@ def get_request_data_from_flask():
6868
http_request = {
6969
"requestMethod": flask.request.method,
7070
"requestUrl": flask.request.url,
71-
"requestSize": flask.request.content_length,
7271
"userAgent": flask.request.user_agent.string,
73-
"remoteIp": flask.request.remote_addr,
74-
"referer": flask.request.referrer,
7572
"protocol": flask.request.environ.get(_PROTOCOL_HEADER),
7673
}
7774

@@ -96,20 +93,11 @@ def get_request_data_from_django():
9693
if request is None:
9794
return None, None, None
9895

99-
# convert content_length to int if it exists
100-
content_length = None
101-
try:
102-
content_length = int(request.META.get(_DJANGO_CONTENT_LENGTH))
103-
except (ValueError, TypeError):
104-
content_length = None
10596
# build http_request
10697
http_request = {
10798
"requestMethod": request.method,
10899
"requestUrl": request.build_absolute_uri(),
109-
"requestSize": content_length,
110100
"userAgent": request.META.get(_DJANGO_USERAGENT_HEADER),
111-
"remoteIp": request.META.get(_DJANGO_REMOTE_ADDR_HEADER),
112-
"referer": request.META.get(_DJANGO_REFERER_HEADER),
113101
"protocol": request.META.get(_PROTOCOL_HEADER),
114102
}
115103

google/cloud/logging_v2/handlers/handlers.py

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
"""Python :mod:`logging` handlers for Cloud Logging."""
1616

17+
import json
1718
import logging
1819

1920
from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE
@@ -32,50 +33,57 @@ class CloudLoggingFilter(logging.Filter):
3233
"""Python standard ``logging`` Filter class to add Cloud Logging
3334
information to each LogRecord.
3435
35-
When attached to a LogHandler, each incoming log will receive trace and
36-
http_request related to the request. This data can be overwritten using
37-
the `extras` argument when writing logs.
36+
When attached to a LogHandler, each incoming log will be modified
37+
to include new Cloud Logging relevant data. This data can be manually
38+
overwritten using the `extras` argument when writing logs.
3839
"""
3940

4041
def __init__(self, project=None, default_labels=None):
4142
self.project = project
4243
self.default_labels = default_labels if default_labels else {}
4344

44-
def filter(self, record):
45-
# ensure record has all required fields set
45+
@staticmethod
46+
def _infer_source_location(record):
47+
"""Helper function to infer source location data from a LogRecord.
48+
Will default to record.source_location if already set
49+
"""
4650
if hasattr(record, "source_location"):
47-
record.line = int(record.source_location.get("line", 0))
48-
record.file = record.source_location.get("file", "")
49-
record.function = record.source_location.get("function", "")
51+
return record.source_location
5052
else:
51-
record.line = record.lineno if record.lineno else 0
52-
record.file = record.pathname if record.pathname else ""
53-
record.function = record.funcName if record.funcName else ""
54-
if any([record.line, record.file, record.function]):
55-
record.source_location = {
56-
"line": record.line,
57-
"file": record.file,
58-
"function": record.function,
59-
}
60-
record.msg = "" if record.msg is None else record.msg
61-
# find http request data
53+
name_map = [
54+
("line", "lineno"),
55+
("file", "pathname"),
56+
("function", "funcName"),
57+
]
58+
output = {}
59+
for (gcp_name, std_lib_name) in name_map:
60+
value = getattr(record, std_lib_name, None)
61+
if value is not None:
62+
output[gcp_name] = value
63+
return output if output else None
64+
65+
def filter(self, record):
66+
"""
67+
Add new Cloud Logging data to each LogRecord as it comes in
68+
"""
69+
user_labels = getattr(record, "labels", {})
6270
inferred_http, inferred_trace, inferred_span = get_request_data()
6371
if inferred_trace is not None and self.project is not None:
6472
inferred_trace = f"projects/{self.project}/traces/{inferred_trace}"
65-
# set labels
66-
user_labels = getattr(record, "labels", {})
67-
record.total_labels = {**self.default_labels, **user_labels}
68-
record.total_labels_str = ", ".join(
69-
[f'"{k}": "{v}"' for k, v in record.total_labels.items()]
70-
)
71-
72-
record.trace = getattr(record, "trace", inferred_trace) or ""
73-
record.span_id = getattr(record, "span_id", inferred_span) or ""
74-
record.http_request = getattr(record, "http_request", inferred_http) or {}
75-
record.request_method = record.http_request.get("requestMethod", "")
76-
record.request_url = record.http_request.get("requestUrl", "")
77-
record.user_agent = record.http_request.get("userAgent", "")
78-
record.protocol = record.http_request.get("protocol", "")
73+
# set new record values
74+
record._resource = getattr(record, "resource", None)
75+
record._trace = getattr(record, "trace", inferred_trace) or None
76+
record._span_id = getattr(record, "span_id", inferred_span) or None
77+
record._http_request = getattr(record, "http_request", inferred_http)
78+
record._source_location = CloudLoggingFilter._infer_source_location(record)
79+
record._labels = {**self.default_labels, **user_labels} or None
80+
# create guaranteed string representations for structured logging
81+
record._msg_str = record.msg or ""
82+
record._trace_str = record._trace or ""
83+
record._span_id_str = record._span_id or ""
84+
record._http_request_str = json.dumps(record._http_request or {})
85+
record._source_location_str = json.dumps(record._source_location or {})
86+
record._labels_str = json.dumps(record._labels or {})
7987
return True
8088

8189

@@ -163,12 +171,12 @@ def emit(self, record):
163171
self.transport.send(
164172
record,
165173
message,
166-
resource=getattr(record, "resource", self.resource),
167-
labels=getattr(record, "total_labels", None) or None,
168-
trace=getattr(record, "trace", None) or None,
169-
span_id=getattr(record, "span_id", None) or None,
170-
http_request=getattr(record, "http_request", None) or None,
171-
source_location=getattr(record, "source_location", None) or None,
174+
resource=(record._resource or self.resource),
175+
labels=record._labels,
176+
trace=record._trace,
177+
span_id=record._span_id,
178+
http_request=record._http_request,
179+
source_location=record._source_location,
172180
)
173181

174182

google/cloud/logging_v2/handlers/structured_log.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
from google.cloud.logging_v2.handlers.handlers import CloudLoggingFilter
2121

2222
GCP_FORMAT = (
23-
'{"message": "%(message)s", '
23+
'{"message": "%(_msg_str)s", '
2424
'"severity": "%(levelname)s", '
25-
'"logging.googleapis.com/labels": { %(total_labels_str)s }, '
26-
'"logging.googleapis.com/trace": "%(trace)s", '
27-
'"logging.googleapis.com/spanId": "%(span_id)s", '
28-
'"logging.googleapis.com/sourceLocation": { "file": "%(file)s", "line": "%(line)d", "function": "%(function)s"}, '
29-
'"httpRequest": {"requestMethod": "%(request_method)s", "requestUrl": "%(request_url)s", "userAgent": "%(user_agent)s", "protocol": "%(protocol)s"} }'
25+
'"logging.googleapis.com/labels": %(_labels_str)s, '
26+
'"logging.googleapis.com/trace": "%(_trace_str)s", '
27+
'"logging.googleapis.com/spanId": "%(_span_id_str)s", '
28+
'"logging.googleapis.com/sourceLocation": %(_source_location_str)s, '
29+
'"httpRequest": %(_http_request_str)s }'
3030
)
3131

3232

tests/unit/handlers/test__helpers.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,6 @@ def test_http_request_populated(self):
9494
self.assertEqual(http_request["requestMethod"], "PUT")
9595
self.assertEqual(http_request["requestUrl"], expected_path)
9696
self.assertEqual(http_request["userAgent"], expected_agent)
97-
self.assertEqual(http_request["referer"], expected_referrer)
98-
self.assertEqual(http_request["remoteIp"], expected_ip)
99-
self.assertEqual(http_request["requestSize"], len(body_content))
10097
self.assertEqual(http_request["protocol"], "HTTP/1.1")
10198

10299
def test_http_request_sparse(self):
@@ -191,9 +188,6 @@ def test_http_request_populated(self):
191188
self.assertEqual(http_request["requestMethod"], "PUT")
192189
self.assertEqual(http_request["requestUrl"], expected_path)
193190
self.assertEqual(http_request["userAgent"], expected_agent)
194-
self.assertEqual(http_request["referer"], expected_referrer)
195-
self.assertEqual(http_request["remoteIp"], "127.0.0.1")
196-
self.assertEqual(http_request["requestSize"], len(body_content))
197191
self.assertEqual(http_request["protocol"], "HTTP/1.1")
198192

199193
def test_http_request_sparse(self):
@@ -207,7 +201,6 @@ def test_http_request_sparse(self):
207201
http_request, *_ = self._call_fut()
208202
self.assertEqual(http_request["requestMethod"], "PUT")
209203
self.assertEqual(http_request["requestUrl"], expected_path)
210-
self.assertEqual(http_request["remoteIp"], "127.0.0.1")
211204
self.assertEqual(http_request["protocol"], "HTTP/1.1")
212205

213206

tests/unit/handlers/test_handlers.py

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import unittest
1717
from unittest.mock import patch
1818
import mock
19+
import json
1920

2021
from google.cloud.logging_v2.handlers._monitored_resources import (
2122
_FUNCTION_ENV_VARS,
@@ -57,26 +58,38 @@ def test_filter_record(self):
5758
filter_obj = self._make_one()
5859
logname = "loggername"
5960
message = "hello world,嗨 世界"
60-
pathname = "testpath"
61-
lineno = 1
62-
func = "test-function"
61+
expected_location = {
62+
"line": 1,
63+
"file": "testpath",
64+
"function": "test-function",
65+
}
6366
record = logging.LogRecord(
64-
logname, logging.INFO, pathname, lineno, message, None, None, func=func
67+
logname,
68+
logging.INFO,
69+
expected_location["file"],
70+
expected_location["line"],
71+
message,
72+
None,
73+
None,
74+
func=expected_location["function"],
6575
)
6676

6777
success = filter_obj.filter(record)
6878
self.assertTrue(success)
6979

70-
self.assertEqual(record.line, lineno)
7180
self.assertEqual(record.msg, message)
72-
self.assertEqual(record.function, func)
73-
self.assertEqual(record.file, pathname)
74-
self.assertEqual(record.trace, "")
75-
self.assertEqual(record.http_request, {})
76-
self.assertEqual(record.request_method, "")
77-
self.assertEqual(record.request_url, "")
78-
self.assertEqual(record.user_agent, "")
79-
self.assertEqual(record.protocol, "")
81+
self.assertEqual(record._msg_str, message)
82+
self.assertEqual(record._source_location, expected_location)
83+
self.assertEqual(record._source_location_str, json.dumps(expected_location))
84+
self.assertIsNone(record._resource)
85+
self.assertIsNone(record._trace)
86+
self.assertEqual(record._trace_str, "")
87+
self.assertIsNone(record._span_id)
88+
self.assertEqual(record._span_id_str, "")
89+
self.assertIsNone(record._http_request)
90+
self.assertEqual(record._http_request_str, "{}")
91+
self.assertIsNone(record._labels)
92+
self.assertEqual(record._labels_str, "{}")
8093

8194
def test_minimal_record(self):
8295
"""
@@ -91,16 +104,19 @@ def test_minimal_record(self):
91104
success = filter_obj.filter(record)
92105
self.assertTrue(success)
93106

94-
self.assertEqual(record.line, 0)
95-
self.assertEqual(record.msg, "")
96-
self.assertEqual(record.function, "")
97-
self.assertEqual(record.file, "")
98-
self.assertEqual(record.trace, "")
99-
self.assertEqual(record.http_request, {})
100-
self.assertEqual(record.request_method, "")
101-
self.assertEqual(record.request_url, "")
102-
self.assertEqual(record.user_agent, "")
103-
self.assertEqual(record.protocol, "")
107+
self.assertIsNone(record.msg)
108+
self.assertEqual(record._msg_str, "")
109+
self.assertIsNone(record._source_location)
110+
self.assertEqual(record._source_location_str, "{}")
111+
self.assertIsNone(record._resource)
112+
self.assertIsNone(record._trace)
113+
self.assertEqual(record._trace_str, "")
114+
self.assertIsNone(record._span_id)
115+
self.assertEqual(record._span_id_str, "")
116+
self.assertIsNone(record._http_request)
117+
self.assertEqual(record._http_request_str, "{}")
118+
self.assertIsNone(record._labels)
119+
self.assertEqual(record._labels_str, "{}")
104120

105121
def test_record_with_request(self):
106122
"""
@@ -115,6 +131,8 @@ def test_record_with_request(self):
115131
expected_path = "http://testserver/123"
116132
expected_agent = "Mozilla/5.0"
117133
expected_trace = "123"
134+
expected_span = "456"
135+
combined_trace = f"{expected_trace}/{expected_span}"
118136
expected_request = {
119137
"requestMethod": "PUT",
120138
"requestUrl": expected_path,
@@ -129,19 +147,18 @@ def test_record_with_request(self):
129147
data="body",
130148
headers={
131149
"User-Agent": expected_agent,
132-
"X_CLOUD_TRACE_CONTEXT": expected_trace,
150+
"X_CLOUD_TRACE_CONTEXT": combined_trace,
133151
},
134152
)
135153
success = filter_obj.filter(record)
136154
self.assertTrue(success)
137155

138-
self.assertEqual(record.trace, expected_trace)
139-
for key, val in expected_request.items():
140-
self.assertEqual(record.http_request[key], val)
141-
self.assertEqual(record.request_method, "PUT")
142-
self.assertEqual(record.request_url, expected_path)
143-
self.assertEqual(record.user_agent, expected_agent)
144-
self.assertEqual(record.protocol, "HTTP/1.1")
156+
self.assertEqual(record._trace, expected_trace)
157+
self.assertEqual(record._trace_str, expected_trace)
158+
self.assertEqual(record._span_id, expected_span)
159+
self.assertEqual(record._span_id_str, expected_span)
160+
self.assertEqual(record._http_request, expected_request)
161+
self.assertEqual(record._http_request_str, json.dumps(expected_request))
145162

146163
def test_user_overrides(self):
147164
"""
@@ -163,8 +180,12 @@ def test_user_overrides(self):
163180
headers={"User-Agent": "default", "X_CLOUD_TRACE_CONTEXT": "default"},
164181
)
165182
# override values
183+
overwritten_resource = "test"
184+
record.resource = overwritten_resource
166185
overwritten_trace = "456"
167186
record.trace = overwritten_trace
187+
overwritten_span = "789"
188+
record.span_id = overwritten_span
168189
overwritten_method = "GET"
169190
overwritten_url = "www.google.com"
170191
overwritten_agent = "custom"
@@ -188,15 +209,19 @@ def test_user_overrides(self):
188209
success = filter_obj.filter(record)
189210
self.assertTrue(success)
190211

191-
self.assertEqual(record.trace, overwritten_trace)
192-
self.assertEqual(record.http_request, overwritten_request_object)
193-
self.assertEqual(record.request_method, overwritten_method)
194-
self.assertEqual(record.request_url, overwritten_url)
195-
self.assertEqual(record.user_agent, overwritten_agent)
196-
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)
212+
self.assertEqual(record._trace, overwritten_trace)
213+
self.assertEqual(record._trace_str, overwritten_trace)
214+
self.assertEqual(record._span_id, overwritten_span)
215+
self.assertEqual(record._span_id_str, overwritten_span)
216+
self.assertEqual(record._http_request, overwritten_request_object)
217+
self.assertEqual(
218+
record._http_request_str, json.dumps(overwritten_request_object)
219+
)
220+
self.assertEqual(record._source_location, overwritten_source_location)
221+
self.assertEqual(
222+
record._source_location_str, json.dumps(overwritten_source_location)
223+
)
224+
self.assertEqual(record._resource, overwritten_resource)
200225

201226

202227
class TestCloudLoggingHandler(unittest.TestCase):

0 commit comments

Comments
 (0)