From 5d48c651d1e927d693493fa1a6e1bb9fa547f496 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 25 Mar 2016 11:08:23 -0400 Subject: [PATCH 1/4] Add 'Logger.log_proto'. See: #1577. --- gcloud/logging/logger.py | 35 +++++++++++++++++++++++++- gcloud/logging/test_logger.py | 47 +++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) 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_logger.py b/gcloud/logging/test_logger.py index 920233aaeab4..f17cfe85613e 100644 --- a/gcloud/logging/test_logger.py +++ b/gcloud/logging/test_logger.py @@ -127,6 +127,53 @@ 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): + from google.protobuf.unittest_pb2 import BoolMessage + MESSAGE = BoolMessage(data=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': {'data': True}, + '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): + from google.protobuf.unittest_pb2 import BoolMessage + MESSAGE = BoolMessage(data=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': {'data': True}, + '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({}) From f6e9de84ec34fea50ee2f000ff6438640c1d1d31 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 25 Mar 2016 11:20:55 -0400 Subject: [PATCH 2/4] Add 'ProtobufEntry.parse_message' helper. Closes: #1577. --- gcloud/logging/entries.py | 12 ++++++++++++ gcloud/logging/test_entries.py | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/gcloud/logging/entries.py b/gcloud/logging/entries.py index b867bf208765..e97d5cb39bb4 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,11 @@ 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. + + :type message: Protobuf message + :param message: the message to be logged + """ + Parse(json.dumps(self.payload), message) diff --git a/gcloud/logging/test_entries.py b/gcloud/logging/test_entries.py index 17136f2559d0..bef7b6ef6d12 100644 --- a/gcloud/logging/test_entries.py +++ b/gcloud/logging/test_entries.py @@ -122,6 +122,29 @@ 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_message(self): + from google.protobuf.unittest_pb2 import BoolMessage + LOGGER = object() + PAYLOAD = {'data': True} + message = BoolMessage(data=False) + self.assertFalse(message.data) + entry = self._makeOne(PAYLOAD, LOGGER) + entry.parse_message(message) + self.assertTrue(message.data) + + def _datetime_to_rfc3339_w_nanos(value): from gcloud._helpers import _RFC3339_NO_FRACTION no_fraction = value.strftime(_RFC3339_NO_FRACTION) From 2de3e0a2c8590a74d7c4f8ff91e210d93e153642 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 25 Mar 2016 13:41:26 -0400 Subject: [PATCH 3/4] Drop use of 'google.protobuf.unittest_pb2.BoolMessage'. See: https://github.com/google/protobuf/issues/1352. --- gcloud/logging/test_entries.py | 14 ++++++++------ gcloud/logging/test_logger.py | 20 ++++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/gcloud/logging/test_entries.py b/gcloud/logging/test_entries.py index bef7b6ef6d12..4505c7655ff6 100644 --- a/gcloud/logging/test_entries.py +++ b/gcloud/logging/test_entries.py @@ -134,15 +134,17 @@ def _getTargetClass(self): def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) - def test_message(self): - from google.protobuf.unittest_pb2 import BoolMessage + def test_parse_message(self): + import json + from google.protobuf.json_format import MessageToJson + from google.protobuf.struct_pb2 import Struct, Value LOGGER = object() - PAYLOAD = {'data': True} - message = BoolMessage(data=False) - self.assertFalse(message.data) + 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.data) + self.assertTrue(message.fields['foo']) def _datetime_to_rfc3339_w_nanos(value): diff --git a/gcloud/logging/test_logger.py b/gcloud/logging/test_logger.py index f17cfe85613e..1c345f713f3e 100644 --- a/gcloud/logging/test_logger.py +++ b/gcloud/logging/test_logger.py @@ -128,19 +128,21 @@ def test_log_struct_w_explicit_client(self): self.assertEqual(req['data'], SENT) def test_log_proto_w_implicit_client(self): - from google.protobuf.unittest_pb2 import BoolMessage - MESSAGE = BoolMessage(data=True) + 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) + 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': {'data': True}, + 'protoPayload': json.loads(MessageToJson(message)), 'resource': { 'type': 'global', }, @@ -151,20 +153,22 @@ def test_log_proto_w_implicit_client(self): self.assertEqual(req['data'], SENT) def test_log_proto_w_explicit_client(self): - from google.protobuf.unittest_pb2 import BoolMessage - MESSAGE = BoolMessage(data=True) + 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) + 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': {'data': True}, + 'protoPayload': json.loads(MessageToJson(message)), 'resource': { 'type': 'global', }, From e9a01eaa9054af218948acebd10662042cf6b1c0 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 25 Mar 2016 23:49:59 -0400 Subject: [PATCH 4/4] Document 'ProtobufEntry.parse_message' mutates passed-in message in place. Addreses: https://github.com/GoogleCloudPlatform/gcloud-python/pull/1661#discussion_r57493555 --- gcloud/logging/entries.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gcloud/logging/entries.py b/gcloud/logging/entries.py index e97d5cb39bb4..d94d7d984a1a 100644 --- a/gcloud/logging/entries.py +++ b/gcloud/logging/entries.py @@ -108,6 +108,8 @@ class ProtobufEntry(_BaseEntry): 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 """