Skip to content

Commit cd18373

Browse files
committed
Merge pull request #1661 from tseaver/1577-logging-protobuf_load_dump
#1577: allow logging protobuf entries
2 parents cf7c371 + e9a01ea commit cd18373

File tree

4 files changed

+124
-1
lines changed

4 files changed

+124
-1
lines changed

gcloud/logging/entries.py

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

1515
"""Log entries within the Google Cloud Logging API."""
1616

17+
import json
18+
19+
from google.protobuf.json_format import Parse
20+
1721
from gcloud._helpers import _rfc3339_nanos_to_datetime
1822
from gcloud.logging._helpers import logger_name_from_path
1923

@@ -100,3 +104,13 @@ class ProtobufEntry(_BaseEntry):
100104
https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/LogEntry
101105
"""
102106
_PAYLOAD_KEY = 'protoPayload'
107+
108+
def parse_message(self, message):
109+
"""Parse payload into a protobuf message.
110+
111+
Mutates the passed-in ``message`` in place.
112+
113+
:type message: Protobuf message
114+
:param message: the message to be logged
115+
"""
116+
Parse(json.dumps(self.payload), message)

gcloud/logging/logger.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
"""Define API Loggers."""
1616

17+
import json
18+
19+
from google.protobuf.json_format import MessageToJson
20+
1721

1822
class Logger(object):
1923
"""Loggers represent named targets for log entries.
@@ -89,7 +93,7 @@ def log_text(self, text, client=None):
8993
method='POST', path='/entries:write', data=data)
9094

9195
def log_struct(self, info, client=None):
92-
"""API call: log a text message via a POST request
96+
"""API call: log a structured message via a POST request
9397
9498
See:
9599
https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/entries/write
@@ -115,6 +119,35 @@ def log_struct(self, info, client=None):
115119
client.connection.api_request(
116120
method='POST', path='/entries:write', data=data)
117121

122+
def log_proto(self, message, client=None):
123+
"""API call: log a protobuf message via a POST request
124+
125+
See:
126+
https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/entries/write
127+
128+
:type message: Protobuf message
129+
:param message: the message to be logged
130+
131+
:type client: :class:`gcloud.logging.client.Client` or ``NoneType``
132+
:param client: the client to use. If not passed, falls back to the
133+
``client`` stored on the current logger.
134+
"""
135+
client = self._require_client(client)
136+
as_json_str = MessageToJson(message)
137+
as_json = json.loads(as_json_str)
138+
139+
data = {
140+
'entries': [{
141+
'logName': self.full_name,
142+
'protoPayload': as_json,
143+
'resource': {
144+
'type': 'global',
145+
},
146+
}],
147+
}
148+
client.connection.api_request(
149+
method='POST', path='/entries:write', data=data)
150+
118151
def delete(self, client=None):
119152
"""API call: delete all entries in a logger via a DELETE request
120153

gcloud/logging/test_entries.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,31 @@ def test_from_api_repr_w_loggers_w_logger_match(self):
122122
self.assertTrue(entry.logger is LOGGER)
123123

124124

125+
class TestProtobufEntry(unittest2.TestCase):
126+
127+
PROJECT = 'PROJECT'
128+
LOGGER_NAME = 'LOGGER_NAME'
129+
130+
def _getTargetClass(self):
131+
from gcloud.logging.entries import ProtobufEntry
132+
return ProtobufEntry
133+
134+
def _makeOne(self, *args, **kw):
135+
return self._getTargetClass()(*args, **kw)
136+
137+
def test_parse_message(self):
138+
import json
139+
from google.protobuf.json_format import MessageToJson
140+
from google.protobuf.struct_pb2 import Struct, Value
141+
LOGGER = object()
142+
message = Struct(fields={'foo': Value(bool_value=False)})
143+
with_true = Struct(fields={'foo': Value(bool_value=True)})
144+
PAYLOAD = json.loads(MessageToJson(with_true))
145+
entry = self._makeOne(PAYLOAD, LOGGER)
146+
entry.parse_message(message)
147+
self.assertTrue(message.fields['foo'])
148+
149+
125150
def _datetime_to_rfc3339_w_nanos(value):
126151
from gcloud._helpers import _RFC3339_NO_FRACTION
127152
no_fraction = value.strftime(_RFC3339_NO_FRACTION)

gcloud/logging/test_logger.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,57 @@ def test_log_struct_w_explicit_client(self):
127127
self.assertEqual(req['path'], '/entries:write')
128128
self.assertEqual(req['data'], SENT)
129129

130+
def test_log_proto_w_implicit_client(self):
131+
import json
132+
from google.protobuf.json_format import MessageToJson
133+
from google.protobuf.struct_pb2 import Struct, Value
134+
message = Struct(fields={'foo': Value(bool_value=True)})
135+
conn = _Connection({})
136+
client = _Client(self.PROJECT, conn)
137+
logger = self._makeOne(self.LOGGER_NAME, client=client)
138+
logger.log_proto(message)
139+
self.assertEqual(len(conn._requested), 1)
140+
req = conn._requested[0]
141+
SENT = {
142+
'entries': [{
143+
'logName': 'projects/%s/logs/%s' % (
144+
self.PROJECT, self.LOGGER_NAME),
145+
'protoPayload': json.loads(MessageToJson(message)),
146+
'resource': {
147+
'type': 'global',
148+
},
149+
}],
150+
}
151+
self.assertEqual(req['method'], 'POST')
152+
self.assertEqual(req['path'], '/entries:write')
153+
self.assertEqual(req['data'], SENT)
154+
155+
def test_log_proto_w_explicit_client(self):
156+
import json
157+
from google.protobuf.json_format import MessageToJson
158+
from google.protobuf.struct_pb2 import Struct, Value
159+
message = Struct(fields={'foo': Value(bool_value=True)})
160+
conn = _Connection({})
161+
client1 = _Client(self.PROJECT, object())
162+
client2 = _Client(self.PROJECT, conn)
163+
logger = self._makeOne(self.LOGGER_NAME, client=client1)
164+
logger.log_proto(message, client=client2)
165+
self.assertEqual(len(conn._requested), 1)
166+
req = conn._requested[0]
167+
SENT = {
168+
'entries': [{
169+
'logName': 'projects/%s/logs/%s' % (
170+
self.PROJECT, self.LOGGER_NAME),
171+
'protoPayload': json.loads(MessageToJson(message)),
172+
'resource': {
173+
'type': 'global',
174+
},
175+
}],
176+
}
177+
self.assertEqual(req['method'], 'POST')
178+
self.assertEqual(req['path'], '/entries:write')
179+
self.assertEqual(req['data'], SENT)
180+
130181
def test_delete_w_bound_client(self):
131182
PATH = 'projects/%s/logs/%s' % (self.PROJECT, self.LOGGER_NAME)
132183
conn = _Connection({})

0 commit comments

Comments
 (0)