Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions logging/google/cloud/logging/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,42 @@

"""Common logging helpers."""

import logging

import requests

from google.cloud.logging.entries import LogEntry
from google.cloud.logging.entries import ProtobufEntry
from google.cloud.logging.entries import StructEntry
from google.cloud.logging.entries import TextEntry

try:
from google.cloud.logging_v2.gapic.enums import LogSeverity
except ImportError: # pragma: NO COVER

class LogSeverity(object):
"""Map severities for non-GAPIC usage."""

DEFAULT = 0
DEBUG = 100
INFO = 200
NOTICE = 300
WARNING = 400
ERROR = 500
CRITICAL = 600
ALERT = 700
EMERGENCY = 800


_NORMALIZED_SEVERITIES = {
logging.CRITICAL: LogSeverity.CRITICAL,
logging.ERROR: LogSeverity.ERROR,
logging.WARNING: LogSeverity.WARNING,
logging.INFO: LogSeverity.INFO,
logging.DEBUG: LogSeverity.DEBUG,
logging.NOTSET: LogSeverity.DEFAULT,
}

METADATA_URL = "http://metadata.google.internal./computeMetadata/v1/"
METADATA_HEADERS = {"Metadata-Flavor": "Google"}

Expand Down Expand Up @@ -82,3 +111,15 @@ def retrieve_metadata_server(metadata_key):
pass

return None


def _normalize_severity(stdlib_level):
"""Normalize a Python stdlib severity to LogSeverity enum.

:type stdlib_level: int
:param stdlib_level: 'levelno' from a :class:`logging.LogRecord`

:rtype: int
:returns: Corresponding Stackdriver severity.
"""
return _NORMALIZED_SEVERITIES.get(stdlib_level, stdlib_level)
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

from six.moves import queue

from google.cloud.logging import _helpers
from google.cloud.logging.handlers.transports.base import Transport

