diff --git a/gcloud/logging/entries.py b/gcloud/logging/entries.py index b867bf208765..d94d7d984a1a 100644 --- a/gcloud/logging/entries.py +++ b/gcloud/logging/entries.py @@ -14,6 +14,10 @@ """Log entries within the Google Cloud Logging API.""" +import json + +from google.protobuf.json_format import Parse + from gcloud._helpers import _rfc3339_nanos_to_datetime from gcloud.logging._helpers import logger_name_from_path @@ -100,3 +104,13 @@ class ProtobufEntry(_BaseEntry): https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/LogEntry """ _PAYLOAD_KEY = 'protoPayload' + + def parse_message(self, message): + """Parse payload into a protobuf message. + + Mutates the passed-in ``message`` in place. + + :type message: Protobuf message + :param message: the message to be logged + """ + Parse(json.dumps(self.payload), message) diff --git a/gcloud/logging/logger.py b/gcloud/logging/logger.py index f071802bd5c9..26323f328811 100644 --- a/gcloud/logging/logger.py +++ b/gcloud/logging/logger.py @@ -14,6 +14,10 @@ """Define API Loggers.""" +import json + +from google.protobuf.json_format import MessageToJson + class Logger(object): """Loggers represent named targets for log entries. @@ -89,7 +93,7 @@ def log_text(self, text, client=None): method='POST', path='/entries:write', data=data) def log_struct(self, info, client=None): - """API call: log a text message via a POST request + """API call: log a structured message via a POST request See: https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/entries/write @@ -115,6 +119,35 @@ def log_struct(self, info, client=None): client.connection.api_request( method='POST', path='/entries:write', data=data) + def log_proto(self, message, client=None): + """API call: log a protobuf message via a POST request + + See: + https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/entries/write + + :type message: Protobuf message + :param message: the message to be logged + + :type client: :class:`gcloud.logging.client.Client` or ``NoneType`` + :param client: the client to use. If not passed, falls back to the + ``client`` stored on the current logger. + """ + client = self._require_client(client) + as_json_str = MessageToJson(message) + as_json = json.loads(as_json_str) + + data = { + 'entries': [{ + 'logName': self.full_name, + 'protoPayload': as_json, + 'resource': { + 'type': 'global', + }, + }], + } + client.connection.api_request( + method='POST', path='/entries:write', data=data) + def delete(self, client=None): """API call: delete all entries in a logger via a DELETE request diff --git a/gcloud/logging/test_entries.py b/gcloud/logging/test_entries.py index 17136f2559d0..4505c7655ff6 100644 --- a/gcloud/logging/test_entries.py +++ b/gcloud/logging/test_entries.py @@ -122,6 +122,31 @@ def test_from_api_repr_w_loggers_w_logger_match(self): self.assertTrue(entry.logger is LOGGER) +class TestProtobufEntry(unittest2.TestCase): + + PROJECT = 'PROJECT' + LOGGER_NAME = 'LOGGER_NAME' + + def _getTargetClass(self): + from gcloud.logging.entries import ProtobufEntry + return ProtobufEntry + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_parse_message(self): + import json + from google.protobuf.json_format import MessageToJson + from google.protobuf.struct_pb2 import Struct, Value + LOGGER = object() + message = Struct(fields={'foo': Value(bool_value=False)}) + with_true = Struct(fields={'foo': Value(bool_value=True)}) + PAYLOAD = json.loads(MessageToJson(with_true)) + entry = self._makeOne(PAYLOAD, LOGGER) + entry.parse_message(message) + self.assertTrue(message.fields['foo']) + + def _datetime_to_rfc3339_w_nanos(value): from gcloud._helpers import _RFC3339_NO_FRACTION no_fraction = value.strftime(_RFC3339_NO_FRACTION) diff --git a/gcloud/logging/test_logger.py b/gcloud/logging/test_logger.py index 920233aaeab4..1c345f713f3e 100644 --- a/gcloud/logging/test_logger.py +++ b/gcloud/logging/test_logger.py @@ -127,6 +127,57 @@ def test_log_struct_w_explicit_client(self): self.assertEqual(req['path'], '/entries:write') self.assertEqual(req['data'], SENT) + def test_log_proto_w_implicit_client(self): + import json + from google.protobuf.json_format import MessageToJson + from google.protobuf.struct_pb2 import Struct, Value + message = Struct(fields={'foo': Value(bool_value=True)}) + conn = _Connection({}) + client = _Client(self.PROJECT, conn) + logger = self._makeOne(self.LOGGER_NAME, client=client) + logger.log_proto(message) + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + SENT = { + 'entries': [{ + 'logName': 'projects/%s/logs/%s' % ( + self.PROJECT, self.LOGGER_NAME), + 'protoPayload': json.loads(MessageToJson(message)), + 'resource': { + 'type': 'global', + }, + }], + } + self.assertEqual(req['method'], 'POST') + self.assertEqual(req['path'], '/entries:write') + self.assertEqual(req['data'], SENT) + + def test_log_proto_w_explicit_client(self): + import json + from google.protobuf.json_format import MessageToJson + from google.protobuf.struct_pb2 import Struct, Value + message = Struct(fields={'foo': Value(bool_value=True)}) + conn = _Connection({}) + client1 = _Client(self.PROJECT, object()) + client2 = _Client(self.PROJECT, conn) + logger = self._makeOne(self.LOGGER_NAME, client=client1) + logger.log_proto(message, client=client2) + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + SENT = { + 'entries': [{ + 'logName': 'projects/%s/logs/%s' % ( + self.PROJECT, self.LOGGER_NAME), + 'protoPayload': json.loads(MessageToJson(message)), + 'resource': { + 'type': 'global', + }, + }], + } + self.assertEqual(req['method'], 'POST') + self.assertEqual(req['path'], '/entries:write') + self.assertEqual(req['data'], SENT) + def test_delete_w_bound_client(self): PATH = 'projects/%s/logs/%s' % (self.PROJECT, self.LOGGER_NAME) conn = _Connection({})