Skip to content

Commit

Permalink
Merge pull request #1669 from tseaver/logging-support_entry_insertid_…
Browse files Browse the repository at this point in the history
…severity_httprequest

Add support for logging w/ per-request metadata.
  • Loading branch information
tseaver committed Mar 29, 2016
2 parents f1b9261 + 145cbfb commit 79c8e2c
Show file tree
Hide file tree
Showing 4 changed files with 320 additions and 56 deletions.
18 changes: 15 additions & 3 deletions gcloud/logging/entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,23 @@ class _BaseEntry(object):
:type labels: dict or :class:`NoneType`
:param labels: (optional) mapping of labels for the entry
:type severity: string or :class:`NoneType`
:param severity: (optional) severity of event being logged.
:type http_request: dict or :class:`NoneType`
:param http_request: (optional) info about HTTP request associated with
the entry
"""
def __init__(self, payload, logger,
insert_id=None, timestamp=None, labels=None):
def __init__(self, payload, logger, insert_id=None, timestamp=None,
labels=None, severity=None, http_request=None):
self.payload = payload
self.logger = logger
self.insert_id = insert_id
self.timestamp = timestamp
self.labels = labels
self.severity = severity
self.http_request = http_request

@classmethod
def from_api_repr(cls, resource, client, loggers=None):
Expand Down Expand Up @@ -82,7 +91,10 @@ def from_api_repr(cls, resource, client, loggers=None):
if timestamp is not None:
timestamp = _rfc3339_nanos_to_datetime(timestamp)
labels = resource.get('labels')
return cls(payload, logger, insert_id, timestamp, labels)
severity = resource.get('severity')
http_request = resource.get('httpRequest')
return cls(payload, logger, insert_id=insert_id, timestamp=timestamp,
labels=labels, severity=severity, http_request=http_request)


class TextEntry(_BaseEntry):
Expand Down
129 changes: 114 additions & 15 deletions gcloud/logging/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ def batch(self, client=None):
return Batch(self, client)

def _make_entry_resource(self, text=None, info=None, message=None,
labels=None):
labels=None, insert_id=None, severity=None,
http_request=None):
"""Return a log entry resource of the appropriate type.
Helper for :meth:`log_text`, :meth:`log_struct`, and :meth:`log_proto`.
Expand All @@ -107,6 +108,16 @@ def _make_entry_resource(self, text=None, info=None, message=None,
:type labels: dict or :class:`NoneType`
:param labels: labels passed in to calling method.
:type insert_id: string or :class:`NoneType`
:param insert_id: (optional) unique ID for log entry.
:type severity: string or :class:`NoneType`
:param severity: (optional) severity of event being logged.
:type http_request: dict or :class:`NoneType`
:param http_request: (optional) info about HTTP request associated with
the entry
"""
resource = {
'logName': self.full_name,
Expand All @@ -130,9 +141,19 @@ def _make_entry_resource(self, text=None, info=None, message=None,
if labels is not None:
resource['labels'] = labels

if insert_id is not None:
resource['insertId'] = insert_id

if severity is not None:
resource['severity'] = severity

if http_request is not None:
resource['httpRequest'] = http_request

return resource

def log_text(self, text, client=None, labels=None):
def log_text(self, text, client=None, labels=None, insert_id=None,
severity=None, http_request=None):
"""API call: log a text message via a POST request
See:
Expand All @@ -147,16 +168,28 @@ def log_text(self, text, client=None, labels=None):
:type labels: dict or :class:`NoneType`
:param labels: (optional) mapping of labels for the entry.
:type insert_id: string or :class:`NoneType`
:param insert_id: (optional) unique ID for log entry.
:type severity: string or :class:`NoneType`
:param severity: (optional) severity of event being logged.
:type http_request: dict or :class:`NoneType`
:param http_request: (optional) info about HTTP request associated with
the entry
"""
client = self._require_client(client)
entry_resource = self._make_entry_resource(text=text, labels=labels)

entry_resource = self._make_entry_resource(
text=text, labels=labels, insert_id=insert_id, severity=severity,
http_request=http_request)
data = {'entries': [entry_resource]}

client.connection.api_request(
method='POST', path='/entries:write', data=data)

def log_struct(self, info, client=None, labels=None):
def log_struct(self, info, client=None, labels=None, insert_id=None,
severity=None, http_request=None):
"""API call: log a structured message via a POST request
See:
Expand All @@ -171,15 +204,28 @@ def log_struct(self, info, client=None, labels=None):
:type labels: dict or :class:`NoneType`
:param labels: (optional) mapping of labels for the entry.
:type insert_id: string or :class:`NoneType`
:param insert_id: (optional) unique ID for log entry.
:type severity: string or :class:`NoneType`
:param severity: (optional) severity of event being logged.
:type http_request: dict or :class:`NoneType`
:param http_request: (optional) info about HTTP request associated with
the entry
"""
client = self._require_client(client)
entry_resource = self._make_entry_resource(info=info, labels=labels)
entry_resource = self._make_entry_resource(
info=info, labels=labels, insert_id=insert_id, severity=severity,
http_request=http_request)
data = {'entries': [entry_resource]}

client.connection.api_request(
method='POST', path='/entries:write', data=data)

def log_proto(self, message, client=None, labels=None):
def log_proto(self, message, client=None, labels=None, insert_id=None,
severity=None, http_request=None):
"""API call: log a protobuf message via a POST request
See:
Expand All @@ -194,10 +240,21 @@ def log_proto(self, message, client=None, labels=None):
:type labels: dict or :class:`NoneType`
:param labels: (optional) mapping of labels for the entry.
:type insert_id: string or :class:`NoneType`
:param insert_id: (optional) unique ID for log entry.
:type severity: string or :class:`NoneType`
:param severity: (optional) severity of event being logged.
:type http_request: dict or :class:`NoneType`
:param http_request: (optional) info about HTTP request associated with
the entry
"""
client = self._require_client(client)
entry_resource = self._make_entry_resource(
message=message, labels=labels)
message=message, labels=labels, insert_id=insert_id,
severity=severity, http_request=http_request)
data = {'entries': [entry_resource]}

client.connection.api_request(
Expand Down Expand Up @@ -283,38 +340,74 @@ def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.commit()

def log_text(self, text, labels=None):
def log_text(self, text, labels=None, insert_id=None, severity=None,
http_request=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.
:type insert_id: string or :class:`NoneType`
:param insert_id: (optional) unique ID for log entry.
:type severity: string or :class:`NoneType`
:param severity: (optional) severity of event being logged.
:type http_request: dict or :class:`NoneType`
:param http_request: (optional) info about HTTP request associated with
the entry.
"""
self.entries.append(('text', text, labels))
self.entries.append(
('text', text, labels, insert_id, severity, http_request))

def log_struct(self, info, labels=None):
def log_struct(self, info, labels=None, insert_id=None, severity=None,
http_request=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.
:type insert_id: string or :class:`NoneType`
:param insert_id: (optional) unique ID for log entry.
:type severity: string or :class:`NoneType`
:param severity: (optional) severity of event being logged.
:type http_request: dict or :class:`NoneType`
:param http_request: (optional) info about HTTP request associated with
the entry.
"""
self.entries.append(('struct', info, labels))
self.entries.append(
('struct', info, labels, insert_id, severity, http_request))

def log_proto(self, message, labels=None):
def log_proto(self, message, labels=None, insert_id=None, severity=None,
http_request=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.
:type insert_id: string or :class:`NoneType`
:param insert_id: (optional) unique ID for log entry.
:type severity: string or :class:`NoneType`
:param severity: (optional) severity of event being logged.
:type http_request: dict or :class:`NoneType`
:param http_request: (optional) info about HTTP request associated with
the entry.
"""
self.entries.append(('proto', message, labels))
self.entries.append(
('proto', message, labels, insert_id, severity, http_request))

def commit(self, client=None):
"""Send saved log entries as a single API call.
Expand All @@ -334,7 +427,7 @@ def commit(self, client=None):
data['labels'] = self.logger.labels

entries = data['entries'] = []
for entry_type, entry, labels in self.entries:
for entry_type, entry, labels, iid, severity, http_req in self.entries:
if entry_type == 'text':
info = {'textPayload': entry}
elif entry_type == 'struct':
Expand All @@ -347,6 +440,12 @@ def commit(self, client=None):
raise ValueError('Unknown entry type: %s' % (entry_type,))
if labels is not None:
info['labels'] = labels
if iid is not None:
info['insertId'] = iid
if severity is not None:
info['severity'] = severity
if http_req is not None:
info['httpRequest'] = http_req
entries.append(info)

client.connection.api_request(
Expand Down
40 changes: 38 additions & 2 deletions gcloud/logging/test_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,40 @@ def test_ctor_defaults(self):
self.assertTrue(entry.insert_id is None)
self.assertTrue(entry.timestamp is None)
self.assertTrue(entry.labels is None)
self.assertTrue(entry.severity is None)
self.assertTrue(entry.http_request is None)

def test_ctor_explicit(self):
import datetime
PAYLOAD = 'PAYLOAD'
IID = 'IID'
TIMESTAMP = datetime.datetime.now()
LABELS = {'foo': 'bar', 'baz': 'qux'}
SEVERITY = 'CRITICAL'
METHOD = 'POST'
URI = 'https://api.example.com/endpoint'
STATUS = '500'
REQUEST = {
'requestMethod': METHOD,
'requestUrl': URI,
'status': STATUS,
}
logger = _Logger(self.LOGGER_NAME, self.PROJECT)
entry = self._makeOne(PAYLOAD, logger, IID, TIMESTAMP, LABELS)
entry = self._makeOne(PAYLOAD, logger,
insert_id=IID,
timestamp=TIMESTAMP,
labels=LABELS,
severity=SEVERITY,
http_request=REQUEST)
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)
self.assertEqual(entry.severity, SEVERITY)
self.assertEqual(entry.http_request['requestMethod'], METHOD)
self.assertEqual(entry.http_request['requestUrl'], URI)
self.assertEqual(entry.http_request['status'], STATUS)

def test_from_api_repr_missing_data_no_loggers(self):
client = _Client(self.PROJECT)
Expand All @@ -68,6 +88,8 @@ def test_from_api_repr_missing_data_no_loggers(self):
self.assertEqual(entry.payload, PAYLOAD)
self.assertTrue(entry.insert_id is None)
self.assertTrue(entry.timestamp is None)
self.assertTrue(entry.severity is None)
self.assertTrue(entry.http_request is None)
logger = entry.logger
self.assertTrue(isinstance(logger, _Logger))
self.assertTrue(logger.client is client)
Expand All @@ -76,27 +98,41 @@ 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 gcloud._helpers import UTC
klass = self._getTargetClass()
client = _Client(self.PROJECT)
PAYLOAD = 'PAYLOAD'
SEVERITY = 'CRITICAL'
IID = 'IID'
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'}
METHOD = 'POST'
URI = 'https://api.example.com/endpoint'
STATUS = '500'
API_REPR = {
'dummyPayload': PAYLOAD,
'logName': LOG_NAME,
'insertId': IID,
'timestamp': TIMESTAMP,
'labels': LABELS,
'severity': SEVERITY,
'httpRequest': {
'requestMethod': METHOD,
'requestUrl': URI,
'status': STATUS,
},
}
loggers = {}
klass = self._getTargetClass()
entry = klass.from_api_repr(API_REPR, client, loggers=loggers)
self.assertEqual(entry.payload, PAYLOAD)
self.assertEqual(entry.insert_id, IID)
self.assertEqual(entry.timestamp, NOW)
self.assertEqual(entry.labels, LABELS)
self.assertEqual(entry.severity, SEVERITY)
self.assertEqual(entry.http_request['requestMethod'], METHOD)
self.assertEqual(entry.http_request['requestUrl'], URI)
self.assertEqual(entry.http_request['status'], STATUS)
logger = entry.logger
self.assertTrue(isinstance(logger, _Logger))
self.assertTrue(logger.client is client)
Expand Down
Loading

0 comments on commit 79c8e2c

Please sign in to comment.