_DEFAULT_GRACE_PERIOD = 5.0 # Seconds
Expand Down Expand Up @@ -254,17 +255,16 @@ def enqueue(
:param span_id: (optional) span_id within the trace for the log entry.
Specify the trace parameter if span_id is set.
"""
self._queue.put_nowait(
{
"info": {"message": message, "python_logger": record.name},
"severity": record.levelname,
"resource": resource,
"labels": labels,
"trace": trace,
"span_id": span_id,
"timestamp": datetime.datetime.utcfromtimestamp(record.created),
}
)
queue_entry = {
"info": {"message": message, "python_logger": record.name},
"severity": _helpers._normalize_severity(record.levelno),
"resource": resource,
"labels": labels,
"trace": trace,
"span_id": span_id,
"timestamp": datetime.datetime.utcfromtimestamp(record.created),
}
self._queue.put_nowait(queue_entry)

def flush(self):
"""Submit any pending log records."""
Expand Down
3 changes: 2 additions & 1 deletion logging/google/cloud/logging/handlers/transports/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
Logs directly to the the Stackdriver Logging API with a synchronous call.
"""

from google.cloud.logging import _helpers
from google.cloud.logging.handlers.transports.base import Transport


Expand Down Expand Up @@ -50,7 +51,7 @@ def send(
info = {"message": message, "python_logger": record.name}
self.logger.log_struct(
info,
severity=record.levelname,
severity=_helpers._normalize_severity(record.levelno),
resource=resource,
labels=labels,
trace=trace,
Expand Down
9 changes: 6 additions & 3 deletions logging/tests/system/test_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,16 @@ def setUp(self):
self._handlers_cache = logging.getLogger().handlers[:]

def tearDown(self):
retry = RetryErrors((NotFound, TooManyRequests, RetryError), max_tries=9)
retry_not_found = RetryErrors((NotFound), max_tries=4)
retry_other = RetryErrors((TooManyRequests, RetryError))
for doomed in self.to_delete:
try:
retry(doomed.delete)()
retry_not_found(retry_other(doomed.delete))()
except AttributeError:
client, dataset = doomed
retry(client.delete_dataset)(dataset)
retry_not_found(retry_other(client.delete_dataset))(dataset)
except NotFound:
pass
logging.getLogger().handlers = self._handlers_cache[:]

@staticmethod
Expand Down
59 changes: 55 additions & 4 deletions logging/tests/unit/handlers/transports/test_background_thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,62 @@ def test__main_thread_terminated_did_not_join(self):
self.assertFalse(worker.is_alive)

@staticmethod
def _enqueue_record(worker, message):
record = logging.LogRecord(
"python_logger", logging.INFO, None, None, message, None, None
def _enqueue_record(worker, message, levelno=logging.INFO, **kw):
record = logging.LogRecord("testing", levelno, None, None, message, None, None)
worker.enqueue(record, message, **kw)

def test_enqueue_defaults(self):
import datetime
from google.cloud.logging._helpers import LogSeverity

worker = self._make_one(_Logger(self.NAME))
self.assertTrue(worker._queue.empty())
message = "TEST SEVERITY"

self._enqueue_record(worker, message)

entry = worker._queue.get_nowait()
expected_info = {"message": message, "python_logger": "testing"}
self.assertEqual(entry["info"], expected_info)
self.assertEqual(entry["severity"], LogSeverity.INFO)
self.assertIsNone(entry["resource"])
self.assertIsNone(entry["labels"])
self.assertIsNone(entry["trace"])
self.assertIsNone(entry["span_id"])
self.assertIsInstance(entry["timestamp"], datetime.datetime)

def test_enqueue_explicit(self):
import datetime
from google.cloud.logging._helpers import LogSeverity

worker = self._make_one(_Logger(self.NAME))
self.assertTrue(worker._queue.empty())
message = "TEST SEVERITY"
resource = object()
labels = {"foo": "bar"}
trace = "TRACE"
span_id = "SPAN_ID"

self._enqueue_record(
worker,
message,
levelno=logging.ERROR,
resource=resource,
labels=labels,
trace=trace,
span_id=span_id,
)
worker.enqueue(record, message)

entry = worker._queue.get_nowait()

expected_info = {"message": message, "python_logger": "testing"}
self.assertEqual(entry["info"], expected_info)
self.assertEqual(entry["severity"], LogSeverity.ERROR)
self.assertIs(entry["resource"], resource)
self.assertIs(entry["labels"], labels)
self.assertIs(entry["trace"], trace)
self.assertIs(entry["span_id"], span_id)
self.assertIsInstance(entry["timestamp"], datetime.datetime)

def test__thread_main(self):
from google.cloud.logging.handlers.transports import background_thread
Expand Down
10 changes: 9 additions & 1 deletion logging/tests/unit/handlers/transports/test_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def test_ctor(self):

def test_send(self):
from google.cloud.logging.logger import _GLOBAL_RESOURCE
from google.cloud.logging._helpers import LogSeverity

client = _Client(self.PROJECT)

Expand All @@ -50,7 +51,14 @@ def test_send(self):

transport.send(record, message, _GLOBAL_RESOURCE)
EXPECTED_STRUCT = {"message": message, "python_logger": python_logger_name}
EXPECTED_SENT = (EXPECTED_STRUCT, "INFO", _GLOBAL_RESOURCE, None, None, None)
EXPECTED_SENT = (
EXPECTED_STRUCT,
LogSeverity.INFO,
_GLOBAL_RESOURCE,
None,
None,
None,
)
self.assertEqual(transport.logger.log_struct_called_with, EXPECTED_SENT)


Expand Down
42 changes: 42 additions & 0 deletions logging/tests/unit/test__helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.


import logging
import unittest

import mock
Expand Down Expand Up @@ -121,6 +122,47 @@ def test_request_exception(self):
self.assertIsNone(metadata)


class Test__normalize_severity(unittest.TestCase):
@staticmethod
def _stackdriver_severity():
from google.cloud.logging._helpers import LogSeverity

return LogSeverity

def _normalize_severity_helper(self, stdlib_level, enum_level):
from google.cloud.logging._helpers import _normalize_severity

self.assertEqual(_normalize_severity(stdlib_level), enum_level)

def test__normalize_severity_critical(self):
severity = self._stackdriver_severity()
self._normalize_severity_helper(logging.CRITICAL, severity.CRITICAL)

def test__normalize_severity_error(self):
severity = self._stackdriver_severity()
self._normalize_severity_helper(logging.ERROR, severity.ERROR)

def test__normalize_severity_warning(self):
severity = self._stackdriver_severity()
self._normalize_severity_helper(logging.WARNING, severity.WARNING)

def test__normalize_severity_info(self):
severity = self._stackdriver_severity()
self._normalize_severity_helper(logging.INFO, severity.INFO)

def test__normalize_severity_debug(self):
severity = self._stackdriver_severity()
self._normalize_severity_helper(logging.DEBUG, severity.DEBUG)

def test__normalize_severity_notset(self):
severity = self._stackdriver_severity()
self._normalize_severity_helper(logging.NOTSET, severity.DEFAULT)

def test__normalize_severity_non_standard(self):
unknown_level = 35
self._normalize_severity_helper(unknown_level, unknown_level)


class EntryMock(object):
def __init__(self):
self.sentinel = object()
Expand Down