Skip to content

Commit e638416

Browse files
authored
Map stdlib loglevels to Stackdriver severity enum values. (#8837)
Closes #7213.
1 parent f152c79 commit e638416

File tree

7 files changed

+166
-20
lines changed

7 files changed

+166
-20
lines changed

google/cloud/logging/_helpers.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,42 @@
1414

1515
"""Common logging helpers."""
1616

17+
import logging
18+
1719
import requests
1820

1921
from google.cloud.logging.entries import LogEntry
2022
from google.cloud.logging.entries import ProtobufEntry
2123
from google.cloud.logging.entries import StructEntry
2224
from google.cloud.logging.entries import TextEntry
2325

26+
try:
27+
from google.cloud.logging_v2.gapic.enums import LogSeverity
28+
except ImportError: # pragma: NO COVER
29+
30+
class LogSeverity(object):
31+
"""Map severities for non-GAPIC usage."""
32+
33+
DEFAULT = 0
34+
DEBUG = 100
35+
INFO = 200
36+
NOTICE = 300
37+
WARNING = 400
38+
ERROR = 500
39+
CRITICAL = 600
40+
ALERT = 700
41+
EMERGENCY = 800
42+
43+
44+
_NORMALIZED_SEVERITIES = {
45+
logging.CRITICAL: LogSeverity.CRITICAL,
46+
logging.ERROR: LogSeverity.ERROR,
47+
logging.WARNING: LogSeverity.WARNING,
48+
logging.INFO: LogSeverity.INFO,
49+
logging.DEBUG: LogSeverity.DEBUG,
50+
logging.NOTSET: LogSeverity.DEFAULT,
51+
}
52+
2453
METADATA_URL = "http://metadata.google.internal./computeMetadata/v1/"
2554
METADATA_HEADERS = {"Metadata-Flavor": "Google"}
2655

@@ -82,3 +111,15 @@ def retrieve_metadata_server(metadata_key):
82111
pass
83112

84113
return None
114+
115+
116+
def _normalize_severity(stdlib_level):
117+
"""Normalize a Python stdlib severity to LogSeverity enum.
118+
119+
:type stdlib_level: int
120+
:param stdlib_level: 'levelno' from a :class:`logging.LogRecord`
121+
122+
:rtype: int
123+
:returns: Corresponding Stackdriver severity.
124+
"""
125+
return _NORMALIZED_SEVERITIES.get(stdlib_level, stdlib_level)

google/cloud/logging/handlers/transports/background_thread.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
from six.moves import queue
3030

31+
from google.cloud.logging import _helpers
3132
from google.cloud.logging.handlers.transports.base import Transport
3233

3334
_DEFAULT_GRACE_PERIOD = 5.0 # Seconds
@@ -254,17 +255,16 @@ def enqueue(
254255
:param span_id: (optional) span_id within the trace for the log entry.
255256
Specify the trace parameter if span_id is set.
256257
"""
257-
self._queue.put_nowait(
258-
{
259-
"info": {"message": message, "python_logger": record.name},
260-
"severity": record.levelname,
261-
"resource": resource,
262-
"labels": labels,
263-
"trace": trace,
264-
"span_id": span_id,
265-
"timestamp": datetime.datetime.utcfromtimestamp(record.created),
266-
}
267-
)
258+
queue_entry = {
259+
"info": {"message": message, "python_logger": record.name},
260+
"severity": _helpers._normalize_severity(record.levelno),
261+
"resource": resource,
262+
"labels": labels,
263+
"trace": trace,
264+
"span_id": span_id,
265+
"timestamp": datetime.datetime.utcfromtimestamp(record.created),
266+
}
267+
self._queue.put_nowait(queue_entry)
268268

269269
def flush(self):
270270
"""Submit any pending log records."""

google/cloud/logging/handlers/transports/sync.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
Logs directly to the the Stackdriver Logging API with a synchronous call.
1818
"""
1919

20+
from google.cloud.logging import _helpers
2021
from google.cloud.logging.handlers.transports.base import Transport
2122

2223

@@ -50,7 +51,7 @@ def send(
5051
info = {"message": message, "python_logger": record.name}
5152
self.logger.log_struct(
5253
info,
53-
severity=record.levelname,
54+
severity=_helpers._normalize_severity(record.levelno),
5455
resource=resource,
5556
labels=labels,
5657
trace=trace,

tests/system/test_system.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,16 @@ def setUp(self):
105105
self._handlers_cache = logging.getLogger().handlers[:]
106106

107107
def tearDown(self):
108-
retry = RetryErrors((NotFound, TooManyRequests, RetryError), max_tries=9)
108+
retry_not_found = RetryErrors((NotFound), max_tries=4)
109+
retry_other = RetryErrors((TooManyRequests, RetryError))
109110
for doomed in self.to_delete:
110111
try:
111-
retry(doomed.delete)()
112+
retry_not_found(retry_other(doomed.delete))()
112113
except AttributeError:
113114
client, dataset = doomed
114-
retry(client.delete_dataset)(dataset)
115+
retry_not_found(retry_other(client.delete_dataset))(dataset)
116+
except NotFound:
117+
pass
115118
logging.getLogger().handlers = self._handlers_cache[:]
116119

117120
@staticmethod

tests/unit/handlers/transports/test_background_thread.py

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,62 @@ def test__main_thread_terminated_did_not_join(self):
264264
self.assertFalse(worker.is_alive)
265265

266266
@staticmethod
267-
def _enqueue_record(worker, message):
268-
record = logging.LogRecord(
269-
"python_logger", logging.INFO, None, None, message, None, None
267+
def _enqueue_record(worker, message, levelno=logging.INFO, **kw):
268+
record = logging.LogRecord("testing", levelno, None, None, message, None, None)
269+
worker.enqueue(record, message, **kw)
270+
271+
def test_enqueue_defaults(self):
272+
import datetime
273+
from google.cloud.logging._helpers import LogSeverity
274+
275+
worker = self._make_one(_Logger(self.NAME))
276+
self.assertTrue(worker._queue.empty())
277+
message = "TEST SEVERITY"
278+
279+
self._enqueue_record(worker, message)
280+
281+
entry = worker._queue.get_nowait()
282+
expected_info = {"message": message, "python_logger": "testing"}
283+
self.assertEqual(entry["info"], expected_info)
284+
self.assertEqual(entry["severity"], LogSeverity.INFO)
285+
self.assertIsNone(entry["resource"])
286+
self.assertIsNone(entry["labels"])
287+
self.assertIsNone(entry["trace"])
288+
self.assertIsNone(entry["span_id"])
289+
self.assertIsInstance(entry["timestamp"], datetime.datetime)
290+
291+
def test_enqueue_explicit(self):
292+
import datetime
293+
from google.cloud.logging._helpers import LogSeverity
294+
295+
worker = self._make_one(_Logger(self.NAME))
296+
self.assertTrue(worker._queue.empty())
297+
message = "TEST SEVERITY"
298+
resource = object()
299+
labels = {"foo": "bar"}
300+
trace = "TRACE"
301+
span_id = "SPAN_ID"
302+
303+
self._enqueue_record(
304+
worker,
305+
message,
306+
levelno=logging.ERROR,
307+
resource=resource,
308+
labels=labels,
309+
trace=trace,
310+
span_id=span_id,
270311
)
271-
worker.enqueue(record, message)
312+
313+
entry = worker._queue.get_nowait()
314+
315+
expected_info = {"message": message, "python_logger": "testing"}
316+
self.assertEqual(entry["info"], expected_info)
317+
self.assertEqual(entry["severity"], LogSeverity.ERROR)
318+
self.assertIs(entry["resource"], resource)
319+
self.assertIs(entry["labels"], labels)
320+
self.assertIs(entry["trace"], trace)
321+
self.assertIs(entry["span_id"], span_id)
322+
self.assertIsInstance(entry["timestamp"], datetime.datetime)
272323

273324
def test__thread_main(self):
274325
from google.cloud.logging.handlers.transports import background_thread

tests/unit/handlers/transports/test_sync.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def test_ctor(self):
3737

3838
def test_send(self):
3939
from google.cloud.logging.logger import _GLOBAL_RESOURCE
40+
from google.cloud.logging._helpers import LogSeverity
4041

4142
client = _Client(self.PROJECT)
4243

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

5152
transport.send(record, message, _GLOBAL_RESOURCE)
5253
EXPECTED_STRUCT = {"message": message, "python_logger": python_logger_name}
53-
EXPECTED_SENT = (EXPECTED_STRUCT, "INFO", _GLOBAL_RESOURCE, None, None, None)
54+
EXPECTED_SENT = (
55+
EXPECTED_STRUCT,
56+
LogSeverity.INFO,
57+
_GLOBAL_RESOURCE,
58+
None,
59+
None,
60+
None,
61+
)
5462
self.assertEqual(transport.logger.log_struct_called_with, EXPECTED_SENT)
5563

5664

tests/unit/test__helpers.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515

16+
import logging
1617
import unittest
1718

1819
import mock
@@ -121,6 +122,47 @@ def test_request_exception(self):
121122
self.assertIsNone(metadata)
122123

123124

125+
class Test__normalize_severity(unittest.TestCase):
126+
@staticmethod
127+
def _stackdriver_severity():
128+
from google.cloud.logging._helpers import LogSeverity
129+
130+
return LogSeverity
131+
132+
def _normalize_severity_helper(self, stdlib_level, enum_level):
133+
from google.cloud.logging._helpers import _normalize_severity
134+
135+
self.assertEqual(_normalize_severity(stdlib_level), enum_level)
136+
137+
def test__normalize_severity_critical(self):
138+
severity = self._stackdriver_severity()
139+
self._normalize_severity_helper(logging.CRITICAL, severity.CRITICAL)
140+
141+
def test__normalize_severity_error(self):
142+
severity = self._stackdriver_severity()
143+
self._normalize_severity_helper(logging.ERROR, severity.ERROR)
144+
145+
def test__normalize_severity_warning(self):
146+
severity = self._stackdriver_severity()
147+
self._normalize_severity_helper(logging.WARNING, severity.WARNING)
148+
149+
def test__normalize_severity_info(self):
150+
severity = self._stackdriver_severity()
151+
self._normalize_severity_helper(logging.INFO, severity.INFO)
152+
153+
def test__normalize_severity_debug(self):
154+
severity = self._stackdriver_severity()
155+
self._normalize_severity_helper(logging.DEBUG, severity.DEBUG)
156+
157+
def test__normalize_severity_notset(self):
158+
severity = self._stackdriver_severity()
159+
self._normalize_severity_helper(logging.NOTSET, severity.DEFAULT)
160+
161+
def test__normalize_severity_non_standard(self):
162+
unknown_level = 35
163+
self._normalize_severity_helper(unknown_level, unknown_level)
164+
165+
124166
class EntryMock(object):
125167
def __init__(self):
126168
self.sentinel = object()

0 commit comments

Comments
 (0)