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
sentry_sdk/client.py
CHANGED
|
@@ -1,132 +1,601 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import uuid
|
|
3
3
|
import random
|
|
4
|
-
|
|
4
|
+
import socket
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from importlib import import_module
|
|
8
|
+
from typing import TYPE_CHECKING, List, Dict, cast, overload
|
|
9
|
+
import warnings
|
|
5
10
|
|
|
6
|
-
|
|
11
|
+
import sentry_sdk
|
|
12
|
+
from sentry_sdk._compat import PY37, check_uwsgi_thread_support
|
|
13
|
+
from sentry_sdk._metrics_batcher import MetricsBatcher
|
|
7
14
|
from sentry_sdk.utils import (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
convert_types,
|
|
11
|
-
handle_in_app,
|
|
12
|
-
get_type_name,
|
|
15
|
+
AnnotatedValue,
|
|
16
|
+
ContextVar,
|
|
13
17
|
capture_internal_exceptions,
|
|
14
18
|
current_stacktrace,
|
|
19
|
+
env_to_bool,
|
|
20
|
+
format_timestamp,
|
|
21
|
+
get_sdk_name,
|
|
22
|
+
get_type_name,
|
|
23
|
+
get_default_release,
|
|
24
|
+
handle_in_app,
|
|
25
|
+
is_gevent,
|
|
15
26
|
logger,
|
|
27
|
+
get_before_send_log,
|
|
28
|
+
get_before_send_metric,
|
|
29
|
+
has_logs_enabled,
|
|
30
|
+
has_metrics_enabled,
|
|
31
|
+
)
|
|
32
|
+
from sentry_sdk.serializer import serialize
|
|
33
|
+
from sentry_sdk.tracing import trace
|
|
34
|
+
from sentry_sdk.transport import BaseHttpTransport, make_transport
|
|
35
|
+
from sentry_sdk.consts import (
|
|
36
|
+
SPANDATA,
|
|
37
|
+
DEFAULT_MAX_VALUE_LENGTH,
|
|
38
|
+
DEFAULT_OPTIONS,
|
|
39
|
+
INSTRUMENTER,
|
|
40
|
+
VERSION,
|
|
41
|
+
ClientConstructor,
|
|
16
42
|
)
|
|
17
|
-
from sentry_sdk.
|
|
18
|
-
from sentry_sdk.
|
|
19
|
-
from sentry_sdk.
|
|
20
|
-
from sentry_sdk.
|
|
43
|
+
from sentry_sdk.integrations import _DEFAULT_INTEGRATIONS, setup_integrations
|
|
44
|
+
from sentry_sdk.integrations.dedupe import DedupeIntegration
|
|
45
|
+
from sentry_sdk.sessions import SessionFlusher
|
|
46
|
+
from sentry_sdk.envelope import Envelope
|
|
47
|
+
from sentry_sdk.profiler.continuous_profiler import setup_continuous_profiler
|
|
48
|
+
from sentry_sdk.profiler.transaction_profiler import (
|
|
49
|
+
has_profiling_enabled,
|
|
50
|
+
Profile,
|
|
51
|
+
setup_profiler,
|
|
52
|
+
)
|
|
53
|
+
from sentry_sdk.scrubber import EventScrubber
|
|
54
|
+
from sentry_sdk.monitor import Monitor
|
|
21
55
|
|
|
22
|
-
if
|
|
23
|
-
from sentry_sdk.consts import ClientOptions
|
|
24
|
-
from sentry_sdk.scope import Scope
|
|
56
|
+
if TYPE_CHECKING:
|
|
25
57
|
from typing import Any
|
|
26
|
-
from typing import
|
|
58
|
+
from typing import Callable
|
|
27
59
|
from typing import Optional
|
|
60
|
+
from typing import Sequence
|
|
61
|
+
from typing import Type
|
|
62
|
+
from typing import Union
|
|
63
|
+
from typing import TypeVar
|
|
64
|
+
|
|
65
|
+
from sentry_sdk._types import Event, Hint, SDKInfo, Log, Metric, EventDataCategory
|
|
66
|
+
from sentry_sdk.integrations import Integration
|
|
67
|
+
from sentry_sdk.scope import Scope
|
|
68
|
+
from sentry_sdk.session import Session
|
|
69
|
+
from sentry_sdk.spotlight import SpotlightClient
|
|
70
|
+
from sentry_sdk.transport import Transport
|
|
71
|
+
from sentry_sdk._log_batcher import LogBatcher
|
|
72
|
+
from sentry_sdk._metrics_batcher import MetricsBatcher
|
|
28
73
|
|
|
74
|
+
I = TypeVar("I", bound=Integration) # noqa: E741
|
|
29
75
|
|
|
30
76
|
_client_init_debug = ContextVar("client_init_debug")
|
|
31
77
|
|
|
32
78
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
79
|
+
SDK_INFO = {
|
|
80
|
+
"name": "sentry.python", # SDK name will be overridden after integrations have been loaded with sentry_sdk.integrations.setup_integrations()
|
|
81
|
+
"version": VERSION,
|
|
82
|
+
"packages": [{"name": "pypi:sentry-sdk", "version": VERSION}],
|
|
83
|
+
} # type: SDKInfo
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _get_options(*args, **kwargs):
|
|
87
|
+
# type: (*Optional[str], **Any) -> Dict[str, Any]
|
|
88
|
+
if args and (isinstance(args[0], (bytes, str)) or args[0] is None):
|
|
36
89
|
dsn = args[0] # type: Optional[str]
|
|
37
90
|
args = args[1:]
|
|
38
91
|
else:
|
|
39
92
|
dsn = None
|
|
40
93
|
|
|
94
|
+
if len(args) > 1:
|
|
95
|
+
raise TypeError("Only single positional argument is expected")
|
|
96
|
+
|
|
41
97
|
rv = dict(DEFAULT_OPTIONS)
|
|
42
|
-
options = dict(*args, **kwargs)
|
|
98
|
+
options = dict(*args, **kwargs)
|
|
43
99
|
if dsn is not None and options.get("dsn") is None:
|
|
44
|
-
options["dsn"] = dsn
|
|
100
|
+
options["dsn"] = dsn
|
|
45
101
|
|
|
46
102
|
for key, value in options.items():
|
|
47
103
|
if key not in rv:
|
|
48
104
|
raise TypeError("Unknown option %r" % (key,))
|
|
49
|
-
|
|
105
|
+
|
|
106
|
+
rv[key] = value
|
|
50
107
|
|
|
51
108
|
if rv["dsn"] is None:
|
|
52
109
|
rv["dsn"] = os.environ.get("SENTRY_DSN")
|
|
53
110
|
|
|
54
111
|
if rv["release"] is None:
|
|
55
|
-
rv["release"] =
|
|
112
|
+
rv["release"] = get_default_release()
|
|
56
113
|
|
|
57
114
|
if rv["environment"] is None:
|
|
58
|
-
rv["environment"] = os.environ.get("SENTRY_ENVIRONMENT")
|
|
115
|
+
rv["environment"] = os.environ.get("SENTRY_ENVIRONMENT") or "production"
|
|
116
|
+
|
|
117
|
+
if rv["debug"] is None:
|
|
118
|
+
rv["debug"] = env_to_bool(os.environ.get("SENTRY_DEBUG"), strict=True) or False
|
|
119
|
+
|
|
120
|
+
if rv["server_name"] is None and hasattr(socket, "gethostname"):
|
|
121
|
+
rv["server_name"] = socket.gethostname()
|
|
122
|
+
|
|
123
|
+
if rv["instrumenter"] is None:
|
|
124
|
+
rv["instrumenter"] = INSTRUMENTER.SENTRY
|
|
125
|
+
|
|
126
|
+
if rv["project_root"] is None:
|
|
127
|
+
try:
|
|
128
|
+
project_root = os.getcwd()
|
|
129
|
+
except Exception:
|
|
130
|
+
project_root = None
|
|
131
|
+
|
|
132
|
+
rv["project_root"] = project_root
|
|
133
|
+
|
|
134
|
+
if rv["enable_tracing"] is True and rv["traces_sample_rate"] is None:
|
|
135
|
+
rv["traces_sample_rate"] = 1.0
|
|
136
|
+
|
|
137
|
+
if rv["event_scrubber"] is None:
|
|
138
|
+
rv["event_scrubber"] = EventScrubber(
|
|
139
|
+
send_default_pii=(
|
|
140
|
+
False if rv["send_default_pii"] is None else rv["send_default_pii"]
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if rv["socket_options"] and not isinstance(rv["socket_options"], list):
|
|
145
|
+
logger.warning(
|
|
146
|
+
"Ignoring socket_options because of unexpected format. See urllib3.HTTPConnection.socket_options for the expected format."
|
|
147
|
+
)
|
|
148
|
+
rv["socket_options"] = None
|
|
149
|
+
|
|
150
|
+
if rv["keep_alive"] is None:
|
|
151
|
+
rv["keep_alive"] = (
|
|
152
|
+
env_to_bool(os.environ.get("SENTRY_KEEP_ALIVE"), strict=True) or False
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if rv["enable_tracing"] is not None:
|
|
156
|
+
warnings.warn(
|
|
157
|
+
"The `enable_tracing` parameter is deprecated. Please use `traces_sample_rate` instead.",
|
|
158
|
+
DeprecationWarning,
|
|
159
|
+
stacklevel=2,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return rv
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
# Python 3.6+
|
|
167
|
+
module_not_found_error = ModuleNotFoundError
|
|
168
|
+
except Exception:
|
|
169
|
+
# Older Python versions
|
|
170
|
+
module_not_found_error = ImportError # type: ignore
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class BaseClient:
|
|
174
|
+
"""
|
|
175
|
+
.. versionadded:: 2.0.0
|
|
176
|
+
|
|
177
|
+
The basic definition of a client that is used for sending data to Sentry.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
spotlight = None # type: Optional[SpotlightClient]
|
|
181
|
+
|
|
182
|
+
def __init__(self, options=None):
|
|
183
|
+
# type: (Optional[Dict[str, Any]]) -> None
|
|
184
|
+
self.options = options if options is not None else DEFAULT_OPTIONS # type: Dict[str, Any]
|
|
185
|
+
|
|
186
|
+
self.transport = None # type: Optional[Transport]
|
|
187
|
+
self.monitor = None # type: Optional[Monitor]
|
|
188
|
+
self.log_batcher = None # type: Optional[LogBatcher]
|
|
189
|
+
self.metrics_batcher = None # type: Optional[MetricsBatcher]
|
|
190
|
+
|
|
191
|
+
def __getstate__(self, *args, **kwargs):
|
|
192
|
+
# type: (*Any, **Any) -> Any
|
|
193
|
+
return {"options": {}}
|
|
194
|
+
|
|
195
|
+
def __setstate__(self, *args, **kwargs):
|
|
196
|
+
# type: (*Any, **Any) -> None
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def dsn(self):
|
|
201
|
+
# type: () -> Optional[str]
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
def should_send_default_pii(self):
|
|
205
|
+
# type: () -> bool
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
def is_active(self):
|
|
209
|
+
# type: () -> bool
|
|
210
|
+
"""
|
|
211
|
+
.. versionadded:: 2.0.0
|
|
212
|
+
|
|
213
|
+
Returns whether the client is active (able to send data to Sentry)
|
|
214
|
+
"""
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
def capture_event(self, *args, **kwargs):
|
|
218
|
+
# type: (*Any, **Any) -> Optional[str]
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
def _capture_log(self, log):
|
|
222
|
+
# type: (Log) -> None
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
def _capture_metric(self, metric):
|
|
226
|
+
# type: (Metric) -> None
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
def capture_session(self, *args, **kwargs):
|
|
230
|
+
# type: (*Any, **Any) -> None
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
if TYPE_CHECKING:
|
|
234
|
+
|
|
235
|
+
@overload
|
|
236
|
+
def get_integration(self, name_or_class):
|
|
237
|
+
# type: (str) -> Optional[Integration]
|
|
238
|
+
...
|
|
239
|
+
|
|
240
|
+
@overload
|
|
241
|
+
def get_integration(self, name_or_class):
|
|
242
|
+
# type: (type[I]) -> Optional[I]
|
|
243
|
+
...
|
|
244
|
+
|
|
245
|
+
def get_integration(self, name_or_class):
|
|
246
|
+
# type: (Union[str, type[Integration]]) -> Optional[Integration]
|
|
247
|
+
return None
|
|
59
248
|
|
|
60
|
-
|
|
249
|
+
def close(self, *args, **kwargs):
|
|
250
|
+
# type: (*Any, **Any) -> None
|
|
251
|
+
return None
|
|
61
252
|
|
|
253
|
+
def flush(self, *args, **kwargs):
|
|
254
|
+
# type: (*Any, **Any) -> None
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
def __enter__(self):
|
|
258
|
+
# type: () -> BaseClient
|
|
259
|
+
return self
|
|
260
|
+
|
|
261
|
+
def __exit__(self, exc_type, exc_value, tb):
|
|
262
|
+
# type: (Any, Any, Any) -> None
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class NonRecordingClient(BaseClient):
|
|
267
|
+
"""
|
|
268
|
+
.. versionadded:: 2.0.0
|
|
62
269
|
|
|
63
|
-
|
|
64
|
-
"""
|
|
270
|
+
A client that does not send any events to Sentry. This is used as a fallback when the Sentry SDK is not yet initialized.
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class _Client(BaseClient):
|
|
277
|
+
"""
|
|
278
|
+
The client is internally responsible for capturing the events and
|
|
65
279
|
forwarding them to sentry through the configured transport. It takes
|
|
66
280
|
the client options as keyword arguments and optionally the DSN as first
|
|
67
281
|
argument.
|
|
282
|
+
|
|
283
|
+
Alias of :py:class:`sentry_sdk.Client`. (Was created for better intelisense support)
|
|
68
284
|
"""
|
|
69
285
|
|
|
70
286
|
def __init__(self, *args, **kwargs):
|
|
71
|
-
# type: (*
|
|
287
|
+
# type: (*Any, **Any) -> None
|
|
288
|
+
super(_Client, self).__init__(options=get_options(*args, **kwargs))
|
|
289
|
+
self._init_impl()
|
|
290
|
+
|
|
291
|
+
def __getstate__(self):
|
|
292
|
+
# type: () -> Any
|
|
293
|
+
return {"options": self.options}
|
|
294
|
+
|
|
295
|
+
def __setstate__(self, state):
|
|
296
|
+
# type: (Any) -> None
|
|
297
|
+
self.options = state["options"]
|
|
298
|
+
self._init_impl()
|
|
299
|
+
|
|
300
|
+
def _setup_instrumentation(self, functions_to_trace):
|
|
301
|
+
# type: (Sequence[Dict[str, str]]) -> None
|
|
302
|
+
"""
|
|
303
|
+
Instruments the functions given in the list `functions_to_trace` with the `@sentry_sdk.tracing.trace` decorator.
|
|
304
|
+
"""
|
|
305
|
+
for function in functions_to_trace:
|
|
306
|
+
class_name = None
|
|
307
|
+
function_qualname = function["qualified_name"]
|
|
308
|
+
module_name, function_name = function_qualname.rsplit(".", 1)
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
# Try to import module and function
|
|
312
|
+
# ex: "mymodule.submodule.funcname"
|
|
313
|
+
|
|
314
|
+
module_obj = import_module(module_name)
|
|
315
|
+
function_obj = getattr(module_obj, function_name)
|
|
316
|
+
setattr(module_obj, function_name, trace(function_obj))
|
|
317
|
+
logger.debug("Enabled tracing for %s", function_qualname)
|
|
318
|
+
except module_not_found_error:
|
|
319
|
+
try:
|
|
320
|
+
# Try to import a class
|
|
321
|
+
# ex: "mymodule.submodule.MyClassName.member_function"
|
|
322
|
+
|
|
323
|
+
module_name, class_name = module_name.rsplit(".", 1)
|
|
324
|
+
module_obj = import_module(module_name)
|
|
325
|
+
class_obj = getattr(module_obj, class_name)
|
|
326
|
+
function_obj = getattr(class_obj, function_name)
|
|
327
|
+
function_type = type(class_obj.__dict__[function_name])
|
|
328
|
+
traced_function = trace(function_obj)
|
|
329
|
+
|
|
330
|
+
if function_type in (staticmethod, classmethod):
|
|
331
|
+
traced_function = staticmethod(traced_function)
|
|
332
|
+
|
|
333
|
+
setattr(class_obj, function_name, traced_function)
|
|
334
|
+
setattr(module_obj, class_name, class_obj)
|
|
335
|
+
logger.debug("Enabled tracing for %s", function_qualname)
|
|
336
|
+
|
|
337
|
+
except Exception as e:
|
|
338
|
+
logger.warning(
|
|
339
|
+
"Can not enable tracing for '%s'. (%s) Please check your `functions_to_trace` parameter.",
|
|
340
|
+
function_qualname,
|
|
341
|
+
e,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
except Exception as e:
|
|
345
|
+
logger.warning(
|
|
346
|
+
"Can not enable tracing for '%s'. (%s) Please check your `functions_to_trace` parameter.",
|
|
347
|
+
function_qualname,
|
|
348
|
+
e,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
def _init_impl(self):
|
|
352
|
+
# type: () -> None
|
|
72
353
|
old_debug = _client_init_debug.get(False)
|
|
354
|
+
|
|
355
|
+
def _capture_envelope(envelope):
|
|
356
|
+
# type: (Envelope) -> None
|
|
357
|
+
if self.transport is not None:
|
|
358
|
+
self.transport.capture_envelope(envelope)
|
|
359
|
+
|
|
360
|
+
def _record_lost_event(
|
|
361
|
+
reason, # type: str
|
|
362
|
+
data_category, # type: EventDataCategory
|
|
363
|
+
quantity=1, # type: int
|
|
364
|
+
):
|
|
365
|
+
# type: (...) -> None
|
|
366
|
+
if self.transport is not None:
|
|
367
|
+
self.transport.record_lost_event(
|
|
368
|
+
reason=reason,
|
|
369
|
+
data_category=data_category,
|
|
370
|
+
quantity=quantity,
|
|
371
|
+
)
|
|
372
|
+
|
|
73
373
|
try:
|
|
74
|
-
self.options
|
|
75
|
-
|
|
76
|
-
|
|
374
|
+
_client_init_debug.set(self.options["debug"])
|
|
375
|
+
self.transport = make_transport(self.options)
|
|
376
|
+
|
|
377
|
+
self.monitor = None
|
|
378
|
+
if self.transport:
|
|
379
|
+
if self.options["enable_backpressure_handling"]:
|
|
380
|
+
self.monitor = Monitor(self.transport)
|
|
381
|
+
|
|
382
|
+
self.session_flusher = SessionFlusher(capture_func=_capture_envelope)
|
|
383
|
+
|
|
384
|
+
self.log_batcher = None
|
|
385
|
+
|
|
386
|
+
if has_logs_enabled(self.options):
|
|
387
|
+
from sentry_sdk._log_batcher import LogBatcher
|
|
388
|
+
|
|
389
|
+
self.log_batcher = LogBatcher(
|
|
390
|
+
capture_func=_capture_envelope,
|
|
391
|
+
record_lost_func=_record_lost_event,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
self.metrics_batcher = None
|
|
395
|
+
if has_metrics_enabled(self.options):
|
|
396
|
+
self.metrics_batcher = MetricsBatcher(
|
|
397
|
+
capture_func=_capture_envelope,
|
|
398
|
+
record_lost_func=_record_lost_event,
|
|
399
|
+
)
|
|
77
400
|
|
|
78
|
-
|
|
79
|
-
if options["
|
|
401
|
+
max_request_body_size = ("always", "never", "small", "medium")
|
|
402
|
+
if self.options["max_request_body_size"] not in max_request_body_size:
|
|
80
403
|
raise ValueError(
|
|
81
|
-
"Invalid value for
|
|
82
|
-
|
|
404
|
+
"Invalid value for max_request_body_size. Must be one of {}".format(
|
|
405
|
+
max_request_body_size
|
|
83
406
|
)
|
|
84
407
|
)
|
|
85
408
|
|
|
409
|
+
if self.options["_experiments"].get("otel_powered_performance", False):
|
|
410
|
+
logger.debug(
|
|
411
|
+
"[OTel] Enabling experimental OTel-powered performance monitoring."
|
|
412
|
+
)
|
|
413
|
+
self.options["instrumenter"] = INSTRUMENTER.OTEL
|
|
414
|
+
if (
|
|
415
|
+
"sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration"
|
|
416
|
+
not in _DEFAULT_INTEGRATIONS
|
|
417
|
+
):
|
|
418
|
+
_DEFAULT_INTEGRATIONS.append(
|
|
419
|
+
"sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration",
|
|
420
|
+
)
|
|
421
|
+
|
|
86
422
|
self.integrations = setup_integrations(
|
|
87
|
-
options["integrations"],
|
|
423
|
+
self.options["integrations"],
|
|
424
|
+
with_defaults=self.options["default_integrations"],
|
|
425
|
+
with_auto_enabling_integrations=self.options[
|
|
426
|
+
"auto_enabling_integrations"
|
|
427
|
+
],
|
|
428
|
+
disabled_integrations=self.options["disabled_integrations"],
|
|
429
|
+
options=self.options,
|
|
88
430
|
)
|
|
431
|
+
|
|
432
|
+
spotlight_config = self.options.get("spotlight")
|
|
433
|
+
if spotlight_config is None and "SENTRY_SPOTLIGHT" in os.environ:
|
|
434
|
+
spotlight_env_value = os.environ["SENTRY_SPOTLIGHT"]
|
|
435
|
+
spotlight_config = env_to_bool(spotlight_env_value, strict=True)
|
|
436
|
+
self.options["spotlight"] = (
|
|
437
|
+
spotlight_config
|
|
438
|
+
if spotlight_config is not None
|
|
439
|
+
else spotlight_env_value
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
if self.options.get("spotlight"):
|
|
443
|
+
# This is intentionally here to prevent setting up spotlight
|
|
444
|
+
# stuff we don't need unless spotlight is explicitly enabled
|
|
445
|
+
from sentry_sdk.spotlight import setup_spotlight
|
|
446
|
+
|
|
447
|
+
self.spotlight = setup_spotlight(self.options)
|
|
448
|
+
if not self.options["dsn"]:
|
|
449
|
+
sample_all = lambda *_args, **_kwargs: 1.0
|
|
450
|
+
self.options["send_default_pii"] = True
|
|
451
|
+
self.options["error_sampler"] = sample_all
|
|
452
|
+
self.options["traces_sampler"] = sample_all
|
|
453
|
+
self.options["profiles_sampler"] = sample_all
|
|
454
|
+
|
|
455
|
+
sdk_name = get_sdk_name(list(self.integrations.keys()))
|
|
456
|
+
SDK_INFO["name"] = sdk_name
|
|
457
|
+
logger.debug("Setting SDK name to '%s'", sdk_name)
|
|
458
|
+
|
|
459
|
+
if has_profiling_enabled(self.options):
|
|
460
|
+
try:
|
|
461
|
+
setup_profiler(self.options)
|
|
462
|
+
except Exception as e:
|
|
463
|
+
logger.debug("Can not set up profiler. (%s)", e)
|
|
464
|
+
else:
|
|
465
|
+
try:
|
|
466
|
+
setup_continuous_profiler(
|
|
467
|
+
self.options,
|
|
468
|
+
sdk_info=SDK_INFO,
|
|
469
|
+
capture_func=_capture_envelope,
|
|
470
|
+
)
|
|
471
|
+
except Exception as e:
|
|
472
|
+
logger.debug("Can not set up continuous profiler. (%s)", e)
|
|
473
|
+
|
|
89
474
|
finally:
|
|
90
475
|
_client_init_debug.set(old_debug)
|
|
91
476
|
|
|
477
|
+
self._setup_instrumentation(self.options.get("functions_to_trace", []))
|
|
478
|
+
|
|
479
|
+
if (
|
|
480
|
+
self.monitor
|
|
481
|
+
or self.log_batcher
|
|
482
|
+
or has_profiling_enabled(self.options)
|
|
483
|
+
or isinstance(self.transport, BaseHttpTransport)
|
|
484
|
+
):
|
|
485
|
+
# If we have anything on that could spawn a background thread, we
|
|
486
|
+
# need to check if it's safe to use them.
|
|
487
|
+
check_uwsgi_thread_support()
|
|
488
|
+
|
|
489
|
+
def is_active(self):
|
|
490
|
+
# type: () -> bool
|
|
491
|
+
"""
|
|
492
|
+
.. versionadded:: 2.0.0
|
|
493
|
+
|
|
494
|
+
Returns whether the client is active (able to send data to Sentry)
|
|
495
|
+
"""
|
|
496
|
+
return True
|
|
497
|
+
|
|
498
|
+
def should_send_default_pii(self):
|
|
499
|
+
# type: () -> bool
|
|
500
|
+
"""
|
|
501
|
+
.. versionadded:: 2.0.0
|
|
502
|
+
|
|
503
|
+
Returns whether the client should send default PII (Personally Identifiable Information) data to Sentry.
|
|
504
|
+
"""
|
|
505
|
+
return self.options.get("send_default_pii") or False
|
|
506
|
+
|
|
92
507
|
@property
|
|
93
508
|
def dsn(self):
|
|
509
|
+
# type: () -> Optional[str]
|
|
94
510
|
"""Returns the configured DSN as string."""
|
|
95
511
|
return self.options["dsn"]
|
|
96
512
|
|
|
97
513
|
def _prepare_event(
|
|
98
514
|
self,
|
|
99
|
-
event, # type:
|
|
100
|
-
hint, # type:
|
|
515
|
+
event, # type: Event
|
|
516
|
+
hint, # type: Hint
|
|
101
517
|
scope, # type: Optional[Scope]
|
|
102
518
|
):
|
|
103
|
-
# type: (...) -> Optional[
|
|
519
|
+
# type: (...) -> Optional[Event]
|
|
520
|
+
|
|
521
|
+
previous_total_spans = None # type: Optional[int]
|
|
522
|
+
previous_total_breadcrumbs = None # type: Optional[int]
|
|
523
|
+
|
|
104
524
|
if event.get("timestamp") is None:
|
|
105
|
-
event["timestamp"] = datetime.
|
|
525
|
+
event["timestamp"] = datetime.now(timezone.utc)
|
|
526
|
+
|
|
527
|
+
is_transaction = event.get("type") == "transaction"
|
|
106
528
|
|
|
107
529
|
if scope is not None:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
530
|
+
spans_before = len(cast(List[Dict[str, object]], event.get("spans", [])))
|
|
531
|
+
event_ = scope.apply_to_event(event, hint, self.options)
|
|
532
|
+
|
|
533
|
+
# one of the event/error processors returned None
|
|
534
|
+
if event_ is None:
|
|
535
|
+
if self.transport:
|
|
536
|
+
self.transport.record_lost_event(
|
|
537
|
+
"event_processor",
|
|
538
|
+
data_category=("transaction" if is_transaction else "error"),
|
|
539
|
+
)
|
|
540
|
+
if is_transaction:
|
|
541
|
+
self.transport.record_lost_event(
|
|
542
|
+
"event_processor",
|
|
543
|
+
data_category="span",
|
|
544
|
+
quantity=spans_before + 1, # +1 for the transaction itself
|
|
545
|
+
)
|
|
546
|
+
return None
|
|
547
|
+
|
|
548
|
+
event = event_
|
|
549
|
+
spans_delta = spans_before - len(
|
|
550
|
+
cast(List[Dict[str, object]], event.get("spans", []))
|
|
551
|
+
)
|
|
552
|
+
if is_transaction and spans_delta > 0 and self.transport is not None:
|
|
553
|
+
self.transport.record_lost_event(
|
|
554
|
+
"event_processor", data_category="span", quantity=spans_delta
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
dropped_spans = event.pop("_dropped_spans", 0) + spans_delta # type: int
|
|
558
|
+
if dropped_spans > 0:
|
|
559
|
+
previous_total_spans = spans_before + dropped_spans
|
|
560
|
+
if scope._n_breadcrumbs_truncated > 0:
|
|
561
|
+
breadcrumbs = event.get("breadcrumbs", {})
|
|
562
|
+
values = (
|
|
563
|
+
breadcrumbs.get("values", [])
|
|
564
|
+
if not isinstance(breadcrumbs, AnnotatedValue)
|
|
565
|
+
else []
|
|
566
|
+
)
|
|
567
|
+
previous_total_breadcrumbs = (
|
|
568
|
+
len(values) + scope._n_breadcrumbs_truncated
|
|
569
|
+
)
|
|
111
570
|
|
|
112
571
|
if (
|
|
113
|
-
|
|
572
|
+
not is_transaction
|
|
573
|
+
and self.options["attach_stacktrace"]
|
|
114
574
|
and "exception" not in event
|
|
115
575
|
and "stacktrace" not in event
|
|
116
576
|
and "threads" not in event
|
|
117
577
|
):
|
|
118
578
|
with capture_internal_exceptions():
|
|
119
|
-
event["threads"] =
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
579
|
+
event["threads"] = {
|
|
580
|
+
"values": [
|
|
581
|
+
{
|
|
582
|
+
"stacktrace": current_stacktrace(
|
|
583
|
+
include_local_variables=self.options.get(
|
|
584
|
+
"include_local_variables", True
|
|
585
|
+
),
|
|
586
|
+
max_value_length=self.options.get(
|
|
587
|
+
"max_value_length", DEFAULT_MAX_VALUE_LENGTH
|
|
588
|
+
),
|
|
589
|
+
),
|
|
590
|
+
"crashed": False,
|
|
591
|
+
"current": True,
|
|
592
|
+
}
|
|
593
|
+
]
|
|
594
|
+
}
|
|
126
595
|
|
|
127
596
|
for key in "release", "environment", "server_name", "dist":
|
|
128
|
-
if event.get(key) is None and self.options[key] is not None:
|
|
129
|
-
event[key] =
|
|
597
|
+
if event.get(key) is None and self.options[key] is not None:
|
|
598
|
+
event[key] = str(self.options[key]).strip()
|
|
130
599
|
if event.get("sdk") is None:
|
|
131
600
|
sdk_info = dict(SDK_INFO)
|
|
132
601
|
sdk_info["integrations"] = sorted(self.integrations.keys())
|
|
@@ -136,122 +605,573 @@ class Client(object):
|
|
|
136
605
|
event["platform"] = "python"
|
|
137
606
|
|
|
138
607
|
event = handle_in_app(
|
|
139
|
-
event,
|
|
608
|
+
event,
|
|
609
|
+
self.options["in_app_exclude"],
|
|
610
|
+
self.options["in_app_include"],
|
|
611
|
+
self.options["project_root"],
|
|
140
612
|
)
|
|
141
613
|
|
|
614
|
+
if event is not None:
|
|
615
|
+
event_scrubber = self.options["event_scrubber"]
|
|
616
|
+
if event_scrubber:
|
|
617
|
+
event_scrubber.scrub_event(event)
|
|
618
|
+
|
|
619
|
+
if scope is not None and scope._gen_ai_original_message_count:
|
|
620
|
+
spans = event.get("spans", []) # type: List[Dict[str, Any]] | AnnotatedValue
|
|
621
|
+
if isinstance(spans, list):
|
|
622
|
+
for span in spans:
|
|
623
|
+
span_id = span.get("span_id", None)
|
|
624
|
+
span_data = span.get("data", {})
|
|
625
|
+
if (
|
|
626
|
+
span_id
|
|
627
|
+
and span_id in scope._gen_ai_original_message_count
|
|
628
|
+
and SPANDATA.GEN_AI_REQUEST_MESSAGES in span_data
|
|
629
|
+
):
|
|
630
|
+
span_data[SPANDATA.GEN_AI_REQUEST_MESSAGES] = AnnotatedValue(
|
|
631
|
+
span_data[SPANDATA.GEN_AI_REQUEST_MESSAGES],
|
|
632
|
+
{"len": scope._gen_ai_original_message_count[span_id]},
|
|
633
|
+
)
|
|
634
|
+
if previous_total_spans is not None:
|
|
635
|
+
event["spans"] = AnnotatedValue(
|
|
636
|
+
event.get("spans", []), {"len": previous_total_spans}
|
|
637
|
+
)
|
|
638
|
+
if previous_total_breadcrumbs is not None:
|
|
639
|
+
event["breadcrumbs"] = AnnotatedValue(
|
|
640
|
+
event.get("breadcrumbs", {"values": []}),
|
|
641
|
+
{"len": previous_total_breadcrumbs},
|
|
642
|
+
)
|
|
643
|
+
|
|
142
644
|
# Postprocess the event here so that annotated types do
|
|
143
645
|
# generally not surface in before_send
|
|
144
646
|
if event is not None:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
647
|
+
event = cast(
|
|
648
|
+
"Event",
|
|
649
|
+
serialize(
|
|
650
|
+
cast("Dict[str, Any]", event),
|
|
651
|
+
max_request_body_size=self.options.get("max_request_body_size"),
|
|
652
|
+
max_value_length=self.options.get("max_value_length"),
|
|
653
|
+
custom_repr=self.options.get("custom_repr"),
|
|
654
|
+
),
|
|
655
|
+
)
|
|
148
656
|
|
|
149
657
|
before_send = self.options["before_send"]
|
|
150
|
-
if
|
|
658
|
+
if (
|
|
659
|
+
before_send is not None
|
|
660
|
+
and event is not None
|
|
661
|
+
and event.get("type") != "transaction"
|
|
662
|
+
):
|
|
663
|
+
new_event = None
|
|
664
|
+
with capture_internal_exceptions():
|
|
665
|
+
new_event = before_send(event, hint or {})
|
|
666
|
+
if new_event is None:
|
|
667
|
+
logger.info("before send dropped event")
|
|
668
|
+
if self.transport:
|
|
669
|
+
self.transport.record_lost_event(
|
|
670
|
+
"before_send", data_category="error"
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
# If this is an exception, reset the DedupeIntegration. It still
|
|
674
|
+
# remembers the dropped exception as the last exception, meaning
|
|
675
|
+
# that if the same exception happens again and is not dropped
|
|
676
|
+
# in before_send, it'd get dropped by DedupeIntegration.
|
|
677
|
+
if event.get("exception"):
|
|
678
|
+
DedupeIntegration.reset_last_seen()
|
|
679
|
+
|
|
680
|
+
event = new_event
|
|
681
|
+
|
|
682
|
+
before_send_transaction = self.options["before_send_transaction"]
|
|
683
|
+
if (
|
|
684
|
+
before_send_transaction is not None
|
|
685
|
+
and event is not None
|
|
686
|
+
and event.get("type") == "transaction"
|
|
687
|
+
):
|
|
151
688
|
new_event = None
|
|
689
|
+
spans_before = len(cast(List[Dict[str, object]], event.get("spans", [])))
|
|
152
690
|
with capture_internal_exceptions():
|
|
153
|
-
new_event =
|
|
691
|
+
new_event = before_send_transaction(event, hint or {})
|
|
154
692
|
if new_event is None:
|
|
155
|
-
logger.info("before send dropped event
|
|
156
|
-
|
|
693
|
+
logger.info("before send transaction dropped event")
|
|
694
|
+
if self.transport:
|
|
695
|
+
self.transport.record_lost_event(
|
|
696
|
+
reason="before_send", data_category="transaction"
|
|
697
|
+
)
|
|
698
|
+
self.transport.record_lost_event(
|
|
699
|
+
reason="before_send",
|
|
700
|
+
data_category="span",
|
|
701
|
+
quantity=spans_before + 1, # +1 for the transaction itself
|
|
702
|
+
)
|
|
703
|
+
else:
|
|
704
|
+
spans_delta = spans_before - len(new_event.get("spans", []))
|
|
705
|
+
if spans_delta > 0 and self.transport is not None:
|
|
706
|
+
self.transport.record_lost_event(
|
|
707
|
+
reason="before_send", data_category="span", quantity=spans_delta
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
event = new_event
|
|
157
711
|
|
|
158
712
|
return event
|
|
159
713
|
|
|
160
714
|
def _is_ignored_error(self, event, hint):
|
|
161
|
-
# type: (
|
|
715
|
+
# type: (Event, Hint) -> bool
|
|
162
716
|
exc_info = hint.get("exc_info")
|
|
163
717
|
if exc_info is None:
|
|
164
718
|
return False
|
|
165
719
|
|
|
166
|
-
|
|
167
|
-
|
|
720
|
+
error = exc_info[0]
|
|
721
|
+
error_type_name = get_type_name(exc_info[0])
|
|
722
|
+
error_full_name = "%s.%s" % (exc_info[0].__module__, error_type_name)
|
|
168
723
|
|
|
169
|
-
for
|
|
724
|
+
for ignored_error in self.options["ignore_errors"]:
|
|
170
725
|
# String types are matched against the type name in the
|
|
171
726
|
# exception only
|
|
172
|
-
if isinstance(
|
|
173
|
-
if
|
|
727
|
+
if isinstance(ignored_error, str):
|
|
728
|
+
if ignored_error == error_full_name or ignored_error == error_type_name:
|
|
174
729
|
return True
|
|
175
730
|
else:
|
|
176
|
-
if issubclass(
|
|
731
|
+
if issubclass(error, ignored_error):
|
|
177
732
|
return True
|
|
178
733
|
|
|
179
734
|
return False
|
|
180
735
|
|
|
181
736
|
def _should_capture(
|
|
182
737
|
self,
|
|
183
|
-
event, # type:
|
|
184
|
-
hint, # type:
|
|
185
|
-
scope=None, # type: Scope
|
|
738
|
+
event, # type: Event
|
|
739
|
+
hint, # type: Hint
|
|
740
|
+
scope=None, # type: Optional[Scope]
|
|
186
741
|
):
|
|
187
742
|
# type: (...) -> bool
|
|
188
|
-
|
|
743
|
+
# Transactions are sampled independent of error events.
|
|
744
|
+
is_transaction = event.get("type") == "transaction"
|
|
745
|
+
if is_transaction:
|
|
746
|
+
return True
|
|
747
|
+
|
|
748
|
+
ignoring_prevents_recursion = scope is not None and not scope._should_capture
|
|
749
|
+
if ignoring_prevents_recursion:
|
|
189
750
|
return False
|
|
190
751
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
and random.random() >= self.options["sample_rate"]
|
|
194
|
-
):
|
|
752
|
+
ignored_by_config_option = self._is_ignored_error(event, hint)
|
|
753
|
+
if ignored_by_config_option:
|
|
195
754
|
return False
|
|
196
755
|
|
|
197
|
-
|
|
756
|
+
return True
|
|
757
|
+
|
|
758
|
+
def _should_sample_error(
|
|
759
|
+
self,
|
|
760
|
+
event, # type: Event
|
|
761
|
+
hint, # type: Hint
|
|
762
|
+
):
|
|
763
|
+
# type: (...) -> bool
|
|
764
|
+
error_sampler = self.options.get("error_sampler", None)
|
|
765
|
+
|
|
766
|
+
if callable(error_sampler):
|
|
767
|
+
with capture_internal_exceptions():
|
|
768
|
+
sample_rate = error_sampler(event, hint)
|
|
769
|
+
else:
|
|
770
|
+
sample_rate = self.options["sample_rate"]
|
|
771
|
+
|
|
772
|
+
try:
|
|
773
|
+
not_in_sample_rate = sample_rate < 1.0 and random.random() >= sample_rate
|
|
774
|
+
except NameError:
|
|
775
|
+
logger.warning(
|
|
776
|
+
"The provided error_sampler raised an error. Defaulting to sampling the event."
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
# If the error_sampler raised an error, we should sample the event, since the default behavior
|
|
780
|
+
# (when no sample_rate or error_sampler is provided) is to sample all events.
|
|
781
|
+
not_in_sample_rate = False
|
|
782
|
+
except TypeError:
|
|
783
|
+
parameter, verb = (
|
|
784
|
+
("error_sampler", "returned")
|
|
785
|
+
if callable(error_sampler)
|
|
786
|
+
else ("sample_rate", "contains")
|
|
787
|
+
)
|
|
788
|
+
logger.warning(
|
|
789
|
+
"The provided %s %s an invalid value of %s. The value should be a float or a bool. Defaulting to sampling the event."
|
|
790
|
+
% (parameter, verb, repr(sample_rate))
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
# If the sample_rate has an invalid value, we should sample the event, since the default behavior
|
|
794
|
+
# (when no sample_rate or error_sampler is provided) is to sample all events.
|
|
795
|
+
not_in_sample_rate = False
|
|
796
|
+
|
|
797
|
+
if not_in_sample_rate:
|
|
798
|
+
# because we will not sample this event, record a "lost event".
|
|
799
|
+
if self.transport:
|
|
800
|
+
self.transport.record_lost_event("sample_rate", data_category="error")
|
|
801
|
+
|
|
198
802
|
return False
|
|
199
803
|
|
|
200
804
|
return True
|
|
201
805
|
|
|
202
|
-
def
|
|
203
|
-
|
|
806
|
+
def _update_session_from_event(
|
|
807
|
+
self,
|
|
808
|
+
session, # type: Session
|
|
809
|
+
event, # type: Event
|
|
810
|
+
):
|
|
811
|
+
# type: (...) -> None
|
|
812
|
+
|
|
813
|
+
crashed = False
|
|
814
|
+
errored = False
|
|
815
|
+
user_agent = None
|
|
816
|
+
|
|
817
|
+
exceptions = (event.get("exception") or {}).get("values")
|
|
818
|
+
if exceptions:
|
|
819
|
+
errored = True
|
|
820
|
+
for error in exceptions:
|
|
821
|
+
if isinstance(error, AnnotatedValue):
|
|
822
|
+
error = error.value or {}
|
|
823
|
+
mechanism = error.get("mechanism")
|
|
824
|
+
if isinstance(mechanism, Mapping) and mechanism.get("handled") is False:
|
|
825
|
+
crashed = True
|
|
826
|
+
break
|
|
827
|
+
|
|
828
|
+
user = event.get("user")
|
|
829
|
+
|
|
830
|
+
if session.user_agent is None:
|
|
831
|
+
headers = (event.get("request") or {}).get("headers")
|
|
832
|
+
headers_dict = headers if isinstance(headers, dict) else {}
|
|
833
|
+
for k, v in headers_dict.items():
|
|
834
|
+
if k.lower() == "user-agent":
|
|
835
|
+
user_agent = v
|
|
836
|
+
break
|
|
837
|
+
|
|
838
|
+
session.update(
|
|
839
|
+
status="crashed" if crashed else None,
|
|
840
|
+
user=user,
|
|
841
|
+
user_agent=user_agent,
|
|
842
|
+
errors=session.errors + (errored or crashed),
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
def capture_event(
|
|
846
|
+
self,
|
|
847
|
+
event, # type: Event
|
|
848
|
+
hint=None, # type: Optional[Hint]
|
|
849
|
+
scope=None, # type: Optional[Scope]
|
|
850
|
+
):
|
|
851
|
+
# type: (...) -> Optional[str]
|
|
204
852
|
"""Captures an event.
|
|
205
853
|
|
|
206
|
-
|
|
207
|
-
hint is internally used to further customize the representation of the
|
|
208
|
-
error. When provided it's a dictionary of optional information such
|
|
209
|
-
as exception info.
|
|
854
|
+
:param event: A ready-made event that can be directly sent to Sentry.
|
|
210
855
|
|
|
211
|
-
|
|
212
|
-
|
|
856
|
+
:param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object.
|
|
857
|
+
|
|
858
|
+
:param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events.
|
|
859
|
+
|
|
860
|
+
:returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help.
|
|
213
861
|
"""
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if hint is None:
|
|
217
|
-
hint = {}
|
|
218
|
-
rv = event.get("event_id")
|
|
219
|
-
if rv is None:
|
|
220
|
-
event["event_id"] = rv = uuid.uuid4().hex
|
|
862
|
+
hint = dict(hint or ()) # type: Hint
|
|
863
|
+
|
|
221
864
|
if not self._should_capture(event, hint, scope):
|
|
222
865
|
return None
|
|
223
|
-
|
|
224
|
-
|
|
866
|
+
|
|
867
|
+
profile = event.pop("profile", None)
|
|
868
|
+
|
|
869
|
+
event_id = event.get("event_id")
|
|
870
|
+
if event_id is None:
|
|
871
|
+
event["event_id"] = event_id = uuid.uuid4().hex
|
|
872
|
+
event_opt = self._prepare_event(event, hint, scope)
|
|
873
|
+
if event_opt is None:
|
|
874
|
+
return None
|
|
875
|
+
|
|
876
|
+
# whenever we capture an event we also check if the session needs
|
|
877
|
+
# to be updated based on that information.
|
|
878
|
+
session = scope._session if scope else None
|
|
879
|
+
if session:
|
|
880
|
+
self._update_session_from_event(session, event)
|
|
881
|
+
|
|
882
|
+
is_transaction = event_opt.get("type") == "transaction"
|
|
883
|
+
is_checkin = event_opt.get("type") == "check_in"
|
|
884
|
+
|
|
885
|
+
if (
|
|
886
|
+
not is_transaction
|
|
887
|
+
and not is_checkin
|
|
888
|
+
and not self._should_sample_error(event, hint)
|
|
889
|
+
):
|
|
225
890
|
return None
|
|
226
|
-
self.transport.capture_event(event)
|
|
227
|
-
return rv
|
|
228
891
|
|
|
229
|
-
|
|
892
|
+
attachments = hint.get("attachments")
|
|
893
|
+
|
|
894
|
+
trace_context = event_opt.get("contexts", {}).get("trace") or {}
|
|
895
|
+
dynamic_sampling_context = trace_context.pop("dynamic_sampling_context", {})
|
|
896
|
+
|
|
897
|
+
headers = {
|
|
898
|
+
"event_id": event_opt["event_id"],
|
|
899
|
+
"sent_at": format_timestamp(datetime.now(timezone.utc)),
|
|
900
|
+
} # type: dict[str, object]
|
|
901
|
+
|
|
902
|
+
if dynamic_sampling_context:
|
|
903
|
+
headers["trace"] = dynamic_sampling_context
|
|
904
|
+
|
|
905
|
+
envelope = Envelope(headers=headers)
|
|
906
|
+
|
|
907
|
+
if is_transaction:
|
|
908
|
+
if isinstance(profile, Profile):
|
|
909
|
+
envelope.add_profile(profile.to_json(event_opt, self.options))
|
|
910
|
+
envelope.add_transaction(event_opt)
|
|
911
|
+
elif is_checkin:
|
|
912
|
+
envelope.add_checkin(event_opt)
|
|
913
|
+
else:
|
|
914
|
+
envelope.add_event(event_opt)
|
|
915
|
+
|
|
916
|
+
for attachment in attachments or ():
|
|
917
|
+
envelope.add_item(attachment.to_envelope_item())
|
|
918
|
+
|
|
919
|
+
return_value = None
|
|
920
|
+
if self.spotlight:
|
|
921
|
+
self.spotlight.capture_envelope(envelope)
|
|
922
|
+
return_value = event_id
|
|
923
|
+
|
|
924
|
+
if self.transport is not None:
|
|
925
|
+
self.transport.capture_envelope(envelope)
|
|
926
|
+
return_value = event_id
|
|
927
|
+
|
|
928
|
+
return return_value
|
|
929
|
+
|
|
930
|
+
def _capture_log(self, log):
|
|
931
|
+
# type: (Optional[Log]) -> None
|
|
932
|
+
if not has_logs_enabled(self.options) or log is None:
|
|
933
|
+
return
|
|
934
|
+
|
|
935
|
+
current_scope = sentry_sdk.get_current_scope()
|
|
936
|
+
isolation_scope = sentry_sdk.get_isolation_scope()
|
|
937
|
+
|
|
938
|
+
log["attributes"]["sentry.sdk.name"] = SDK_INFO["name"]
|
|
939
|
+
log["attributes"]["sentry.sdk.version"] = SDK_INFO["version"]
|
|
940
|
+
|
|
941
|
+
server_name = self.options.get("server_name")
|
|
942
|
+
if server_name is not None and SPANDATA.SERVER_ADDRESS not in log["attributes"]:
|
|
943
|
+
log["attributes"][SPANDATA.SERVER_ADDRESS] = server_name
|
|
944
|
+
|
|
945
|
+
environment = self.options.get("environment")
|
|
946
|
+
if environment is not None and "sentry.environment" not in log["attributes"]:
|
|
947
|
+
log["attributes"]["sentry.environment"] = environment
|
|
948
|
+
|
|
949
|
+
release = self.options.get("release")
|
|
950
|
+
if release is not None and "sentry.release" not in log["attributes"]:
|
|
951
|
+
log["attributes"]["sentry.release"] = release
|
|
952
|
+
|
|
953
|
+
trace_context = current_scope.get_trace_context()
|
|
954
|
+
trace_id = trace_context.get("trace_id")
|
|
955
|
+
span_id = trace_context.get("span_id")
|
|
956
|
+
|
|
957
|
+
if trace_id is not None and log.get("trace_id") is None:
|
|
958
|
+
log["trace_id"] = trace_id
|
|
959
|
+
|
|
960
|
+
if (
|
|
961
|
+
span_id is not None
|
|
962
|
+
and "sentry.trace.parent_span_id" not in log["attributes"]
|
|
963
|
+
):
|
|
964
|
+
log["attributes"]["sentry.trace.parent_span_id"] = span_id
|
|
965
|
+
|
|
966
|
+
# The user, if present, is always set on the isolation scope.
|
|
967
|
+
if isolation_scope._user is not None:
|
|
968
|
+
for log_attribute, user_attribute in (
|
|
969
|
+
("user.id", "id"),
|
|
970
|
+
("user.name", "username"),
|
|
971
|
+
("user.email", "email"),
|
|
972
|
+
):
|
|
973
|
+
if (
|
|
974
|
+
user_attribute in isolation_scope._user
|
|
975
|
+
and log_attribute not in log["attributes"]
|
|
976
|
+
):
|
|
977
|
+
log["attributes"][log_attribute] = isolation_scope._user[
|
|
978
|
+
user_attribute
|
|
979
|
+
]
|
|
980
|
+
|
|
981
|
+
# If debug is enabled, log the log to the console
|
|
982
|
+
debug = self.options.get("debug", False)
|
|
983
|
+
if debug:
|
|
984
|
+
logger.debug(
|
|
985
|
+
f"[Sentry Logs] [{log.get('severity_text')}] {log.get('body')}"
|
|
986
|
+
)
|
|
987
|
+
|
|
988
|
+
before_send_log = get_before_send_log(self.options)
|
|
989
|
+
if before_send_log is not None:
|
|
990
|
+
log = before_send_log(log, {})
|
|
991
|
+
|
|
992
|
+
if log is None:
|
|
993
|
+
return
|
|
994
|
+
|
|
995
|
+
if self.log_batcher:
|
|
996
|
+
self.log_batcher.add(log)
|
|
997
|
+
|
|
998
|
+
def _capture_metric(self, metric):
|
|
999
|
+
# type: (Optional[Metric]) -> None
|
|
1000
|
+
if not has_metrics_enabled(self.options) or metric is None:
|
|
1001
|
+
return
|
|
1002
|
+
|
|
1003
|
+
current_scope = sentry_sdk.get_current_scope()
|
|
1004
|
+
isolation_scope = sentry_sdk.get_isolation_scope()
|
|
1005
|
+
|
|
1006
|
+
metric["attributes"]["sentry.sdk.name"] = SDK_INFO["name"]
|
|
1007
|
+
metric["attributes"]["sentry.sdk.version"] = SDK_INFO["version"]
|
|
1008
|
+
|
|
1009
|
+
server_name = self.options.get("server_name")
|
|
1010
|
+
if (
|
|
1011
|
+
server_name is not None
|
|
1012
|
+
and SPANDATA.SERVER_ADDRESS not in metric["attributes"]
|
|
1013
|
+
):
|
|
1014
|
+
metric["attributes"][SPANDATA.SERVER_ADDRESS] = server_name
|
|
1015
|
+
|
|
1016
|
+
environment = self.options.get("environment")
|
|
1017
|
+
if environment is not None and "sentry.environment" not in metric["attributes"]:
|
|
1018
|
+
metric["attributes"]["sentry.environment"] = environment
|
|
1019
|
+
|
|
1020
|
+
release = self.options.get("release")
|
|
1021
|
+
if release is not None and "sentry.release" not in metric["attributes"]:
|
|
1022
|
+
metric["attributes"]["sentry.release"] = release
|
|
1023
|
+
|
|
1024
|
+
trace_context = current_scope.get_trace_context()
|
|
1025
|
+
trace_id = trace_context.get("trace_id")
|
|
1026
|
+
span_id = trace_context.get("span_id")
|
|
1027
|
+
|
|
1028
|
+
metric["trace_id"] = trace_id or "00000000-0000-0000-0000-000000000000"
|
|
1029
|
+
if span_id is not None:
|
|
1030
|
+
metric["span_id"] = span_id
|
|
1031
|
+
|
|
1032
|
+
if isolation_scope._user is not None:
|
|
1033
|
+
for metric_attribute, user_attribute in (
|
|
1034
|
+
("user.id", "id"),
|
|
1035
|
+
("user.name", "username"),
|
|
1036
|
+
("user.email", "email"),
|
|
1037
|
+
):
|
|
1038
|
+
if (
|
|
1039
|
+
user_attribute in isolation_scope._user
|
|
1040
|
+
and metric_attribute not in metric["attributes"]
|
|
1041
|
+
):
|
|
1042
|
+
metric["attributes"][metric_attribute] = isolation_scope._user[
|
|
1043
|
+
user_attribute
|
|
1044
|
+
]
|
|
1045
|
+
|
|
1046
|
+
debug = self.options.get("debug", False)
|
|
1047
|
+
if debug:
|
|
1048
|
+
logger.debug(
|
|
1049
|
+
f"[Sentry Metrics] [{metric.get('type')}] {metric.get('name')}: {metric.get('value')}"
|
|
1050
|
+
)
|
|
1051
|
+
|
|
1052
|
+
before_send_metric = get_before_send_metric(self.options)
|
|
1053
|
+
if before_send_metric is not None:
|
|
1054
|
+
metric = before_send_metric(metric, {})
|
|
1055
|
+
|
|
1056
|
+
if metric is None:
|
|
1057
|
+
return
|
|
1058
|
+
|
|
1059
|
+
if self.metrics_batcher:
|
|
1060
|
+
self.metrics_batcher.add(metric)
|
|
1061
|
+
|
|
1062
|
+
def capture_session(
|
|
1063
|
+
self,
|
|
1064
|
+
session, # type: Session
|
|
1065
|
+
):
|
|
1066
|
+
# type: (...) -> None
|
|
1067
|
+
if not session.release:
|
|
1068
|
+
logger.info("Discarded session update because of missing release")
|
|
1069
|
+
else:
|
|
1070
|
+
self.session_flusher.add_session(session)
|
|
1071
|
+
|
|
1072
|
+
if TYPE_CHECKING:
|
|
1073
|
+
|
|
1074
|
+
@overload
|
|
1075
|
+
def get_integration(self, name_or_class):
|
|
1076
|
+
# type: (str) -> Optional[Integration]
|
|
1077
|
+
...
|
|
1078
|
+
|
|
1079
|
+
@overload
|
|
1080
|
+
def get_integration(self, name_or_class):
|
|
1081
|
+
# type: (type[I]) -> Optional[I]
|
|
1082
|
+
...
|
|
1083
|
+
|
|
1084
|
+
def get_integration(
|
|
1085
|
+
self,
|
|
1086
|
+
name_or_class, # type: Union[str, Type[Integration]]
|
|
1087
|
+
):
|
|
1088
|
+
# type: (...) -> Optional[Integration]
|
|
1089
|
+
"""Returns the integration for this client by name or class.
|
|
1090
|
+
If the client does not have that integration then `None` is returned.
|
|
1091
|
+
"""
|
|
1092
|
+
if isinstance(name_or_class, str):
|
|
1093
|
+
integration_name = name_or_class
|
|
1094
|
+
elif name_or_class.identifier is not None:
|
|
1095
|
+
integration_name = name_or_class.identifier
|
|
1096
|
+
else:
|
|
1097
|
+
raise ValueError("Integration has no name")
|
|
1098
|
+
|
|
1099
|
+
return self.integrations.get(integration_name)
|
|
1100
|
+
|
|
1101
|
+
def close(
|
|
1102
|
+
self,
|
|
1103
|
+
timeout=None, # type: Optional[float]
|
|
1104
|
+
callback=None, # type: Optional[Callable[[int, float], None]]
|
|
1105
|
+
):
|
|
1106
|
+
# type: (...) -> None
|
|
230
1107
|
"""
|
|
231
1108
|
Close the client and shut down the transport. Arguments have the same
|
|
232
|
-
semantics as
|
|
1109
|
+
semantics as :py:meth:`Client.flush`.
|
|
233
1110
|
"""
|
|
234
1111
|
if self.transport is not None:
|
|
235
1112
|
self.flush(timeout=timeout, callback=callback)
|
|
1113
|
+
self.session_flusher.kill()
|
|
1114
|
+
if self.log_batcher is not None:
|
|
1115
|
+
self.log_batcher.kill()
|
|
1116
|
+
if self.metrics_batcher is not None:
|
|
1117
|
+
self.metrics_batcher.kill()
|
|
1118
|
+
if self.monitor:
|
|
1119
|
+
self.monitor.kill()
|
|
236
1120
|
self.transport.kill()
|
|
237
1121
|
self.transport = None
|
|
238
1122
|
|
|
239
|
-
def flush(
|
|
1123
|
+
def flush(
|
|
1124
|
+
self,
|
|
1125
|
+
timeout=None, # type: Optional[float]
|
|
1126
|
+
callback=None, # type: Optional[Callable[[int, float], None]]
|
|
1127
|
+
):
|
|
1128
|
+
# type: (...) -> None
|
|
240
1129
|
"""
|
|
241
|
-
Wait
|
|
242
|
-
`timeout` is provided, the `shutdown_timeout` option value is used.
|
|
1130
|
+
Wait for the current events to be sent.
|
|
243
1131
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
1132
|
+
:param timeout: Wait for at most `timeout` seconds. If no `timeout` is provided, the `shutdown_timeout` option value is used.
|
|
1133
|
+
|
|
1134
|
+
:param callback: Is invoked with the number of pending events and the configured timeout.
|
|
247
1135
|
"""
|
|
248
1136
|
if self.transport is not None:
|
|
249
1137
|
if timeout is None:
|
|
250
1138
|
timeout = self.options["shutdown_timeout"]
|
|
1139
|
+
self.session_flusher.flush()
|
|
1140
|
+
if self.log_batcher is not None:
|
|
1141
|
+
self.log_batcher.flush()
|
|
1142
|
+
if self.metrics_batcher is not None:
|
|
1143
|
+
self.metrics_batcher.flush()
|
|
251
1144
|
self.transport.flush(timeout=timeout, callback=callback)
|
|
252
1145
|
|
|
253
1146
|
def __enter__(self):
|
|
1147
|
+
# type: () -> _Client
|
|
254
1148
|
return self
|
|
255
1149
|
|
|
256
1150
|
def __exit__(self, exc_type, exc_value, tb):
|
|
1151
|
+
# type: (Any, Any, Any) -> None
|
|
257
1152
|
self.close()
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
from typing import TYPE_CHECKING
|
|
1156
|
+
|
|
1157
|
+
if TYPE_CHECKING:
|
|
1158
|
+
# Make mypy, PyCharm and other static analyzers think `get_options` is a
|
|
1159
|
+
# type to have nicer autocompletion for params.
|
|
1160
|
+
#
|
|
1161
|
+
# Use `ClientConstructor` to define the argument types of `init` and
|
|
1162
|
+
# `Dict[str, Any]` to tell static analyzers about the return type.
|
|
1163
|
+
|
|
1164
|
+
class get_options(ClientConstructor, Dict[str, Any]): # noqa: N801
|
|
1165
|
+
pass
|
|
1166
|
+
|
|
1167
|
+
class Client(ClientConstructor, _Client):
|
|
1168
|
+
pass
|
|
1169
|
+
|
|
1170
|
+
else:
|
|
1171
|
+
# Alias `get_options` for actual usage. Go through the lambda indirection
|
|
1172
|
+
# to throw PyCharm off of the weakly typed signature (it would otherwise
|
|
1173
|
+
# discover both the weakly typed signature of `_init` and our faked `init`
|
|
1174
|
+
# type).
|
|
1175
|
+
|
|
1176
|
+
get_options = (lambda: _get_options)()
|
|
1177
|
+
Client = (lambda: _Client)()
|