Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 673c34c

Browse filesBrowse files
committed
Merge branch 'master' into potel-base
2 parents 6ad4031 + 51db87c commit 673c34c
Copy full SHA for 673c34c

File tree

Expand file treeCollapse file tree

9 files changed

+747
-253
lines changed
Filter options
Expand file treeCollapse file tree

9 files changed

+747
-253
lines changed

‎CHANGELOG.md

Copy file name to clipboardExpand all lines: CHANGELOG.md
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ for your feedback. How was the migration? Is everything working as expected? Is
142142
sentry_sdk.init(
143143
dsn="...",
144144
_experiments={
145-
"enable_sentry_logs": True
145+
"enable_logs": True
146146
}
147147
integrations=[
148148
LoggingIntegration(sentry_logs_level=logging.ERROR),

‎sentry_sdk/client.py

Copy file name to clipboardExpand all lines: sentry_sdk/client.py
+23-5Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from importlib import import_module
88
from typing import TYPE_CHECKING, List, Dict, cast, overload
99

10+
import sentry_sdk
1011
from sentry_sdk._compat import check_uwsgi_thread_support
1112
from sentry_sdk.utils import (
1213
AnnotatedValue,
@@ -190,8 +191,8 @@ def capture_event(self, *args, **kwargs):
190191
# type: (*Any, **Any) -> Optional[str]
191192
return None
192193

193-
def _capture_experimental_log(self, scope, log):
194-
# type: (Scope, Log) -> None
194+
def _capture_experimental_log(self, log):
195+
# type: (Log) -> None
195196
pass
196197

197198
def capture_session(self, *args, **kwargs):
@@ -846,12 +847,14 @@ def capture_event(
846847

847848
return return_value
848849

849-
def _capture_experimental_log(self, current_scope, log):
850-
# type: (Scope, Log) -> None
850+
def _capture_experimental_log(self, log):
851+
# type: (Log) -> None
851852
logs_enabled = self.options["_experiments"].get("enable_logs", False)
852853
if not logs_enabled:
853854
return
854-
isolation_scope = current_scope.get_isolation_scope()
855+
856+
current_scope = sentry_sdk.get_current_scope()
857+
isolation_scope = sentry_sdk.get_isolation_scope()
855858

856859
log["attributes"]["sentry.sdk.name"] = SDK_INFO["name"]
857860
log["attributes"]["sentry.sdk.version"] = SDK_INFO["version"]
@@ -880,6 +883,21 @@ def _capture_experimental_log(self, current_scope, log):
880883
elif propagation_context is not None:
881884
log["trace_id"] = propagation_context.trace_id
882885

886+
# The user, if present, is always set on the isolation scope.
887+
if isolation_scope._user is not None:
888+
for log_attribute, user_attribute in (
889+
("user.id", "id"),
890+
("user.name", "username"),
891+
("user.email", "email"),
892+
):
893+
if (
894+
user_attribute in isolation_scope._user
895+
and log_attribute not in log["attributes"]
896+
):
897+
log["attributes"][log_attribute] = isolation_scope._user[
898+
user_attribute
899+
]
900+
883901
# If debug is enabled, log the log to the console
884902
debug = self.options.get("debug", False)
885903
if debug:

‎sentry_sdk/integrations/logging.py

Copy file name to clipboardExpand all lines: sentry_sdk/integrations/logging.py
+21-25Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import sentry_sdk
77
from sentry_sdk.client import BaseClient
8+
from sentry_sdk.logger import _log_level_to_otel
89
from sentry_sdk.utils import (
910
safe_repr,
1011
to_string,
@@ -14,7 +15,7 @@
1415
)
1516
from sentry_sdk.integrations import Integration
1617

17-
from typing import TYPE_CHECKING, Tuple
18+
from typing import TYPE_CHECKING
1819

1920
if TYPE_CHECKING:
2021
from collections.abc import MutableMapping
@@ -36,6 +37,16 @@
3637
logging.CRITICAL: "fatal", # CRITICAL is same as FATAL
3738
}
3839

40+
# Map logging level numbers to corresponding OTel level numbers
41+
SEVERITY_TO_OTEL_SEVERITY = {
42+
logging.CRITICAL: 21, # fatal
43+
logging.ERROR: 17, # error
44+
logging.WARNING: 13, # warn
45+
logging.INFO: 9, # info
46+
logging.DEBUG: 5, # debug
47+
}
48+
49+
3950
# Capturing events from those loggers causes recursion errors. We cannot allow
4051
# the user to unconditionally create events from those loggers under any
4152
# circumstances.
@@ -124,7 +135,10 @@ def sentry_patched_callhandlers(self, record):
124135
# the integration. Otherwise we have a high chance of getting
125136
# into a recursion error when the integration is resolved
126137
# (this also is slower).
127-
if ignored_loggers is not None and record.name not in ignored_loggers:
138+
if (
139+
ignored_loggers is not None
140+
and record.name.strip() not in ignored_loggers
141+
):
128142
integration = sentry_sdk.get_client().get_integration(
129143
LoggingIntegration
130144
)
@@ -169,7 +183,7 @@ def _can_record(self, record):
169183
# type: (LogRecord) -> bool
170184
"""Prevents ignored loggers from recording"""
171185
for logger in _IGNORED_LOGGERS:
172-
if fnmatch(record.name, logger):
186+
if fnmatch(record.name.strip(), logger):
173187
return False
174188
return True
175189

@@ -317,21 +331,6 @@ def _breadcrumb_from_record(self, record):
317331
}
318332

319333

320-
def _python_level_to_otel(record_level):
321-
# type: (int) -> Tuple[int, str]
322-
for py_level, otel_severity_number, otel_severity_text in [
323-
(50, 21, "fatal"),
324-
(40, 17, "error"),
325-
(30, 13, "warn"),
326-
(20, 9, "info"),
327-
(10, 5, "debug"),
328-
(5, 1, "trace"),
329-
]:
330-
if record_level >= py_level:
331-
return otel_severity_number, otel_severity_text
332-
return 0, "default"
333-
334-
335334
class SentryLogsHandler(_BaseHandler):
336335
"""
337336
A logging handler that records Sentry logs for each Python log record.
@@ -357,8 +356,9 @@ def emit(self, record):
357356

358357
def _capture_log_from_record(self, client, record):
359358
# type: (BaseClient, LogRecord) -> None
360-
scope = sentry_sdk.get_current_scope()
361-
otel_severity_number, otel_severity_text = _python_level_to_otel(record.levelno)
359+
otel_severity_number, otel_severity_text = _log_level_to_otel(
360+
record.levelno, SEVERITY_TO_OTEL_SEVERITY
361+
)
362362
project_root = client.options["project_root"]
363363
attrs = self._extra_from_record(record) # type: Any
364364
attrs["sentry.origin"] = "auto.logger.log"
@@ -369,10 +369,7 @@ def _capture_log_from_record(self, client, record):
369369
for i, arg in enumerate(record.args):
370370
attrs[f"sentry.message.parameter.{i}"] = (
371371
arg
372-
if isinstance(arg, str)
373-
or isinstance(arg, float)
374-
or isinstance(arg, int)
375-
or isinstance(arg, bool)
372+
if isinstance(arg, (str, float, int, bool))
376373
else safe_repr(arg)
377374
)
378375
if record.lineno:
@@ -399,7 +396,6 @@ def _capture_log_from_record(self, client, record):
399396

400397
# noinspection PyProtectedMember
401398
client._capture_experimental_log(
402-
scope,
403399
{
404400
"severity_text": otel_severity_text,
405401
"severity_number": otel_severity_number,

‎sentry_sdk/integrations/loguru.py

Copy file name to clipboardExpand all lines: sentry_sdk/integrations/loguru.py
+102-19Lines changed: 102 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
import enum
22

3+
import sentry_sdk
34
from sentry_sdk.integrations import Integration, DidNotEnable
45
from sentry_sdk.integrations.logging import (
56
BreadcrumbHandler,
67
EventHandler,
78
_BaseHandler,
89
)
10+
from sentry_sdk.logger import _log_level_to_otel
911

1012
from typing import TYPE_CHECKING
1113

1214
if TYPE_CHECKING:
1315
from logging import LogRecord
14-
from typing import Optional, Any
16+
from typing import Any, Optional
1517

1618
try:
1719
import loguru
1820
from loguru import logger
1921
from loguru._defaults import LOGURU_FORMAT as DEFAULT_FORMAT
22+
23+
if TYPE_CHECKING:
24+
from loguru import Message
2025
except ImportError:
2126
raise DidNotEnable("LOGURU is not installed")
2227

@@ -31,6 +36,10 @@ class LoggingLevels(enum.IntEnum):
3136
CRITICAL = 50
3237

3338

39+
DEFAULT_LEVEL = LoggingLevels.INFO.value
40+
DEFAULT_EVENT_LEVEL = LoggingLevels.ERROR.value
41+
42+
3443
SENTRY_LEVEL_FROM_LOGURU_LEVEL = {
3544
"TRACE": "DEBUG",
3645
"DEBUG": "DEBUG",
@@ -41,8 +50,16 @@ class LoggingLevels(enum.IntEnum):
4150
"CRITICAL": "CRITICAL",
4251
}
4352

44-
DEFAULT_LEVEL = LoggingLevels.INFO.value
45-
DEFAULT_EVENT_LEVEL = LoggingLevels.ERROR.value
53+
# Map Loguru level numbers to corresponding OTel level numbers
54+
SEVERITY_TO_OTEL_SEVERITY = {
55+
LoggingLevels.CRITICAL: 21, # fatal
56+
LoggingLevels.ERROR: 17, # error
57+
LoggingLevels.WARNING: 13, # warn
58+
LoggingLevels.SUCCESS: 11, # info
59+
LoggingLevels.INFO: 9, # info
60+
LoggingLevels.DEBUG: 5, # debug
61+
LoggingLevels.TRACE: 1, # trace
62+
}
4663

4764

4865
class LoguruIntegration(Integration):
@@ -52,19 +69,22 @@ class LoguruIntegration(Integration):
5269
event_level = DEFAULT_EVENT_LEVEL # type: Optional[int]
5370
breadcrumb_format = DEFAULT_FORMAT
5471
event_format = DEFAULT_FORMAT
72+
sentry_logs_level = DEFAULT_LEVEL # type: Optional[int]
5573

5674
def __init__(
5775
self,
5876
level=DEFAULT_LEVEL,
5977
event_level=DEFAULT_EVENT_LEVEL,
6078
breadcrumb_format=DEFAULT_FORMAT,
6179
event_format=DEFAULT_FORMAT,
80+
sentry_logs_level=DEFAULT_LEVEL,
6281
):
63-
# type: (Optional[int], Optional[int], str | loguru.FormatFunction, str | loguru.FormatFunction) -> None
82+
# type: (Optional[int], Optional[int], str | loguru.FormatFunction, str | loguru.FormatFunction, Optional[int]) -> None
6483
LoguruIntegration.level = level
6584
LoguruIntegration.event_level = event_level
6685
LoguruIntegration.breadcrumb_format = breadcrumb_format
6786
LoguruIntegration.event_format = event_format
87+
LoguruIntegration.sentry_logs_level = sentry_logs_level
6888

6989
@staticmethod
7090
def setup_once():
@@ -83,8 +103,23 @@ def setup_once():
83103
format=LoguruIntegration.event_format,
84104
)
85105

106+
if LoguruIntegration.sentry_logs_level is not None:
107+
logger.add(
108+
loguru_sentry_logs_handler,
109+
level=LoguruIntegration.sentry_logs_level,
110+
)
111+
86112

87113
class _LoguruBaseHandler(_BaseHandler):
114+
def __init__(self, *args, **kwargs):
115+
# type: (*Any, **Any) -> None
116+
if kwargs.get("level"):
117+
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
118+
kwargs.get("level", ""), DEFAULT_LEVEL
119+
)
120+
121+
super().__init__(*args, **kwargs)
122+
88123
def _logging_to_event_level(self, record):
89124
# type: (LogRecord) -> str
90125
try:
@@ -98,24 +133,72 @@ def _logging_to_event_level(self, record):
98133
class LoguruEventHandler(_LoguruBaseHandler, EventHandler):
99134
"""Modified version of :class:`sentry_sdk.integrations.logging.EventHandler` to use loguru's level names."""
100135

101-
def __init__(self, *args, **kwargs):
102-
# type: (*Any, **Any) -> None
103-
if kwargs.get("level"):
104-
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
105-
kwargs.get("level", ""), DEFAULT_LEVEL
106-
)
107-
108-
super().__init__(*args, **kwargs)
136+
pass
109137

110138

111139
class LoguruBreadcrumbHandler(_LoguruBaseHandler, BreadcrumbHandler):
112140
"""Modified version of :class:`sentry_sdk.integrations.logging.BreadcrumbHandler` to use loguru's level names."""
113141

114-
def __init__(self, *args, **kwargs):
115-
# type: (*Any, **Any) -> None
116-
if kwargs.get("level"):
117-
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
118-
kwargs.get("level", ""), DEFAULT_LEVEL
119-
)
142+
pass
120143

