Skip to content

Commit 05b5059

Browse files
authored
Merge pull request #2589 from dhermes/fix-2552
Allowing for arbitrarily nested dictionaries in _log_entry_mapping_pb.
2 parents fd0cf13 + 98450a8 commit 05b5059

File tree

3 files changed

+104
-46
lines changed

3 files changed

+104
-46
lines changed

logging/google/cloud/logging/_gax.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414

1515
"""GAX wrapper for Logging API requests."""
1616

17-
import json
18-
1917
from google.gax import CallOptions
2018
from google.gax import INITIAL_PAGE
2119
from google.gax.errors import GaxError
@@ -24,7 +22,7 @@
2422
from google.logging.v2.logging_config_pb2 import LogSink
2523
from google.logging.v2.logging_metrics_pb2 import LogMetric
2624
from google.logging.v2.log_entry_pb2 import LogEntry
27-
from google.protobuf.json_format import Parse
25+
from google.protobuf.json_format import ParseDict
2826
from grpc import StatusCode
2927

3028
from google.cloud._helpers import _datetime_to_pb_timestamp
@@ -603,11 +601,10 @@ def _log_entry_mapping_to_pb(mapping):
603601
entry_pb.labels[key] = value
604602

605603
if 'jsonPayload' in mapping:
606-
for key, value in mapping['jsonPayload'].items():
607-
entry_pb.json_payload[key] = value
604+
ParseDict(mapping['jsonPayload'], entry_pb.json_payload)
608605

609606
if 'protoPayload' in mapping:
610-
Parse(json.dumps(mapping['protoPayload']), entry_pb.proto_payload)
607+
ParseDict(mapping['protoPayload'], entry_pb.proto_payload)
611608

