Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1577: allow logging protobuf entries #1661

Merged
merged 4 commits into from
Mar 26, 2016
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions gcloud/logging/entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

35 changes: 34 additions & 1 deletion gcloud/logging/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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)

This comment was marked as spam.

This comment was marked as spam.


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

Expand Down
25 changes: 25 additions & 0 deletions gcloud/logging/test_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
51 changes: 51 additions & 0 deletions gcloud/logging/test_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -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({})
Expand Down