Skip to content

Commit 1be8d9a

Browse files
committed
.
1 parent f992680 commit 1be8d9a

File tree

4 files changed

+222
-218
lines changed

4 files changed

+222
-218
lines changed

sentry_sdk/integrations/logging.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -364,10 +364,7 @@ def _capture_log_from_record(self, client, record):
364364
for i, arg in enumerate(record.args):
365365
attrs[f"sentry.message.parameter.{i}"] = (
366366
arg
367-
if isinstance(arg, str)
368-
or isinstance(arg, float)
369-
or isinstance(arg, int)
370-
or isinstance(arg, bool)
367+
if isinstance(arg, (str, float, int, bool))
371368
else safe_repr(arg)
372369
)
373370
if record.lineno:

sentry_sdk/integrations/loguru.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,23 @@ def setup_once():
8787
)
8888

8989
if LoguruIntegration.sentry_logs_level is not None:
90-
logger.add(SentryLogsHandler(level=LoguruIntegration.sentry_logs_level))
90+
logger.add(
91+
LoguruSentryLogsHandler(level=LoguruIntegration.sentry_logs_level),
92+
level=LoguruIntegration.sentry_logs_level,
93+
format=LoguruIntegration.event_format,
94+
)
9195

9296

9397
class _LoguruBaseHandler(_BaseHandler):
98+
def __init__(self, *args, **kwargs):
99+
# type: (*Any, **Any) -> None
100+
if kwargs.get("level"):
101+
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
102+
kwargs.get("level", ""), DEFAULT_LEVEL
103+
)
104+
105+
super().__init__(*args, **kwargs)
106+
94107
def _logging_to_event_level(self, record):
95108
# type: (LogRecord) -> str
96109
try:
@@ -103,25 +116,16 @@ def _logging_to_event_level(self, record):
103116

104117
class LoguruEventHandler(_LoguruBaseHandler, EventHandler):
105118
"""Modified version of :class:`sentry_sdk.integrations.logging.EventHandler` to use loguru's level names."""
106-
107-
def __init__(self, *args, **kwargs):
108-
# type: (*Any, **Any) -> None
109-
if kwargs.get("level"):
110-
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
111-
kwargs.get("level", ""), DEFAULT_LEVEL
112-
)
113-
114-
super().__init__(*args, **kwargs)
119+
pass
115120

116121

117122
class LoguruBreadcrumbHandler(_LoguruBaseHandler, BreadcrumbHandler):
118123
"""Modified version of :class:`sentry_sdk.integrations.logging.BreadcrumbHandler` to use loguru's level names."""
124+
pass
119125

120-
def __init__(self, *args, **kwargs):
121-
# type: (*Any, **Any) -> None
122-
if kwargs.get("level"):
123-
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
124-
kwargs.get("level", ""), DEFAULT_LEVEL
125-
)
126126

127-
super().__init__(*args, **kwargs)
127+
class LoguruSentryLogsHandler(_LoguruBaseHandler, SentryLogsHandler):
128+
"""Modified version of :class:`sentry_sdk.integrations.logging.SentryLogsHandler` to use loguru's level names."""
129+
def _capture_log_from_record(self, client, record):
130+
# type: (BaseClient, LogRecord)
131+
return super()._capture_log_from_record(client, record)

tests/integrations/logging/test_logging.py

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33

44
import pytest
55

6+
from sentry_sdk import get_client
7+
from sentry_sdk.consts import VERSION
68
from sentry_sdk.integrations.logging import LoggingIntegration, ignore_logger
9+
from tests.test_logs import envelopes_to_logs
710