612609
if 'httpRequest' in mapping:
613610
_http_request_mapping_to_pb(

logging/unit_tests/test__gax.py

Lines changed: 81 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,12 @@ def test_list_entries_no_paging(self):
8080
self.assertEqual(page_size, 0)
8181
self.assertIs(options.page_token, INITIAL_PAGE)
8282

83-
def test_list_entries_with_paging(self):
84-
from google.protobuf.struct_pb2 import Value
83+
def _list_entries_with_paging_helper(self, payload, struct_pb):
8584
from google.cloud._testing import _GAXPageIterator
85+
8686
SIZE = 23
8787
TOKEN = 'TOKEN'
8888
NEW_TOKEN = 'NEW_TOKEN'
89-
PAYLOAD = {'message': 'MESSAGE', 'weather': 'sunny'}
90-
struct_pb = _StructPB({
91-
key: Value(string_value=value) for key, value in PAYLOAD.items()
92-
})
9389
response = _GAXPageIterator(
9490
[_LogEntryPB(self.LOG_NAME, json_payload=struct_pb)], NEW_TOKEN)
9591
gax_api = _GAXLoggingAPI(_list_log_entries_response=response)
@@ -103,7 +99,7 @@ def test_list_entries_with_paging(self):
10399
self.assertIsInstance(entry, dict)
104100
self.assertEqual(entry['logName'], self.LOG_NAME)
105101
self.assertEqual(entry['resource'], {'type': 'global'})
106-
self.assertEqual(entry['jsonPayload'], PAYLOAD)
102+
self.assertEqual(entry['jsonPayload'], payload)
107103
self.assertEqual(next_token, NEW_TOKEN)
108104

109105
projects, filter_, order_by, page_size, options = (
@@ -114,6 +110,43 @@ def test_list_entries_with_paging(self):
114110
self.assertEqual(page_size, SIZE)
115111
self.assertEqual(options.page_token, TOKEN)
116112

113+
def test_list_entries_with_paging(self):
114+
from google.protobuf.struct_pb2 import Struct
115+
from google.protobuf.struct_pb2 import Value
116+
117+
payload = {'message': 'MESSAGE', 'weather': 'sunny'}
118+
struct_pb = Struct(fields={
119+
key: Value(string_value=value) for key, value in payload.items()
120+
})
121+
self._list_entries_with_paging_helper(payload, struct_pb)
122+
123+
def test_list_entries_with_paging_nested_payload(self):
124+
from google.protobuf.struct_pb2 import Struct
125+
from google.protobuf.struct_pb2 import Value
126+
127+
payload = {}
128+
struct_fields = {}
129+
# Add a simple key.
130+
key = 'message'
131+
payload[key] = 'MESSAGE'
132+
struct_fields[key] = Value(string_value=payload[key])
133+
# Add a nested key.
134+
key = 'weather'
135+
sub_value = {}
136+
sub_fields = {}
137+
sub_key = 'temperature'
138+
sub_value[sub_key] = 75
139+
sub_fields[sub_key] = Value(number_value=sub_value[sub_key])
140+
sub_key = 'precipitation'
141+
sub_value[sub_key] = False
142+
sub_fields[sub_key] = Value(bool_value=sub_value[sub_key])
143+
# Update the parent payload.
144+
payload[key] = sub_value
145+
struct_fields[key] = Value(struct_value=Struct(fields=sub_fields))
146+
# Make the struct_pb for our dict.
147+
struct_pb = Struct(fields=struct_fields)
148+
self._list_entries_with_paging_helper(payload, struct_pb)
149+
117150
def test_list_entries_with_extra_properties(self):
118151
from datetime import datetime
119152
from google.logging.type.log_severity_pb2 import WARNING
@@ -317,18 +350,16 @@ def test_write_entries_w_extra_properties(self):
317350
self.assertIsNone(options)
318351
# pylint: enable=too-many-statements
319352

320-
def test_write_entries_multiple(self):
353+
def _write_entries_multiple_helper(self, json_payload, json_struct_pb):
321354
# pylint: disable=too-many-statements
322355
import datetime
323356
from google.logging.type.log_severity_pb2 import WARNING
324357
from google.logging.v2.log_entry_pb2 import LogEntry
325358
from google.protobuf.any_pb2 import Any
326-
from google.protobuf.struct_pb2 import Struct
327359
from google.cloud._helpers import _datetime_to_rfc3339, UTC
328360
TEXT = 'TEXT'
329361
NOW = datetime.datetime.utcnow().replace(tzinfo=UTC)
330362
TIMESTAMP_TYPE_URL = 'type.googleapis.com/google.protobuf.Timestamp'
331-
JSON = {'payload': 'PAYLOAD', 'type': 'json'}
332363
PROTO = {
333364
'@type': TIMESTAMP_TYPE_URL,
334365
'value': _datetime_to_rfc3339(NOW),
@@ -339,7 +370,7 @@ def test_write_entries_multiple(self):
339370
ENTRIES = [
340371
{'textPayload': TEXT,
341372
'severity': WARNING},
342-
{'jsonPayload': JSON,
373+
{'jsonPayload': json_payload,
343374
'operation': {'producer': PRODUCER, 'id': OPID}},
344375
{'protoPayload': PROTO,
345376
'httpRequest': {'requestUrl': URL}},
@@ -373,10 +404,7 @@ def test_write_entries_multiple(self):
373404
self.assertEqual(entry.log_name, '')
374405
self.assertEqual(entry.resource.type, '')
375406
self.assertEqual(entry.labels, {})
376-
json_struct = entry.json_payload
377-
self.assertIsInstance(json_struct, Struct)
378-
self.assertEqual(json_struct.fields['payload'].string_value,
379-
JSON['payload'])
407+
self.assertEqual(entry.json_payload, json_struct_pb)
380408
operation = entry.operation
381409
self.assertEqual(operation.producer, PRODUCER)
382410
self.assertEqual(operation.id, OPID)
@@ -399,6 +427,44 @@ def test_write_entries_multiple(self):
399427
self.assertIsNone(options)
400428
# pylint: enable=too-many-statements
401429

430+
def test_write_entries_multiple(self):
431+
from google.protobuf.struct_pb2 import Struct
432+
from google.protobuf.struct_pb2 import Value
433+
434+
json_payload = {'payload': 'PAYLOAD', 'type': 'json'}
435+
json_struct_pb = Struct(fields={
436+
key: Value(string_value=value)
437+
for key, value in json_payload.items()
438+
})
439+
self._write_entries_multiple_helper(json_payload, json_struct_pb)
440+
441+
def test_write_entries_multiple_nested_payload(self):
442+
from google.protobuf.struct_pb2 import Struct
443+
from google.protobuf.struct_pb2 import Value
444+
445+
json_payload = {}
446+
struct_fields = {}
447+
# Add a simple key.
448+
key = 'hello'
449+
json_payload[key] = 'me you looking for'
450+
struct_fields[key] = Value(string_value=json_payload[key])
451+
# Add a nested key.
452+
key = 'everything'
453+
sub_value = {}
454+
sub_fields = {}
455+
sub_key = 'answer'
456+
sub_value[sub_key] = 42
457+
sub_fields[sub_key] = Value(number_value=sub_value[sub_key])
458+
sub_key = 'really?'
459+
sub_value[sub_key] = False
460+
sub_fields[sub_key] = Value(bool_value=sub_value[sub_key])
461+
# Update the parent payload.
462+
json_payload[key] = sub_value
463+
struct_fields[key] = Value(struct_value=Struct(fields=sub_fields))
464+
# Make the struct_pb for our dict.
465+
json_struct_pb = Struct(fields=struct_fields)
466+
self._write_entries_multiple_helper(json_payload, json_struct_pb)
467+
402468
def test_logger_delete(self):
403469
LOG_PATH = 'projects/%s/logs/%s' % (self.PROJECT, self.LOG_NAME)
404470
gax_api = _GAXLoggingAPI()
@@ -1057,12 +1123,6 @@ def __init__(self, type_='global', **labels):
10571123
self.labels = labels
10581124

10591125

1060-
class _StructPB(object):
1061-
1062-
def __init__(self, fields):
1063-
self.fields = fields
1064-
1065-
10661126
class _LogEntryPB(object):
10671127

10681128
severity = 0

system_tests/logging_.py

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ def setUpModule():
5757

5858
class TestLogging(unittest.TestCase):
5959

60+
JSON_PAYLOAD = {
61+
'message': 'System test: test_log_struct',
62+
'weather': {
63+
'clouds': 'party or partly',
64+
'temperature': 70,
65+
'precipitation': False,
66+
},
67+
}
68+
6069
def setUp(self):
6170
self.to_delete = []
6271
self._handlers_cache = logging.getLogger().handlers[:]
@@ -120,18 +129,14 @@ def test_log_text_w_metadata(self):
120129
self.assertEqual(request['status'], STATUS)
121130

122131
def test_log_struct(self):
123-
JSON_PAYLOAD = {
124-
'message': 'System test: test_log_struct',
125-
'weather': 'partly cloudy',
126-
}
127132
logger = Config.CLIENT.logger(self._logger_name())
128133
self.to_delete.append(logger)
129134

130-
logger.log_struct(JSON_PAYLOAD)
135+
logger.log_struct(self.JSON_PAYLOAD)
131136
entries, _ = self._list_entries(logger)
132137

133138
self.assertEqual(len(entries), 1)
134-
self.assertEqual(entries[0].payload, JSON_PAYLOAD)
139+
self.assertEqual(entries[0].payload, self.JSON_PAYLOAD)
135140

136141
def test_log_handler_async(self):
137142
LOG_MESSAGE = 'It was the worst of times'
@@ -145,12 +150,12 @@ def test_log_handler_async(self):
145150
cloud_logger.addHandler(handler)
146151
cloud_logger.warn(LOG_MESSAGE)
147152
entries, _ = self._list_entries(logger)
148-
JSON_PAYLOAD = {
153+
expected_payload = {
149154
'message': LOG_MESSAGE,
150155
'python_logger': handler.name
151156
}
152157
self.assertEqual(len(entries), 1)
153-
self.assertEqual(entries[0].payload, JSON_PAYLOAD)
158+
self.assertEqual(entries[0].payload, expected_payload)
154159

155160
def test_log_handler_sync(self):
156161
LOG_MESSAGE = 'It was the best of times.'
@@ -169,12 +174,12 @@ def test_log_handler_sync(self):
169174
cloud_logger.warn(LOG_MESSAGE)
170175

171176
entries, _ = self._list_entries(logger)
172-
JSON_PAYLOAD = {
177+
expected_payload = {
173178
'message': LOG_MESSAGE,
174179
'python_logger': LOGGER_NAME
175180
}
176181
self.assertEqual(len(entries), 1)
177-
self.assertEqual(entries[0].payload, JSON_PAYLOAD)
182+
self.assertEqual(entries[0].payload, expected_payload)
178183

179184
def test_log_root_handler(self):
180185
LOG_MESSAGE = 'It was the best of times.'
@@ -188,19 +193,15 @@ def test_log_root_handler(self):
188193
logging.warn(LOG_MESSAGE)
189194

190195
entries, _ = self._list_entries(logger)
191-
JSON_PAYLOAD = {
196+
expected_payload = {
192197
'message': LOG_MESSAGE,
193198
'python_logger': 'root'
194199
}
195200

196201
self.assertEqual(len(entries), 1)
197-
self.assertEqual(entries[0].payload, JSON_PAYLOAD)
202+
self.assertEqual(entries[0].payload, expected_payload)
198203

199204
def test_log_struct_w_metadata(self):
200-
JSON_PAYLOAD = {
201-
'message': 'System test: test_log_struct',
202-
'weather': 'partly cloudy',
203-
}
204205
INSERT_ID = 'INSERTID'
205206
SEVERITY = 'INFO'
206207
METHOD = 'POST'
@@ -214,12 +215,12 @@ def test_log_struct_w_metadata(self):
214215
logger = Config.CLIENT.logger(self._logger_name())
215216
self.to_delete.append(logger)
216217

217-
logger.log_struct(JSON_PAYLOAD, insert_id=INSERT_ID, severity=SEVERITY,
218-
http_request=REQUEST)
218+
logger.log_struct(self.JSON_PAYLOAD, insert_id=INSERT_ID,
219+
severity=SEVERITY, http_request=REQUEST)
219220
entries, _ = self._list_entries(logger)
220221

221222
self.assertEqual(len(entries), 1)
222-
self.assertEqual(entries[0].payload, JSON_PAYLOAD)
223+
self.assertEqual(entries[0].payload, self.JSON_PAYLOAD)
223224
self.assertEqual(entries[0].insert_id, INSERT_ID)
224225
self.assertEqual(entries[0].severity, SEVERITY)
225226
request = entries[0].http_request

0 commit comments

Comments
 (0)