From c9c073323a7cd4dacd596baf21598f6c78fccff9 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Mon, 8 May 2017 15:50:00 -0700 Subject: [PATCH 01/22] Add MonitoredResource to interface --- logging/google/cloud/logging/logger.py | 87 ++++--- logging/google/cloud/logging/resource.py | 275 +++++++++++++++++++++++ 2 files changed, 335 insertions(+), 27 deletions(-) create mode 100644 logging/google/cloud/logging/resource.py diff --git a/logging/google/cloud/logging/logger.py b/logging/google/cloud/logging/logger.py index f093e6e48c88..3a0f2ba0d2ca 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=None): """Return a log entry resource of the appropriate type. Helper for :meth:`log_text`, :meth:`log_struct`, and :meth:`log_proto`. @@ -123,19 +128,24 @@ 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` + :param resource: (Optional) Monitored resource of the entry + :rtype: dict :returns: The JSON resource created. """ - resource = { + entry = { 'logName': self.full_name, - 'resource': {'type': 'global'}, } + if resource is not None: + entry['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 +154,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 +205,23 @@ 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` + :param resource: (Optional) 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 +248,23 @@ 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` + :param resource: (Optional) 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: @@ -274,7 +297,8 @@ def log_proto(self, message, client=None, labels=None, insert_id=None, 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): @@ -358,7 +382,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 @@ -382,10 +406,11 @@ def log_text(self, text, labels=None, insert_id=None, severity=None, """ 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 @@ -409,10 +434,11 @@ def log_struct(self, info, labels=None, insert_id=None, severity=None, """ 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 @@ -436,29 +462,34 @@ def log_proto(self, message, labels=None, insert_id=None, severity=None, """ self.entries.append( ('proto', message, labels, insert_id, severity, http_request, - timestamp)) + timestamp, resource)) - def commit(self, client=None): + def commit(self, client=None, resource=None): """Send saved log entries as a single API call. :type client: :class:`~google.cloud.logging.client.Client` or ``NoneType`` :param client: the client to use. If not passed, falls back to the ``client`` stored on the current batch. + + :type resource :class:``google.cloud.logging.resource` + :param resource: (Optional) Monitored resource of the batch """ if client is None: client = self.client kwargs = { 'logger_name': self.logger.full_name, - 'resource': {'type': 'global'}, } + + if resource is not None: + kwargs['resource'] = 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 +504,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..09dae62b2e6c --- /dev/null +++ b/logging/google/cloud/logging/resource.py @@ -0,0 +1,275 @@ +# Copyright 2016 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 Descriptors for the +`Google Stackdriver Logging API (V2)`_. +""" + +import collections + + +class LabelValueType(object): + """Allowed values for the `type of a label`_. + + .. _type of a label: + https://cloud.google.com/monitoring/api/ref_v3/rest/v3/\ + LabelDescriptor#ValueType + """ + + STRING = 'STRING' + BOOL = 'BOOL' + INT64 = 'INT64' + + +class LabelDescriptor(object): + """Schema specification and documentation for a single label. + + :type key: str + :param key: The name of the label. + + :type value_type: str + :param value_type: + The type of the label. It must be one of :data:`LabelValueType.STRING`, + :data:`LabelValueType.BOOL`, or :data:`LabelValueType.INT64`. + See :class:`LabelValueType`. + + :type description: str + :param description: A human-readable description for the label. + """ + + def __init__(self, key, value_type=LabelValueType.STRING, description=''): + self.key = key + self.value_type = value_type + self.description = description + + @classmethod + def _from_dict(cls, info): + """Construct a label descriptor from the parsed JSON representation. + + :type info: dict + :param info: + A ``dict`` parsed from the JSON wire-format representation. + + :rtype: :class:`LabelDescriptor` + :returns: A label descriptor. + """ + return cls( + info['key'], + info.get('valueType', LabelValueType.STRING), + info.get('description', ''), + ) + + def _to_dict(self): + """Build a dictionary ready to be serialized to the JSON wire format. + + :rtype: dict + :returns: A dictionary. + """ + info = { + 'key': self.key, + 'valueType': self.value_type, + } + + if self.description: + info['description'] = self.description + + return info + + def __eq__(self, other): + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + return self.__dict__ != other.__dict__ + + def __repr__(self): + return ( + 'LabelDescriptor(key={key!r}, value_type={value_type!r},' + ' description={description!r})' + ).format(**self.__dict__) + + +class ResourceDescriptor(object): + """Specification of a monitored resource type and its schema. + + :type name: str + :param name: + The "resource name" of the monitored resource descriptor: + ``"projects//monitoredResourceDescriptors/"`` + + :type type_: str + :param type_: + The monitored resource type. For example: ``"gce_instance"`` + + :type display_name: str + :param display_name: + A concise name that might be displayed in user interfaces. + + :type description: str + :param description: + A detailed description that might be used in documentation. + + :type labels: + list of :class:`~google.cloud.monitoring.label.LabelDescriptor` + :param labels: + A sequence of label descriptors specifying the labels used + to identify a specific instance of this monitored resource. + """ + + def __init__(self, name, type_, display_name, description, labels): + self.name = name + self.type = type_ + self.display_name = display_name + self.description = description + self.labels = labels + + @classmethod + def _fetch(cls, client, resource_type): + """Look up a monitored resource descriptor by type. + + :type client: :class:`google.cloud.monitoring.client.Client` + :param client: The client to use. + + :type resource_type: str + :param resource_type: The resource type name. + + :rtype: :class:`ResourceDescriptor` + :returns: The resource descriptor instance. + + :raises: :class:`google.cloud.exceptions.NotFound` if the resource + descriptor is not found. + """ + path = ('/projects/{project}/monitoredResourceDescriptors/{type}' + .format(project=client.project, + type=resource_type)) + info = client._connection.api_request(method='GET', path=path) + return cls._from_dict(info) + + @classmethod + def _list(cls, client, filter_string=None): + """List all monitored resource descriptors for the project. + + :type client: :class:`google.cloud.monitoring.client.Client` + :param client: The client to use. + + :type filter_string: str + :param filter_string: + (Optional) An optional filter expression describing the resource + descriptors to be returned. See the `filter documentation`_. + + :rtype: list of :class:`ResourceDescriptor` + :returns: A list of resource descriptor instances. + + .. _filter documentation: + https://cloud.google.com/monitoring/api/v3/filters + """ + path = '/projects/{project}/monitoredResourceDescriptors/'.format( + project=client.project) + + descriptors = [] + + page_token = None + while True: + params = {} + + if filter_string is not None: + params['filter'] = filter_string + + if page_token is not None: + params['pageToken'] = page_token + + response = client._connection.api_request( + method='GET', path=path, query_params=params) + for info in response.get('resourceDescriptors', ()): + descriptors.append(cls._from_dict(info)) + + page_token = response.get('nextPageToken') + if not page_token: + break + + return descriptors + + @classmethod + def _from_dict(cls, info): + """Construct a resource descriptor from the parsed JSON representation. + + :type info: dict + :param info: + A ``dict`` parsed from the JSON wire-format representation. + + :rtype: :class:`ResourceDescriptor` + :returns: A resource descriptor. + """ + return cls( + name=info['name'], + type_=info['type'], + display_name=info.get('displayName', ''), + description=info.get('description', ''), + labels=tuple(LabelDescriptor._from_dict(label) + for label in info.get('labels', ())), + ) + + def __repr__(self): + return ( + '' + ).format(**self.__dict__) + + +class Resource(collections.namedtuple('Resource', 'type labels')): + """A monitored resource identified by specifying values for all labels. + + The preferred way to construct a resource object is using the + :meth:`~google.cloud.monitoring.client.Client.resource` factory method + of the :class:`~google.cloud.monitoring.client.Client` class. + + :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, + } From bb8bf4a3d8a5fc7a17eff217a77c2812444ff4d3 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Tue, 9 May 2017 14:15:48 -0700 Subject: [PATCH 02/22] Add tests, fix stuff --- logging/google/cloud/logging/logger.py | 31 +++- logging/google/cloud/logging/resource.py | 219 +---------------------- logging/tests/system.py | 18 ++ logging/tests/unit/test_logger.py | 138 +++++++++++--- 4 files changed, 151 insertions(+), 255 deletions(-) diff --git a/logging/google/cloud/logging/logger.py b/logging/google/cloud/logging/logger.py index 3a0f2ba0d2ca..33b9db100a8d 100644 --- a/logging/google/cloud/logging/logger.py +++ b/logging/google/cloud/logging/logger.py @@ -128,7 +128,7 @@ 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` + :type resource :class:`google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the entry :rtype: dict @@ -205,7 +205,7 @@ 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` + :type resource :class:`google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the entry, defaults to the global resource type. @@ -291,6 +291,9 @@ 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` + :param resource: (Optional) Monitored resource of the entry + :type timestamp: :class:`datetime.datetime` :param timestamp: (optional) timestamp of event being logged. """ @@ -368,11 +371,15 @@ 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 """ - 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 @@ -403,6 +410,9 @@ 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` + :param resource: (Optional) Monitored resource of the entry """ self.entries.append( ('text', text, labels, insert_id, severity, http_request, @@ -431,6 +441,9 @@ 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` + :param resource: (Optional) Monitored resource of the entry """ self.entries.append( ('struct', info, labels, insert_id, severity, http_request, @@ -459,21 +472,21 @@ 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` + :param resource: (Optional) Monitored resource of the entry """ self.entries.append( ('proto', message, labels, insert_id, severity, http_request, timestamp, resource)) - def commit(self, client=None, resource=None): + def commit(self, client=None): """Send saved log entries as a single API call. :type client: :class:`~google.cloud.logging.client.Client` or ``NoneType`` :param client: the client to use. If not passed, falls back to the ``client`` stored on the current batch. - - :type resource :class:``google.cloud.logging.resource` - :param resource: (Optional) Monitored resource of the batch """ if client is None: client = self.client @@ -482,8 +495,8 @@ def commit(self, client=None, resource=None): 'logger_name': self.logger.full_name, } - if resource is not None: - kwargs['resource'] = resource._to_dict() + if self.resource is not None: + kwargs['resource'] = self.resource._to_dict() if self.logger.labels is not None: kwargs['labels'] = self.logger.labels diff --git a/logging/google/cloud/logging/resource.py b/logging/google/cloud/logging/resource.py index 09dae62b2e6c..3ea1a06ef4ee 100644 --- a/logging/google/cloud/logging/resource.py +++ b/logging/google/cloud/logging/resource.py @@ -12,231 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Monitored Resource Descriptors for the -`Google Stackdriver Logging API (V2)`_. -""" +"""Monitored Resource for the Google Logging API V2.""" import collections -class LabelValueType(object): - """Allowed values for the `type of a label`_. - - .. _type of a label: - https://cloud.google.com/monitoring/api/ref_v3/rest/v3/\ - LabelDescriptor#ValueType - """ - - STRING = 'STRING' - BOOL = 'BOOL' - INT64 = 'INT64' - - -class LabelDescriptor(object): - """Schema specification and documentation for a single label. - - :type key: str - :param key: The name of the label. - - :type value_type: str - :param value_type: - The type of the label. It must be one of :data:`LabelValueType.STRING`, - :data:`LabelValueType.BOOL`, or :data:`LabelValueType.INT64`. - See :class:`LabelValueType`. - - :type description: str - :param description: A human-readable description for the label. - """ - - def __init__(self, key, value_type=LabelValueType.STRING, description=''): - self.key = key - self.value_type = value_type - self.description = description - - @classmethod - def _from_dict(cls, info): - """Construct a label descriptor from the parsed JSON representation. - - :type info: dict - :param info: - A ``dict`` parsed from the JSON wire-format representation. - - :rtype: :class:`LabelDescriptor` - :returns: A label descriptor. - """ - return cls( - info['key'], - info.get('valueType', LabelValueType.STRING), - info.get('description', ''), - ) - - def _to_dict(self): - """Build a dictionary ready to be serialized to the JSON wire format. - - :rtype: dict - :returns: A dictionary. - """ - info = { - 'key': self.key, - 'valueType': self.value_type, - } - - if self.description: - info['description'] = self.description - - return info - - def __eq__(self, other): - return self.__dict__ == other.__dict__ - - def __ne__(self, other): - return self.__dict__ != other.__dict__ - - def __repr__(self): - return ( - 'LabelDescriptor(key={key!r}, value_type={value_type!r},' - ' description={description!r})' - ).format(**self.__dict__) - - -class ResourceDescriptor(object): - """Specification of a monitored resource type and its schema. - - :type name: str - :param name: - The "resource name" of the monitored resource descriptor: - ``"projects//monitoredResourceDescriptors/"`` - - :type type_: str - :param type_: - The monitored resource type. For example: ``"gce_instance"`` - - :type display_name: str - :param display_name: - A concise name that might be displayed in user interfaces. - - :type description: str - :param description: - A detailed description that might be used in documentation. - - :type labels: - list of :class:`~google.cloud.monitoring.label.LabelDescriptor` - :param labels: - A sequence of label descriptors specifying the labels used - to identify a specific instance of this monitored resource. - """ - - def __init__(self, name, type_, display_name, description, labels): - self.name = name - self.type = type_ - self.display_name = display_name - self.description = description - self.labels = labels - - @classmethod - def _fetch(cls, client, resource_type): - """Look up a monitored resource descriptor by type. - - :type client: :class:`google.cloud.monitoring.client.Client` - :param client: The client to use. - - :type resource_type: str - :param resource_type: The resource type name. - - :rtype: :class:`ResourceDescriptor` - :returns: The resource descriptor instance. - - :raises: :class:`google.cloud.exceptions.NotFound` if the resource - descriptor is not found. - """ - path = ('/projects/{project}/monitoredResourceDescriptors/{type}' - .format(project=client.project, - type=resource_type)) - info = client._connection.api_request(method='GET', path=path) - return cls._from_dict(info) - - @classmethod - def _list(cls, client, filter_string=None): - """List all monitored resource descriptors for the project. - - :type client: :class:`google.cloud.monitoring.client.Client` - :param client: The client to use. - - :type filter_string: str - :param filter_string: - (Optional) An optional filter expression describing the resource - descriptors to be returned. See the `filter documentation`_. - - :rtype: list of :class:`ResourceDescriptor` - :returns: A list of resource descriptor instances. - - .. _filter documentation: - https://cloud.google.com/monitoring/api/v3/filters - """ - path = '/projects/{project}/monitoredResourceDescriptors/'.format( - project=client.project) - - descriptors = [] - - page_token = None - while True: - params = {} - - if filter_string is not None: - params['filter'] = filter_string - - if page_token is not None: - params['pageToken'] = page_token - - response = client._connection.api_request( - method='GET', path=path, query_params=params) - for info in response.get('resourceDescriptors', ()): - descriptors.append(cls._from_dict(info)) - - page_token = response.get('nextPageToken') - if not page_token: - break - - return descriptors - - @classmethod - def _from_dict(cls, info): - """Construct a resource descriptor from the parsed JSON representation. - - :type info: dict - :param info: - A ``dict`` parsed from the JSON wire-format representation. - - :rtype: :class:`ResourceDescriptor` - :returns: A resource descriptor. - """ - return cls( - name=info['name'], - type_=info['type'], - display_name=info.get('displayName', ''), - description=info.get('description', ''), - labels=tuple(LabelDescriptor._from_dict(label) - for label in info.get('labels', ())), - ) - - def __repr__(self): - return ( - '' - ).format(**self.__dict__) - - class Resource(collections.namedtuple('Resource', 'type labels')): """A monitored resource identified by specifying values for all labels. - The preferred way to construct a resource object is using the - :meth:`~google.cloud.monitoring.client.Client.resource` factory method - of the :class:`~google.cloud.monitoring.client.Client` class. - :type type: str :param type: The resource type name. diff --git a/logging/tests/system.py b/logging/tests/system.py index 89047edef5c4..9457ac625b8a 100644 --- a/logging/tests/system.py +++ b/logging/tests/system.py @@ -171,6 +171,24 @@ 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): + from google.cloud.logging.resource import Resource + 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) + self.assertEqual(entries[0].resource, RESOURCE._to_dict()) + def test_log_text_w_metadata(self): TEXT_PAYLOAD = 'System test: test_log_text' INSERT_ID = 'INSERTID' diff --git a/logging/tests/unit/test_logger.py b/logging/tests/unit/test_logger.py index 0501bee1fd39..f1be0fbd9d03 100644 --- a/logging/tests/unit/test_logger.py +++ b/logging/tests/unit/test_logger.py @@ -89,6 +89,7 @@ def test_batch_w_alternate_client(self): self.assertIs(batch.client, client2) def test_log_text_w_str_implicit_client(self): + from google.cloud.logging.logger import _GLOBAL_RESOURCE TEXT = 'TEXT' ENTRIES = [{ 'logName': 'projects/%s/logs/%s' % ( @@ -96,6 +97,7 @@ def test_log_text_w_str_implicit_client(self): 'textPayload': TEXT, 'resource': { 'type': 'global', + 'labels': {} }, }] client = _Client(self.PROJECT) @@ -108,6 +110,7 @@ def test_log_text_w_str_implicit_client(self): (ENTRIES, None, None, None)) def test_log_text_w_default_labels(self): + from google.cloud.logging.logger import _GLOBAL_RESOURCE TEXT = 'TEXT' DEFAULT_LABELS = {'foo': 'spam'} ENTRIES = [{ @@ -116,6 +119,7 @@ def test_log_text_w_default_labels(self): 'textPayload': TEXT, 'resource': { 'type': 'global', + 'labels': {} }, 'labels': DEFAULT_LABELS, }] @@ -131,6 +135,7 @@ def test_log_text_w_default_labels(self): def test_log_text_w_timestamp(self): import datetime + from google.cloud.logging.logger import _GLOBAL_RESOURCE TEXT = 'TEXT' TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999) @@ -141,6 +146,7 @@ def test_log_text_w_timestamp(self): 'timestamp': '2016-12-31T00:01:02.999999Z', 'resource': { 'type': 'global', + 'labels': {} }, }] client = _Client(self.PROJECT) @@ -153,6 +159,7 @@ def test_log_text_w_timestamp(self): (ENTRIES, None, None, None)) def test_log_text_w_unicode_explicit_client_labels_severity_httpreq(self): + from google.cloud.logging.logger import _GLOBAL_RESOURCE TEXT = u'TEXT' DEFAULT_LABELS = {'foo': 'spam'} LABELS = {'foo': 'bar', 'baz': 'qux'} @@ -172,6 +179,7 @@ def test_log_text_w_unicode_explicit_client_labels_severity_httpreq(self): 'textPayload': TEXT, 'resource': { 'type': 'global', + 'labels': {} }, 'labels': LABELS, 'insertId': IID, @@ -191,6 +199,7 @@ def test_log_text_w_unicode_explicit_client_labels_severity_httpreq(self): (ENTRIES, None, None, None)) def test_log_struct_w_implicit_client(self): + from google.cloud.logging.logger import _GLOBAL_RESOURCE STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'} ENTRIES = [{ 'logName': 'projects/%s/logs/%s' % ( @@ -198,6 +207,7 @@ def test_log_struct_w_implicit_client(self): 'jsonPayload': STRUCT, 'resource': { 'type': 'global', + 'labels': {} }, }] client = _Client(self.PROJECT) @@ -210,6 +220,7 @@ def test_log_struct_w_implicit_client(self): (ENTRIES, None, None, None)) def test_log_struct_w_default_labels(self): + from google.cloud.logging.logger import _GLOBAL_RESOURCE STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'} DEFAULT_LABELS = {'foo': 'spam'} ENTRIES = [{ @@ -218,6 +229,7 @@ def test_log_struct_w_default_labels(self): 'jsonPayload': STRUCT, 'resource': { 'type': 'global', + 'labels': {} }, 'labels': DEFAULT_LABELS, }] @@ -232,6 +244,7 @@ def test_log_struct_w_default_labels(self): (ENTRIES, None, None, None)) def test_log_struct_w_explicit_client_labels_severity_httpreq(self): + from google.cloud.logging.logger import _GLOBAL_RESOURCE STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'} DEFAULT_LABELS = {'foo': 'spam'} LABELS = {'foo': 'bar', 'baz': 'qux'} @@ -251,6 +264,7 @@ def test_log_struct_w_explicit_client_labels_severity_httpreq(self): 'jsonPayload': STRUCT, 'resource': { 'type': 'global', + 'labels': {} }, 'labels': LABELS, 'insertId': IID, @@ -272,6 +286,7 @@ def test_log_struct_w_explicit_client_labels_severity_httpreq(self): def test_log_struct_w_timestamp(self): import datetime + from google.cloud.logging.logger import _GLOBAL_RESOURCE STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'} TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999) @@ -282,6 +297,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 +321,7 @@ def test_log_proto_w_implicit_client(self): 'protoPayload': json.loads(MessageToJson(message)), 'resource': { 'type': 'global', + 'labels': {} }, }] client = _Client(self.PROJECT) @@ -329,6 +346,8 @@ def test_log_proto_w_default_labels(self): 'protoPayload': json.loads(MessageToJson(message)), 'resource': { 'type': 'global', + 'labels': {} + }, 'labels': DEFAULT_LABELS, }] @@ -367,6 +386,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 +422,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 +551,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 +578,23 @@ 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 +602,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 +622,23 @@ 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 +648,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,15 +670,19 @@ 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() @@ -647,6 +692,28 @@ def test_commit_w_invalid_entry_type(self): 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 + from google.protobuf.struct_pb2 import Struct, Value + + logger = _Logger() + client = _Client(project=self.PROJECT, connection=_make_credentials()) + api = client.logging_api = _DummyLoggingAPI() + STRUCT = Struct(fields={'foo': Value(bool_value=True)}) + 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}] + batch.log_text(MESSAGE, resource=None) + 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 +721,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'} @@ -669,12 +738,15 @@ def test_commit_w_bound_client(self): } 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 +760,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 +768,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 +791,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 +809,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 +817,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,14 +837,14 @@ 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)), - 'severity': SEVERITY}, + 'resource': _GLOBAL_RESOURCE._to_dict(), + 'severity': SEVERITY} ] batch = self._make_one(logger, client=client) @@ -778,12 +855,14 @@ 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 +883,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) From 5559ba9c9f811f29629963e40f9203863bada942 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Tue, 9 May 2017 14:17:02 -0700 Subject: [PATCH 03/22] more docs fixups --- logging/google/cloud/logging/logger.py | 6 +++--- logging/nox.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/logging/google/cloud/logging/logger.py b/logging/google/cloud/logging/logger.py index 33b9db100a8d..0d5d49bc1e57 100644 --- a/logging/google/cloud/logging/logger.py +++ b/logging/google/cloud/logging/logger.py @@ -248,7 +248,7 @@ 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` + :type resource :class:``google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the entry, defaults to the global resource type. @@ -291,7 +291,7 @@ 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` + :type resource :class:``google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the entry :type timestamp: :class:`datetime.datetime` @@ -473,7 +473,7 @@ 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` + :type resource :class:``google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the entry """ self.entries.append( diff --git a/logging/nox.py b/logging/nox.py index 7f6447c56924..65ceefae5c08 100644 --- a/logging/nox.py +++ b/logging/nox.py @@ -73,7 +73,7 @@ def lint(session): Returns a failure if flake8 finds linting errors or sufficiently serious code quality issues. """ - session.interpreter = 'python3.6' + session.interpreter = 'python3.5' session.install('flake8', *LOCAL_DEPS) session.install('.') session.run('flake8', 'google/cloud/logging') @@ -95,7 +95,7 @@ def cover(session): This outputs the coverage report aggregating coverage from the unit test runs (not system test runs), and then erases coverage data. """ - session.interpreter = 'python3.6' + session.interpreter = 'python3.5' session.install('coverage', 'pytest-cov') session.run('coverage', 'report', '--show-missing', '--fail-under=100') session.run('coverage', 'erase') From 46eff7a356860bb9c922b9e013d96e293536cf58 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Tue, 9 May 2017 17:20:03 -0700 Subject: [PATCH 04/22] system tests fix and dhermes review --- logging/google/cloud/logging/entries.py | 23 +++++++++++++--- logging/google/cloud/logging/logger.py | 16 +++++------ logging/nox.py | 2 +- logging/tests/system.py | 15 +++++++---- logging/tests/unit/test_entries.py | 19 ++++++++++++- logging/tests/unit/test_logger.py | 36 +++++++++---------------- 6 files changed, 69 insertions(+), 42 deletions(-) diff --git a/logging/google/cloud/logging/entries.py b/logging/google/cloud/logging/entries.py index 284562c5de5b..68857dd5ca43 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 @@ -72,9 +73,11 @@ class _BaseEntry(object): :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 """ 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 +85,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 +122,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 +181,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 0d5d49bc1e57..b2a33af43b41 100644 --- a/logging/google/cloud/logging/logger.py +++ b/logging/google/cloud/logging/logger.py @@ -128,7 +128,7 @@ 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` + :type resource: :class:`~google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the entry :rtype: dict @@ -205,7 +205,7 @@ 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` + :type resource: :class:`~google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the entry, defaults to the global resource type. @@ -248,7 +248,7 @@ 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` + :type resource: :class:`~google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the entry, defaults to the global resource type. @@ -291,7 +291,7 @@ 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` + :type resource: :class:`~google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the entry :type timestamp: :class:`datetime.datetime` @@ -372,7 +372,7 @@ 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` + :type resource: :class:`google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the batch """ def __init__(self, logger, client, resource=None): @@ -411,7 +411,7 @@ 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` + :type resource: :class:`~google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the entry """ self.entries.append( @@ -442,7 +442,7 @@ 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` + :type resource: :class:`~google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the entry """ self.entries.append( @@ -473,7 +473,7 @@ 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` + :type resource: :class:`~google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the entry """ self.entries.append( diff --git a/logging/nox.py b/logging/nox.py index 65ceefae5c08..2c8cbf1df1ab 100644 --- a/logging/nox.py +++ b/logging/nox.py @@ -73,7 +73,7 @@ def lint(session): Returns a failure if flake8 finds linting errors or sufficiently serious code quality issues. """ - session.interpreter = 'python3.5' + session.interpreter = 'python3.6' session.install('flake8', *LOCAL_DEPS) session.install('.') session.run('flake8', 'google/cloud/logging') diff --git a/logging/tests/system.py b/logging/tests/system.py index 9457ac625b8a..82e92f0655b4 100644 --- a/logging/tests/system.py +++ b/logging/tests/system.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# 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. @@ -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 @@ -172,14 +173,16 @@ def test_log_text_with_timestamp(self): self.assertEqual(entries[0].timestamp, now.replace(tzinfo=UTC)) def test_log_text_with_resource(self): - from google.cloud.logging.resource import Resource 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'}) + type='gae_app', + labels={ + 'module_id': 'default', + 'version_id': 'test' + }) self.to_delete.append(logger) @@ -187,7 +190,9 @@ def test_log_text_with_resource(self): entries = _list_entries(logger) self.assertEqual(len(entries), 1) self.assertEqual(entries[0].payload, text_payload) - self.assertEqual(entries[0].resource, RESOURCE._to_dict()) + # 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' diff --git a/logging/tests/unit/test_entries.py b/logging/tests/unit/test_entries.py index 4d254eb9d1ef..9c88b10793ed 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,14 @@ 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 +164,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 +181,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 f1be0fbd9d03..42840c3efe21 100644 --- a/logging/tests/unit/test_logger.py +++ b/logging/tests/unit/test_logger.py @@ -89,7 +89,6 @@ def test_batch_w_alternate_client(self): self.assertIs(batch.client, client2) def test_log_text_w_str_implicit_client(self): - from google.cloud.logging.logger import _GLOBAL_RESOURCE TEXT = 'TEXT' ENTRIES = [{ 'logName': 'projects/%s/logs/%s' % ( @@ -97,7 +96,7 @@ def test_log_text_w_str_implicit_client(self): 'textPayload': TEXT, 'resource': { 'type': 'global', - 'labels': {} + 'labels': {}, }, }] client = _Client(self.PROJECT) @@ -110,7 +109,6 @@ def test_log_text_w_str_implicit_client(self): (ENTRIES, None, None, None)) def test_log_text_w_default_labels(self): - from google.cloud.logging.logger import _GLOBAL_RESOURCE TEXT = 'TEXT' DEFAULT_LABELS = {'foo': 'spam'} ENTRIES = [{ @@ -119,7 +117,7 @@ def test_log_text_w_default_labels(self): 'textPayload': TEXT, 'resource': { 'type': 'global', - 'labels': {} + 'labels': {}, }, 'labels': DEFAULT_LABELS, }] @@ -135,7 +133,6 @@ def test_log_text_w_default_labels(self): def test_log_text_w_timestamp(self): import datetime - from google.cloud.logging.logger import _GLOBAL_RESOURCE TEXT = 'TEXT' TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999) @@ -146,7 +143,7 @@ def test_log_text_w_timestamp(self): 'timestamp': '2016-12-31T00:01:02.999999Z', 'resource': { 'type': 'global', - 'labels': {} + 'labels': {}, }, }] client = _Client(self.PROJECT) @@ -159,7 +156,6 @@ def test_log_text_w_timestamp(self): (ENTRIES, None, None, None)) def test_log_text_w_unicode_explicit_client_labels_severity_httpreq(self): - from google.cloud.logging.logger import _GLOBAL_RESOURCE TEXT = u'TEXT' DEFAULT_LABELS = {'foo': 'spam'} LABELS = {'foo': 'bar', 'baz': 'qux'} @@ -179,7 +175,7 @@ def test_log_text_w_unicode_explicit_client_labels_severity_httpreq(self): 'textPayload': TEXT, 'resource': { 'type': 'global', - 'labels': {} + 'labels': {}, }, 'labels': LABELS, 'insertId': IID, @@ -199,7 +195,6 @@ def test_log_text_w_unicode_explicit_client_labels_severity_httpreq(self): (ENTRIES, None, None, None)) def test_log_struct_w_implicit_client(self): - from google.cloud.logging.logger import _GLOBAL_RESOURCE STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'} ENTRIES = [{ 'logName': 'projects/%s/logs/%s' % ( @@ -207,7 +202,7 @@ def test_log_struct_w_implicit_client(self): 'jsonPayload': STRUCT, 'resource': { 'type': 'global', - 'labels': {} + 'labels': {}, }, }] client = _Client(self.PROJECT) @@ -220,7 +215,6 @@ def test_log_struct_w_implicit_client(self): (ENTRIES, None, None, None)) def test_log_struct_w_default_labels(self): - from google.cloud.logging.logger import _GLOBAL_RESOURCE STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'} DEFAULT_LABELS = {'foo': 'spam'} ENTRIES = [{ @@ -229,7 +223,7 @@ def test_log_struct_w_default_labels(self): 'jsonPayload': STRUCT, 'resource': { 'type': 'global', - 'labels': {} + 'labels': {}, }, 'labels': DEFAULT_LABELS, }] @@ -244,7 +238,6 @@ def test_log_struct_w_default_labels(self): (ENTRIES, None, None, None)) def test_log_struct_w_explicit_client_labels_severity_httpreq(self): - from google.cloud.logging.logger import _GLOBAL_RESOURCE STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'} DEFAULT_LABELS = {'foo': 'spam'} LABELS = {'foo': 'bar', 'baz': 'qux'} @@ -264,7 +257,7 @@ def test_log_struct_w_explicit_client_labels_severity_httpreq(self): 'jsonPayload': STRUCT, 'resource': { 'type': 'global', - 'labels': {} + 'labels': {}, }, 'labels': LABELS, 'insertId': IID, @@ -286,7 +279,6 @@ def test_log_struct_w_explicit_client_labels_severity_httpreq(self): def test_log_struct_w_timestamp(self): import datetime - from google.cloud.logging.logger import _GLOBAL_RESOURCE STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'} TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999) @@ -297,7 +289,7 @@ def test_log_struct_w_timestamp(self): 'timestamp': '2016-12-31T00:01:02.999999Z', 'resource': { 'type': 'global', - 'labels': {} + 'labels': {}, }, }] client = _Client(self.PROJECT) @@ -321,7 +313,7 @@ def test_log_proto_w_implicit_client(self): 'protoPayload': json.loads(MessageToJson(message)), 'resource': { 'type': 'global', - 'labels': {} + 'labels': {}, }, }] client = _Client(self.PROJECT) @@ -346,7 +338,7 @@ def test_log_proto_w_default_labels(self): 'protoPayload': json.loads(MessageToJson(message)), 'resource': { 'type': 'global', - 'labels': {} + 'labels': {}, }, 'labels': DEFAULT_LABELS, @@ -386,7 +378,7 @@ def test_log_proto_w_explicit_client_labels_severity_httpreq(self): 'protoPayload': json.loads(MessageToJson(message)), 'resource': { 'type': 'global', - 'labels': {} + 'labels': {}, }, 'labels': LABELS, 'insertId': IID, @@ -422,7 +414,7 @@ def test_log_proto_w_timestamp(self): 'timestamp': '2016-12-31T00:01:02.999999Z', 'resource': { 'type': 'global', - 'labels': {} + 'labels': {}, }, }] client = _Client(self.PROJECT) @@ -693,7 +685,6 @@ def test_commit_w_invalid_entry_type(self): batch.commit() def test_commit_w_resource_specified(self): - from google.cloud.logging.logger import _GLOBAL_RESOURCE from google.cloud.logging.resource import Resource from google.protobuf.struct_pb2 import Struct, Value @@ -843,7 +834,7 @@ def test_context_mgr_success(self): {'jsonPayload': STRUCT, 'labels': LABELS, 'resource':_GLOBAL_RESOURCE._to_dict()}, {'protoPayload': json.loads(MessageToJson(message)), - 'resource': _GLOBAL_RESOURCE._to_dict(), + 'resource': _GLOBAL_RESOURCE._to_dict(), 'severity': SEVERITY} ] batch = self._make_one(logger, client=client) @@ -863,7 +854,6 @@ def test_context_mgr_failure(self): 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'} LABELS = {'foo': 'bar', 'baz': 'qux'} From 02d5ff36d62b4567f5e3cae8a97bc5aa6745cd66 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Tue, 9 May 2017 17:26:25 -0700 Subject: [PATCH 05/22] fix year --- logging/google/cloud/logging/resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logging/google/cloud/logging/resource.py b/logging/google/cloud/logging/resource.py index 3ea1a06ef4ee..aa37287db3ef 100644 --- a/logging/google/cloud/logging/resource.py +++ b/logging/google/cloud/logging/resource.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# 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. From ce6f32f66981eaa086a71925560122eeea700765 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Tue, 9 May 2017 17:33:24 -0700 Subject: [PATCH 06/22] style fixups --- logging/tests/unit/test_logger.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/logging/tests/unit/test_logger.py b/logging/tests/unit/test_logger.py index 42840c3efe21..888daaacf382 100644 --- a/logging/tests/unit/test_logger.py +++ b/logging/tests/unit/test_logger.py @@ -571,8 +571,11 @@ def test_log_text_explicit(self): } TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999) RESOURCE = Resource( - type='gae_app', labels={'module_id': 'default', - 'version_id': 'test'}) + type='gae_app', + labels={ + 'module_id': 'default', + 'version_id': 'test', + }) client = _Client(project=self.PROJECT, connection=_make_credentials()) logger = _Logger() @@ -691,7 +694,6 @@ def test_commit_w_resource_specified(self): logger = _Logger() client = _Client(project=self.PROJECT, connection=_make_credentials()) api = client.logging_api = _DummyLoggingAPI() - STRUCT = Struct(fields={'foo': Value(bool_value=True)}) RESOURCE = Resource( type='gae_app', labels={'module_id': 'default', 'version_id': 'test'}) @@ -724,9 +726,6 @@ 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), From cf0bd5c128e7cd64e46dc424ff73642d5fd3d77f Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Tue, 9 May 2017 17:34:20 -0700 Subject: [PATCH 07/22] style fixups --- logging/tests/system.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/logging/tests/system.py b/logging/tests/system.py index 82e92f0655b4..669d9812707e 100644 --- a/logging/tests/system.py +++ b/logging/tests/system.py @@ -177,7 +177,7 @@ def test_log_text_with_resource(self): logger = Config.CLIENT.logger(self._logger_name()) now = datetime.datetime.utcnow() - RESOURCE = Resource( + resource = Resource( type='gae_app', labels={ 'module_id': 'default', @@ -186,13 +186,13 @@ def test_log_text_with_resource(self): self.to_delete.append(logger) - logger.log_text(text_payload, timestamp=now, resource=RESOURCE) + 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) + self.assertEqual(entries[0].resource, resource) def test_log_text_w_metadata(self): TEXT_PAYLOAD = 'System test: test_log_text' From 1aec1e8bf97a53c7a1464447877fa1a5dea0be85 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 09:51:01 -0700 Subject: [PATCH 08/22] make entry resource resource is never none --- logging/google/cloud/logging/logger.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/logging/google/cloud/logging/logger.py b/logging/google/cloud/logging/logger.py index b2a33af43b41..54eac5eb4885 100644 --- a/logging/google/cloud/logging/logger.py +++ b/logging/google/cloud/logging/logger.py @@ -96,7 +96,7 @@ 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, - resource=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`. @@ -138,8 +138,7 @@ def _make_entry_resource(self, text=None, info=None, message=None, 'logName': self.full_name, } - if resource is not None: - entry['resource'] = resource._to_dict() + entry['resource'] = resource._to_dict() if text is not None: entry['textPayload'] = text @@ -206,10 +205,9 @@ def log_text(self, text, client=None, labels=None, insert_id=None, the entry :type resource: :class:`~google.cloud.logging.resource.Resource` - :param resource: (Optional) Monitored resource of the entry, defaults + :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. """ @@ -249,10 +247,9 @@ def log_struct(self, info, client=None, labels=None, insert_id=None, the entry. :type resource: :class:`~google.cloud.logging.resource.Resource` - :param resource: (Optional) Monitored resource of the entry, defaults + :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. """ @@ -292,7 +289,7 @@ def log_proto(self, message, client=None, labels=None, insert_id=None, the entry. :type resource: :class:`~google.cloud.logging.resource.Resource` - :param resource: (Optional) Monitored resource of the entry + :param resource: Monitored resource of the entry :type timestamp: :class:`datetime.datetime` :param timestamp: (optional) timestamp of event being logged. From 23f3c903d9f8080723b0aa347c06a7e4a0233ced Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 09:52:22 -0700 Subject: [PATCH 09/22] style mixup --- logging/google/cloud/logging/logger.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/logging/google/cloud/logging/logger.py b/logging/google/cloud/logging/logger.py index 54eac5eb4885..74bab06d4678 100644 --- a/logging/google/cloud/logging/logger.py +++ b/logging/google/cloud/logging/logger.py @@ -136,10 +136,9 @@ def _make_entry_resource(self, text=None, info=None, message=None, """ entry = { 'logName': self.full_name, + 'resource': resource._to_dict() } - entry['resource'] = resource._to_dict() - if text is not None: entry['textPayload'] = text From de04ce3ac8e37ca3e62349cea99bdfdf4b377562 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 10:24:46 -0700 Subject: [PATCH 10/22] Add coverage --- logging/tests/unit/test_logger.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/logging/tests/unit/test_logger.py b/logging/tests/unit/test_logger.py index 888daaacf382..cca251b051dc 100644 --- a/logging/tests/unit/test_logger.py +++ b/logging/tests/unit/test_logger.py @@ -688,8 +688,8 @@ def test_commit_w_invalid_entry_type(self): batch.commit() def test_commit_w_resource_specified(self): + from google.cloud.logging.logger import _GLOBAL_RESOURCE from google.cloud.logging.resource import Resource - from google.protobuf.struct_pb2 import Struct, Value logger = _Logger() client = _Client(project=self.PROJECT, connection=_make_credentials()) @@ -700,8 +700,12 @@ def test_commit_w_resource_specified(self): batch = self._make_one(logger, client, resource=RESOURCE) MESSAGE = 'This is the entry text' - ENTRIES = [{'textPayload': MESSAGE}] + 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, @@ -829,9 +833,9 @@ def test_context_mgr_success(self): logger = Logger('logger_name', client, labels=DEFAULT_LABELS) ENTRIES = [ {'textPayload': TEXT, 'httpRequest': REQUEST, - 'resource':_GLOBAL_RESOURCE._to_dict()}, + 'resource': _GLOBAL_RESOURCE._to_dict()}, {'jsonPayload': STRUCT, 'labels': LABELS, - 'resource':_GLOBAL_RESOURCE._to_dict()}, + 'resource': _GLOBAL_RESOURCE._to_dict()}, {'protoPayload': json.loads(MessageToJson(message)), 'resource': _GLOBAL_RESOURCE._to_dict(), 'severity': SEVERITY} From 762983c9392ba6da82d252d749abaf35cc526e47 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 11:28:03 -0700 Subject: [PATCH 11/22] fix cover --- logging/tests/unit/test_logger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/logging/tests/unit/test_logger.py b/logging/tests/unit/test_logger.py index cca251b051dc..c1bf127d0bba 100644 --- a/logging/tests/unit/test_logger.py +++ b/logging/tests/unit/test_logger.py @@ -683,7 +683,8 @@ 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() From b0ce33a5a1fc9900141cb7a62aa144a6962dd141 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 12:12:24 -0700 Subject: [PATCH 12/22] missing tilde --- logging/google/cloud/logging/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logging/google/cloud/logging/logger.py b/logging/google/cloud/logging/logger.py index 74bab06d4678..d1357ed338d7 100644 --- a/logging/google/cloud/logging/logger.py +++ b/logging/google/cloud/logging/logger.py @@ -368,7 +368,7 @@ 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` + :type resource: :class:`~google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the batch """ def __init__(self, logger, client, resource=None): From 040bd1cb8163fb185bba44a0ef0c708985ae89f3 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 12:30:11 -0700 Subject: [PATCH 13/22] dhermes comments --- logging/google/cloud/logging/logger.py | 2 +- logging/tests/system.py | 5 +++-- logging/tests/unit/test_entries.py | 4 +++- logging/tests/unit/test_logger.py | 1 - 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/logging/google/cloud/logging/logger.py b/logging/google/cloud/logging/logger.py index d1357ed338d7..ff7ae98e954f 100644 --- a/logging/google/cloud/logging/logger.py +++ b/logging/google/cloud/logging/logger.py @@ -136,7 +136,7 @@ def _make_entry_resource(self, text=None, info=None, message=None, """ entry = { 'logName': self.full_name, - 'resource': resource._to_dict() + 'resource': resource._to_dict(), } if text is not None: diff --git a/logging/tests/system.py b/logging/tests/system.py index 669d9812707e..075ff5ffd6cc 100644 --- a/logging/tests/system.py +++ b/logging/tests/system.py @@ -1,4 +1,4 @@ -# Copyright 2017 Google Inc. +# Copyright 2016 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -182,7 +182,8 @@ def test_log_text_with_resource(self): labels={ 'module_id': 'default', 'version_id': 'test' - }) + } + ) self.to_delete.append(logger) diff --git a/logging/tests/unit/test_entries.py b/logging/tests/unit/test_entries.py index 9c88b10793ed..71cd9592c701 100644 --- a/logging/tests/unit/test_entries.py +++ b/logging/tests/unit/test_entries.py @@ -150,7 +150,9 @@ def test_from_api_repr_w_loggers_no_logger_match(self): 'labels': { 'module_id':'default', 'version': 'test' - }}) + } + } + ) STATUS = '500' API_REPR = { 'dummyPayload': PAYLOAD, diff --git a/logging/tests/unit/test_logger.py b/logging/tests/unit/test_logger.py index c1bf127d0bba..044421b50d60 100644 --- a/logging/tests/unit/test_logger.py +++ b/logging/tests/unit/test_logger.py @@ -339,7 +339,6 @@ def test_log_proto_w_default_labels(self): 'resource': { 'type': 'global', 'labels': {}, - }, 'labels': DEFAULT_LABELS, }] From bcf54f89ed468972ee90be751a7fe1a8f60cbb99 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 12:31:47 -0700 Subject: [PATCH 14/22] fix 3.6 --- logging/nox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logging/nox.py b/logging/nox.py index 2c8cbf1df1ab..371dcfb309b1 100644 --- a/logging/nox.py +++ b/logging/nox.py @@ -36,7 +36,7 @@ def unit_tests(session, python_version): # Run py.test against the unit tests. session.run( - 'py.test', '--quiet', + 'py.test', '-s', '--quiet', '--cov=google.cloud.logging', '--cov=tests.unit', '--cov-append', '--cov-config=.coveragerc', '--cov-report=', '--cov-fail-under=97', 'tests/unit', From 84ed6ac8a6ce4f49a21fe2ffc3c67df9343ac94e Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 12:33:29 -0700 Subject: [PATCH 15/22] dhermes --- logging/google/cloud/logging/entries.py | 4 ++-- nox.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/logging/google/cloud/logging/entries.py b/logging/google/cloud/logging/entries.py index 68857dd5ca43..7b058c8912f9 100644 --- a/logging/google/cloud/logging/entries.py +++ b/logging/google/cloud/logging/entries.py @@ -73,7 +73,7 @@ class _BaseEntry(object): :type http_request: dict :param http_request: (optional) info about HTTP request associated with the entry - :type resource :class:`google.cloud.logging.resource.Resource` + :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, @@ -182,7 +182,7 @@ class ProtobufEntry(_BaseEntry): :param http_request: (optional) info about HTTP request associated with the entry - :type resource :class:`google.cloud.logging.resource.Resource` + :type resource: :class:`~google.cloud.logging.resource.Resource` :param resource: (Optional) Monitored resource of the entry """ _PAYLOAD_KEY = 'protoPayload' diff --git a/nox.py b/nox.py index 0496492572f9..2aebf6276f92 100644 --- a/nox.py +++ b/nox.py @@ -22,7 +22,7 @@ def docs(session): """Build the docs.""" # Build docs against the latest version of Python, because we can. - session.interpreter = 'python3.6' + session.interpreter = 'python3.5' # Install Sphinx and also all of the google-cloud-* packages. session.chdir(os.path.realpath(os.path.dirname(__file__))) From 8df8c834217159e112c83e50051a7a0a09cde0f6 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 12:34:26 -0700 Subject: [PATCH 16/22] fix nox --- logging/nox.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logging/nox.py b/logging/nox.py index 371dcfb309b1..7f6447c56924 100644 --- a/logging/nox.py +++ b/logging/nox.py @@ -36,7 +36,7 @@ def unit_tests(session, python_version): # Run py.test against the unit tests. session.run( - 'py.test', '-s', '--quiet', + 'py.test', '--quiet', '--cov=google.cloud.logging', '--cov=tests.unit', '--cov-append', '--cov-config=.coveragerc', '--cov-report=', '--cov-fail-under=97', 'tests/unit', @@ -95,7 +95,7 @@ def cover(session): This outputs the coverage report aggregating coverage from the unit test runs (not system test runs), and then erases coverage data. """ - session.interpreter = 'python3.5' + session.interpreter = 'python3.6' session.install('coverage', 'pytest-cov') session.run('coverage', 'report', '--show-missing', '--fail-under=100') session.run('coverage', 'erase') From 4ba34a0640c8e7d68c9b5803ee6e285cd64e0d98 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 12:35:48 -0700 Subject: [PATCH 17/22] fix global nox --- nox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nox.py b/nox.py index 2aebf6276f92..0496492572f9 100644 --- a/nox.py +++ b/nox.py @@ -22,7 +22,7 @@ def docs(session): """Build the docs.""" # Build docs against the latest version of Python, because we can. - session.interpreter = 'python3.5' + session.interpreter = 'python3.6' # Install Sphinx and also all of the google-cloud-* packages. session.chdir(os.path.realpath(os.path.dirname(__file__))) From 4d1ee717da17a6fe0f326361300762ffae09e8cd Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 12:41:04 -0700 Subject: [PATCH 18/22] touchups --- logging/tests/unit/test_logger.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/logging/tests/unit/test_logger.py b/logging/tests/unit/test_logger.py index 044421b50d60..40d6e04b62bd 100644 --- a/logging/tests/unit/test_logger.py +++ b/logging/tests/unit/test_logger.py @@ -617,8 +617,12 @@ def test_log_struct_explicit(self): } TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999) RESOURCE = Resource( - type='gae_app', labels={'module_id': 'default', - 'version_id': 'test'}) + type='gae_app', + labels={ + 'module_id': 'default', + 'version_id': 'test' + } + ) client = _Client(project=self.PROJECT, connection=_make_credentials()) logger = _Logger() @@ -665,8 +669,12 @@ def test_log_proto_explicit(self): } TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999) RESOURCE = Resource( - type='gae_app', labels={'module_id': 'default', - 'version_id': 'test'}) + 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) @@ -695,8 +703,12 @@ def test_commit_w_resource_specified(self): 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'}) + type='gae_app', + labels={ + 'module_id': 'default', + 'version_id': 'test' + } + ) batch = self._make_one(logger, client, resource=RESOURCE) MESSAGE = 'This is the entry text' @@ -838,7 +850,7 @@ def test_context_mgr_success(self): 'resource': _GLOBAL_RESOURCE._to_dict()}, {'protoPayload': json.loads(MessageToJson(message)), 'resource': _GLOBAL_RESOURCE._to_dict(), - 'severity': SEVERITY} + 'severity': SEVERITY}, ] batch = self._make_one(logger, client=client) From 6bb5ddda4d9de3749a304f00ab8d489056263dd1 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 12:42:51 -0700 Subject: [PATCH 19/22] style touchups --- logging/tests/unit/test_logger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/logging/tests/unit/test_logger.py b/logging/tests/unit/test_logger.py index 40d6e04b62bd..5c184f7c3dec 100644 --- a/logging/tests/unit/test_logger.py +++ b/logging/tests/unit/test_logger.py @@ -620,7 +620,7 @@ def test_log_struct_explicit(self): type='gae_app', labels={ 'module_id': 'default', - 'version_id': 'test' + 'version_id': 'test', } ) @@ -672,7 +672,7 @@ def test_log_proto_explicit(self): type='gae_app', labels={ 'module_id': 'default', - 'version_id': 'test' + 'version_id': 'test', } ) client = _Client(project=self.PROJECT, connection=_make_credentials()) @@ -706,7 +706,7 @@ def test_commit_w_resource_specified(self): type='gae_app', labels={ 'module_id': 'default', - 'version_id': 'test' + 'version_id': 'test', } ) From a2a6be2e43170f4b342e8a5536819ef5a34b5d85 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 12:53:52 -0700 Subject: [PATCH 20/22] style fixup --- logging/tests/unit/test_entries.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/logging/tests/unit/test_entries.py b/logging/tests/unit/test_entries.py index 71cd9592c701..75cb641636a0 100644 --- a/logging/tests/unit/test_entries.py +++ b/logging/tests/unit/test_entries.py @@ -148,8 +148,8 @@ def test_from_api_repr_w_loggers_no_logger_match(self): labels={ 'type': 'gae_app', 'labels': { - 'module_id':'default', - 'version': 'test' + 'module_id': 'default', + 'version': 'test', } } ) @@ -166,7 +166,7 @@ def test_from_api_repr_w_loggers_no_logger_match(self): 'requestUrl': URI, 'status': STATUS, }, - 'resource': RESOURCE._to_dict() + 'resource': RESOURCE._to_dict(), } loggers = {} entry = klass.from_api_repr(API_REPR, client, loggers=loggers) From ef54bc17663668706467049f285cd72845b9cd26 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 14:56:07 -0700 Subject: [PATCH 21/22] dhermes comments --- logging/google/cloud/logging/entries.py | 3 ++- logging/google/cloud/logging/logger.py | 28 ++++++++++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/logging/google/cloud/logging/entries.py b/logging/google/cloud/logging/entries.py index 7b058c8912f9..24c8392eba14 100644 --- a/logging/google/cloud/logging/entries.py +++ b/logging/google/cloud/logging/entries.py @@ -72,7 +72,8 @@ 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 """ diff --git a/logging/google/cloud/logging/logger.py b/logging/google/cloud/logging/logger.py index ff7ae98e954f..d865a762c7d4 100644 --- a/logging/google/cloud/logging/logger.py +++ b/logging/google/cloud/logging/logger.py @@ -288,7 +288,8 @@ def log_proto(self, message, client=None, labels=None, insert_id=None, the entry. :type resource: :class:`~google.cloud.logging.resource.Resource` - :param resource: Monitored resource of the entry + :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. @@ -369,7 +370,12 @@ class Batch(object): :param client: The client to use. :type resource: :class:`~google.cloud.logging.resource.Resource` - :param resource: (Optional) Monitored resource of the batch + :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 and used + if an entry resource is explicitly set to None. """ def __init__(self, logger, client, resource=None): self.logger = logger @@ -408,7 +414,11 @@ def log_text(self, text, labels=None, insert_id=None, severity=None, :param timestamp: (optional) timestamp of event being logged. :type resource: :class:`~google.cloud.logging.resource.Resource` - :param resource: (Optional) Monitored resource of the entry + :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, @@ -439,7 +449,11 @@ def log_struct(self, info, labels=None, insert_id=None, severity=None, :param timestamp: (optional) timestamp of event being logged. :type resource: :class:`~google.cloud.logging.resource.Resource` - :param resource: (Optional) Monitored resource of the entry + :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, @@ -470,7 +484,11 @@ def log_proto(self, message, labels=None, insert_id=None, severity=None, :param timestamp: (optional) timestamp of event being logged. :type resource: :class:`~google.cloud.logging.resource.Resource` - :param resource: (Optional) Monitored resource of the entry + :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, From 09c4d9030afd4accc5745e4d924e13960b0709b0 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Wed, 10 May 2017 15:48:41 -0700 Subject: [PATCH 22/22] tweak docs --- logging/google/cloud/logging/logger.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/logging/google/cloud/logging/logger.py b/logging/google/cloud/logging/logger.py index d865a762c7d4..874d05014479 100644 --- a/logging/google/cloud/logging/logger.py +++ b/logging/google/cloud/logging/logger.py @@ -374,8 +374,9 @@ class Batch(object): 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 and used - if an entry resource is explicitly set to None. + 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, resource=None): self.logger = logger