811
other_logger = logging.getLogger("testfoo")
912
logger = logging.getLogger(__name__)
@@ -283,3 +286,200 @@ def test_logging_dictionary_args(sentry_init, capture_events):
283286
== "the value of foo is bar, and the value of bar is baz"
284287
)
285288
assert event["logentry"]["params"] == {"foo": "bar", "bar": "baz"}
289+
290+
291+
@minimum_python_37
292+
def test_sentry_logs_warning(sentry_init, capture_envelopes):
293+
"""
294+
The python logger module should create 'warn' sentry logs if the flag is on.
295+
"""
296+
sentry_init(_experiments={"enable_logs": True})
297+
envelopes = capture_envelopes()
298+
299+
python_logger = logging.Logger("test-logger")
300+
python_logger.warning("this is %s a template %s", "1", "2")
301+
302+
get_client().flush()
303+
logs = envelopes_to_logs(envelopes)
304+
attrs = logs[0]["attributes"]
305+
assert attrs["sentry.message.template"] == "this is %s a template %s"
306+
assert "code.file.path" in attrs
307+
assert "code.line.number" in attrs
308+
assert attrs["logger.name"] == "test-logger"
309+
assert attrs["sentry.environment"] == "production"
310+
assert attrs["sentry.message.parameter.0"] == "1"
311+
assert attrs["sentry.message.parameter.1"] == "2"
312+
assert attrs["sentry.origin"] == "auto.logger.log"
313+
assert logs[0]["severity_number"] == 13
314+
assert logs[0]["severity_text"] == "warn"
315+
316+
317+
@minimum_python_37
318+
def test_sentry_logs_debug(sentry_init, capture_envelopes):
319+
"""
320+
The python logger module should not create 'debug' sentry logs if the flag is on by default
321+
"""
322+
sentry_init(_experiments={"enable_logs": True})
323+
envelopes = capture_envelopes()
324+
325+
python_logger = logging.Logger("test-logger")
326+
python_logger.debug("this is %s a template %s", "1", "2")
327+
get_client().flush()
328+
329+
assert len(envelopes) == 0
330+
331+
332+
@minimum_python_37
333+
def test_no_log_infinite_loop(sentry_init, capture_envelopes):
334+
"""
335+
If 'debug' mode is true, and you set a low log level in the logging integration, there should be no infinite loops.
336+
"""
337+
sentry_init(
338+
_experiments={"enable_logs": True},
339+
integrations=[LoggingIntegration(sentry_logs_level=logging.DEBUG)],
340+
debug=True,
341+
)
342+
envelopes = capture_envelopes()
343+
344+
python_logger = logging.Logger("test-logger")
345+
python_logger.debug("this is %s a template %s", "1", "2")
346+
get_client().flush()
347+
348+
assert len(envelopes) == 1
349+
350+
351+
@minimum_python_37
352+
def test_logging_errors(sentry_init, capture_envelopes):
353+
"""
354+
The python logger module should be able to log errors without erroring
355+
"""
356+
sentry_init(_experiments={"enable_logs": True})
357+
envelopes = capture_envelopes()
358+
359+
python_logger = logging.Logger("test-logger")
360+
python_logger.error(Exception("test exc 1"))
361+
python_logger.error("error is %s", Exception("test exc 2"))
362+
get_client().flush()
363+
364+
error_event_1 = envelopes[0].items[0].payload.json
365+
assert error_event_1["level"] == "error"
366+
error_event_2 = envelopes[1].items[0].payload.json
367+
assert error_event_2["level"] == "error"
368+
369+
logs = envelopes_to_logs(envelopes)
370+
assert logs[0]["severity_text"] == "error"
371+
assert "sentry.message.template" not in logs[0]["attributes"]
372+
assert "sentry.message.parameter.0" not in logs[0]["attributes"]
373+
assert "code.line.number" in logs[0]["attributes"]
374+
375+
assert logs[1]["severity_text"] == "error"
376+
assert logs[1]["attributes"]["sentry.message.template"] == "error is %s"
377+
assert (
378+
logs[1]["attributes"]["sentry.message.parameter.0"] == "Exception('test exc 2')"
379+
)
380+
assert "code.line.number" in logs[1]["attributes"]
381+
382+
assert len(logs) == 2
383+
384+
385+
def test_log_strips_project_root(sentry_init, capture_envelopes):
386+
"""
387+
The python logger should strip project roots from the log record path
388+
"""
389+
sentry_init(
390+
_experiments={"enable_logs": True},
391+
project_root="/custom/test",
392+
)
393+
envelopes = capture_envelopes()
394+
395+
python_logger = logging.Logger("test-logger")
396+
python_logger.handle(
397+
logging.LogRecord(
398+
name="test-logger",
399+
level=logging.WARN,
400+
pathname="/custom/test/blah/path.py",
401+
lineno=123,
402+
msg="This is a test log with a custom pathname",
403+
args=(),
404+
exc_info=None,
405+
)
406+
)
407+
get_client().flush()
408+
409+
logs = envelopes_to_logs(envelopes)
410+
assert len(logs) == 1
411+
attrs = logs[0]["attributes"]
412+
assert attrs["code.file.path"] == "blah/path.py"
413+
414+
415+
def test_logger_with_all_attributes(sentry_init, capture_envelopes):
416+
"""
417+
The python logger should be able to log all attributes, including extra data.
418+
"""
419+
sentry_init(_experiments={"enable_logs": True})
420+
envelopes = capture_envelopes()
421+
422+
python_logger = logging.Logger("test-logger")
423+
python_logger.warning(
424+
"log #%d",
425+
1,
426+
extra={"foo": "bar", "numeric": 42, "more_complex": {"nested": "data"}},
427+
)
428+
get_client().flush()
429+
430+
logs = envelopes_to_logs(envelopes)
431+
432+
attributes = logs[0]["attributes"]
433+
434+
assert "process.pid" in attributes
435+
assert isinstance(attributes["process.pid"], int)
436+
del attributes["process.pid"]
437+
438+
assert "sentry.release" in attributes
439+
assert isinstance(attributes["sentry.release"], str)
440+
del attributes["sentry.release"]
441+
442+
assert "server.address" in attributes
443+
assert isinstance(attributes["server.address"], str)
444+
del attributes["server.address"]
445+
446+
assert "thread.id" in attributes
447+
assert isinstance(attributes["thread.id"], int)
448+
del attributes["thread.id"]
449+
450+
assert "code.file.path" in attributes
451+
assert isinstance(attributes["code.file.path"], str)
452+
del attributes["code.file.path"]
453+
454+
assert "code.function.name" in attributes
455+
assert isinstance(attributes["code.function.name"], str)
456+
del attributes["code.function.name"]
457+
458+
assert "code.line.number" in attributes
459+
assert isinstance(attributes["code.line.number"], int)
460+
del attributes["code.line.number"]
461+
462+
assert "process.executable.name" in attributes
463+
assert isinstance(attributes["process.executable.name"], str)
464+
del attributes["process.executable.name"]
465+
466+
assert "thread.name" in attributes
467+
assert isinstance(attributes["thread.name"], str)
468+
del attributes["thread.name"]
469+
470+
assert attributes.pop("sentry.sdk.name").startswith("sentry.python")
471+
472+
# Assert on the remaining non-dynamic attributes.
473+
assert attributes == {
474+
"foo": "bar",
475+
"numeric": 42,
476+
"more_complex": "{'nested': 'data'}",
477+
"logger.name": "test-logger",
478+
"sentry.origin": "auto.logger.log",
479+
"sentry.message.template": "log #%d",
480+
"sentry.message.parameter.0": 1,
481+
"sentry.environment": "production",
482+
"sentry.sdk.version": VERSION,
483+
"sentry.severity_number": 13,
484+
"sentry.severity_text": "warn",
485+
}

0 commit comments

Comments
 (0)