sentry-sdk 3.0.0a1__py2.py3-none-any.whl → 3.0.0a3__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sentry-sdk might be problematic. Click here for more details.
- sentry_sdk/__init__.py +2 -0
- sentry_sdk/_compat.py +5 -12
- sentry_sdk/_init_implementation.py +7 -7
- sentry_sdk/_log_batcher.py +17 -29
- sentry_sdk/_lru_cache.py +7 -9
- sentry_sdk/_queue.py +2 -4
- sentry_sdk/_types.py +11 -18
- sentry_sdk/_werkzeug.py +5 -7
- sentry_sdk/ai/monitoring.py +44 -31
- sentry_sdk/ai/utils.py +3 -4
- sentry_sdk/api.py +75 -87
- sentry_sdk/attachments.py +10 -12
- sentry_sdk/client.py +137 -155
- sentry_sdk/consts.py +430 -174
- sentry_sdk/crons/api.py +16 -17
- sentry_sdk/crons/decorator.py +25 -27
- sentry_sdk/debug.py +4 -6
- sentry_sdk/envelope.py +46 -112
- sentry_sdk/feature_flags.py +9 -15
- sentry_sdk/integrations/__init__.py +24 -19
- sentry_sdk/integrations/_asgi_common.py +15 -18
- sentry_sdk/integrations/_wsgi_common.py +22 -33
- sentry_sdk/integrations/aiohttp.py +32 -30
- sentry_sdk/integrations/anthropic.py +42 -37
- sentry_sdk/integrations/argv.py +3 -4
- sentry_sdk/integrations/ariadne.py +16 -18
- sentry_sdk/integrations/arq.py +21 -29
- sentry_sdk/integrations/asgi.py +63 -37
- sentry_sdk/integrations/asyncio.py +14 -16
- sentry_sdk/integrations/atexit.py +6 -10
- sentry_sdk/integrations/aws_lambda.py +26 -36
- sentry_sdk/integrations/beam.py +10 -18
- sentry_sdk/integrations/boto3.py +18 -16
- sentry_sdk/integrations/bottle.py +25 -34
- sentry_sdk/integrations/celery/__init__.py +41 -61
- sentry_sdk/integrations/celery/beat.py +23 -27
- sentry_sdk/integrations/celery/utils.py +15 -17
- sentry_sdk/integrations/chalice.py +8 -10
- sentry_sdk/integrations/clickhouse_driver.py +21 -31
- sentry_sdk/integrations/cloud_resource_context.py +9 -16
- sentry_sdk/integrations/cohere.py +27 -33
- sentry_sdk/integrations/dedupe.py +5 -8
- sentry_sdk/integrations/django/__init__.py +57 -72
- sentry_sdk/integrations/django/asgi.py +26 -34
- sentry_sdk/integrations/django/caching.py +23 -19
- sentry_sdk/integrations/django/middleware.py +17 -20
- sentry_sdk/integrations/django/signals_handlers.py +11 -10
- sentry_sdk/integrations/django/templates.py +19 -16
- sentry_sdk/integrations/django/transactions.py +16 -11
- sentry_sdk/integrations/django/views.py +6 -10
- sentry_sdk/integrations/dramatiq.py +21 -21
- sentry_sdk/integrations/excepthook.py +10 -10
- sentry_sdk/integrations/executing.py +3 -4
- sentry_sdk/integrations/falcon.py +27 -42
- sentry_sdk/integrations/fastapi.py +13 -16
- sentry_sdk/integrations/flask.py +31 -38
- sentry_sdk/integrations/gcp.py +13 -16
- sentry_sdk/integrations/gnu_backtrace.py +4 -6
- sentry_sdk/integrations/gql.py +16 -17
- sentry_sdk/integrations/graphene.py +13 -12
- sentry_sdk/integrations/grpc/__init__.py +19 -1
- sentry_sdk/integrations/grpc/aio/server.py +15 -14
- sentry_sdk/integrations/grpc/client.py +19 -9
- sentry_sdk/integrations/grpc/consts.py +2 -0
- sentry_sdk/integrations/grpc/server.py +12 -8
- sentry_sdk/integrations/httpx.py +9 -12
- sentry_sdk/integrations/huey.py +13 -20
- sentry_sdk/integrations/huggingface_hub.py +18 -18
- sentry_sdk/integrations/langchain.py +203 -113
- sentry_sdk/integrations/launchdarkly.py +13 -10
- sentry_sdk/integrations/litestar.py +37 -35
- sentry_sdk/integrations/logging.py +52 -65
- sentry_sdk/integrations/loguru.py +127 -57
- sentry_sdk/integrations/modules.py +3 -4
- sentry_sdk/integrations/openai.py +100 -88
- sentry_sdk/integrations/openai_agents/__init__.py +49 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +4 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +152 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +52 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +42 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +84 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +20 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +46 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +47 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +24 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +41 -0
- sentry_sdk/integrations/openai_agents/utils.py +201 -0
- sentry_sdk/integrations/openfeature.py +11 -6
- sentry_sdk/integrations/pure_eval.py +6 -10
- sentry_sdk/integrations/pymongo.py +13 -17
- sentry_sdk/integrations/pyramid.py +31 -36
- sentry_sdk/integrations/quart.py +23 -28
- sentry_sdk/integrations/ray.py +73 -64
- sentry_sdk/integrations/redis/__init__.py +7 -4
- sentry_sdk/integrations/redis/_async_common.py +25 -12
- sentry_sdk/integrations/redis/_sync_common.py +19 -13
- sentry_sdk/integrations/redis/modules/caches.py +17 -8
- sentry_sdk/integrations/redis/modules/queries.py +9 -8
- sentry_sdk/integrations/redis/rb.py +3 -2
- sentry_sdk/integrations/redis/redis.py +4 -4
- sentry_sdk/integrations/redis/redis_cluster.py +21 -13
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +3 -2
- sentry_sdk/integrations/redis/utils.py +23 -24
- sentry_sdk/integrations/rq.py +13 -16
- sentry_sdk/integrations/rust_tracing.py +9 -6
- sentry_sdk/integrations/sanic.py +34 -46
- sentry_sdk/integrations/serverless.py +22 -27
- sentry_sdk/integrations/socket.py +27 -15
- sentry_sdk/integrations/spark/__init__.py +1 -0
- sentry_sdk/integrations/spark/spark_driver.py +45 -83
- sentry_sdk/integrations/spark/spark_worker.py +7 -11
- sentry_sdk/integrations/sqlalchemy.py +22 -19
- sentry_sdk/integrations/starlette.py +86 -90
- sentry_sdk/integrations/starlite.py +28 -34
- sentry_sdk/integrations/statsig.py +5 -4
- sentry_sdk/integrations/stdlib.py +28 -24
- sentry_sdk/integrations/strawberry.py +62 -49
- sentry_sdk/integrations/sys_exit.py +7 -11
- sentry_sdk/integrations/threading.py +12 -14
- sentry_sdk/integrations/tornado.py +28 -32
- sentry_sdk/integrations/trytond.py +4 -3
- sentry_sdk/integrations/typer.py +8 -6
- sentry_sdk/integrations/unleash.py +5 -4
- sentry_sdk/integrations/wsgi.py +47 -46
- sentry_sdk/logger.py +41 -10
- sentry_sdk/monitor.py +16 -28
- sentry_sdk/opentelemetry/consts.py +11 -4
- sentry_sdk/opentelemetry/contextvars_context.py +26 -16
- sentry_sdk/opentelemetry/propagator.py +38 -21
- sentry_sdk/opentelemetry/sampler.py +51 -34
- sentry_sdk/opentelemetry/scope.py +36 -37
- sentry_sdk/opentelemetry/span_processor.py +48 -58
- sentry_sdk/opentelemetry/tracing.py +58 -14
- sentry_sdk/opentelemetry/utils.py +186 -194
- sentry_sdk/profiler/continuous_profiler.py +108 -97
- sentry_sdk/profiler/transaction_profiler.py +70 -97
- sentry_sdk/profiler/utils.py +11 -15
- sentry_sdk/scope.py +251 -273
- sentry_sdk/scrubber.py +22 -26
- sentry_sdk/serializer.py +40 -54
- sentry_sdk/session.py +44 -61
- sentry_sdk/sessions.py +35 -49
- sentry_sdk/spotlight.py +15 -21
- sentry_sdk/tracing.py +121 -187
- sentry_sdk/tracing_utils.py +104 -122
- sentry_sdk/transport.py +131 -157
- sentry_sdk/utils.py +232 -309
- sentry_sdk/worker.py +16 -28
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/METADATA +3 -3
- sentry_sdk-3.0.0a3.dist-info/RECORD +168 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/WHEEL +1 -1
- sentry_sdk-3.0.0a1.dist-info/RECORD +0 -154
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/entry_points.txt +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/licenses/LICENSE +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
from collections.abc import Set
|
|
2
3
|
import sentry_sdk
|
|
3
4
|
from sentry_sdk.consts import OP, TransactionSource, SOURCE_FOR_STYLE
|
|
@@ -52,13 +53,12 @@ class LitestarIntegration(Integration):
|
|
|
52
53
|
|
|
53
54
|
def __init__(
|
|
54
55
|
self,
|
|
55
|
-
failed_request_status_codes
|
|
56
|
+
failed_request_status_codes: Set[int] = _DEFAULT_FAILED_REQUEST_STATUS_CODES,
|
|
56
57
|
) -> None:
|
|
57
58
|
self.failed_request_status_codes = failed_request_status_codes
|
|
58
59
|
|
|
59
60
|
@staticmethod
|
|
60
|
-
def setup_once():
|
|
61
|
-
# type: () -> None
|
|
61
|
+
def setup_once() -> None:
|
|
62
62
|
patch_app_init()
|
|
63
63
|
patch_middlewares()
|
|
64
64
|
patch_http_route_handle()
|
|
@@ -75,8 +75,9 @@ class LitestarIntegration(Integration):
|
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
class SentryLitestarASGIMiddleware(SentryAsgiMiddleware):
|
|
78
|
-
def __init__(
|
|
79
|
-
|
|
78
|
+
def __init__(
|
|
79
|
+
self, app: ASGIApp, span_origin: str = LitestarIntegration.origin
|
|
80
|
+
) -> None:
|
|
80
81
|
|
|
81
82
|
super().__init__(
|
|
82
83
|
app=app,
|
|
@@ -86,9 +87,16 @@ class SentryLitestarASGIMiddleware(SentryAsgiMiddleware):
|
|
|
86
87
|
span_origin=span_origin,
|
|
87
88
|
)
|
|
88
89
|
|
|
90
|
+
def _capture_request_exception(self, exc: Exception) -> None:
|
|
91
|
+
"""Avoid catching exceptions from request handlers.
|
|
92
|
+
|
|
93
|
+
Those exceptions are already handled in Litestar.after_exception handler.
|
|
94
|
+
We still catch exceptions from application lifespan handlers.
|
|
95
|
+
"""
|
|
96
|
+
pass
|
|
97
|
+
|
|
89
98
|
|
|
90
|
-
def patch_app_init():
|
|
91
|
-
# type: () -> None
|
|
99
|
+
def patch_app_init() -> None:
|
|
92
100
|
"""
|
|
93
101
|
Replaces the Litestar class's `__init__` function in order to inject `after_exception` handlers and set the
|
|
94
102
|
`SentryLitestarASGIMiddleware` as the outmost middleware in the stack.
|
|
@@ -99,8 +107,7 @@ def patch_app_init():
|
|
|
99
107
|
old__init__ = Litestar.__init__
|
|
100
108
|
|
|
101
109
|
@ensure_integration_enabled(LitestarIntegration, old__init__)
|
|
102
|
-
def injection_wrapper(self, *args, **kwargs):
|
|
103
|
-
# type: (Litestar, *Any, **Any) -> None
|
|
110
|
+
def injection_wrapper(self: Litestar, *args: Any, **kwargs: Any) -> None:
|
|
104
111
|
kwargs["after_exception"] = [
|
|
105
112
|
exception_handler,
|
|
106
113
|
*(kwargs.get("after_exception") or []),
|
|
@@ -114,13 +121,11 @@ def patch_app_init():
|
|
|
114
121
|
Litestar.__init__ = injection_wrapper
|
|
115
122
|
|
|
116
123
|
|
|
117
|
-
def patch_middlewares():
|
|
118
|
-
# type: () -> None
|
|
124
|
+
def patch_middlewares() -> None:
|
|
119
125
|
old_resolve_middleware_stack = BaseRouteHandler.resolve_middleware
|
|
120
126
|
|
|
121
127
|
@ensure_integration_enabled(LitestarIntegration, old_resolve_middleware_stack)
|
|
122
|
-
def resolve_middleware_wrapper(self):
|
|
123
|
-
# type: (BaseRouteHandler) -> list[Middleware]
|
|
128
|
+
def resolve_middleware_wrapper(self: BaseRouteHandler) -> list[Middleware]:
|
|
124
129
|
return [
|
|
125
130
|
enable_span_for_middleware(middleware)
|
|
126
131
|
for middleware in old_resolve_middleware_stack(self)
|
|
@@ -129,8 +134,7 @@ def patch_middlewares():
|
|
|
129
134
|
BaseRouteHandler.resolve_middleware = resolve_middleware_wrapper
|
|
130
135
|
|
|
131
136
|
|
|
132
|
-
def enable_span_for_middleware(middleware):
|
|
133
|
-
# type: (Middleware) -> Middleware
|
|
137
|
+
def enable_span_for_middleware(middleware: Middleware) -> Middleware:
|
|
134
138
|
if (
|
|
135
139
|
not hasattr(middleware, "__call__") # noqa: B004
|
|
136
140
|
or middleware is SentryLitestarASGIMiddleware
|
|
@@ -138,12 +142,13 @@ def enable_span_for_middleware(middleware):
|
|
|
138
142
|
return middleware
|
|
139
143
|
|
|
140
144
|
if isinstance(middleware, DefineMiddleware):
|
|
141
|
-
old_call = middleware.middleware.__call__
|
|
145
|
+
old_call: ASGIApp = middleware.middleware.__call__
|
|
142
146
|
else:
|
|
143
147
|
old_call = middleware.__call__
|
|
144
148
|
|
|
145
|
-
async def _create_span_call(
|
|
146
|
-
|
|
149
|
+
async def _create_span_call(
|
|
150
|
+
self: MiddlewareProtocol, scope: LitestarScope, receive: Receive, send: Send
|
|
151
|
+
) -> None:
|
|
147
152
|
if sentry_sdk.get_client().get_integration(LitestarIntegration) is None:
|
|
148
153
|
return await old_call(self, scope, receive, send)
|
|
149
154
|
|
|
@@ -157,8 +162,9 @@ def enable_span_for_middleware(middleware):
|
|
|
157
162
|
middleware_span.set_tag("litestar.middleware_name", middleware_name)
|
|
158
163
|
|
|
159
164
|
# Creating spans for the "receive" callback
|
|
160
|
-
async def _sentry_receive(
|
|
161
|
-
|
|
165
|
+
async def _sentry_receive(
|
|
166
|
+
*args: Any, **kwargs: Any
|
|
167
|
+
) -> Union[HTTPReceiveMessage, WebSocketReceiveMessage]:
|
|
162
168
|
if sentry_sdk.get_client().get_integration(LitestarIntegration) is None:
|
|
163
169
|
return await receive(*args, **kwargs)
|
|
164
170
|
with sentry_sdk.start_span(
|
|
@@ -175,8 +181,7 @@ def enable_span_for_middleware(middleware):
|
|
|
175
181
|
new_receive = _sentry_receive if not receive_patched else receive
|
|
176
182
|
|
|
177
183
|
# Creating spans for the "send" callback
|
|
178
|
-
async def _sentry_send(message):
|
|
179
|
-
# type: (Message) -> None
|
|
184
|
+
async def _sentry_send(message: Message) -> None:
|
|
180
185
|
if sentry_sdk.get_client().get_integration(LitestarIntegration) is None:
|
|
181
186
|
return await send(message)
|
|
182
187
|
with sentry_sdk.start_span(
|
|
@@ -205,19 +210,19 @@ def enable_span_for_middleware(middleware):
|
|
|
205
210
|
return middleware
|
|
206
211
|
|
|
207
212
|
|
|
208
|
-
def patch_http_route_handle():
|
|
209
|
-
# type: () -> None
|
|
213
|
+
def patch_http_route_handle() -> None:
|
|
210
214
|
old_handle = HTTPRoute.handle
|
|
211
215
|
|
|
212
|
-
async def handle_wrapper(
|
|
213
|
-
|
|
216
|
+
async def handle_wrapper(
|
|
217
|
+
self: HTTPRoute, scope: HTTPScope, receive: Receive, send: Send
|
|
218
|
+
) -> None:
|
|
214
219
|
if sentry_sdk.get_client().get_integration(LitestarIntegration) is None:
|
|
215
220
|
return await old_handle(self, scope, receive, send)
|
|
216
221
|
|
|
217
222
|
sentry_scope = sentry_sdk.get_isolation_scope()
|
|
218
|
-
request = scope["app"].request_class(
|
|
223
|
+
request: Request[Any, Any] = scope["app"].request_class(
|
|
219
224
|
scope=scope, receive=receive, send=send
|
|
220
|
-
)
|
|
225
|
+
)
|
|
221
226
|
extracted_request_data = ConnectionDataExtractor(
|
|
222
227
|
parse_body=True, parse_query=True
|
|
223
228
|
)(request)
|
|
@@ -225,8 +230,7 @@ def patch_http_route_handle():
|
|
|
225
230
|
|
|
226
231
|
request_data = await body
|
|
227
232
|
|
|
228
|
-
def event_processor(event, _):
|
|
229
|
-
# type: (Event, Hint) -> Event
|
|
233
|
+
def event_processor(event: Event, _: Hint) -> Event:
|
|
230
234
|
route_handler = scope.get("route_handler")
|
|
231
235
|
|
|
232
236
|
request_info = event.get("request", {})
|
|
@@ -270,8 +274,7 @@ def patch_http_route_handle():
|
|
|
270
274
|
HTTPRoute.handle = handle_wrapper
|
|
271
275
|
|
|
272
276
|
|
|
273
|
-
def retrieve_user_from_scope(scope):
|
|
274
|
-
# type: (LitestarScope) -> Optional[dict[str, Any]]
|
|
277
|
+
def retrieve_user_from_scope(scope: LitestarScope) -> Optional[dict[str, Any]]:
|
|
275
278
|
scope_user = scope.get("user")
|
|
276
279
|
if isinstance(scope_user, dict):
|
|
277
280
|
return scope_user
|
|
@@ -282,9 +285,8 @@ def retrieve_user_from_scope(scope):
|
|
|
282
285
|
|
|
283
286
|
|
|
284
287
|
@ensure_integration_enabled(LitestarIntegration)
|
|
285
|
-
def exception_handler(exc, scope):
|
|
286
|
-
|
|
287
|
-
user_info = None # type: Optional[dict[str, Any]]
|
|
288
|
+
def exception_handler(exc: Exception, scope: LitestarScope) -> None:
|
|
289
|
+
user_info: Optional[dict[str, Any]] = None
|
|
288
290
|
if should_send_default_pii():
|
|
289
291
|
user_info = retrieve_user_from_scope(scope)
|
|
290
292
|
if user_info and isinstance(user_info, dict):
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import logging
|
|
2
3
|
import sys
|
|
3
4
|
from datetime import datetime, timezone
|
|
@@ -5,6 +6,7 @@ from fnmatch import fnmatch
|
|
|
5
6
|
|
|
6
7
|
import sentry_sdk
|
|
7
8
|
from sentry_sdk.client import BaseClient
|
|
9
|
+
from sentry_sdk.logger import _log_level_to_otel
|
|
8
10
|
from sentry_sdk.utils import (
|
|
9
11
|
safe_repr,
|
|
10
12
|
to_string,
|
|
@@ -14,7 +16,7 @@ from sentry_sdk.utils import (
|
|
|
14
16
|
)
|
|
15
17
|
from sentry_sdk.integrations import Integration
|
|
16
18
|
|
|
17
|
-
from typing import TYPE_CHECKING
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
18
20
|
|
|
19
21
|
if TYPE_CHECKING:
|
|
20
22
|
from collections.abc import MutableMapping
|
|
@@ -36,6 +38,16 @@ LOGGING_TO_EVENT_LEVEL = {
|
|
|
36
38
|
logging.CRITICAL: "fatal", # CRITICAL is same as FATAL
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
# Map logging level numbers to corresponding OTel level numbers
|
|
42
|
+
SEVERITY_TO_OTEL_SEVERITY = {
|
|
43
|
+
logging.CRITICAL: 21, # fatal
|
|
44
|
+
logging.ERROR: 17, # error
|
|
45
|
+
logging.WARNING: 13, # warn
|
|
46
|
+
logging.INFO: 9, # info
|
|
47
|
+
logging.DEBUG: 5, # debug
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
39
51
|
# Capturing events from those loggers causes recursion errors. We cannot allow
|
|
40
52
|
# the user to unconditionally create events from those loggers under any
|
|
41
53
|
# circumstances.
|
|
@@ -53,9 +65,8 @@ _IGNORED_LOGGERS = set(
|
|
|
53
65
|
|
|
54
66
|
|
|
55
67
|
def ignore_logger(
|
|
56
|
-
name
|
|
57
|
-
):
|
|
58
|
-
# type: (...) -> None
|
|
68
|
+
name: str,
|
|
69
|
+
) -> None:
|
|
59
70
|
"""This disables recording (both in breadcrumbs and as events) calls to
|
|
60
71
|
a logger of a specific name. Among other uses, many of our integrations
|
|
61
72
|
use this to prevent their actions being recorded as breadcrumbs. Exposed
|
|
@@ -71,11 +82,10 @@ class LoggingIntegration(Integration):
|
|
|
71
82
|
|
|
72
83
|
def __init__(
|
|
73
84
|
self,
|
|
74
|
-
level=DEFAULT_LEVEL,
|
|
75
|
-
event_level=DEFAULT_EVENT_LEVEL,
|
|
76
|
-
sentry_logs_level=DEFAULT_LEVEL,
|
|
77
|
-
):
|
|
78
|
-
# type: (Optional[int], Optional[int], Optional[int]) -> None
|
|
85
|
+
level: Optional[int] = DEFAULT_LEVEL,
|
|
86
|
+
event_level: Optional[int] = DEFAULT_EVENT_LEVEL,
|
|
87
|
+
sentry_logs_level: Optional[int] = DEFAULT_LEVEL,
|
|
88
|
+
) -> None:
|
|
79
89
|
self._handler = None
|
|
80
90
|
self._breadcrumb_handler = None
|
|
81
91
|
self._sentry_logs_handler = None
|
|
@@ -89,8 +99,7 @@ class LoggingIntegration(Integration):
|
|
|
89
99
|
if event_level is not None:
|
|
90
100
|
self._handler = EventHandler(level=event_level)
|
|
91
101
|
|
|
92
|
-
def _handle_record(self, record):
|
|
93
|
-
# type: (LogRecord) -> None
|
|
102
|
+
def _handle_record(self, record: LogRecord) -> None:
|
|
94
103
|
if self._handler is not None and record.levelno >= self._handler.level:
|
|
95
104
|
self._handler.handle(record)
|
|
96
105
|
|
|
@@ -107,12 +116,10 @@ class LoggingIntegration(Integration):
|
|
|
107
116
|
self._sentry_logs_handler.handle(record)
|
|
108
117
|
|
|
109
118
|
@staticmethod
|
|
110
|
-
def setup_once():
|
|
111
|
-
# type: () -> None
|
|
119
|
+
def setup_once() -> None:
|
|
112
120
|
old_callhandlers = logging.Logger.callHandlers
|
|
113
121
|
|
|
114
|
-
def sentry_patched_callhandlers(self, record):
|
|
115
|
-
# type: (Any, LogRecord) -> Any
|
|
122
|
+
def sentry_patched_callhandlers(self: Any, record: LogRecord) -> Any:
|
|
116
123
|
# keeping a local reference because the
|
|
117
124
|
# global might be discarded on shutdown
|
|
118
125
|
ignored_loggers = _IGNORED_LOGGERS
|
|
@@ -124,7 +131,10 @@ class LoggingIntegration(Integration):
|
|
|
124
131
|
# the integration. Otherwise we have a high chance of getting
|
|
125
132
|
# into a recursion error when the integration is resolved
|
|
126
133
|
# (this also is slower).
|
|
127
|
-
if
|
|
134
|
+
if (
|
|
135
|
+
ignored_loggers is not None
|
|
136
|
+
and record.name.strip() not in ignored_loggers
|
|
137
|
+
):
|
|
128
138
|
integration = sentry_sdk.get_client().get_integration(
|
|
129
139
|
LoggingIntegration
|
|
130
140
|
)
|
|
@@ -165,22 +175,19 @@ class _BaseHandler(logging.Handler):
|
|
|
165
175
|
)
|
|
166
176
|
)
|
|
167
177
|
|
|
168
|
-
def _can_record(self, record):
|
|
169
|
-
# type: (LogRecord) -> bool
|
|
178
|
+
def _can_record(self, record: LogRecord) -> bool:
|
|
170
179
|
"""Prevents ignored loggers from recording"""
|
|
171
180
|
for logger in _IGNORED_LOGGERS:
|
|
172
|
-
if fnmatch(record.name, logger):
|
|
181
|
+
if fnmatch(record.name.strip(), logger):
|
|
173
182
|
return False
|
|
174
183
|
return True
|
|
175
184
|
|
|
176
|
-
def _logging_to_event_level(self, record):
|
|
177
|
-
# type: (LogRecord) -> str
|
|
185
|
+
def _logging_to_event_level(self, record: LogRecord) -> str:
|
|
178
186
|
return LOGGING_TO_EVENT_LEVEL.get(
|
|
179
187
|
record.levelno, record.levelname.lower() if record.levelname else ""
|
|
180
188
|
)
|
|
181
189
|
|
|
182
|
-
def _extra_from_record(self, record):
|
|
183
|
-
# type: (LogRecord) -> MutableMapping[str, object]
|
|
190
|
+
def _extra_from_record(self, record: LogRecord) -> MutableMapping[str, object]:
|
|
184
191
|
return {
|
|
185
192
|
k: v
|
|
186
193
|
for k, v in vars(record).items()
|
|
@@ -196,14 +203,12 @@ class EventHandler(_BaseHandler):
|
|
|
196
203
|
Note that you do not have to use this class if the logging integration is enabled, which it is by default.
|
|
197
204
|
"""
|
|
198
205
|
|
|
199
|
-
def emit(self, record):
|
|
200
|
-
# type: (LogRecord) -> Any
|
|
206
|
+
def emit(self, record: LogRecord) -> Any:
|
|
201
207
|
with capture_internal_exceptions():
|
|
202
208
|
self.format(record)
|
|
203
209
|
return self._emit(record)
|
|
204
210
|
|
|
205
|
-
def _emit(self, record):
|
|
206
|
-
# type: (LogRecord) -> None
|
|
211
|
+
def _emit(self, record: LogRecord) -> None:
|
|
207
212
|
if not self._can_record(record):
|
|
208
213
|
return
|
|
209
214
|
|
|
@@ -290,14 +295,12 @@ class BreadcrumbHandler(_BaseHandler):
|
|
|
290
295
|
Note that you do not have to use this class if the logging integration is enabled, which it is by default.
|
|
291
296
|
"""
|
|
292
297
|
|
|
293
|
-
def emit(self, record):
|
|
294
|
-
# type: (LogRecord) -> Any
|
|
298
|
+
def emit(self, record: LogRecord) -> Any:
|
|
295
299
|
with capture_internal_exceptions():
|
|
296
300
|
self.format(record)
|
|
297
301
|
return self._emit(record)
|
|
298
302
|
|
|
299
|
-
def _emit(self, record):
|
|
300
|
-
# type: (LogRecord) -> None
|
|
303
|
+
def _emit(self, record: LogRecord) -> None:
|
|
301
304
|
if not self._can_record(record):
|
|
302
305
|
return
|
|
303
306
|
|
|
@@ -305,8 +308,7 @@ class BreadcrumbHandler(_BaseHandler):
|
|
|
305
308
|
self._breadcrumb_from_record(record), hint={"log_record": record}
|
|
306
309
|
)
|
|
307
310
|
|
|
308
|
-
def _breadcrumb_from_record(self, record):
|
|
309
|
-
# type: (LogRecord) -> Dict[str, Any]
|
|
311
|
+
def _breadcrumb_from_record(self, record: LogRecord) -> Dict[str, Any]:
|
|
310
312
|
return {
|
|
311
313
|
"type": "log",
|
|
312
314
|
"level": self._logging_to_event_level(record),
|
|
@@ -317,21 +319,6 @@ class BreadcrumbHandler(_BaseHandler):
|
|
|
317
319
|
}
|
|
318
320
|
|
|
319
321
|
|
|
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
|
-
|
|
335
322
|
class SentryLogsHandler(_BaseHandler):
|
|
336
323
|
"""
|
|
337
324
|
A logging handler that records Sentry logs for each Python log record.
|
|
@@ -339,8 +326,7 @@ class SentryLogsHandler(_BaseHandler):
|
|
|
339
326
|
Note that you do not have to use this class if the logging integration is enabled, which it is by default.
|
|
340
327
|
"""
|
|
341
328
|
|
|
342
|
-
def emit(self, record):
|
|
343
|
-
# type: (LogRecord) -> Any
|
|
329
|
+
def emit(self, record: LogRecord) -> Any:
|
|
344
330
|
with capture_internal_exceptions():
|
|
345
331
|
self.format(record)
|
|
346
332
|
if not self._can_record(record):
|
|
@@ -353,30 +339,32 @@ class SentryLogsHandler(_BaseHandler):
|
|
|
353
339
|
if not client.options["_experiments"].get("enable_logs", False):
|
|
354
340
|
return
|
|
355
341
|
|
|
356
|
-
|
|
342
|
+
self._capture_log_from_record(client, record)
|
|
357
343
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
otel_severity_number, otel_severity_text = _python_level_to_otel(record.levelno)
|
|
344
|
+
def _capture_log_from_record(self, client: BaseClient, record: LogRecord) -> None:
|
|
345
|
+
otel_severity_number, otel_severity_text = _log_level_to_otel(
|
|
346
|
+
record.levelno, SEVERITY_TO_OTEL_SEVERITY
|
|
347
|
+
)
|
|
363
348
|
project_root = client.options["project_root"]
|
|
364
|
-
attrs =
|
|
365
|
-
|
|
366
|
-
} # type: dict[str, str | bool | float | int]
|
|
349
|
+
attrs: Any = self._extra_from_record(record)
|
|
350
|
+
attrs["sentry.origin"] = "auto.logger.log"
|
|
367
351
|
if isinstance(record.msg, str):
|
|
368
352
|
attrs["sentry.message.template"] = record.msg
|
|
369
353
|
if record.args is not None:
|
|
370
354
|
if isinstance(record.args, tuple):
|
|
371
355
|
for i, arg in enumerate(record.args):
|
|
372
|
-
attrs[f"sentry.message.
|
|
356
|
+
attrs[f"sentry.message.parameter.{i}"] = (
|
|
373
357
|
arg
|
|
374
|
-
if isinstance(arg, str)
|
|
375
|
-
or isinstance(arg, float)
|
|
376
|
-
or isinstance(arg, int)
|
|
377
|
-
or isinstance(arg, bool)
|
|
358
|
+
if isinstance(arg, (str, float, int, bool))
|
|
378
359
|
else safe_repr(arg)
|
|
379
360
|
)
|
|
361
|
+
elif isinstance(record.args, dict):
|
|
362
|
+
for key, value in record.args.items():
|
|
363
|
+
attrs[f"sentry.message.parameter.{key}"] = (
|
|
364
|
+
value
|
|
365
|
+
if isinstance(value, (str, float, int, bool))
|
|
366
|
+
else safe_repr(value)
|
|
367
|
+
)
|
|
380
368
|
if record.lineno:
|
|
381
369
|
attrs["code.line.number"] = record.lineno
|
|
382
370
|
if record.pathname:
|
|
@@ -401,7 +389,6 @@ class SentryLogsHandler(_BaseHandler):
|
|
|
401
389
|
|
|
402
390
|
# noinspection PyProtectedMember
|
|
403
391
|
client._capture_experimental_log(
|
|
404
|
-
scope,
|
|
405
392
|
{
|
|
406
393
|
"severity_text": otel_severity_text,
|
|
407
394
|
"severity_number": otel_severity_number,
|
|
@@ -1,22 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import enum
|
|
2
3
|
|
|
4
|
+
import sentry_sdk
|
|
3
5
|
from sentry_sdk.integrations import Integration, DidNotEnable
|
|
4
6
|
from sentry_sdk.integrations.logging import (
|
|
5
7
|
BreadcrumbHandler,
|
|
6
8
|
EventHandler,
|
|
7
9
|
_BaseHandler,
|
|
8
10
|
)
|
|
11
|
+
from sentry_sdk.logger import _log_level_to_otel
|
|
9
12
|
|
|
10
13
|
from typing import TYPE_CHECKING
|
|
11
14
|
|
|
12
15
|
if TYPE_CHECKING:
|
|
13
16
|
from logging import LogRecord
|
|
14
|
-
from typing import
|
|
17
|
+
from typing import Any, Optional
|
|
15
18
|
|
|
16
19
|
try:
|
|
17
20
|
import loguru
|
|
18
21
|
from loguru import logger
|
|
19
22
|
from loguru._defaults import LOGURU_FORMAT as DEFAULT_FORMAT
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from loguru import Message
|
|
20
26
|
except ImportError:
|
|
21
27
|
raise DidNotEnable("LOGURU is not installed")
|
|
22
28
|
|
|
@@ -31,6 +37,10 @@ class LoggingLevels(enum.IntEnum):
|
|
|
31
37
|
CRITICAL = 50
|
|
32
38
|
|
|
33
39
|
|
|
40
|
+
DEFAULT_LEVEL = LoggingLevels.INFO.value
|
|
41
|
+
DEFAULT_EVENT_LEVEL = LoggingLevels.ERROR.value
|
|
42
|
+
|
|
43
|
+
|
|
34
44
|
SENTRY_LEVEL_FROM_LOGURU_LEVEL = {
|
|
35
45
|
"TRACE": "DEBUG",
|
|
36
46
|
"DEBUG": "DEBUG",
|
|
@@ -41,61 +51,74 @@ SENTRY_LEVEL_FROM_LOGURU_LEVEL = {
|
|
|
41
51
|
"CRITICAL": "CRITICAL",
|
|
42
52
|
}
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
|
|
54
|
+
# Map Loguru level numbers to corresponding OTel level numbers
|
|
55
|
+
SEVERITY_TO_OTEL_SEVERITY = {
|
|
56
|
+
LoggingLevels.CRITICAL: 21, # fatal
|
|
57
|
+
LoggingLevels.ERROR: 17, # error
|
|
58
|
+
LoggingLevels.WARNING: 13, # warn
|
|
59
|
+
LoggingLevels.SUCCESS: 11, # info
|
|
60
|
+
LoggingLevels.INFO: 9, # info
|
|
61
|
+
LoggingLevels.DEBUG: 5, # debug
|
|
62
|
+
LoggingLevels.TRACE: 1, # trace
|
|
63
|
+
}
|
|
51
64
|
|
|
52
65
|
|
|
53
66
|
class LoguruIntegration(Integration):
|
|
54
67
|
identifier = "loguru"
|
|
55
68
|
|
|
69
|
+
level: Optional[int] = DEFAULT_LEVEL
|
|
70
|
+
event_level: Optional[int] = DEFAULT_EVENT_LEVEL
|
|
71
|
+
breadcrumb_format = DEFAULT_FORMAT
|
|
72
|
+
event_format = DEFAULT_FORMAT
|
|
73
|
+
sentry_logs_level: Optional[int] = DEFAULT_LEVEL
|
|
74
|
+
|
|
56
75
|
def __init__(
|
|
57
76
|
self,
|
|
58
|
-
level=DEFAULT_LEVEL,
|
|
59
|
-
event_level=DEFAULT_EVENT_LEVEL,
|
|
60
|
-
breadcrumb_format=DEFAULT_FORMAT,
|
|
61
|
-
event_format=DEFAULT_FORMAT,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
breadcrumb_handler = None
|
|
70
|
-
if event_handler is not None:
|
|
71
|
-
logger.remove(event_handler)
|
|
72
|
-
event_handler = None
|
|
73
|
-
|
|
74
|
-
if level is not None:
|
|
75
|
-
breadcrumb_handler = logger.add(
|
|
76
|
-
LoguruBreadcrumbHandler(level=level),
|
|
77
|
-
level=level,
|
|
78
|
-
format=breadcrumb_format,
|
|
79
|
-
)
|
|
77
|
+
level: Optional[int] = DEFAULT_LEVEL,
|
|
78
|
+
event_level: Optional[int] = DEFAULT_EVENT_LEVEL,
|
|
79
|
+
breadcrumb_format: str | loguru.FormatFunction = DEFAULT_FORMAT,
|
|
80
|
+
event_format: str | loguru.FormatFunction = DEFAULT_FORMAT,
|
|
81
|
+
sentry_logs_level: Optional[int] = DEFAULT_LEVEL,
|
|
82
|
+
) -> None:
|
|
83
|
+
LoguruIntegration.level = level
|
|
84
|
+
LoguruIntegration.event_level = event_level
|
|
85
|
+
LoguruIntegration.breadcrumb_format = breadcrumb_format
|
|
86
|
+
LoguruIntegration.event_format = event_format
|
|
87
|
+
LoguruIntegration.sentry_logs_level = sentry_logs_level
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
@staticmethod
|
|
90
|
+
def setup_once() -> None:
|
|
91
|
+
if LoguruIntegration.level is not None:
|
|
92
|
+
logger.add(
|
|
93
|
+
LoguruBreadcrumbHandler(level=LoguruIntegration.level),
|
|
94
|
+
level=LoguruIntegration.level,
|
|
95
|
+
format=LoguruIntegration.breadcrumb_format,
|
|
86
96
|
)
|
|
87
97
|
|
|
88
|
-
|
|
98
|
+
if LoguruIntegration.event_level is not None:
|
|
99
|
+
logger.add(
|
|
100
|
+
LoguruEventHandler(level=LoguruIntegration.event_level),
|
|
101
|
+
level=LoguruIntegration.event_level,
|
|
102
|
+
format=LoguruIntegration.event_format,
|
|
103
|
+
)
|
|
89
104
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
105
|
+
if LoguruIntegration.sentry_logs_level is not None:
|
|
106
|
+
logger.add(
|
|
107
|
+
loguru_sentry_logs_handler,
|
|
108
|
+
level=LoguruIntegration.sentry_logs_level,
|
|
109
|
+
)
|
|
94
110
|
|
|
95
111
|
|
|
96
112
|
class _LoguruBaseHandler(_BaseHandler):
|
|
97
|
-
def
|
|
98
|
-
|
|
113
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
114
|
+
if kwargs.get("level"):
|
|
115
|
+
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
|
|
116
|
+
kwargs.get("level", ""), DEFAULT_LEVEL
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
super().__init__(*args, **kwargs)
|
|
120
|
+
|
|
121
|
+
def _logging_to_event_level(self, record: LogRecord) -> str:
|
|
99
122
|
try:
|
|
100
123
|
return SENTRY_LEVEL_FROM_LOGURU_LEVEL[
|
|
101
124
|
LoggingLevels(record.levelno).name
|
|
@@ -107,24 +130,71 @@ class _LoguruBaseHandler(_BaseHandler):
|
|
|
107
130
|
class LoguruEventHandler(_LoguruBaseHandler, EventHandler):
|
|
108
131
|
"""Modified version of :class:`sentry_sdk.integrations.logging.EventHandler` to use loguru's level names."""
|
|
109
132
|
|
|
110
|
-
|
|
111
|
-
# type: (*Any, **Any) -> None
|
|
112
|
-
if kwargs.get("level"):
|
|
113
|
-
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
|
|
114
|
-
kwargs.get("level", ""), DEFAULT_LEVEL
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
super().__init__(*args, **kwargs)
|
|
133
|
+
pass
|
|
118
134
|
|
|
119
135
|
|
|
120
136
|
class LoguruBreadcrumbHandler(_LoguruBaseHandler, BreadcrumbHandler):
|
|
121
137
|
"""Modified version of :class:`sentry_sdk.integrations.logging.BreadcrumbHandler` to use loguru's level names."""
|
|
122
138
|
|
|
123
|
-
|
|
124
|
-
# type: (*Any, **Any) -> None
|
|
125
|
-
if kwargs.get("level"):
|
|
126
|
-
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
|
|
127
|
-
kwargs.get("level", ""), DEFAULT_LEVEL
|
|
128
|
-
)
|
|
139
|
+
pass
|
|
129
140
|
|
|
130
|
-
|
|
141
|
+
|
|
142
|
+
def loguru_sentry_logs_handler(message: Message) -> None:
|
|
143
|
+
# This is intentionally a callable sink instead of a standard logging handler
|
|
144
|
+
# since otherwise we wouldn't get direct access to message.record
|
|
145
|
+
client = sentry_sdk.get_client()
|
|
146
|
+
|
|
147
|
+
if not client.is_active():
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
if not client.options["_experiments"].get("enable_logs", False):
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
record = message.record
|
|
154
|
+
|
|
155
|
+
if (
|
|
156
|
+
LoguruIntegration.sentry_logs_level is None
|
|
157
|
+
or record["level"].no < LoguruIntegration.sentry_logs_level
|
|
158
|
+
):
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
otel_severity_number, otel_severity_text = _log_level_to_otel(
|
|
162
|
+
record["level"].no, SEVERITY_TO_OTEL_SEVERITY
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
attrs: dict[str, Any] = {"sentry.origin": "auto.logger.loguru"}
|
|
166
|
+
|
|
167
|
+
project_root = client.options["project_root"]
|
|
168
|
+
if record.get("file"):
|
|
169
|
+
if project_root is not None and record["file"].path.startswith(project_root):
|
|
170
|
+
attrs["code.file.path"] = record["file"].path[len(project_root) + 1 :]
|
|
171
|
+
else:
|
|
172
|
+
attrs["code.file.path"] = record["file"].path
|
|
173
|
+
|
|
174
|
+
if record.get("line") is not None:
|
|
175
|
+
attrs["code.line.number"] = record["line"]
|
|
176
|
+
|
|
177
|
+
if record.get("function"):
|
|
178
|
+
attrs["code.function.name"] = record["function"]
|
|
179
|
+
|
|
180
|
+
if record.get("thread"):
|
|
181
|
+
attrs["thread.name"] = record["thread"].name
|
|
182
|
+
attrs["thread.id"] = record["thread"].id
|
|
183
|
+
|
|
184
|
+
if record.get("process"):
|
|
185
|
+
attrs["process.pid"] = record["process"].id
|
|
186
|
+
attrs["process.executable.name"] = record["process"].name
|
|
187
|
+
|
|
188
|
+
if record.get("name"):
|
|
189
|
+
attrs["logger.name"] = record["name"]
|
|
190
|
+
|
|
191
|
+
client._capture_experimental_log(
|
|
192
|
+
{
|
|
193
|
+
"severity_text": otel_severity_text,
|
|
194
|
+
"severity_number": otel_severity_number,
|
|
195
|
+
"body": record["message"],
|
|
196
|
+
"attributes": attrs,
|
|
197
|
+
"time_unix_nano": int(record["time"].timestamp() * 1e9),
|
|
198
|
+
"trace_id": None,
|
|
199
|
+
}
|
|
200
|
+
)
|