Skip to content

Commit de6856f

Browse files
authored
feat(logs): Forward extra from logger as attributes (#4374)
resolves https://linear.app/getsentry/issue/LOGS-101
1 parent cb82483 commit de6856f

File tree

2 files changed

+77
-7
lines changed

2 files changed

+77
-7
lines changed

sentry_sdk/integrations/logging.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -348,17 +348,15 @@ def emit(self, record):
348348
if not client.options["_experiments"].get("enable_logs", False):
349349
return
350350

351-
SentryLogsHandler._capture_log_from_record(client, record)
351+
self._capture_log_from_record(client, record)
352352

353-
@staticmethod
354-
def _capture_log_from_record(client, record):
353+
def _capture_log_from_record(self, client, record):
355354
# type: (BaseClient, LogRecord) -> None
356355
scope = sentry_sdk.get_current_scope()
357356
otel_severity_number, otel_severity_text = _python_level_to_otel(record.levelno)
358357
project_root = client.options["project_root"]
359-
attrs = {
360-
"sentry.origin": "auto.logger.log",
361-
} # type: dict[str, str | bool | float | int]
358+
attrs = self._extra_from_record(record) # type: Any
359+
attrs["sentry.origin"] = "auto.logger.log"
362360
if isinstance(record.msg, str):
363361
attrs["sentry.message.template"] = record.msg
364362
if record.args is not None:

tests/test_logs.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def _convert_attr(attr):
3030
return attr["value"]
3131
if attr["value"].startswith("{"):
3232
try:
33-
return json.loads(attr["stringValue"])
33+
return json.loads(attr["value"])
3434
except ValueError:
3535
pass
3636
return str(attr["value"])
@@ -393,6 +393,78 @@ def test_log_strips_project_root(sentry_init, capture_envelopes):
393393
assert attrs["code.file.path"] == "blah/path.py"
394394

395395

396+
def test_logger_with_all_attributes(sentry_init, capture_envelopes):
397+
"""
398+
The python logger should be able to log all attributes, including extra data.
399+
"""
400+
sentry_init(_experiments={"enable_logs": True})
401+
envelopes = capture_envelopes()
402+
403+
python_logger = logging.Logger("test-logger")
404+
python_logger.warning(
405+
"log #%d",
406+
1,
407+
extra={"foo": "bar", "numeric": 42, "more_complex": {"nested": "data"}},
408+
)
409+
get_client().flush()
410+
411+
logs = envelopes_to_logs(envelopes)
412+
413+
attributes = logs[0]["attributes"]
414+
415+
assert "process.pid" in attributes
416+
assert isinstance(attributes["process.pid"], int)
417+
del attributes["process.pid"]
418+
419+
assert "sentry.release" in attributes
420+
assert isinstance(attributes["sentry.release"], str)
421+
del attributes["sentry.release"]
422+
423+
assert "server.address" in attributes
424+
assert isinstance(attributes["server.address"], str)
425+
del attributes["server.address"]
426+
427+
assert "thread.id" in attributes
428+
assert isinstance(attributes["thread.id"], int)
429+
del attributes["thread.id"]
430+
431+
assert "code.file.path" in attributes
432+
assert isinstance(attributes["code.file.path"], str)
433+
del attributes["code.file.path"]
434+
435+
assert "code.function.name" in attributes
436+
assert isinstance(attributes["code.function.name"], str)
437+
del attributes["code.function.name"]
438+
439+
assert "code.line.number" in attributes
440+
assert isinstance(attributes["code.line.number"], int)
441+
del attributes["code.line.number"]
442+
443+
assert "process.executable.name" in attributes
444+
assert isinstance(attributes["process.executable.name"], str)
445+
del attributes["process.executable.name"]
446+
447+
assert "thread.name" in attributes
448+
assert isinstance(attributes["thread.name"], str)
449+
del attributes["thread.name"]
450+
451+
# Assert on the remaining non-dynamic attributes.
452+
assert attributes == {
453+
"foo": "bar",
454+
"numeric": 42,
455+
"more_complex": "{'nested': 'data'}",
456+
"logger.name": "test-logger",
457+
"sentry.origin": "auto.logger.log",
458+
"sentry.message.template": "log #%d",
459+
"sentry.message.parameters.0": 1,
460+
"sentry.environment": "production",
461+
"sentry.sdk.name": "sentry.python",
462+
"sentry.sdk.version": VERSION,
463+
"sentry.severity_number": 13,
464+
"sentry.severity_text": "warn",
465+
}
466+
467+
396468
def test_auto_flush_logs_after_100(sentry_init, capture_envelopes):
397469
"""
398470
If you log >100 logs, it should automatically trigger a flush.

0 commit comments

Comments
 (0)