diff --git a/gcloud/logging/entries.py b/gcloud/logging/entries.py index d94d7d984a1a..ca1cf62f5db4 100644 --- a/gcloud/logging/entries.py +++ b/gcloud/logging/entries.py @@ -37,12 +37,17 @@ class _BaseEntry(object): :type timestamp: :class:`datetime.datetime`, or :class:`NoneType` :param timestamp: (optional) timestamp for the entry + + :type labels: dict or :class:`NoneType` + :param labels: (optional) mapping of labels for the entry """ - def __init__(self, payload, logger, insert_id=None, timestamp=None): + def __init__(self, payload, logger, + insert_id=None, timestamp=None, labels=None): self.payload = payload self.logger = logger self.insert_id = insert_id self.timestamp = timestamp + self.labels = labels @classmethod def from_api_repr(cls, resource, client, loggers=None): @@ -76,7 +81,8 @@ def from_api_repr(cls, resource, client, loggers=None): timestamp = resource.get('timestamp') if timestamp is not None: timestamp = _rfc3339_nanos_to_datetime(timestamp) - return cls(payload, logger, insert_id, timestamp) + labels = resource.get('labels') + return cls(payload, logger, insert_id, timestamp, labels) class TextEntry(_BaseEntry): diff --git a/gcloud/logging/logger.py b/gcloud/logging/logger.py index 59c77d49f5e9..f9dc41ff6459 100644 --- a/gcloud/logging/logger.py +++ b/gcloud/logging/logger.py @@ -31,10 +31,15 @@ class Logger(object): :type client: :class:`gcloud.logging.client.Client` :param client: A client which holds credentials and project configuration for the logger (which requires a project). + + :type labels: dict or :class:`NoneType` + :param labels: (optional) mapping of default labels for entries written + via this logger. """ - def __init__(self, name, client): + def __init__(self, name, client, labels=None): self.name = name self._client = client + self.labels = labels @property def client(self): @@ -51,6 +56,11 @@ def full_name(self): """Fully-qualified name used in logging APIs""" return 'projects/%s/logs/%s' % (self.project, self.name) + @property + def path(self): + """URI path for use in logging APIs""" + return '/%s' % (self.full_name,) + def _require_client(self, client): """Check client or verify over-ride. @@ -78,7 +88,51 @@ def batch(self, client=None): client = self._require_client(client) return Batch(self, client) - def log_text(self, text, client=None): + def _make_entry_resource(self, text=None, info=None, message=None, + labels=None): + """Return a log entry resource of the appropriate type. + + Helper for :meth:`log_text`, :meth:`log_struct`, and :meth:`log_proto`. + + Only one of ``text``, ``info``, or ``message`` should be passed. + + :type text: string or :class:`NoneType` + :param text: text payload + + :type info: dict or :class:`NoneType` + :param info: struct payload + + :type message: Protobuf message or :class:`NoneType` + :param message: protobuf payload + + :type labels: dict or :class:`NoneType` + :param labels: labels passed in to calling method. + """ + resource = { + 'logName': self.full_name, + 'resource': {'type': 'global'}, + } + + if text is not None: + resource['textPayload'] = text + + if info is not None: + resource['jsonPayload'] = info + + if message is not None: + as_json_str = MessageToJson(message) + as_json = json.loads(as_json_str) + resource['protoPayload'] = as_json + + if labels is None: + labels = self.labels + + if labels is not None: + resource['labels'] = labels + + return resource + + def log_text(self, text, client=None, labels=None): """API call: log a text message via a POST request See: @@ -90,22 +144,19 @@ def log_text(self, text, client=None): :type client: :class:`gcloud.logging.client.Client` or ``NoneType`` :param client: the client to use. If not passed, falls back to the ``client`` stored on the current logger. + + :type labels: dict or :class:`NoneType` + :param labels: (optional) mapping of labels for the entry. """ client = self._require_client(client) + entry_resource = self._make_entry_resource(text=text, labels=labels) + + data = {'entries': [entry_resource]} - data = { - 'entries': [{ - 'logName': self.full_name, - 'textPayload': text, - 'resource': { - 'type': 'global', - }, - }], - } client.connection.api_request( method='POST', path='/entries:write', data=data) - def log_struct(self, info, client=None): + def log_struct(self, info, client=None, labels=None): """API call: log a structured message via a POST request See: @@ -117,22 +168,18 @@ def log_struct(self, info, client=None): :type client: :class:`gcloud.logging.client.Client` or ``NoneType`` :param client: the client to use. If not passed, falls back to the ``client`` stored on the current logger. + + :type labels: dict or :class:`NoneType` + :param labels: (optional) mapping of labels for the entry. """ client = self._require_client(client) + entry_resource = self._make_entry_resource(info=info, labels=labels) + data = {'entries': [entry_resource]} - data = { - 'entries': [{ - 'logName': self.full_name, - 'jsonPayload': info, - 'resource': { - 'type': 'global', - }, - }], - } client.connection.api_request( method='POST', path='/entries:write', data=data) - def log_proto(self, message, client=None): + def log_proto(self, message, client=None, labels=None): """API call: log a protobuf message via a POST request See: @@ -144,20 +191,15 @@ def log_proto(self, message, client=None): :type client: :class:`gcloud.logging.client.Client` or ``NoneType`` :param client: the client to use. If not passed, falls back to the ``client`` stored on the current logger. + + :type labels: dict or :class:`NoneType` + :param labels: (optional) mapping of labels for the entry. """ client = self._require_client(client) - as_json_str = MessageToJson(message) - as_json = json.loads(as_json_str) + entry_resource = self._make_entry_resource( + message=message, labels=labels) + data = {'entries': [entry_resource]} - data = { - 'entries': [{ - 'logName': self.full_name, - 'protoPayload': as_json, - 'resource': { - 'type': 'global', - }, - }], - } client.connection.api_request( method='POST', path='/entries:write', data=data) @@ -172,8 +214,7 @@ def delete(self, client=None): ``client`` stored on the current logger. """ client = self._require_client(client) - client.connection.api_request( - method='DELETE', path='/%s' % self.full_name) + client.connection.api_request(method='DELETE', path=self.path) def list_entries(self, projects=None, filter_=None, order_by=None, page_size=None, page_token=None): @@ -242,29 +283,38 @@ def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is None: self.commit() - def log_text(self, text): + def log_text(self, text, labels=None): """Add a text entry to be logged during :meth:`commit`. :type text: string :param text: the text entry + + :type labels: dict or :class:`NoneType` + :param labels: (optional) mapping of labels for the entry. """ - self.entries.append(('text', text)) + self.entries.append(('text', text, labels)) - def log_struct(self, info): + def log_struct(self, info, labels=None): """Add a struct entry to be logged during :meth:`commit`. :type info: dict :param info: the struct entry + + :type labels: dict or :class:`NoneType` + :param labels: (optional) mapping of labels for the entry. """ - self.entries.append(('struct', info)) + self.entries.append(('struct', info, labels)) - def log_proto(self, message): + def log_proto(self, message, labels=None): """Add a protobuf entry to be logged during :meth:`commit`. :type message: protobuf message :param message: the protobuf entry + + :type labels: dict or :class:`NoneType` + :param labels: (optional) mapping of labels for the entry. """ - self.entries.append(('proto', message)) + self.entries.append(('proto', message, labels)) def commit(self, client=None): """Send saved log entries as a single API call. @@ -275,22 +325,28 @@ def commit(self, client=None): """ if client is None: client = self.client + data = { 'logName': self.logger.path, 'resource': {'type': 'global'}, } + if self.logger.labels is not None: + data['labels'] = self.logger.labels + entries = data['entries'] = [] - for entry_type, entry in self.entries: + for entry_type, entry, labels in self.entries: if entry_type == 'text': info = {'textPayload': entry} elif entry_type == 'struct': - info = {'structPayload': entry} + info = {'jsonPayload': entry} elif entry_type == 'proto': as_json_str = MessageToJson(entry) as_json = json.loads(as_json_str) info = {'protoPayload': as_json} else: raise ValueError('Unknown entry type: %s' % (entry_type,)) + if labels is not None: + info['labels'] = labels entries.append(info) client.connection.api_request( diff --git a/gcloud/logging/test_entries.py b/gcloud/logging/test_entries.py index 4505c7655ff6..2da275d71ea0 100644 --- a/gcloud/logging/test_entries.py +++ b/gcloud/logging/test_entries.py @@ -39,18 +39,21 @@ def test_ctor_defaults(self): self.assertTrue(entry.logger is logger) self.assertTrue(entry.insert_id is None) self.assertTrue(entry.timestamp is None) + self.assertTrue(entry.labels is None) def test_ctor_explicit(self): import datetime PAYLOAD = 'PAYLOAD' IID = 'IID' TIMESTAMP = datetime.datetime.now() + LABELS = {'foo': 'bar', 'baz': 'qux'} logger = _Logger(self.LOGGER_NAME, self.PROJECT) - entry = self._makeOne(PAYLOAD, logger, IID, TIMESTAMP) + entry = self._makeOne(PAYLOAD, logger, IID, TIMESTAMP, LABELS) self.assertEqual(entry.payload, PAYLOAD) self.assertTrue(entry.logger is logger) self.assertEqual(entry.insert_id, IID) self.assertEqual(entry.timestamp, TIMESTAMP) + self.assertEqual(entry.labels, LABELS) def test_from_api_repr_missing_data_no_loggers(self): client = _Client(self.PROJECT) @@ -79,11 +82,13 @@ def test_from_api_repr_w_loggers_no_logger_match(self): NOW = datetime.utcnow().replace(tzinfo=UTC) TIMESTAMP = _datetime_to_rfc3339_w_nanos(NOW) LOG_NAME = 'projects/%s/logs/%s' % (self.PROJECT, self.LOGGER_NAME) + LABELS = {'foo': 'bar', 'baz': 'qux'} API_REPR = { 'dummyPayload': PAYLOAD, 'logName': LOG_NAME, 'insertId': IID, 'timestamp': TIMESTAMP, + 'labels': LABELS, } loggers = {} klass = self._getTargetClass() @@ -91,6 +96,7 @@ def test_from_api_repr_w_loggers_no_logger_match(self): self.assertEqual(entry.payload, PAYLOAD) self.assertEqual(entry.insert_id, IID) self.assertEqual(entry.timestamp, NOW) + self.assertEqual(entry.labels, LABELS) logger = entry.logger self.assertTrue(isinstance(logger, _Logger)) self.assertTrue(logger.client is client) @@ -106,11 +112,13 @@ def test_from_api_repr_w_loggers_w_logger_match(self): NOW = datetime.utcnow().replace(tzinfo=UTC) TIMESTAMP = _datetime_to_rfc3339_w_nanos(NOW) LOG_NAME = 'projects/%s/logs/%s' % (self.PROJECT, self.LOGGER_NAME) + LABELS = {'foo': 'bar', 'baz': 'qux'} API_REPR = { 'dummyPayload': PAYLOAD, 'logName': LOG_NAME, 'insertId': IID, 'timestamp': TIMESTAMP, + 'labels': LABELS, } LOGGER = object() loggers = {LOG_NAME: LOGGER} @@ -119,6 +127,7 @@ def test_from_api_repr_w_loggers_w_logger_match(self): self.assertEqual(entry.payload, PAYLOAD) self.assertEqual(entry.insert_id, IID) self.assertEqual(entry.timestamp, NOW) + self.assertEqual(entry.labels, LABELS) self.assertTrue(entry.logger is LOGGER) diff --git a/gcloud/logging/test_logger.py b/gcloud/logging/test_logger.py index a155ce693fa9..ad698de504f9 100644 --- a/gcloud/logging/test_logger.py +++ b/gcloud/logging/test_logger.py @@ -27,7 +27,7 @@ def _getTargetClass(self): def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) - def test_ctor(self): + def test_ctor_defaults(self): conn = _Connection() client = _Client(self.PROJECT, conn) logger = self._makeOne(self.LOGGER_NAME, client=client) @@ -36,6 +36,23 @@ def test_ctor(self): self.assertEqual(logger.project, self.PROJECT) self.assertEqual(logger.full_name, 'projects/%s/logs/%s' % (self.PROJECT, self.LOGGER_NAME)) + self.assertEqual(logger.path, '/projects/%s/logs/%s' + % (self.PROJECT, self.LOGGER_NAME)) + self.assertEqual(logger.labels, None) + + def test_ctor_explicit(self): + LABELS = {'foo': 'bar', 'baz': 'qux'} + conn = _Connection() + client = _Client(self.PROJECT, conn) + logger = self._makeOne(self.LOGGER_NAME, client=client, labels=LABELS) + self.assertEqual(logger.name, self.LOGGER_NAME) + self.assertTrue(logger.client is client) + self.assertEqual(logger.project, self.PROJECT) + self.assertEqual(logger.full_name, 'projects/%s/logs/%s' + % (self.PROJECT, self.LOGGER_NAME)) + self.assertEqual(logger.path, '/projects/%s/logs/%s' + % (self.PROJECT, self.LOGGER_NAME)) + self.assertEqual(logger.labels, LABELS) def test_batch_w_bound_client(self): from gcloud.logging.logger import Batch @@ -81,13 +98,41 @@ def test_log_text_w_str_implicit_client(self): self.assertEqual(req['path'], '/entries:write') self.assertEqual(req['data'], SENT) - def test_log_text_w_unicode_explicit_client(self): + def test_log_text_w_default_labels(self): + TEXT = 'TEXT' + DEFAULT_LABELS = {'foo': 'spam'} + conn = _Connection({}) + client = _Client(self.PROJECT, conn) + logger = self._makeOne(self.LOGGER_NAME, client=client, + labels=DEFAULT_LABELS) + logger.log_text(TEXT) + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + SENT = { + 'entries': [{ + 'logName': 'projects/%s/logs/%s' % ( + self.PROJECT, self.LOGGER_NAME), + 'textPayload': TEXT, + 'resource': { + 'type': 'global', + }, + 'labels': DEFAULT_LABELS, + }], + } + self.assertEqual(req['method'], 'POST') + self.assertEqual(req['path'], '/entries:write') + self.assertEqual(req['data'], SENT) + + def test_log_text_w_unicode_explicit_client_and_labels(self): TEXT = u'TEXT' + DEFAULT_LABELS = {'foo': 'spam'} + LABELS = {'foo': 'bar', 'baz': 'qux'} conn = _Connection({}) client1 = _Client(self.PROJECT, object()) client2 = _Client(self.PROJECT, conn) - logger = self._makeOne(self.LOGGER_NAME, client=client1) - logger.log_text(TEXT, client=client2) + logger = self._makeOne(self.LOGGER_NAME, client=client1, + labels=DEFAULT_LABELS) + logger.log_text(TEXT, client=client2, labels=LABELS) self.assertEqual(len(conn._requested), 1) req = conn._requested[0] SENT = { @@ -98,6 +143,7 @@ def test_log_text_w_unicode_explicit_client(self): 'resource': { 'type': 'global', }, + 'labels': LABELS, }], } self.assertEqual(req['method'], 'POST') @@ -126,13 +172,41 @@ def test_log_struct_w_implicit_client(self): self.assertEqual(req['path'], '/entries:write') self.assertEqual(req['data'], SENT) - def test_log_struct_w_explicit_client(self): + def test_log_struct_w_default_labels(self): + STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'} + DEFAULT_LABELS = {'foo': 'spam'} + conn = _Connection({}) + client = _Client(self.PROJECT, conn) + logger = self._makeOne(self.LOGGER_NAME, client=client, + labels=DEFAULT_LABELS) + logger.log_struct(STRUCT) + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + SENT = { + 'entries': [{ + 'logName': 'projects/%s/logs/%s' % ( + self.PROJECT, self.LOGGER_NAME), + 'jsonPayload': STRUCT, + 'resource': { + 'type': 'global', + }, + 'labels': DEFAULT_LABELS, + }], + } + self.assertEqual(req['method'], 'POST') + self.assertEqual(req['path'], '/entries:write') + self.assertEqual(req['data'], SENT) + + def test_log_struct_w_explicit_client_and_labels(self): STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'} + DEFAULT_LABELS = {'foo': 'spam'} + LABELS = {'foo': 'bar', 'baz': 'qux'} conn = _Connection({}) client1 = _Client(self.PROJECT, object()) client2 = _Client(self.PROJECT, conn) - logger = self._makeOne(self.LOGGER_NAME, client=client1) - logger.log_struct(STRUCT, client=client2) + logger = self._makeOne(self.LOGGER_NAME, client=client1, + labels=DEFAULT_LABELS) + logger.log_struct(STRUCT, client=client2, labels=LABELS) self.assertEqual(len(conn._requested), 1) req = conn._requested[0] SENT = { @@ -143,6 +217,7 @@ def test_log_struct_w_explicit_client(self): 'resource': { 'type': 'global', }, + 'labels': LABELS, }], } self.assertEqual(req['method'], 'POST') @@ -174,16 +249,47 @@ def test_log_proto_w_implicit_client(self): self.assertEqual(req['path'], '/entries:write') self.assertEqual(req['data'], SENT) - def test_log_proto_w_explicit_client(self): + def test_log_proto_w_default_labels(self): import json from google.protobuf.json_format import MessageToJson from google.protobuf.struct_pb2 import Struct, Value message = Struct(fields={'foo': Value(bool_value=True)}) + DEFAULT_LABELS = {'foo': 'spam'} + conn = _Connection({}) + client = _Client(self.PROJECT, conn) + logger = self._makeOne(self.LOGGER_NAME, client=client, + labels=DEFAULT_LABELS) + logger.log_proto(message) + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + SENT = { + 'entries': [{ + 'logName': 'projects/%s/logs/%s' % ( + self.PROJECT, self.LOGGER_NAME), + 'protoPayload': json.loads(MessageToJson(message)), + 'resource': { + 'type': 'global', + }, + 'labels': DEFAULT_LABELS, + }], + } + self.assertEqual(req['method'], 'POST') + self.assertEqual(req['path'], '/entries:write') + self.assertEqual(req['data'], SENT) + + def test_log_proto_w_explicit_client_and_labels(self): + import json + from google.protobuf.json_format import MessageToJson + from google.protobuf.struct_pb2 import Struct, Value + message = Struct(fields={'foo': Value(bool_value=True)}) + DEFAULT_LABELS = {'foo': 'spam'} + LABELS = {'foo': 'bar', 'baz': 'qux'} conn = _Connection({}) client1 = _Client(self.PROJECT, object()) client2 = _Client(self.PROJECT, conn) - logger = self._makeOne(self.LOGGER_NAME, client=client1) - logger.log_proto(message, client=client2) + logger = self._makeOne(self.LOGGER_NAME, client=client1, + labels=DEFAULT_LABELS) + logger.log_proto(message, client=client2, labels=LABELS) self.assertEqual(len(conn._requested), 1) req = conn._requested[0] SENT = { @@ -194,6 +300,7 @@ def test_log_proto_w_explicit_client(self): 'resource': { 'type': 'global', }, + 'labels': LABELS, }], } self.assertEqual(req['method'], 'POST') @@ -287,7 +394,7 @@ def test_ctor_defaults(self): self.assertTrue(batch.client is CLIENT) self.assertEqual(len(batch.entries), 0) - def test_log_text(self): + def test_log_text_defaults(self): TEXT = 'This is the entry text' connection = _Connection() CLIENT = _Client(project=self.PROJECT, connection=connection) @@ -295,9 +402,20 @@ def test_log_text(self): batch = self._makeOne(logger, client=CLIENT) batch.log_text(TEXT) self.assertEqual(len(connection._requested), 0) - self.assertEqual(batch.entries, [('text', TEXT)]) + self.assertEqual(batch.entries, [('text', TEXT, None)]) - def test_log_struct(self): + def test_log_text_explicit(self): + TEXT = 'This is the entry text' + LABELS = {'foo': 'bar', 'baz': 'qux'} + connection = _Connection() + CLIENT = _Client(project=self.PROJECT, connection=connection) + logger = _Logger() + batch = self._makeOne(logger, client=CLIENT) + batch.log_text(TEXT, labels=LABELS) + self.assertEqual(len(connection._requested), 0) + self.assertEqual(batch.entries, [('text', TEXT, LABELS)]) + + def test_log_struct_defaults(self): STRUCT = {'message': 'Message text', 'weather': 'partly cloudy'} connection = _Connection() CLIENT = _Client(project=self.PROJECT, connection=connection) @@ -305,9 +423,20 @@ def test_log_struct(self): batch = self._makeOne(logger, client=CLIENT) batch.log_struct(STRUCT) self.assertEqual(len(connection._requested), 0) - self.assertEqual(batch.entries, [('struct', STRUCT)]) + self.assertEqual(batch.entries, [('struct', STRUCT, None)]) - def test_log_proto(self): + def test_log_struct_explicit(self): + STRUCT = {'message': 'Message text', 'weather': 'partly cloudy'} + LABELS = {'foo': 'bar', 'baz': 'qux'} + connection = _Connection() + CLIENT = _Client(project=self.PROJECT, connection=connection) + logger = _Logger() + batch = self._makeOne(logger, client=CLIENT) + batch.log_struct(STRUCT, labels=LABELS) + self.assertEqual(len(connection._requested), 0) + self.assertEqual(batch.entries, [('struct', STRUCT, LABELS)]) + + def test_log_proto_defaults(self): from google.protobuf.struct_pb2 import Struct, Value message = Struct(fields={'foo': Value(bool_value=True)}) connection = _Connection() @@ -316,14 +445,26 @@ def test_log_proto(self): batch = self._makeOne(logger, client=CLIENT) batch.log_proto(message) self.assertEqual(len(connection._requested), 0) - self.assertEqual(batch.entries, [('proto', message)]) + self.assertEqual(batch.entries, [('proto', message, None)]) + + def test_log_proto_explicit(self): + from google.protobuf.struct_pb2 import Struct, Value + message = Struct(fields={'foo': Value(bool_value=True)}) + LABELS = {'foo': 'bar', 'baz': 'qux'} + connection = _Connection() + CLIENT = _Client(project=self.PROJECT, connection=connection) + logger = _Logger() + batch = self._makeOne(logger, client=CLIENT) + batch.log_proto(message, labels=LABELS) + self.assertEqual(len(connection._requested), 0) + self.assertEqual(batch.entries, [('proto', message, LABELS)]) def test_commit_w_invalid_entry_type(self): logger = _Logger() conn = _Connection() CLIENT = _Client(project=self.PROJECT, connection=conn) batch = self._makeOne(logger, CLIENT) - batch.entries.append(('bogus', 'BOGUS')) + batch.entries.append(('bogus', 'BOGUS', None)) with self.assertRaises(ValueError): batch.commit() @@ -344,7 +485,7 @@ def test_commit_w_bound_client(self): }, 'entries': [ {'textPayload': TEXT}, - {'structPayload': STRUCT}, + {'jsonPayload': STRUCT}, {'protoPayload': json.loads(MessageToJson(message))}, ], } @@ -364,25 +505,29 @@ def test_commit_w_alternate_client(self): import json from google.protobuf.json_format import MessageToJson from google.protobuf.struct_pb2 import Struct, Value + from gcloud.logging.logger import Logger TEXT = 'This is the entry text' STRUCT = {'message': TEXT, 'weather': 'partly cloudy'} message = Struct(fields={'foo': Value(bool_value=True)}) + DEFAULT_LABELS = {'foo': 'spam'} + LABELS = {'foo': 'bar', 'baz': 'qux'} conn1 = _Connection() conn2 = _Connection({}) CLIENT1 = _Client(project=self.PROJECT, connection=conn1) CLIENT2 = _Client(project=self.PROJECT, connection=conn2) - logger = _Logger() + logger = Logger('logger_name', CLIENT1, labels=DEFAULT_LABELS) SENT = { 'logName': logger.path, 'resource': {'type': 'global'}, + 'labels': DEFAULT_LABELS, 'entries': [ - {'textPayload': TEXT}, - {'structPayload': STRUCT}, + {'textPayload': TEXT, 'labels': LABELS}, + {'jsonPayload': STRUCT}, {'protoPayload': json.loads(MessageToJson(message))}, ], } batch = self._makeOne(logger, client=CLIENT1) - batch.log_text(TEXT) + batch.log_text(TEXT, labels=LABELS) batch.log_struct(STRUCT) batch.log_proto(message) batch.commit(client=CLIENT2) @@ -398,20 +543,24 @@ def test_context_mgr_success(self): import json from google.protobuf.json_format import MessageToJson from google.protobuf.struct_pb2 import Struct, Value + from gcloud.logging.logger import Logger TEXT = 'This is the entry text' STRUCT = {'message': TEXT, 'weather': 'partly cloudy'} message = Struct(fields={'foo': Value(bool_value=True)}) + DEFAULT_LABELS = {'foo': 'spam'} + LABELS = {'foo': 'bar', 'baz': 'qux'} conn = _Connection({}) CLIENT = _Client(project=self.PROJECT, connection=conn) - logger = _Logger() + logger = Logger('logger_name', CLIENT, labels=DEFAULT_LABELS) SENT = { 'logName': logger.path, 'resource': { 'type': 'global', }, + 'labels': DEFAULT_LABELS, 'entries': [ {'textPayload': TEXT}, - {'structPayload': STRUCT}, + {'jsonPayload': STRUCT, 'labels': LABELS}, {'protoPayload': json.loads(MessageToJson(message))}, ], } @@ -419,7 +568,7 @@ def test_context_mgr_success(self): with batch as other: other.log_text(TEXT) - other.log_struct(STRUCT) + other.log_struct(STRUCT, labels=LABELS) other.log_proto(message) self.assertEqual(list(batch.entries), []) @@ -433,18 +582,23 @@ def test_context_mgr_failure(self): from google.protobuf.struct_pb2 import Struct, Value TEXT = 'This is the entry text' STRUCT = {'message': TEXT, 'weather': 'partly cloudy'} + LABELS = {'foo': 'bar', 'baz': 'qux'} message = Struct(fields={'foo': Value(bool_value=True)}) conn = _Connection({}) CLIENT = _Client(project=self.PROJECT, connection=conn) logger = _Logger() - UNSENT = [('text', TEXT), ('struct', STRUCT), ('proto', message)] + UNSENT = [ + ('text', TEXT, None), + ('struct', STRUCT, None), + ('proto', message, LABELS), + ] batch = self._makeOne(logger, client=CLIENT) try: with batch as other: other.log_text(TEXT) other.log_struct(STRUCT) - other.log_proto(message) + other.log_proto(message, labels=LABELS) raise _Bugout() except _Bugout: pass @@ -455,6 +609,8 @@ def test_context_mgr_failure(self): class _Logger(object): + labels = None + def __init__(self, name="NAME", project="PROJECT"): self.path = '/projects/%s/logs/%s' % (project, name)