Skip to content

Commit 92b287f

Browse files
feat: allow modifying LogEntry data using extra argument (#129)
1 parent 212b414 commit 92b287f

File tree

5 files changed

+188
-12
lines changed

5 files changed

+188
-12
lines changed

google/cloud/logging_v2/handlers/app_engine.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,25 @@ def emit(self, record):
113113
record (logging.LogRecord): The record to be logged.
114114
"""
115115
message = super(AppEngineHandler, self).format(record)
116+
inferred_http, inferred_trace = get_request_data()
117+
if inferred_trace is not None:
118+
inferred_trace = f"projects/{self.project_id}/traces/{inferred_trace}"
119+
# allow user overrides
120+
trace = getattr(record, "trace", inferred_trace)
121+
span_id = getattr(record, "span_id", None)
122+
http_request = getattr(record, "http_request", inferred_http)
123+
resource = getattr(record, "resource", self.resource)
124+
user_labels = getattr(record, "labels", {})
125+
# merge labels
116126
gae_labels = self.get_gae_labels()
117-
http_request, trace_id = get_request_data()
118-
if trace_id is not None:
119-
trace_id = f"projects/{self.project_id}/traces/{trace_id}"
127+
gae_labels.update(user_labels)
128+
# send off request
120129
self.transport.send(
121130
record,
122131
message,
123-
resource=self.resource,
132+
resource=resource,
124133
labels=gae_labels,
125-
trace=trace_id,
134+
trace=trace,
135+
span_id=span_id,
126136
http_request=http_request,
127137
)

google/cloud/logging_v2/handlers/handlers.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def __init__(
8787
self.name = name
8888
self.client = client
8989
self.transport = transport(client, name)
90+
self.project_id = client.project
9091
self.resource = resource
9192
self.labels = labels
9293

@@ -101,7 +102,26 @@ def emit(self, record):
101102
record (logging.LogRecord): The record to be logged.
102103
"""
103104
message = super(CloudLoggingHandler, self).format(record)
104-
self.transport.send(record, message, resource=self.resource, labels=self.labels)
105+
trace_id = getattr(record, "trace", None)
106+
span_id = getattr(record, "span_id", None)
107+
http_request = getattr(record, "http_request", None)
108+
resource = getattr(record, "resource", self.resource)
109+
user_labels = getattr(record, "labels", {})
110+
# merge labels
111+
total_labels = self.labels if self.labels is not None else {}
112+
total_labels.update(user_labels)
113+
if len(total_labels) == 0:
114+
total_labels = None
115+
# send off request
116+
self.transport.send(
117+
record,
118+
message,
119+
resource=resource,
120+
labels=(total_labels if total_labels else None),
121+
trace=trace_id,
122+
span_id=span_id,
123+
http_request=http_request,
124+
)
105125

106126

107127
def setup_logging(

tests/system/test_system.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
from google.api_core.exceptions import ServiceUnavailable
2828
import google.cloud.logging
2929
from google.cloud._helpers import UTC
30-
from google.cloud.logging_v2.handlers.handlers import CloudLoggingHandler
30+
from google.cloud.logging_v2.handlers import AppEngineHandler
31+
from google.cloud.logging_v2.handlers import CloudLoggingHandler
3132
from google.cloud.logging_v2.handlers.transports import SyncTransport
3233
from google.cloud.logging_v2 import client
3334
from google.cloud.logging_v2.resource import Resource
@@ -308,6 +309,39 @@ def test_log_handler_sync(self):
308309
self.assertEqual(len(entries), 1)
309310
self.assertEqual(entries[0].payload, expected_payload)
310311

312+
def test_handlers_w_extras(self):
313+
LOG_MESSAGE = "Testing with injected extras."
314+
315+
for cls in [CloudLoggingHandler, AppEngineHandler]:
316+
LOGGER_NAME = f"{cls.__name__}-handler_extras"
317+
handler_name = self._logger_name(LOGGER_NAME)
318+
319+
handler = cls(Config.CLIENT, name=handler_name, transport=SyncTransport)
320+
321+
# only create the logger to delete, hidden otherwise
322+
logger = Config.CLIENT.logger(handler.name)
323+
self.to_delete.append(logger)
324+
325+
cloud_logger = logging.getLogger(LOGGER_NAME)
326+
cloud_logger.addHandler(handler)
327+
expected_request = {"requestUrl": "localhost"}
328+
extra = {
329+
"trace": "123",
330+
"span_id": "456",
331+
"http_request": expected_request,
332+
"resource": Resource(type="cloudiot_device", labels={}),
333+
"labels": {"test-label": "manual"},
334+
}
335+
cloud_logger.warn(LOG_MESSAGE, extra=extra)
336+
337+
entries = _list_entries(logger)
338+
self.assertEqual(len(entries), 1)
339+
self.assertEqual(entries[0].trace, extra["trace"])
340+
self.assertEqual(entries[0].span_id, extra["span_id"])
341+
self.assertEqual(entries[0].http_request, expected_request)
342+
self.assertEqual(entries[0].labels, extra["labels"])
343+
self.assertEqual(entries[0].resource.type, extra["resource"].type)
344+
311345
def test_log_root_handler(self):
312346
LOG_MESSAGE = "It was the best of times."
313347

tests/unit/handlers/test_app_engine.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,60 @@ def test_emit(self):
118118
gae_resource,
119119
gae_labels,
120120
expected_trace_id,
121+
None,
121122
expected_http_request,
122123
),
123124
)
124125