121-
super().__init__(*args, **kwargs)
144+
145+
def loguru_sentry_logs_handler(message):
146+
# type: (Message) -> None
147+
# This is intentionally a callable sink instead of a standard logging handler
148+
# since otherwise we wouldn't get direct access to message.record
149+
client = sentry_sdk.get_client()
150+
151+
if not client.is_active():
152+
return
153+
154+
if not client.options["_experiments"].get("enable_logs", False):
155+
return
156+
157+
record = message.record
158+
159+
if (
160+
LoguruIntegration.sentry_logs_level is None
161+
or record["level"].no < LoguruIntegration.sentry_logs_level
162+
):
163+
return
164+
165+
otel_severity_number, otel_severity_text = _log_level_to_otel(
166+
record["level"].no, SEVERITY_TO_OTEL_SEVERITY
167+
)
168+
169+
attrs = {"sentry.origin": "auto.logger.loguru"} # type: dict[str, Any]
170+
171+
project_root = client.options["project_root"]
172+
if record.get("file"):
173+
if project_root is not None and record["file"].path.startswith(project_root):
174+
attrs["code.file.path"] = record["file"].path[len(project_root) + 1 :]
175+
else:
176+
attrs["code.file.path"] = record["file"].path
177+
178+
if record.get("line") is not None:
179+
attrs["code.line.number"] = record["line"]
180+
181+
if record.get("function"):
182+
attrs["code.function.name"] = record["function"]
183+
184+
if record.get("thread"):
185+
attrs["thread.name"] = record["thread"].name
186+
attrs["thread.id"] = record["thread"].id
187+
188+
if record.get("process"):
189+
attrs["process.pid"] = record["process"].id
190+
attrs["process.executable.name"] = record["process"].name
191+
192+
if record.get("name"):
193+
attrs["logger.name"] = record["name"]
194+
195+
client._capture_experimental_log(
196+
{
197+
"severity_text": otel_severity_text,
198+
"severity_number": otel_severity_number,
199+
"body": record["message"],
200+
"attributes": attrs,
201+
"time_unix_nano": int(record["time"].timestamp() * 1e9),
202+
"trace_id": None,
203+
}
204+
)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.