import os import uuid import random import socket from collections.abc import Mapping from datetime import datetime, timezone from importlib import import_module from typing import TYPE_CHECKING, List, Dict, cast, overload import warnings
import sentry_sdk from sentry_sdk._compat import PY37, check_uwsgi_thread_support from sentry_sdk.utils import (
AnnotatedValue,
ContextVar,
capture_internal_exceptions,
current_stacktrace,
env_to_bool,
format_timestamp,
get_sdk_name,
get_type_name,
get_default_release,
handle_in_app,
is_gevent,
logger,
) from sentry_sdk.serializer import serialize from sentry_sdk.tracing import trace from sentry_sdk.transport import BaseHttpTransport, make_transport from sentry_sdk.consts import (
SPANDATA,
DEFAULT_MAX_VALUE_LENGTH,
DEFAULT_OPTIONS,
INSTRUMENTER,
VERSION,
ClientConstructor,
) from sentry_sdk.integrations import _DEFAULT_INTEGRATIONS, setup_integrations from sentry_sdk.integrations.dedupe import DedupeIntegration from sentry_sdk.sessions import SessionFlusher from sentry_sdk.envelope import Envelope from sentry_sdk.profiler.continuous_profiler import setup_continuous_profiler from sentry_sdk.profiler.transaction_profiler import (
has_profiling_enabled,
Profile,
setup_profiler,
) from sentry_sdk.scrubber import EventScrubber from sentry_sdk.monitor import Monitor from sentry_sdk.spotlight import setup_spotlight
if TYPE_CHECKING: from typing import Any from typing import Callable from typing import Optional from typing import Sequence from typing import Type from typing import Union from typing import TypeVar
from sentry_sdk._types import Event, Hint, SDKInfo, Log from sentry_sdk.integrations import Integration from sentry_sdk.metrics import MetricsAggregator from sentry_sdk.scope import Scope from sentry_sdk.session import Session from sentry_sdk.spotlight import SpotlightClient from sentry_sdk.transport import Transport from sentry_sdk._log_batcher import LogBatcher
SDK_INFO = { "name": "sentry.python", # SDK name will be overridden after integrations have been loaded with sentry_sdk.integrations.setup_integrations() "version": VERSION, "packages": [{"name": "pypi:sentry-sdk", "version": VERSION}],
} # type: SDKInfo
if rv["socket_options"] andnot isinstance(rv["socket_options"], list):
logger.warning( "Ignoring socket_options because of unexpected format. See urllib3.HTTPConnection.socket_options for the expected format."
)
rv["socket_options"] = None
if rv["keep_alive"] isNone:
rv["keep_alive"] = (
env_to_bool(os.environ.get("SENTRY_KEEP_ALIVE"), strict=True) orFalse
)
if rv["enable_tracing"] isnotNone:
warnings.warn( "The `enable_tracing` parameter is deprecated. Please use `traces_sample_rate` instead.",
DeprecationWarning,
stacklevel=2,
)
class NonRecordingClient(BaseClient): """
.. versionadded:: 2.0.0
A client that does not send any events to Sentry. This is used as a fallback when the Sentry SDK isnot yet initialized. """
pass
class _Client(BaseClient): """
The client is internally responsible for capturing the events and
forwarding them to sentry through the configured transport. It takes
the client options as keyword arguments and optionally the DSN as first
argument.
Alias of :py:class:`sentry_sdk.Client`. (Was created for better intelisense support) """
def _setup_instrumentation(self, functions_to_trace): # type: (Sequence[Dict[str, str]]) -> None """
Instruments the functions given in the list `functions_to_trace` with the `@sentry_sdk.tracing.trace` decorator. """ for function in functions_to_trace:
class_name = None
function_qualname = function["qualified_name"]
module_name, function_name = function_qualname.rsplit(".", 1)
try: # Try to import module and function # ex: "mymodule.submodule.funcname"
module_obj = import_module(module_name)
function_obj = getattr(module_obj, function_name)
setattr(module_obj, function_name, trace(function_obj))
logger.debug("Enabled tracing for %s", function_qualname) except module_not_found_error: try: # Try to import a class # ex: "mymodule.submodule.MyClassName.member_function"
except Exception as e:
logger.warning( "Can not enable tracing for '%s'. (%s) Please check your `functions_to_trace` parameter.",
function_qualname,
e,
)
except Exception as e:
logger.warning( "Can not enable tracing for '%s'. (%s) Please check your `functions_to_trace` parameter.",
function_qualname,
e,
)
self.metrics_aggregator = None# type: Optional[MetricsAggregator]
experiments = self.options.get("_experiments", {}) if experiments.get("enable_metrics", True): # Context vars are not working correctly on Python <=3.6 # with gevent.
metrics_supported = not is_gevent() or PY37 if metrics_supported: from sentry_sdk.metrics import MetricsAggregator
self.metrics_aggregator = MetricsAggregator(
capture_func=_capture_envelope,
enable_code_locations=bool(
experiments.get("metric_code_locations", True)
),
) else:
logger.info( "Metrics not supported on Python 3.6 and lower with gevent."
)
self.log_batcher = None if experiments.get("enable_logs", False): from sentry_sdk._log_batcher import LogBatcher
max_request_body_size = ("always", "never", "small", "medium") if self.options["max_request_body_size"] notin max_request_body_size: raise ValueError( "Invalid value for max_request_body_size. Must be one of {}".format(
max_request_body_size
)
)
sdk_name = get_sdk_name(list(self.integrations.keys()))
SDK_INFO["name"] = sdk_name
logger.debug("Setting SDK name to '%s'", sdk_name)
if has_profiling_enabled(self.options): try:
setup_profiler(self.options) except Exception as e:
logger.debug("Can not set up profiler. (%s)", e) else: try:
setup_continuous_profiler(
self.options,
sdk_info=SDK_INFO,
capture_func=_capture_envelope,
) except Exception as e:
logger.debug("Can not set up continuous profiler. (%s)", e)
if (
self.monitor or self.metrics_aggregator or self.log_batcher or has_profiling_enabled(self.options) or isinstance(self.transport, BaseHttpTransport)
): # If we have anything on that could spawn a background thread, we # need to check if it's safe to use them.
check_uwsgi_thread_support()
Returns whether the client should send default PII (Personally Identifiable Information) data to Sentry. """ return self.options.get("send_default_pii") orFalse
@property def dsn(self): # type: () -> Optional[str] """Returns the configured DSN as string.""" return self.options["dsn"]
# one of the event/error processors returned None if event_ isNone: if self.transport:
self.transport.record_lost_event( "event_processor",
data_category=("transaction"if is_transaction else"error"),
) if is_transaction:
self.transport.record_lost_event( "event_processor",
data_category="span",
quantity=spans_before + 1, # +1 for the transaction itself
) returnNone
if event isnotNone:
event_scrubber = self.options["event_scrubber"] if event_scrubber:
event_scrubber.scrub_event(event)
if previous_total_spans isnotNone:
event["spans"] = AnnotatedValue(
event.get("spans", []), {"len": previous_total_spans}
) if previous_total_breadcrumbs isnotNone:
event["breadcrumbs"] = AnnotatedValue(
event.get("breadcrumbs", []), {"len": previous_total_breadcrumbs}
) # Postprocess the event here so that annotated types do # generally not surface in before_send if event isnotNone:
event = cast( "Event",
serialize(
cast("Dict[str, Any]", event),
max_request_body_size=self.options.get("max_request_body_size"),
max_value_length=self.options.get("max_value_length"),
custom_repr=self.options.get("custom_repr"),
),
)
before_send = self.options["before_send"] if (
before_send isnotNone and event isnotNone and event.get("type") != "transaction"
):
new_event = None with capture_internal_exceptions():
new_event = before_send(event, hint or {}) if new_event isNone:
logger.info("before send dropped event") if self.transport:
self.transport.record_lost_event( "before_send", data_category="error"
)
# If this is an exception, reset the DedupeIntegration. It still # remembers the dropped exception as the last exception, meaning # that if the same exception happens again and is not dropped # in before_send, it'd get dropped by DedupeIntegration. if event.get("exception"):
DedupeIntegration.reset_last_seen()
event = new_event
before_send_transaction = self.options["before_send_transaction"] if (
before_send_transaction isnotNone and event isnotNone and event.get("type") == "transaction"
):
new_event = None
spans_before = len(cast(List[Dict[str, object]], event.get("spans", []))) with capture_internal_exceptions():
new_event = before_send_transaction(event, hint or {}) if new_event isNone:
logger.info("before send transaction dropped event") if self.transport:
self.transport.record_lost_event(
reason="before_send", data_category="transaction"
)
self.transport.record_lost_event(
reason="before_send",
data_category="span",
quantity=spans_before + 1, # +1 for the transaction itself
) else:
spans_delta = spans_before - len(new_event.get("spans", [])) if spans_delta > 0and self.transport isnotNone:
self.transport.record_lost_event(
reason="before_send", data_category="span", quantity=spans_delta
)
for ignored_error in self.options["ignore_errors"]: # String types are matched against the type name in the # exception only if isinstance(ignored_error, str): if ignored_error == error_full_name or ignored_error == error_type_name: returnTrue else: if issubclass(error, ignored_error): returnTrue
returnFalse
def _should_capture(
self,
event, # type: Event
hint, # type: Hint
scope=None, # type: Optional[Scope]
): # type: (...) -> bool # Transactions are sampled independent of error events.
is_transaction = event.get("type") == "transaction" if is_transaction: returnTrue
ignoring_prevents_recursion = scope isnotNoneandnot scope._should_capture if ignoring_prevents_recursion: returnFalse
ignored_by_config_option = self._is_ignored_error(event, hint) if ignored_by_config_option: returnFalse
if callable(error_sampler): with capture_internal_exceptions():
sample_rate = error_sampler(event, hint) else:
sample_rate = self.options["sample_rate"]
try:
not_in_sample_rate = sample_rate < 1.0and random.random() >= sample_rate except NameError:
logger.warning( "The provided error_sampler raised an error. Defaulting to sampling the event."
)
# If the error_sampler raised an error, we should sample the event, since the default behavior # (when no sample_rate or error_sampler is provided) is to sample all events.
not_in_sample_rate = False except TypeError:
parameter, verb = (
("error_sampler", "returned") if callable(error_sampler) else ("sample_rate", "contains")
)
logger.warning( "The provided %s %s an invalid value of %s. The value should be a float or a bool. Defaulting to sampling the event."
% (parameter, verb, repr(sample_rate))
)
# If the sample_rate has an invalid value, we should sample the event, since the default behavior # (when no sample_rate or error_sampler is provided) is to sample all events.
not_in_sample_rate = False
if not_in_sample_rate: # because we will not sample this event, record a "lost event". if self.transport:
self.transport.record_lost_event("sample_rate", data_category="error")
exceptions = (event.get("exception") or {}).get("values") if exceptions:
errored = True for error in exceptions: if isinstance(error, AnnotatedValue):
error = error.value or {}
mechanism = error.get("mechanism") if isinstance(mechanism, Mapping) and mechanism.get("handled") isFalse:
crashed = True break
user = event.get("user")
if session.user_agent isNone:
headers = (event.get("request") or {}).get("headers")
headers_dict = headers if isinstance(headers, dict) else {} for k, v in headers_dict.items(): if k.lower() == "user-agent":
user_agent = v break
:param event: A ready-made event that can be directly sent to Sentry.
: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.
:param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events.
: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. """
hint = dict(hint or ()) # type: Hint
event_id = event.get("event_id") if event_id isNone:
event["event_id"] = event_id = uuid.uuid4().hex
event_opt = self._prepare_event(event, hint, scope) if event_opt isNone: returnNone
# whenever we capture an event we also check if the session needs # to be updated based on that information.
session = scope._session if scope elseNone if session:
self._update_session_from_event(session, event)
# The user, if present, is always set on the isolation scope. if isolation_scope._user isnotNone: for log_attribute, user_attribute in (
("user.id", "id"),
("user.name", "username"),
("user.email", "email"),
): if (
user_attribute in isolation_scope._user and log_attribute notin log["attributes"]
):
log["attributes"][log_attribute] = isolation_scope._user[
user_attribute
]
# If debug is enabled, log the log to the console
debug = self.options.get("debug", False) if debug:
logger.debug(
f'[Sentry Logs] [{log.get("severity_text")}] {log.get("body")}'
)
before_send_log = self.options["_experiments"].get("before_send_log") if before_send_log isnotNone:
log = before_send_log(log, {}) if log isNone: return
def get_integration(
self, name_or_class # type: Union[str, Type[Integration]]
): # type: (...) -> Optional[Integration] """Returns the integration for this client by name or class. If the client does not have that integration then `None` is returned. """ if isinstance(name_or_class, str):
integration_name = name_or_class elif name_or_class.identifier isnotNone:
integration_name = name_or_class.identifier else: raise ValueError("Integration has no name")
return self.integrations.get(integration_name)
def close(
self,
timeout=None, # type: Optional[float]
callback=None, # type: Optional[Callable[[int, float], None]]
): # type: (...) -> None """
Close the client and shut down the transport. Arguments have the same
semantics as :py:meth:`Client.flush`. """ if self.transport isnotNone:
self.flush(timeout=timeout, callback=callback)
self.session_flusher.kill() if self.metrics_aggregator isnotNone:
self.metrics_aggregator.kill() if self.log_batcher isnotNone:
self.log_batcher.kill() if self.monitor:
self.monitor.kill()
self.transport.kill()
self.transport = None
def flush(
self,
timeout=None, # type: Optional[float]
callback=None, # type: Optional[Callable[[int, float], None]]
): # type: (...) -> None """
Wait for the current events to be sent.
:param timeout: Wait for at most `timeout` seconds. If no `timeout` is provided, the `shutdown_timeout` option value is used.
:param callback: Is invoked with the number of pending events and the configured timeout. """ if self.transport isnotNone: if timeout isNone:
timeout = self.options["shutdown_timeout"]
self.session_flusher.flush() if self.metrics_aggregator isnotNone:
self.metrics_aggregator.flush() if self.log_batcher isnotNone:
self.log_batcher.flush()
self.transport.flush(timeout=timeout, callback=callback)
if TYPE_CHECKING: # Make mypy, PyCharm and other static analyzers think `get_options` is a # type to have nicer autocompletion for params. # # Use `ClientConstructor` to define the argument types of `init` and # `Dict[str, Any]` to tell static analyzers about the return type.
class get_options(ClientConstructor, Dict[str, Any]): # noqa: N801 pass
class Client(ClientConstructor, _Client): pass
else: # Alias `get_options` for actual usage. Go through the lambda indirection # to throw PyCharm off of the weakly typed signature (it would otherwise # discover both the weakly typed signature of `_init` and our faked `init` # type).
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.9Bemerkung:
(vorverarbeitet am 2026-08-25)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.