126+
def test_emit_manual_field_override(self):
127+
from google.cloud.logging_v2.resource import Resource
128+
129+
inferred_http_request = {"request_url": "test"}
130+
inferred_trace_id = "trace-test"
131+
get_request_patch = mock.patch(
132+
"google.cloud.logging_v2.handlers.app_engine.get_request_data",
133+
return_value=(inferred_http_request, inferred_trace_id),
134+
)
135+
with get_request_patch:
136+
# library integrations mocked to return test data
137+
client = mock.Mock(project=self.PROJECT, spec=["project"])
138+
handler = self._make_one(client, transport=_Transport)
139+
gae_labels = handler.get_gae_labels()
140+
logname = "app"
141+
message = "hello world"
142+
record = logging.LogRecord(
143+
logname, logging, None, None, message, None, None
144+
)
145+
handler.project_id = self.PROJECT
146+
# set attributes manually
147+
expected_trace = "123"
148+
setattr(record, "trace", expected_trace)
149+
expected_span = "456"
150+
setattr(record, "span_id", expected_span)
151+
expected_http = {"reuqest_url": "manual"}
152+
setattr(record, "http_request", expected_http)
153+
expected_resource = Resource(type="test", labels={})
154+
setattr(record, "resource", expected_resource)
155+
additional_labels = {"test-label": "manual"}
156+
expected_labels = dict(gae_labels)
157+
expected_labels.update(additional_labels)
158+
setattr(record, "labels", additional_labels)
159+
handler.emit(record)
160+
self.assertIs(handler.transport.client, client)
161+
self.assertEqual(handler.transport.name, logname)
162+
self.assertEqual(
163+
handler.transport.send_called_with,
164+
(
165+
record,
166+
message,
167+
expected_resource,
168+
expected_labels,
169+
expected_trace,
170+
expected_span,
171+
expected_http,
172+
),
173+
)
174+
125175
def _get_gae_labels_helper(self, trace_id):
126176
get_request_patch = mock.patch(
127177
"google.cloud.logging_v2.handlers.app_engine.get_request_data",
@@ -156,5 +206,13 @@ def __init__(self, client, name):
156206
self.client = client
157207
self.name = name
158208

159-
def send(self, record, message, resource, labels, trace, http_request):
160-
self.send_called_with = (record, message, resource, labels, trace, http_request)
209+
def send(self, record, message, resource, labels, trace, span_id, http_request):
210+
self.send_called_with = (
211+
record,
212+
message,
213+
resource,
214+
labels,
215+
trace,
216+
span_id,
217+
http_request,
218+
)

tests/unit/handlers/test_handlers.py

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,44 @@ def test_emit(self):
8585

8686
self.assertEqual(
8787
handler.transport.send_called_with,
88-
(record, message, _GLOBAL_RESOURCE, None),
88+
(record, message, _GLOBAL_RESOURCE, None, None, None, None),
89+
)
90+
91+
def test_emit_manual_field_override(self):
92+
from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE
93+
from google.cloud.logging_v2.resource import Resource
94+
95+
client = _Client(self.PROJECT)
96+
handler = self._make_one(
97+
client, transport=_Transport, resource=_GLOBAL_RESOURCE
98+
)
99+
logname = "loggername"
100+
message = "hello world"
101+
record = logging.LogRecord(logname, logging, None, None, message, None, None)
102+
# set attributes manually
103+
expected_trace = "123"
104+
setattr(record, "trace", expected_trace)
105+
expected_span = "456"
106+
setattr(record, "span_id", expected_span)
107+
expected_http = {"reuqest_url": "manual"}
108+
setattr(record, "http_request", expected_http)
109+
expected_resource = Resource(type="test", labels={})
110+
setattr(record, "resource", expected_resource)
111+
expected_labels = {"test-label": "manual"}
112+
setattr(record, "labels", expected_labels)
113+
handler.emit(record)
114+
115+
self.assertEqual(
116+
handler.transport.send_called_with,
117+
(
118+
record,
119+
message,
120+
expected_resource,
121+
expected_labels,
122+
expected_trace,
123+
expected_span,
124+
expected_http,
125+
),
89126
)
90127

91128

@@ -148,5 +185,22 @@ def __init__(self, client, name):
148185
self.client = client
149186
self.name = name
150187

151-
def send(self, record, message, resource, labels=None):
152-
self.send_called_with = (record, message, resource, labels)
188+
def send(
189+
self,
190+
record,
191+
message,
192+
resource,
193+
labels=None,
194+
trace=None,
195+
span_id=None,
196+
http_request=None,
197+
):
198+
self.send_called_with = (
199+
record,
200+
message,
201+
resource,
202+
labels,
203+
trace,
204+
span_id,
205+
http_request,
206+
)

0 commit comments

Comments
 (0)