sentry-sdk 0.7.5__py2.py3-none-any.whl → 2.46.0__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.
- sentry_sdk/__init__.py +48 -30
- sentry_sdk/_compat.py +74 -61
- sentry_sdk/_init_implementation.py +84 -0
- sentry_sdk/_log_batcher.py +172 -0
- sentry_sdk/_lru_cache.py +47 -0
- sentry_sdk/_metrics_batcher.py +167 -0
- sentry_sdk/_queue.py +289 -0
- sentry_sdk/_types.py +338 -0
- sentry_sdk/_werkzeug.py +98 -0
- sentry_sdk/ai/__init__.py +7 -0
- sentry_sdk/ai/monitoring.py +137 -0
- sentry_sdk/ai/utils.py +144 -0
- sentry_sdk/api.py +496 -80
- sentry_sdk/attachments.py +75 -0
- sentry_sdk/client.py +1023 -103
- sentry_sdk/consts.py +1438 -66
- sentry_sdk/crons/__init__.py +10 -0
- sentry_sdk/crons/api.py +62 -0
- sentry_sdk/crons/consts.py +4 -0
- sentry_sdk/crons/decorator.py +135 -0
- sentry_sdk/debug.py +15 -14
- sentry_sdk/envelope.py +369 -0
- sentry_sdk/feature_flags.py +71 -0
- sentry_sdk/hub.py +611 -280
- sentry_sdk/integrations/__init__.py +276 -49
- sentry_sdk/integrations/_asgi_common.py +108 -0
- sentry_sdk/integrations/_wsgi_common.py +180 -44
- sentry_sdk/integrations/aiohttp.py +291 -42
- sentry_sdk/integrations/anthropic.py +439 -0
- sentry_sdk/integrations/argv.py +9 -8
- sentry_sdk/integrations/ariadne.py +161 -0
- sentry_sdk/integrations/arq.py +247 -0
- sentry_sdk/integrations/asgi.py +341 -0
- sentry_sdk/integrations/asyncio.py +144 -0
- sentry_sdk/integrations/asyncpg.py +208 -0
- sentry_sdk/integrations/atexit.py +17 -10
- sentry_sdk/integrations/aws_lambda.py +377 -62
- sentry_sdk/integrations/beam.py +176 -0
- sentry_sdk/integrations/boto3.py +137 -0
- sentry_sdk/integrations/bottle.py +221 -0
- sentry_sdk/integrations/celery/__init__.py +529 -0
- sentry_sdk/integrations/celery/beat.py +293 -0
- sentry_sdk/integrations/celery/utils.py +43 -0
- sentry_sdk/integrations/chalice.py +134 -0
- sentry_sdk/integrations/clickhouse_driver.py +177 -0
- sentry_sdk/integrations/cloud_resource_context.py +280 -0
- sentry_sdk/integrations/cohere.py +274 -0
- sentry_sdk/integrations/dedupe.py +48 -14
- sentry_sdk/integrations/django/__init__.py +584 -191
- sentry_sdk/integrations/django/asgi.py +245 -0
- sentry_sdk/integrations/django/caching.py +204 -0
- sentry_sdk/integrations/django/middleware.py +187 -0
- sentry_sdk/integrations/django/signals_handlers.py +91 -0
- sentry_sdk/integrations/django/templates.py +79 -5
- sentry_sdk/integrations/django/transactions.py +49 -22
- sentry_sdk/integrations/django/views.py +96 -0
- sentry_sdk/integrations/dramatiq.py +226 -0
- sentry_sdk/integrations/excepthook.py +50 -13
- sentry_sdk/integrations/executing.py +67 -0
- sentry_sdk/integrations/falcon.py +272 -0
- sentry_sdk/integrations/fastapi.py +141 -0
- sentry_sdk/integrations/flask.py +142 -88
- sentry_sdk/integrations/gcp.py +239 -0
- sentry_sdk/integrations/gnu_backtrace.py +99 -0
- sentry_sdk/integrations/google_genai/__init__.py +301 -0
- sentry_sdk/integrations/google_genai/consts.py +16 -0
- sentry_sdk/integrations/google_genai/streaming.py +155 -0
- sentry_sdk/integrations/google_genai/utils.py +576 -0
- sentry_sdk/integrations/gql.py +162 -0
- sentry_sdk/integrations/graphene.py +151 -0
- sentry_sdk/integrations/grpc/__init__.py +168 -0
- sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
- sentry_sdk/integrations/grpc/aio/client.py +95 -0
- sentry_sdk/integrations/grpc/aio/server.py +100 -0
- sentry_sdk/integrations/grpc/client.py +91 -0
- sentry_sdk/integrations/grpc/consts.py +1 -0
- sentry_sdk/integrations/grpc/server.py +66 -0
- sentry_sdk/integrations/httpx.py +178 -0
- sentry_sdk/integrations/huey.py +174 -0
- sentry_sdk/integrations/huggingface_hub.py +378 -0
- sentry_sdk/integrations/langchain.py +1132 -0
- sentry_sdk/integrations/langgraph.py +337 -0
- sentry_sdk/integrations/launchdarkly.py +61 -0
- sentry_sdk/integrations/litellm.py +287 -0
- sentry_sdk/integrations/litestar.py +315 -0
- sentry_sdk/integrations/logging.py +307 -96
- sentry_sdk/integrations/loguru.py +213 -0
- sentry_sdk/integrations/mcp.py +566 -0
- sentry_sdk/integrations/modules.py +14 -31
- sentry_sdk/integrations/openai.py +725 -0
- sentry_sdk/integrations/openai_agents/__init__.py +61 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
- sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
- sentry_sdk/integrations/openai_agents/utils.py +199 -0
- sentry_sdk/integrations/openfeature.py +35 -0
- sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
- sentry_sdk/integrations/opentelemetry/consts.py +5 -0
- sentry_sdk/integrations/opentelemetry/integration.py +58 -0
- sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
- sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
- sentry_sdk/integrations/otlp.py +82 -0
- sentry_sdk/integrations/pure_eval.py +141 -0
- sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
- sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
- sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
- sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
- sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
- sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
- sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
- sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
- sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
- sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
- sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
- sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
- sentry_sdk/integrations/pymongo.py +214 -0
- sentry_sdk/integrations/pyramid.py +112 -68
- sentry_sdk/integrations/quart.py +237 -0
- sentry_sdk/integrations/ray.py +165 -0
- sentry_sdk/integrations/redis/__init__.py +48 -0
- sentry_sdk/integrations/redis/_async_common.py +116 -0
- sentry_sdk/integrations/redis/_sync_common.py +119 -0
- sentry_sdk/integrations/redis/consts.py +19 -0
- sentry_sdk/integrations/redis/modules/__init__.py +0 -0
- sentry_sdk/integrations/redis/modules/caches.py +118 -0
- sentry_sdk/integrations/redis/modules/queries.py +65 -0
- sentry_sdk/integrations/redis/rb.py +32 -0
- sentry_sdk/integrations/redis/redis.py +69 -0
- sentry_sdk/integrations/redis/redis_cluster.py +107 -0
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
- sentry_sdk/integrations/redis/utils.py +148 -0
- sentry_sdk/integrations/rq.py +95 -37
- sentry_sdk/integrations/rust_tracing.py +284 -0
- sentry_sdk/integrations/sanic.py +294 -123
- sentry_sdk/integrations/serverless.py +48 -19
- sentry_sdk/integrations/socket.py +96 -0
- sentry_sdk/integrations/spark/__init__.py +4 -0
- sentry_sdk/integrations/spark/spark_driver.py +316 -0
- sentry_sdk/integrations/spark/spark_worker.py +116 -0
- sentry_sdk/integrations/sqlalchemy.py +142 -0
- sentry_sdk/integrations/starlette.py +737 -0
- sentry_sdk/integrations/starlite.py +292 -0
- sentry_sdk/integrations/statsig.py +37 -0
- sentry_sdk/integrations/stdlib.py +235 -29
- sentry_sdk/integrations/strawberry.py +394 -0
- sentry_sdk/integrations/sys_exit.py +70 -0
- sentry_sdk/integrations/threading.py +158 -28
- sentry_sdk/integrations/tornado.py +84 -52
- sentry_sdk/integrations/trytond.py +50 -0
- sentry_sdk/integrations/typer.py +60 -0
- sentry_sdk/integrations/unleash.py +33 -0
- sentry_sdk/integrations/unraisablehook.py +53 -0
- sentry_sdk/integrations/wsgi.py +201 -119
- sentry_sdk/logger.py +96 -0
- sentry_sdk/metrics.py +81 -0
- sentry_sdk/monitor.py +120 -0
- sentry_sdk/profiler/__init__.py +49 -0
- sentry_sdk/profiler/continuous_profiler.py +730 -0
- sentry_sdk/profiler/transaction_profiler.py +839 -0
- sentry_sdk/profiler/utils.py +195 -0
- sentry_sdk/py.typed +0 -0
- sentry_sdk/scope.py +1713 -85
- sentry_sdk/scrubber.py +177 -0
- sentry_sdk/serializer.py +405 -0
- sentry_sdk/session.py +177 -0
- sentry_sdk/sessions.py +275 -0
- sentry_sdk/spotlight.py +242 -0
- sentry_sdk/tracing.py +1486 -0
- sentry_sdk/tracing_utils.py +1236 -0
- sentry_sdk/transport.py +806 -134
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +1625 -465
- sentry_sdk/worker.py +54 -25
- sentry_sdk-2.46.0.dist-info/METADATA +268 -0
- sentry_sdk-2.46.0.dist-info/RECORD +189 -0
- {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
- sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
- sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
- sentry_sdk/integrations/celery.py +0 -119
- sentry_sdk-0.7.5.dist-info/LICENSE +0 -9
- sentry_sdk-0.7.5.dist-info/METADATA +0 -36
- sentry_sdk-0.7.5.dist-info/RECORD +0 -39
- {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
from __future__ import absolute_import
|
|
2
|
-
|
|
3
1
|
import logging
|
|
4
|
-
import
|
|
2
|
+
import sys
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from fnmatch import fnmatch
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import sentry_sdk
|
|
7
|
+
from sentry_sdk.client import BaseClient
|
|
8
|
+
from sentry_sdk.logger import _log_level_to_otel
|
|
7
9
|
from sentry_sdk.utils import (
|
|
10
|
+
safe_repr,
|
|
8
11
|
to_string,
|
|
9
12
|
event_from_exception,
|
|
10
13
|
current_stacktrace,
|
|
11
14
|
capture_internal_exceptions,
|
|
15
|
+
has_logs_enabled,
|
|
12
16
|
)
|
|
13
17
|
from sentry_sdk.integrations import Integration
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import MutableMapping
|
|
16
23
|
from logging import LogRecord
|
|
17
24
|
from typing import Any
|
|
18
25
|
from typing import Dict
|
|
@@ -20,15 +27,48 @@ if False:
|
|
|
20
27
|
|
|
21
28
|
DEFAULT_LEVEL = logging.INFO
|
|
22
29
|
DEFAULT_EVENT_LEVEL = logging.ERROR
|
|
30
|
+
LOGGING_TO_EVENT_LEVEL = {
|
|
31
|
+
logging.NOTSET: "notset",
|
|
32
|
+
logging.DEBUG: "debug",
|
|
33
|
+
logging.INFO: "info",
|
|
34
|
+
logging.WARN: "warning", # WARN is same a WARNING
|
|
35
|
+
logging.WARNING: "warning",
|
|
36
|
+
logging.ERROR: "error",
|
|
37
|
+
logging.FATAL: "fatal",
|
|
38
|
+
logging.CRITICAL: "fatal", # CRITICAL is same as FATAL
|
|
39
|
+
}
|
|
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
|
+
|
|
51
|
+
# Capturing events from those loggers causes recursion errors. We cannot allow
|
|
52
|
+
# the user to unconditionally create events from those loggers under any
|
|
53
|
+
# circumstances.
|
|
54
|
+
#
|
|
55
|
+
# Note: Ignoring by logger name here is better than mucking with thread-locals.
|
|
56
|
+
# We do not necessarily know whether thread-locals work 100% correctly in the user's environment.
|
|
57
|
+
_IGNORED_LOGGERS = set(
|
|
58
|
+
["sentry_sdk.errors", "urllib3.connectionpool", "urllib3.connection"]
|
|
59
|
+
)
|
|
23
60
|
|
|
24
|
-
_IGNORED_LOGGERS = set(["sentry_sdk.errors"])
|
|
25
61
|
|
|
62
|
+
def ignore_logger(
|
|
63
|
+
name, # type: str
|
|
64
|
+
):
|
|
65
|
+
# type: (...) -> None
|
|
66
|
+
"""This disables recording (both in breadcrumbs and as events) calls to
|
|
67
|
+
a logger of a specific name. Among other uses, many of our integrations
|
|
68
|
+
use this to prevent their actions being recorded as breadcrumbs. Exposed
|
|
69
|
+
to users as a way to quiet spammy loggers.
|
|
26
70
|
|
|
27
|
-
|
|
28
|
-
# type: (str) -> None
|
|
29
|
-
"""This disables the breadcrumb integration for a logger of a specific
|
|
30
|
-
name. This primary use is for some integrations to disable breadcrumbs
|
|
31
|
-
of this integration.
|
|
71
|
+
:param name: The name of the logger to ignore (same string you would pass to ``logging.getLogger``).
|
|
32
72
|
"""
|
|
33
73
|
_IGNORED_LOGGERS.add(name)
|
|
34
74
|
|
|
@@ -36,14 +76,23 @@ def ignore_logger(name):
|
|
|
36
76
|
class LoggingIntegration(Integration):
|
|
37
77
|
identifier = "logging"
|
|
38
78
|
|
|
39
|
-
def __init__(
|
|
40
|
-
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
level=DEFAULT_LEVEL,
|
|
82
|
+
event_level=DEFAULT_EVENT_LEVEL,
|
|
83
|
+
sentry_logs_level=DEFAULT_LEVEL,
|
|
84
|
+
):
|
|
85
|
+
# type: (Optional[int], Optional[int], Optional[int]) -> None
|
|
41
86
|
self._handler = None
|
|
42
87
|
self._breadcrumb_handler = None
|
|
88
|
+
self._sentry_logs_handler = None
|
|
43
89
|
|
|
44
90
|
if level is not None:
|
|
45
91
|
self._breadcrumb_handler = BreadcrumbHandler(level=level)
|
|
46
92
|
|
|
93
|
+
if sentry_logs_level is not None:
|
|
94
|
+
self._sentry_logs_handler = SentryLogsHandler(level=sentry_logs_level)
|
|
95
|
+
|
|
47
96
|
if event_level is not None:
|
|
48
97
|
self._handler = EventHandler(level=event_level)
|
|
49
98
|
|
|
@@ -58,13 +107,23 @@ class LoggingIntegration(Integration):
|
|
|
58
107
|
):
|
|
59
108
|
self._breadcrumb_handler.handle(record)
|
|
60
109
|
|
|
110
|
+
if (
|
|
111
|
+
self._sentry_logs_handler is not None
|
|
112
|
+
and record.levelno >= self._sentry_logs_handler.level
|
|
113
|
+
):
|
|
114
|
+
self._sentry_logs_handler.handle(record)
|
|
115
|
+
|
|
61
116
|
@staticmethod
|
|
62
117
|
def setup_once():
|
|
63
118
|
# type: () -> None
|
|
64
|
-
old_callhandlers = logging.Logger.callHandlers
|
|
119
|
+
old_callhandlers = logging.Logger.callHandlers
|
|
65
120
|
|
|
66
121
|
def sentry_patched_callhandlers(self, record):
|
|
67
122
|
# type: (Any, LogRecord) -> Any
|
|
123
|
+
# keeping a local reference because the
|
|
124
|
+
# global might be discarded on shutdown
|
|
125
|
+
ignored_loggers = _IGNORED_LOGGERS
|
|
126
|
+
|
|
68
127
|
try:
|
|
69
128
|
return old_callhandlers(self, record)
|
|
70
129
|
finally:
|
|
@@ -72,76 +131,81 @@ class LoggingIntegration(Integration):
|
|
|
72
131
|
# the integration. Otherwise we have a high chance of getting
|
|
73
132
|
# into a recursion error when the integration is resolved
|
|
74
133
|
# (this also is slower).
|
|
75
|
-
if
|
|
76
|
-
|
|
134
|
+
if (
|
|
135
|
+
ignored_loggers is not None
|
|
136
|
+
and record.name.strip() not in ignored_loggers
|
|
137
|
+
):
|
|
138
|
+
integration = sentry_sdk.get_client().get_integration(
|
|
139
|
+
LoggingIntegration
|
|
140
|
+
)
|
|
77
141
|
if integration is not None:
|
|
78
142
|
integration._handle_record(record)
|
|
79
143
|
|
|
80
144
|
logging.Logger.callHandlers = sentry_patched_callhandlers # type: ignore
|
|
81
145
|
|
|
82
146
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
"filename",
|
|
113
|
-
"funcName",
|
|
114
|
-
"levelname",
|
|
115
|
-
"levelno",
|
|
116
|
-
"linenno",
|
|
117
|
-
"lineno",
|
|
118
|
-
"message",
|
|
119
|
-
"module",
|
|
120
|
-
"msecs",
|
|
121
|
-
"msg",
|
|
122
|
-
"name",
|
|
123
|
-
"pathname",
|
|
124
|
-
"process",
|
|
125
|
-
"processName",
|
|
126
|
-
"relativeCreated",
|
|
127
|
-
"stack",
|
|
128
|
-
"tags",
|
|
129
|
-
"thread",
|
|
130
|
-
"threadName",
|
|
147
|
+
class _BaseHandler(logging.Handler):
|
|
148
|
+
COMMON_RECORD_ATTRS = frozenset(
|
|
149
|
+
(
|
|
150
|
+
"args",
|
|
151
|
+
"created",
|
|
152
|
+
"exc_info",
|
|
153
|
+
"exc_text",
|
|
154
|
+
"filename",
|
|
155
|
+
"funcName",
|
|
156
|
+
"levelname",
|
|
157
|
+
"levelno",
|
|
158
|
+
"linenno",
|
|
159
|
+
"lineno",
|
|
160
|
+
"message",
|
|
161
|
+
"module",
|
|
162
|
+
"msecs",
|
|
163
|
+
"msg",
|
|
164
|
+
"name",
|
|
165
|
+
"pathname",
|
|
166
|
+
"process",
|
|
167
|
+
"processName",
|
|
168
|
+
"relativeCreated",
|
|
169
|
+
"stack",
|
|
170
|
+
"tags",
|
|
171
|
+
"taskName",
|
|
172
|
+
"thread",
|
|
173
|
+
"threadName",
|
|
174
|
+
"stack_info",
|
|
175
|
+
)
|
|
131
176
|
)
|
|
132
|
-
|
|
177
|
+
|
|
178
|
+
def _can_record(self, record):
|
|
179
|
+
# type: (LogRecord) -> bool
|
|
180
|
+
"""Prevents ignored loggers from recording"""
|
|
181
|
+
for logger in _IGNORED_LOGGERS:
|
|
182
|
+
if fnmatch(record.name.strip(), logger):
|
|
183
|
+
return False
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
def _logging_to_event_level(self, record):
|
|
187
|
+
# type: (LogRecord) -> str
|
|
188
|
+
return LOGGING_TO_EVENT_LEVEL.get(
|
|
189
|
+
record.levelno, record.levelname.lower() if record.levelname else ""
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def _extra_from_record(self, record):
|
|
193
|
+
# type: (LogRecord) -> MutableMapping[str, object]
|
|
194
|
+
return {
|
|
195
|
+
k: v
|
|
196
|
+
for k, v in vars(record).items()
|
|
197
|
+
if k not in self.COMMON_RECORD_ATTRS
|
|
198
|
+
and (not isinstance(k, str) or not k.startswith("_"))
|
|
199
|
+
}
|
|
133
200
|
|
|
134
201
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
k: v
|
|
139
|
-
for k, v in vars(record).items()
|
|
140
|
-
if k not in COMMON_RECORD_ATTRS and not k.startswith("_")
|
|
141
|
-
}
|
|
202
|
+
class EventHandler(_BaseHandler):
|
|
203
|
+
"""
|
|
204
|
+
A logging handler that emits Sentry events for each log record
|
|
142
205
|
|
|
206
|
+
Note that you do not have to use this class if the logging integration is enabled, which it is by default.
|
|
207
|
+
"""
|
|
143
208
|
|
|
144
|
-
class EventHandler(logging.Handler, object):
|
|
145
209
|
def emit(self, record):
|
|
146
210
|
# type: (LogRecord) -> Any
|
|
147
211
|
with capture_internal_exceptions():
|
|
@@ -150,50 +214,92 @@ class EventHandler(logging.Handler, object):
|
|
|
150
214
|
|
|
151
215
|
def _emit(self, record):
|
|
152
216
|
# type: (LogRecord) -> None
|
|
153
|
-
if not _can_record(record):
|
|
217
|
+
if not self._can_record(record):
|
|
154
218
|
return
|
|
155
219
|
|
|
156
|
-
|
|
157
|
-
if
|
|
220
|
+
client = sentry_sdk.get_client()
|
|
221
|
+
if not client.is_active():
|
|
158
222
|
return
|
|
159
223
|
|
|
160
|
-
|
|
161
|
-
client_options = hub.client.options
|
|
224
|
+
client_options = client.options
|
|
162
225
|
|
|
163
226
|
# exc_info might be None or (None, None, None)
|
|
164
|
-
|
|
227
|
+
#
|
|
228
|
+
# exc_info may also be any falsy value due to Python stdlib being
|
|
229
|
+
# liberal with what it receives and Celery's billiard being "liberal"
|
|
230
|
+
# with what it sends. See
|
|
231
|
+
# https://github.com/getsentry/sentry-python/issues/904
|
|
232
|
+
if record.exc_info and record.exc_info[0] is not None:
|
|
165
233
|
event, hint = event_from_exception(
|
|
166
234
|
record.exc_info,
|
|
167
235
|
client_options=client_options,
|
|
168
236
|
mechanism={"type": "logging", "handled": True},
|
|
169
237
|
)
|
|
170
|
-
elif record.exc_info and record.exc_info[0] is None:
|
|
238
|
+
elif (record.exc_info and record.exc_info[0] is None) or record.stack_info:
|
|
171
239
|
event = {}
|
|
172
|
-
hint =
|
|
240
|
+
hint = {}
|
|
173
241
|
with capture_internal_exceptions():
|
|
174
|
-
event["threads"] =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
242
|
+
event["threads"] = {
|
|
243
|
+
"values": [
|
|
244
|
+
{
|
|
245
|
+
"stacktrace": current_stacktrace(
|
|
246
|
+
include_local_variables=client_options[
|
|
247
|
+
"include_local_variables"
|
|
248
|
+
],
|
|
249
|
+
max_value_length=client_options["max_value_length"],
|
|
250
|
+
),
|
|
251
|
+
"crashed": False,
|
|
252
|
+
"current": True,
|
|
253
|
+
}
|
|
254
|
+
]
|
|
255
|
+
}
|
|
181
256
|
else:
|
|
182
257
|
event = {}
|
|
258
|
+
hint = {}
|
|
259
|
+
|
|
260
|
+
hint["log_record"] = record
|
|
183
261
|
|
|
184
|
-
|
|
262
|
+
level = self._logging_to_event_level(record)
|
|
263
|
+
if level in {"debug", "info", "warning", "error", "critical", "fatal"}:
|
|
264
|
+
event["level"] = level # type: ignore[typeddict-item]
|
|
185
265
|
event["logger"] = record.name
|
|
186
|
-
event["logentry"] = {"message": to_string(record.msg), "params": record.args}
|
|
187
|
-
event["extra"] = _extra_from_record(record)
|
|
188
266
|
|
|
189
|
-
|
|
267
|
+
if (
|
|
268
|
+
sys.version_info < (3, 11)
|
|
269
|
+
and record.name == "py.warnings"
|
|
270
|
+
and record.msg == "%s"
|
|
271
|
+
):
|
|
272
|
+
# warnings module on Python 3.10 and below sets record.msg to "%s"
|
|
273
|
+
# and record.args[0] to the actual warning message.
|
|
274
|
+
# This was fixed in https://github.com/python/cpython/pull/30975.
|
|
275
|
+
message = record.args[0]
|
|
276
|
+
params = ()
|
|
277
|
+
else:
|
|
278
|
+
message = record.msg
|
|
279
|
+
params = record.args
|
|
280
|
+
|
|
281
|
+
event["logentry"] = {
|
|
282
|
+
"message": to_string(message),
|
|
283
|
+
"formatted": record.getMessage(),
|
|
284
|
+
"params": params,
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
event["extra"] = self._extra_from_record(record)
|
|
288
|
+
|
|
289
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
190
290
|
|
|
191
291
|
|
|
192
292
|
# Legacy name
|
|
193
293
|
SentryHandler = EventHandler
|
|
194
294
|
|
|
195
295
|
|
|
196
|
-
class BreadcrumbHandler(
|
|
296
|
+
class BreadcrumbHandler(_BaseHandler):
|
|
297
|
+
"""
|
|
298
|
+
A logging handler that records breadcrumbs for each log record.
|
|
299
|
+
|
|
300
|
+
Note that you do not have to use this class if the logging integration is enabled, which it is by default.
|
|
301
|
+
"""
|
|
302
|
+
|
|
197
303
|
def emit(self, record):
|
|
198
304
|
# type: (LogRecord) -> Any
|
|
199
305
|
with capture_internal_exceptions():
|
|
@@ -202,9 +308,114 @@ class BreadcrumbHandler(logging.Handler, object):
|
|
|
202
308
|
|
|
203
309
|
def _emit(self, record):
|
|
204
310
|
# type: (LogRecord) -> None
|
|
205
|
-
if not _can_record(record):
|
|
311
|
+
if not self._can_record(record):
|
|
206
312
|
return
|
|
207
313
|
|
|
208
|
-
|
|
209
|
-
_breadcrumb_from_record(record), hint={"log_record": record}
|
|
314
|
+
sentry_sdk.add_breadcrumb(
|
|
315
|
+
self._breadcrumb_from_record(record), hint={"log_record": record}
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
def _breadcrumb_from_record(self, record):
|
|
319
|
+
# type: (LogRecord) -> Dict[str, Any]
|
|
320
|
+
return {
|
|
321
|
+
"type": "log",
|
|
322
|
+
"level": self._logging_to_event_level(record),
|
|
323
|
+
"category": record.name,
|
|
324
|
+
"message": record.message,
|
|
325
|
+
"timestamp": datetime.fromtimestamp(record.created, timezone.utc),
|
|
326
|
+
"data": self._extra_from_record(record),
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class SentryLogsHandler(_BaseHandler):
|
|
331
|
+
"""
|
|
332
|
+
A logging handler that records Sentry logs for each Python log record.
|
|
333
|
+
|
|
334
|
+
Note that you do not have to use this class if the logging integration is enabled, which it is by default.
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
def emit(self, record):
|
|
338
|
+
# type: (LogRecord) -> Any
|
|
339
|
+
with capture_internal_exceptions():
|
|
340
|
+
self.format(record)
|
|
341
|
+
if not self._can_record(record):
|
|
342
|
+
return
|
|
343
|
+
|
|
344
|
+
client = sentry_sdk.get_client()
|
|
345
|
+
if not client.is_active():
|
|
346
|
+
return
|
|
347
|
+
|
|
348
|
+
if not has_logs_enabled(client.options):
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
self._capture_log_from_record(client, record)
|
|
352
|
+
|
|
353
|
+
def _capture_log_from_record(self, client, record):
|
|
354
|
+
# type: (BaseClient, LogRecord) -> None
|
|
355
|
+
otel_severity_number, otel_severity_text = _log_level_to_otel(
|
|
356
|
+
record.levelno, SEVERITY_TO_OTEL_SEVERITY
|
|
357
|
+
)
|
|
358
|
+
project_root = client.options["project_root"]
|
|
359
|
+
|
|
360
|
+
attrs = self._extra_from_record(record) # type: Any
|
|
361
|
+
attrs["sentry.origin"] = "auto.log.stdlib"
|
|
362
|
+
|
|
363
|
+
parameters_set = False
|
|
364
|
+
if record.args is not None:
|
|
365
|
+
if isinstance(record.args, tuple):
|
|
366
|
+
parameters_set = bool(record.args)
|
|
367
|
+
for i, arg in enumerate(record.args):
|
|
368
|
+
attrs[f"sentry.message.parameter.{i}"] = (
|
|
369
|
+
arg
|
|
370
|
+
if isinstance(arg, (str, float, int, bool))
|
|
371
|
+
else safe_repr(arg)
|
|
372
|
+
)
|
|
373
|
+
elif isinstance(record.args, dict):
|
|
374
|
+
parameters_set = bool(record.args)
|
|
375
|
+
for key, value in record.args.items():
|
|
376
|
+
attrs[f"sentry.message.parameter.{key}"] = (
|
|
377
|
+
value
|
|
378
|
+
if isinstance(value, (str, float, int, bool))
|
|
379
|
+
else safe_repr(value)
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
if parameters_set and isinstance(record.msg, str):
|
|
383
|
+
# only include template if there is at least one
|
|
384
|
+
# sentry.message.parameter.X set
|
|
385
|
+
attrs["sentry.message.template"] = record.msg
|
|
386
|
+
|
|
387
|
+
if record.lineno:
|
|
388
|
+
attrs["code.line.number"] = record.lineno
|
|
389
|
+
|
|
390
|
+
if record.pathname:
|
|
391
|
+
if project_root is not None and record.pathname.startswith(project_root):
|
|
392
|
+
attrs["code.file.path"] = record.pathname[len(project_root) + 1 :]
|
|
393
|
+
else:
|
|
394
|
+
attrs["code.file.path"] = record.pathname
|
|
395
|
+
|
|
396
|
+
if record.funcName:
|
|
397
|
+
attrs["code.function.name"] = record.funcName
|
|
398
|
+
|
|
399
|
+
if record.thread:
|
|
400
|
+
attrs["thread.id"] = record.thread
|
|
401
|
+
if record.threadName:
|
|
402
|
+
attrs["thread.name"] = record.threadName
|
|
403
|
+
|
|
404
|
+
if record.process:
|
|
405
|
+
attrs["process.pid"] = record.process
|
|
406
|
+
if record.processName:
|
|
407
|
+
attrs["process.executable.name"] = record.processName
|
|
408
|
+
if record.name:
|
|
409
|
+
attrs["logger.name"] = record.name
|
|
410
|
+
|
|
411
|
+
# noinspection PyProtectedMember
|
|
412
|
+
client._capture_log(
|
|
413
|
+
{
|
|
414
|
+
"severity_text": otel_severity_text,
|
|
415
|
+
"severity_number": otel_severity_number,
|
|
416
|
+
"body": record.message,
|
|
417
|
+
"attributes": attrs,
|
|
418
|
+
"time_unix_nano": int(record.created * 1e9),
|
|
419
|
+
"trace_id": None,
|
|
420
|
+
},
|
|
210
421
|
)
|