sentry_sdk.client

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

from sentry_sdk._compat import check_uwsgi_thread_support
from sentry_sdk._metrics_batcher import MetricsBatcher
from sentry_sdk._span_batcher import SpanBatcher
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,
    logger,
    get_before_send_log,
    get_before_send_metric,
    has_logs_enabled,
    has_metrics_enabled,
)
from sentry_sdk.serializer import serialize
from sentry_sdk.tracing import trace
from sentry_sdk.tracing_utils import has_span_streaming_enabled
from sentry_sdk.transport import (
    HttpTransportCore,
    make_transport,
    AsyncHttpTransport,
)
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

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, Metric, EventDataCategory
    from sentry_sdk.integrations import Integration
    from sentry_sdk.scope import Scope
    from sentry_sdk.session import Session
    from sentry_sdk.spotlight import SpotlightClient
    from sentry_sdk.traces import StreamedSpan
    from sentry_sdk.transport import Transport, Item
    from sentry_sdk._log_batcher import LogBatcher
    from sentry_sdk._metrics_batcher import MetricsBatcher
    from sentry_sdk.utils import Dsn

    I = TypeVar("I", bound=Integration)  # noqa: E741

_client_init_debug = ContextVar("client_init_debug")


SDK_INFO: "SDKInfo" = {
    "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}],
}


def _get_options(*args: "Optional[str]", **kwargs: "Any") -> "Dict[str, Any]":
    if args and (isinstance(args[0], (bytes, str)) or args[0] is None):
        dsn: "Optional[str]" = args[0]
        args = args[1:]
    else:
        dsn = None

    if len(args) > 1:
        raise TypeError("Only single positional argument is expected")

    rv = dict(DEFAULT_OPTIONS)
    options = dict(*args, **kwargs)
    if dsn is not None and options.get("dsn") is None:
        options["dsn"] = dsn

    for key, value in options.items():
        if key not in rv:
            raise TypeError("Unknown option %r" % (key,))

        rv[key] = value

    if rv["dsn"] is None:
        rv["dsn"] = os.environ.get("SENTRY_DSN")

    if rv["release"] is None:
        rv["release"] = get_default_release()

    if rv["environment"] is None:
        rv["environment"] = os.environ.get("SENTRY_ENVIRONMENT") or "production"

    if rv["debug"] is None:
        rv["debug"] = env_to_bool(os.environ.get("SENTRY_DEBUG"), strict=True) or False

    if rv["server_name"] is None and hasattr(socket, "gethostname"):
        rv["server_name"] = socket.gethostname()

    if rv["instrumenter"] is None:
        rv["instrumenter"] = INSTRUMENTER.SENTRY

    if rv["project_root"] is None:
        try:
            project_root = os.getcwd()
        except Exception:
            project_root = None

        rv["project_root"] = project_root

    if rv["enable_tracing"] is True and rv["traces_sample_rate"] is None:
        rv["traces_sample_rate"] = 1.0

    if rv["event_scrubber"] is None:
        rv["event_scrubber"] = EventScrubber(
            send_default_pii=(
                False if rv["send_default_pii"] is None else rv["send_default_pii"]
            )
        )

    if rv["socket_options"] and not 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"] is None:
        rv["keep_alive"] = (
            env_to_bool(os.environ.get("SENTRY_KEEP_ALIVE"), strict=True) or False
        )

    if rv["enable_tracing"] is not None:
        warnings.warn(
            "The `enable_tracing` parameter is deprecated. Please use `traces_sample_rate` instead.",
            DeprecationWarning,
            stacklevel=2,
        )

    return rv


try:
    # Python 3.6+
    module_not_found_error = ModuleNotFoundError
except Exception:
    # Older Python versions
    module_not_found_error = ImportError  # type: ignore


class BaseClient:
    """
    .. versionadded:: 2.0.0

    The basic definition of a client that is used for sending data to Sentry.
    """

    spotlight: "Optional[SpotlightClient]" = None

    def __init__(self, options: "Optional[Dict[str, Any]]" = None) -> None:
        self.options: "Dict[str, Any]" = (
            options if options is not None else DEFAULT_OPTIONS
        )

        self.transport: "Optional[Transport]" = None
        self.monitor: "Optional[Monitor]" = None
        self.log_batcher: "Optional[LogBatcher]" = None
        self.metrics_batcher: "Optional[MetricsBatcher]" = None
        self.span_batcher: "Optional[SpanBatcher]" = None
        self.integrations: "dict[str, Integration]" = {}

    def __getstate__(self, *args: "Any", **kwargs: "Any") -> "Any":
        return {"options": {}}

    def __setstate__(self, *args: "Any", **kwargs: "Any") -> None:
        pass

    @property
    def dsn(self) -> "Optional[str]":
        return None

    @property
    def parsed_dsn(self) -> "Optional[Dsn]":
        return None

    def should_send_default_pii(self) -> bool:
        return False

    def is_active(self) -> bool:
        """
        .. versionadded:: 2.0.0

        Returns whether the client is active (able to send data to Sentry)
        """
        return False