diff --git a/logging/google/cloud/logging/entries.py b/logging/google/cloud/logging/entries.py index 284562c5de5b..24c8392eba14 100644 --- a/logging/google/cloud/logging/entries.py +++ b/logging/google/cloud/logging/entries.py @@ -20,6 +20,7 @@ from google.protobuf import any_pb2 from google.protobuf.json_format import Parse +from google.cloud.logging.resource import Resource from google.cloud._helpers import _name_from_project_path from google.cloud._helpers import _rfc3339_nanos_to_datetime @@ -71,10 +72,13 @@ class _BaseEntry(object): :type http_request: dict :param http_request: (optional) info about HTTP request associated with - the entry + the entry. + + :type resource: :class:`~google.cloud.logging.resource.Resource` + :param resource: (Optional) Monitored resource of the entry """ def __init__(self, payload, logger, insert_id=None, timestamp=None, - labels=None, severity=None, http_request=None): + labels=None, severity=None, http_request=None, resource=None): self.payload = payload self.logger = logger self.insert_id = insert_id @@ -82,6 +86,7 @@ def __init__(self, payload, logger, insert_id=None, timestamp=None, self.labels = labels self.severity = severity self.http_request = http_request + self.resource = resource @classmethod def from_api_repr(cls, resource, client, loggers=None): @@ -118,8 +123,15 @@ def from_api_repr(cls, resource, client, loggers=None): labels = resource.get('labels') severity = resource.get('severity') http_request = resource.get('httpRequest') + + monitored_resource_dict = resource.get('resource') + monitored_resource = None + if monitored_resource_dict is not None: + monitored_resource = Resource._from_dict(monitored_resource_dict) + return cls(payload, logger, insert_id=insert_id, timestamp=timestamp, - labels=labels, severity=severity, http_request=http_request) + labels=labels, severity=severity, http_request=http_request, + resource=monitored_resource) class TextEntry(_BaseEntry): @@ -170,14 +182,18 @@ class ProtobufEntry(_BaseEntry): :type http_request: dict :param http_request: (optional) info about HTTP request associated with the entry + + :type resource: :class:`~google.cloud.logging.resource.Resource` + :param resource: (Optional) Monitored resource of the entry """ _PAYLOAD_KEY = 'protoPayload' def __init__(self, payload, logger, insert_id=None, timestamp=None, - labels=None, severity=None, http_request=None): + labels=None, severity=None, http_request=None, resource=None): super(ProtobufEntry, self).__init__( payload, logger, insert_id=insert_id, timestamp=timestamp, - labels=labels, severity=severity, http_request=http_request) + labels=labels, severity=severity, http_request=http_request, + resource=resource) if isinstance(self.payload, any_pb2.Any): self.payload_pb = self.payload self.payload = None diff --git a/logging/google/cloud/logging/logger.py b/logging/google/cloud/logging/logger.py index f093e6e48c88..874d05014479 100644 --- a/logging/google/cloud/logging/logger.py +++ b/logging/google/cloud/logging/logger.py @@ -16,6 +16,10 @@ from google.protobuf.json_format import MessageToDict from google.cloud._helpers import _datetime_to_rfc3339 +from google.cloud.logging.resource import Resource + + +_GLOBAL_RESOURCE = Resource(type='global', labels={}) class Logger(object): @@ -91,7 +95,8 @@ def batch(self, client=None): def _make_entry_resource(self, text=None, info=None, message=None, labels=None, insert_id=None, severity=None, - http_request=None, timestamp=None): + http_request=None, timestamp=None, + resource=_GLOBAL_RESOURCE): """Return a log entry resource of the appropriate type. Helper for :meth:`log_text`, :meth:`log_struct`, and :meth:`log_proto`. @@ -123,19 +128,22 @@ def _make_entry_resource(self, text=None, info=None, message=None, :type timestamp: :class:`datetime.datetime` :param timestamp: (Optional) timestamp of event being logged. + :type resource: :class:`~google.cloud.logging.resource.Resource` + :param resource: (Optional) Monitored resource of the entry + :rtype: dict :returns: The JSON resource created. """ - resource = { + entry = { 'logName': self.full_name, - 'resource': {'type': 'global'}, + 'resource': resource._to_dict(), } if text is not None: - resource['textPayload'] = text + entry['textPayload'] = text if info is not None: - resource['jsonPayload'] = info + entry['jsonPayload'] = info if message is not None: # NOTE: If ``message`` contains an ``Any`` field with an @@ -144,30 +152,31 @@ def _make_entry_resource(self, text=None, info=None, message=None, # the assumption is that any types needed for the # protobuf->JSON conversion will be known from already # imported ``pb2`` modules. - resource['protoPayload'] = MessageToDict(message) + entry['protoPayload'] = MessageToDict(message) if labels is None: labels = self.labels if labels is not None: - resource['labels'] = labels + entry['labels'] = labels if insert_id is not None: - resource['insertId'] = insert_id + entry['insertId'] = insert_id if severity is not None: - resource['severity'] = severity + entry['severity'] = severity if http_request is not None: - resource['httpRequest'] = http_request + entry['httpRequest'] = http_request if timestamp is not None: - resource['timestamp'] = _datetime_to_rfc3339(timestamp) + entry['timestamp'] = _datetime_to_rfc3339(timestamp) - return resource + return entry def log_text(self, text, client=None, labels=None, insert_id=None, - severity=None, http_request=None, timestamp=None): + severity=None, http_request=None, timestamp=None, + resource=_GLOBAL_RESOURCE): """API call: log a text message via a POST request See: @@ -194,17 +203,22 @@ def log_text(self, text, client=None, labels=None, insert_id=None, :param http_request: (optional) info about HTTP request associated with the entry + :type resource: :class:`~google.cloud.logging.resource.Resource` + :param resource: Monitored resource of the entry, defaults + to the global resource type. + :type timestamp: :class:`datetime.datetime` :param timestamp: (optional) timestamp of event being logged. """ client = self._require_client(client) entry_resource = self._make_entry_resource( text=text, labels=labels, insert_id=insert_id, severity=severity, - http_request=http_request, timestamp=timestamp) + http_request=http_request, timestamp=timestamp, resource=resource) client.logging_api.write_entries([entry_resource]) def log_struct(self, info, client=None, labels=None, insert_id=None, - severity=None, http_request=None, timestamp=None): + severity=None, http_request=None, timestamp=None, + resource=_GLOBAL_RESOURCE): """API call: log a structured message via a POST request See: @@ -231,17 +245,22 @@ def log_struct(self, info, client=None, labels=None, insert_id=None, :param http_request: (optional) info about HTTP request associated with the entry. + :type resource: :class:`~google.cloud.logging.resource.Resource` + :param resource: Monitored resource of the entry, defaults + to the global resource type. + :type timestamp: :class:`datetime.datetime` :param timestamp: (optional) timestamp of event being logged. """ client = self._require_client(client) entry_resource = self._make_entry_resource( info=info, labels=labels, insert_id=insert_id, severity=severity, - http_request=http_request, timestamp=timestamp) + http_request=http_request, timestamp=timestamp, resource=resource) client.logging_api.write_entries([entry_resource]) def log_proto(self, message, client=None, labels=None, insert_id=None, - severity=None, http_request=None, timestamp=None): + severity=None, http_request=None, timestamp=None, + resource=_GLOBAL_RESOURCE): """API call: log a protobuf message via a POST request See: @@ -268,13 +287,18 @@ def log_proto(self, message, client=None, labels=None, insert_id=None, :param http_request: (optional) info about HTTP request associated with the entry. + :type resource: :class:`~google.cloud.logging.resource.Resource` + :param resource: Monitored resource of the entry, defaults + to the global resource type. + :type timestamp: :class:`datetime.datetime` :param timestamp: (optional) timestamp of event being logged. """ client = self._require_client(client) entry_resource = self._make_entry_resource( message=message, labels=labels, insert_id=insert_id, - severity=severity, http_request=http_request, timestamp=timestamp) + severity=severity, http_request=http_request, timestamp=timestamp, + resource=resource) client.logging_api.write_entries([entry_resource]) def delete(self, client=None): @@ -344,11 +368,21 @@ class Batch(object): :type client: :class:`google.cloud.logging.client.Client` :param client: The client to use. + + :type resource: :class:`~google.cloud.logging.resource.Resource` + :param resource: (Optional) Monitored resource of the batch, defaults + to None, which requires that every entry should have a + resource specified. Since the methods used to write + entries default the entry's resource to the global + resource type, this parameter is only required + if explicitly set to None. If no entries' resource are + set to None, this parameter will be ignored on the server. """ - def __init__(self, logger, client): + def __init__(self, logger, client, resource=None): self.logger = logger self.entries = [] self.client = client + self.resource = resource def __enter__(self): return self @@ -358,7 +392,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.commit() def log_text(self, text, labels=None, insert_id=None, severity=None, - http_request=None, timestamp=None): + http_request=None, timestamp=None, resource=_GLOBAL_RESOURCE): """Add a text entry to be logged during :meth:`commit`. :type text: str @@ -379,13 +413,21 @@ def log_text(self, text, labels=None, insert_id=None, severity=None, :type timestamp: :class:`datetime.datetime` :param timestamp: (optional) timestamp of event being logged. + + :type resource: :class:`~google.cloud.logging.resource.Resource` + :param resource: (Optional) Monitored resource of the entry. Defaults + to the global resource type. If set to None, the + resource of the batch is used for this entry. If + both this resource and the Batch resource are None, + the API will return an error. """ self.entries.append( ('text', text, labels, insert_id, severity, http_request, - timestamp)) + timestamp, resource)) def log_struct(self, info, labels=None, insert_id=None, severity=None, - http_request=None, timestamp=None): + http_request=None, timestamp=None, + resource=_GLOBAL_RESOURCE): """Add a struct entry to be logged during :meth:`commit`. :type info: dict @@ -406,13 +448,21 @@ def log_struct(self, info, labels=None, insert_id=None, severity=None, :type timestamp: :class:`datetime.datetime` :param timestamp: (optional) timestamp of event being logged. + + :type resource: :class:`~google.cloud.logging.resource.Resource` + :param resource: (Optional) Monitored resource of the entry. Defaults + to the global resource type. If set to None, the + resource of the batch is used for this entry. If + both this resource and the Batch resource are None, + the API will return an error. """ self.entries.append( ('struct', info, labels, insert_id, severity, http_request, - timestamp)) + timestamp, resource)) def log_proto(self, message, labels=None, insert_id=None, severity=None, - http_request=None, timestamp=None): + http_request=None, timestamp=None, + resource=_GLOBAL_RESOURCE): """Add a protobuf entry to be logged during :meth:`commit`. :type message: protobuf message @@ -433,10 +483,17 @@ def log_proto(self, message, labels=None, insert_id=None, severity=None, :type timestamp: :class:`datetime.datetime` :param timestamp: (optional) timestamp of event being logged. + + :type resource: :class:`~google.cloud.logging.resource.Resource` + :param resource: (Optional) Monitored resource of the entry. Defaults + to the global resource type. If set to None, the + resource of the batch is used for this entry. If + both this resource and the Batch resource are None, + the API will return an error. """ self.entries.append( ('proto', message, labels, insert_id, severity, http_request, - timestamp)) + timestamp, resource)) def commit(self, client=None): """Send saved log entries as a single API call. @@ -451,14 +508,16 @@ def commit(self, client=None): kwargs = { 'logger_name': self.logger.full_name, - 'resource': {'type': 'global'}, } + + if self.resource is not None: + kwargs['resource'] = self.resource._to_dict() if self.logger.labels is not None: kwargs['labels'] = self.logger.labels entries = [] for (entry_type, entry, labels, iid, severity, http_req, - timestamp) in self.entries: + timestamp, resource) in self.entries: if entry_type == 'text': info = {'textPayload': entry} elif entry_type == 'struct': @@ -473,6 +532,8 @@ def commit(self, client=None): info = {'protoPayload': MessageToDict(entry)} else: raise ValueError('Unknown entry type: %s' % (entry_type,)) + if resource is not None: + info['resource'] = resource._to_dict() if labels is not None: info['labels'] = labels if iid is not None: diff --git a/logging/google/cloud/logging/resource.py b/logging/google/cloud/logging/resource.py new file mode 100644 index 000000000000..aa37287db3ef --- /dev/null +++ b/logging/google/cloud/logging/resource.py @@ -0,0 +1,58 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Monitored Resource for the Google Logging API V2.""" + +import collections + + +class Resource(collections.namedtuple('Resource', 'type labels')): + """A monitored resource identified by specifying values for all labels. + + :type type: str + :param type: The resource type name. + + :type labels: dict + :param labels: A mapping from label names to values for all labels + enumerated in the associated :class:`ResourceDescriptor`. + """ + __slots__ = () + + @classmethod + def _from_dict(cls, info): + """Construct a resource object from the parsed JSON representation. + + :type info: dict + :param info: + A ``dict`` parsed from the JSON wire-format representation. + + :rtype: :class:`Resource` + :returns: A resource object. + """ + return cls( + type=info['type'], + labels=info.get('labels', {}), + ) + + def _to_dict(self): + """Build a dictionary ready to be serialized to the JSON format. + + :rtype: dict + :returns: A dict representation of the object that can be written to + the API. + """ + return { + 'type': self.type, + 'labels': self.labels, + } diff --git a/logging/tests/system.py b/logging/tests/system.py index 89047edef5c4..075ff5ffd6cc 100644 --- a/logging/tests/system.py +++ b/logging/tests/system.py @@ -29,6 +29,7 @@ from google.cloud.logging.handlers.handlers import CloudLoggingHandler from google.cloud.logging.handlers.transports import SyncTransport from google.cloud.logging import client +from google.cloud.logging.resource import Resource from test_utils.retry import RetryErrors from test_utils.retry import RetryResult @@ -171,6 +172,29 @@ def test_log_text_with_timestamp(self): self.assertEqual(entries[0].payload, text_payload) self.assertEqual(entries[0].timestamp, now.replace(tzinfo=UTC)) + def test_log_text_with_resource(self): + text_payload = 'System test: test_log_text_with_timestamp' + + logger = Config.CLIENT.logger(self._logger_name()) + now = datetime.datetime.utcnow() + resource = Resource( + type='gae_app', + labels={ + 'module_id': 'default', + 'version_id': 'test' + } + ) + + self.to_delete.append(logger) + + logger.log_text(text_payload, timestamp=now, resource=resource) + entries = _list_entries(logger) + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0].payload, text_payload) + # project_id is output only so we don't want it in assertion + del entries[0].resource.labels['project_id'] + self.assertEqual(entries[0].resource, resource) + def test_log_text_w_metadata(self): TEXT_PAYLOAD = 'System test: test_log_text' INSERT_ID = 'INSERTID' diff --git a/logging/tests/unit/test_entries.py b/logging/tests/unit/test_entries.py index 4d254eb9d1ef..75cb641636a0 100644 --- a/logging/tests/unit/test_entries.py +++ b/logging/tests/unit/test_entries.py @@ -67,9 +67,11 @@ def test_ctor_defaults(self): self.assertIsNone(entry.labels) self.assertIsNone(entry.severity) self.assertIsNone(entry.http_request) + self.assertIsNone(entry.resource) def test_ctor_explicit(self): import datetime + from google.cloud.logging.resource import Resource PAYLOAD = 'PAYLOAD' IID = 'IID' @@ -84,13 +86,16 @@ def test_ctor_explicit(self): 'requestUrl': URI, 'status': STATUS, } + resource = Resource(type='global', labels={}) + logger = _Logger(self.LOGGER_NAME, self.PROJECT) entry = self._make_one(PAYLOAD, logger, insert_id=IID, timestamp=TIMESTAMP, labels=LABELS, severity=SEVERITY, - http_request=REQUEST) + http_request=REQUEST, + resource=resource) self.assertEqual(entry.payload, PAYLOAD) self.assertIs(entry.logger, logger) self.assertEqual(entry.insert_id, IID) @@ -100,6 +105,7 @@ def test_ctor_explicit(self): self.assertEqual(entry.http_request['requestMethod'], METHOD) self.assertEqual(entry.http_request['requestUrl'], URI) self.assertEqual(entry.http_request['status'], STATUS) + self.assertEqual(entry.resource, resource) def test_from_api_repr_missing_data_no_loggers(self): client = _Client(self.PROJECT) @@ -124,6 +130,7 @@ def test_from_api_repr_missing_data_no_loggers(self): def test_from_api_repr_w_loggers_no_logger_match(self): from datetime import datetime from google.cloud._helpers import UTC + from google.cloud.logging.resource import Resource klass = self._get_target_class() client = _Client(self.PROJECT) @@ -136,6 +143,16 @@ def test_from_api_repr_w_loggers_no_logger_match(self): LABELS = {'foo': 'bar', 'baz': 'qux'} METHOD = 'POST' URI = 'https://api.example.com/endpoint' + RESOURCE = Resource( + type='gae_app', + labels={ + 'type': 'gae_app', + 'labels': { + 'module_id': 'default', + 'version': 'test', + } + } + ) STATUS = '500' API_REPR = { 'dummyPayload': PAYLOAD, @@ -149,6 +166,7 @@ def test_from_api_repr_w_loggers_no_logger_match(self): 'requestUrl': URI, 'status': STATUS, }, + 'resource': RESOURCE._to_dict(), } loggers = {} entry = klass.from_api_repr(API_REPR, client, loggers=loggers) @@ -165,6 +183,7 @@ def test_from_api_repr_w_loggers_no_logger_match(self): self.assertIs(logger.client, client) self.assertEqual(logger.name, self.LOGGER_NAME) self.assertEqual(loggers, {LOG_NAME: logger}) + self.assertEqual(entry.resource, RESOURCE) def test_from_api_repr_w_loggers_w_logger_match(self): from datetime import datetime diff --git a/logging/tests/unit/test_logger.py b/logging/tests/unit/test_logger.py index 0501bee1fd39..5c184f7c3dec 100644 --- a/logging/tests/unit/test_logger.py +++ b/logging/tests/unit/test_logger.py @@ -96,6 +96,7 @@ def test_log_text_w_str_implicit_client(self): 'textPayload': TEXT, 'resource': { 'type': 'global', + 'labels': {}, }, }] client = _Client(self.PROJECT) @@ -116,6 +117,7 @@ def test_log_text_w_default_labels(self): 'textPayload': TEXT, 'resource': { 'type': 'global', + 'labels': {}, }, 'labels': DEFAULT_LABELS, }] @@ -141,6 +143,7 @@ def test_log_text_w_timestamp(self): 'timestamp': '2016-12-31T00:01:02.999999Z', 'resource': { 'type': 'global', + 'labels': {}, }, }] client = _Client(self.PROJECT) @@ -172,6 +175,7 @@ def test_log_text_w_unicode_explicit_client_labels_severity_httpreq(self): 'textPayload': TEXT, 'resource': { 'type': 'global', + 'labels': {}, }, 'labels': LABELS, 'insertId': IID, @@ -198,6 +202,7 @@ def test_log_struct_w_implicit_client(self): 'jsonPayload': STRUCT, 'resource': { 'type': 'global', + 'labels': {}, }, }] client = _Client(self.PROJECT) @@ -218,6 +223,7 @@ def test_log_struct_w_default_labels(self): 'jsonPayload': STRUCT, 'resource': { 'type': 'global', + 'labels': {}, }, 'labels': DEFAULT_LABELS, }] @@ -251,6 +257,7 @@ def test_log_struct_w_explicit_client_labels_severity_httpreq(self): 'jsonPayload': STRUCT, 'resource': { 'type': 'global', + 'labels': {}, }, 'labels': LABELS, 'insertId': IID, @@ -282,6 +289,7 @@ def test_log_struct_w_timestamp(self): 'timestamp': '2016-12-31T00:01:02.999999Z', 'resource': { 'type': 'global', + 'labels': {}, }, }] client = _Client(self.PROJECT) @@ -305,6 +313,7 @@ def test_log_proto_w_implicit_client(self): 'protoPayload': json.loads(MessageToJson(message)), 'resource': { 'type': 'global', + 'labels': {}, }, }] client = _Client(self.PROJECT) @@ -329,6 +338,7 @@ def test_log_proto_w_default_labels(self): 'protoPayload': json.loads(MessageToJson(message)), 'resource': { 'type': 'global', + 'labels': {}, }, 'labels': DEFAULT_LABELS, }] @@ -367,6 +377,7 @@ def test_log_proto_w_explicit_client_labels_severity_httpreq(self): 'protoPayload': json.loads(MessageToJson(message)), 'resource': { 'type': 'global', + 'labels': {}, }, 'labels': LABELS, 'insertId': IID, @@ -402,6 +413,7 @@ def test_log_proto_w_timestamp(self): 'timestamp': '2016-12-31T00:01:02.999999Z', 'resource': { 'type': 'global', + 'labels': {}, }, }] client = _Client(self.PROJECT) @@ -530,16 +542,19 @@ def test_ctor_defaults(self): self.assertEqual(len(batch.entries), 0) def test_log_text_defaults(self): + from google.cloud.logging.logger import _GLOBAL_RESOURCE TEXT = 'This is the entry text' client = _Client(project=self.PROJECT, connection=_make_credentials()) logger = _Logger() batch = self._make_one(logger, client=client) batch.log_text(TEXT) self.assertEqual(batch.entries, - [('text', TEXT, None, None, None, None, None)]) + [('text', TEXT, None, None, None, None, None, + _GLOBAL_RESOURCE)]) def test_log_text_explicit(self): import datetime + from google.cloud.logging.resource import Resource TEXT = 'This is the entry text' LABELS = {'foo': 'bar', 'baz': 'qux'} @@ -554,16 +569,26 @@ def test_log_text_explicit(self): 'status': STATUS, } TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999) + RESOURCE = Resource( + type='gae_app', + labels={ + 'module_id': 'default', + 'version_id': 'test', + }) + client = _Client(project=self.PROJECT, connection=_make_credentials()) logger = _Logger() batch = self._make_one(logger, client=client) batch.log_text(TEXT, labels=LABELS, insert_id=IID, severity=SEVERITY, - http_request=REQUEST, timestamp=TIMESTAMP) + http_request=REQUEST, timestamp=TIMESTAMP, + resource=RESOURCE) self.assertEqual( batch.entries, - [('text', TEXT, LABELS, IID, SEVERITY, REQUEST, TIMESTAMP)]) + [('text', TEXT, LABELS, IID, SEVERITY, REQUEST, TIMESTAMP, + RESOURCE)]) def test_log_struct_defaults(self): + from google.cloud.logging.logger import _GLOBAL_RESOURCE STRUCT = {'message': 'Message text', 'weather': 'partly cloudy'} client = _Client(project=self.PROJECT, connection=_make_credentials()) logger = _Logger() @@ -571,10 +596,12 @@ def test_log_struct_defaults(self): batch.log_struct(STRUCT) self.assertEqual( batch.entries, - [('struct', STRUCT, None, None, None, None, None)]) + [('struct', STRUCT, None, None, None, None, None, + _GLOBAL_RESOURCE)]) def test_log_struct_explicit(self): import datetime + from google.cloud.logging.resource import Resource STRUCT = {'message': 'Message text', 'weather': 'partly cloudy'} LABELS = {'foo': 'bar', 'baz': 'qux'} @@ -589,17 +616,27 @@ def test_log_struct_explicit(self): 'status': STATUS, } TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999) + RESOURCE = Resource( + type='gae_app', + labels={ + 'module_id': 'default', + 'version_id': 'test', + } + ) + client = _Client(project=self.PROJECT, connection=_make_credentials()) logger = _Logger() batch = self._make_one(logger, client=client) batch.log_struct(STRUCT, labels=LABELS, insert_id=IID, severity=SEVERITY, http_request=REQUEST, - timestamp=TIMESTAMP) + timestamp=TIMESTAMP, resource=RESOURCE) self.assertEqual( batch.entries, - [('struct', STRUCT, LABELS, IID, SEVERITY, REQUEST, TIMESTAMP)]) + [('struct', STRUCT, LABELS, IID, SEVERITY, REQUEST, TIMESTAMP , + RESOURCE)]) def test_log_proto_defaults(self): + from google.cloud.logging.logger import _GLOBAL_RESOURCE from google.protobuf.struct_pb2 import Struct from google.protobuf.struct_pb2 import Value @@ -609,10 +646,12 @@ def test_log_proto_defaults(self): batch = self._make_one(logger, client=client) batch.log_proto(message) self.assertEqual(batch.entries, - [('proto', message, None, None, None, None, None)]) + [('proto', message, None, None, None, None, None, + _GLOBAL_RESOURCE)]) def test_log_proto_explicit(self): import datetime + from google.cloud.logging.resource import Resource from google.protobuf.struct_pb2 import Struct from google.protobuf.struct_pb2 import Value @@ -629,24 +668,61 @@ def test_log_proto_explicit(self): 'status': STATUS, } TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999) + RESOURCE = Resource( + type='gae_app', + labels={ + 'module_id': 'default', + 'version_id': 'test', + } + ) client = _Client(project=self.PROJECT, connection=_make_credentials()) logger = _Logger() batch = self._make_one(logger, client=client) batch.log_proto(message, labels=LABELS, insert_id=IID, severity=SEVERITY, http_request=REQUEST, - timestamp=TIMESTAMP) + timestamp=TIMESTAMP, resource=RESOURCE) self.assertEqual( batch.entries, - [('proto', message, LABELS, IID, SEVERITY, REQUEST, TIMESTAMP)]) + [('proto', message, LABELS, IID, SEVERITY, REQUEST, TIMESTAMP, + RESOURCE)]) def test_commit_w_invalid_entry_type(self): logger = _Logger() client = _Client(project=self.PROJECT, connection=_make_credentials()) batch = self._make_one(logger, client) - batch.entries.append(('bogus', 'BOGUS', None, None, None, None, None)) + batch.entries.append(('bogus', 'BOGUS', None, None, None, None, None, + None)) with self.assertRaises(ValueError): batch.commit() + def test_commit_w_resource_specified(self): + from google.cloud.logging.logger import _GLOBAL_RESOURCE + from google.cloud.logging.resource import Resource + + logger = _Logger() + client = _Client(project=self.PROJECT, connection=_make_credentials()) + api = client.logging_api = _DummyLoggingAPI() + RESOURCE = Resource( + type='gae_app', + labels={ + 'module_id': 'default', + 'version_id': 'test', + } + ) + + batch = self._make_one(logger, client, resource=RESOURCE) + MESSAGE = 'This is the entry text' + ENTRIES = [ + {'textPayload': MESSAGE}, + {'textPayload': MESSAGE, 'resource': _GLOBAL_RESOURCE._to_dict()}, + ] + batch.log_text(MESSAGE, resource=None) + batch.log_text(MESSAGE) + batch.commit() + self.assertEqual(api._write_entries_called_with, + (ENTRIES, logger.full_name, + RESOURCE._to_dict(), None)) + def test_commit_w_bound_client(self): import json import datetime @@ -654,6 +730,8 @@ def test_commit_w_bound_client(self): from google.protobuf.struct_pb2 import Struct from google.protobuf.struct_pb2 import Value from google.cloud._helpers import _datetime_to_rfc3339 + from google.cloud.logging.logger import _GLOBAL_RESOURCE + TEXT = 'This is the entry text' STRUCT = {'message': TEXT, 'weather': 'partly cloudy'} @@ -664,17 +742,17 @@ def test_commit_w_bound_client(self): TIMESTAMP1 = datetime.datetime(2016, 12, 31, 0, 0, 1, 999999) TIMESTAMP2 = datetime.datetime(2016, 12, 31, 0, 0, 2, 999999) TIMESTAMP3 = datetime.datetime(2016, 12, 31, 0, 0, 3, 999999) - RESOURCE = { - 'type': 'global', - } ENTRIES = [ {'textPayload': TEXT, 'insertId': IID1, - 'timestamp': _datetime_to_rfc3339(TIMESTAMP1)}, + 'timestamp': _datetime_to_rfc3339(TIMESTAMP1), + 'resource': _GLOBAL_RESOURCE._to_dict()}, {'jsonPayload': STRUCT, 'insertId': IID2, - 'timestamp': _datetime_to_rfc3339(TIMESTAMP2)}, + 'timestamp': _datetime_to_rfc3339(TIMESTAMP2), + 'resource': _GLOBAL_RESOURCE._to_dict()}, {'protoPayload': json.loads(MessageToJson(message)), 'insertId': IID3, - 'timestamp': _datetime_to_rfc3339(TIMESTAMP3)}, + 'timestamp': _datetime_to_rfc3339(TIMESTAMP3), + 'resource': _GLOBAL_RESOURCE._to_dict()}, ] client = _Client(project=self.PROJECT) api = client.logging_api = _DummyLoggingAPI() @@ -688,7 +766,7 @@ def test_commit_w_bound_client(self): self.assertEqual(list(batch.entries), []) self.assertEqual(api._write_entries_called_with, - (ENTRIES, logger.full_name, RESOURCE, None)) + (ENTRIES, logger.full_name, None, None)) def test_commit_w_alternate_client(self): import json @@ -696,6 +774,7 @@ def test_commit_w_alternate_client(self): from google.protobuf.struct_pb2 import Struct from google.protobuf.struct_pb2 import Value from google.cloud.logging.logger import Logger + from google.cloud.logging.logger import _GLOBAL_RESOURCE TEXT = 'This is the entry text' STRUCT = {'message': TEXT, 'weather': 'partly cloudy'} @@ -718,12 +797,14 @@ def test_commit_w_alternate_client(self): client2 = _Client(project=self.PROJECT) api = client2.logging_api = _DummyLoggingAPI() logger = Logger('logger_name', client1, labels=DEFAULT_LABELS) - RESOURCE = {'type': 'global'} ENTRIES = [ - {'textPayload': TEXT, 'labels': LABELS}, - {'jsonPayload': STRUCT, 'severity': SEVERITY}, + {'textPayload': TEXT, 'labels': LABELS, 'resource': + _GLOBAL_RESOURCE._to_dict()}, + {'jsonPayload': STRUCT, 'severity': SEVERITY, + 'resource': _GLOBAL_RESOURCE._to_dict()}, {'protoPayload': json.loads(MessageToJson(message)), - 'httpRequest': REQUEST}, + 'httpRequest': REQUEST, + 'resource': _GLOBAL_RESOURCE._to_dict()}, ] batch = self._make_one(logger, client=client1) @@ -734,7 +815,7 @@ def test_commit_w_alternate_client(self): self.assertEqual(list(batch.entries), []) self.assertEqual(api._write_entries_called_with, - (ENTRIES, logger.full_name, RESOURCE, DEFAULT_LABELS)) + (ENTRIES, logger.full_name, None, DEFAULT_LABELS)) def test_context_mgr_success(self): import json @@ -742,6 +823,8 @@ def test_context_mgr_success(self): from google.protobuf.struct_pb2 import Struct from google.protobuf.struct_pb2 import Value from google.cloud.logging.logger import Logger + from google.cloud.logging.logger import _GLOBAL_RESOURCE + TEXT = 'This is the entry text' STRUCT = {'message': TEXT, 'weather': 'partly cloudy'} @@ -760,13 +843,13 @@ def test_context_mgr_success(self): client = _Client(project=self.PROJECT) api = client.logging_api = _DummyLoggingAPI() logger = Logger('logger_name', client, labels=DEFAULT_LABELS) - RESOURCE = { - 'type': 'global', - } ENTRIES = [ - {'textPayload': TEXT, 'httpRequest': REQUEST}, - {'jsonPayload': STRUCT, 'labels': LABELS}, + {'textPayload': TEXT, 'httpRequest': REQUEST, + 'resource': _GLOBAL_RESOURCE._to_dict()}, + {'jsonPayload': STRUCT, 'labels': LABELS, + 'resource': _GLOBAL_RESOURCE._to_dict()}, {'protoPayload': json.loads(MessageToJson(message)), + 'resource': _GLOBAL_RESOURCE._to_dict(), 'severity': SEVERITY}, ] batch = self._make_one(logger, client=client) @@ -778,12 +861,13 @@ def test_context_mgr_success(self): self.assertEqual(list(batch.entries), []) self.assertEqual(api._write_entries_called_with, - (ENTRIES, logger.full_name, RESOURCE, DEFAULT_LABELS)) + (ENTRIES, logger.full_name, None, DEFAULT_LABELS)) def test_context_mgr_failure(self): import datetime from google.protobuf.struct_pb2 import Struct from google.protobuf.struct_pb2 import Value + from google.cloud.logging.logger import _GLOBAL_RESOURCE TEXT = 'This is the entry text' STRUCT = {'message': TEXT, 'weather': 'partly cloudy'} @@ -804,9 +888,12 @@ def test_context_mgr_failure(self): api = client.logging_api = _DummyLoggingAPI() logger = _Logger() UNSENT = [ - ('text', TEXT, None, IID, None, None, TIMESTAMP), - ('struct', STRUCT, None, None, SEVERITY, None, None), - ('proto', message, LABELS, None, None, REQUEST, None), + ('text', TEXT, None, IID, None, None, TIMESTAMP, + _GLOBAL_RESOURCE), + ('struct', STRUCT, None, None, SEVERITY, None, None, + _GLOBAL_RESOURCE), + ('proto', message, LABELS, None, None, REQUEST, None, + _GLOBAL_RESOURCE), ] batch = self._make_one(logger, client=client)