sentry-sdk 3.0.0a6__tar.gz → 3.0.0a7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sentry-sdk might be problematic. Click here for more details.
- {sentry_sdk-3.0.0a6/sentry_sdk.egg-info → sentry_sdk-3.0.0a7}/PKG-INFO +1 -1
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/_init_implementation.py +5 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/consts.py +4 -2
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/threading.py +1 -1
- sentry_sdk-3.0.0a7/sentry_sdk/spotlight.py +75 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/transport.py +6 -1
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7/sentry_sdk.egg-info}/PKG-INFO +1 -1
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/setup.py +1 -1
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_client.py +2 -2
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_transport.py +49 -19
- sentry_sdk-3.0.0a6/sentry_sdk/spotlight.py +0 -236
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/LICENSE +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/MANIFEST.in +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/README.md +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/pyproject.toml +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/_compat.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/_log_batcher.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/_lru_cache.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/_queue.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/_types.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/_werkzeug.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/ai/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/ai/monitoring.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/ai/utils.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/api.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/attachments.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/client.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/crons/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/crons/api.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/crons/consts.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/crons/decorator.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/debug.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/envelope.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/feature_flags.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/_asgi_common.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/_wsgi_common.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/aiohttp.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/anthropic.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/argv.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/ariadne.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/arq.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/asgi.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/asyncio.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/asyncpg.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/atexit.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/aws_lambda.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/beam.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/boto3.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/bottle.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/celery/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/celery/beat.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/celery/utils.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/chalice.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/clickhouse_driver.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/cloud_resource_context.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/cohere.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/dedupe.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/django/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/django/asgi.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/django/caching.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/django/middleware.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/django/signals_handlers.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/django/templates.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/django/transactions.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/django/views.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/dramatiq.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/excepthook.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/executing.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/falcon.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/fastapi.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/flask.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/gcp.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/gnu_backtrace.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/gql.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/graphene.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/grpc/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/grpc/aio/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/grpc/aio/client.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/grpc/aio/server.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/grpc/client.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/grpc/consts.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/grpc/server.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/httpx.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/huey.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/huggingface_hub.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/langchain.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/launchdarkly.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/litestar.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/logging.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/loguru.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/modules.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/consts.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/patches/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/patches/agent_run.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/patches/models.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/patches/runner.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/patches/tools.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/spans/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/spans/ai_client.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/spans/execute_tool.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/spans/handoff.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openai_agents/utils.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/openfeature.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/pure_eval.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/pymongo.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/pyramid.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/quart.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/ray.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/redis/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/redis/_async_common.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/redis/_sync_common.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/redis/consts.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/redis/modules/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/redis/modules/caches.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/redis/modules/queries.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/redis/rb.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/redis/redis.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/redis/redis_cluster.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/redis/utils.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/rq.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/rust_tracing.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/sanic.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/serverless.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/socket.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/spark/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/spark/spark_driver.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/spark/spark_worker.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/sqlalchemy.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/starlette.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/starlite.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/statsig.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/stdlib.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/strawberry.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/sys_exit.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/tornado.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/trytond.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/typer.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/unleash.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/integrations/wsgi.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/logger.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/monitor.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/opentelemetry/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/opentelemetry/consts.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/opentelemetry/contextvars_context.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/opentelemetry/propagator.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/opentelemetry/sampler.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/opentelemetry/scope.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/opentelemetry/span_processor.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/opentelemetry/tracing.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/opentelemetry/utils.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/profiler/__init__.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/profiler/continuous_profiler.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/profiler/transaction_profiler.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/profiler/utils.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/py.typed +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/scope.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/scrubber.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/serializer.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/session.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/sessions.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/tracing.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/tracing_utils.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/types.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/utils.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk/worker.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk.egg-info/SOURCES.txt +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk.egg-info/dependency_links.txt +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk.egg-info/entry_points.txt +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk.egg-info/not-zip-safe +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk.egg-info/requires.txt +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/sentry_sdk.egg-info/top_level.txt +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/setup.cfg +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_ai_monitoring.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_api.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_basics.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_breadcrumbs.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_conftest.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_crons.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_dsc.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_envelope.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_exceptiongroup.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_feature_flags.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_full_stack_frames.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_gevent.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_import.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_logs.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_lru_cache.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_monitor.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_propagationcontext.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_scope.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_scrubber.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_serializer.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_sessions.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_spotlight.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_tracing_utils.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_types.py +0 -0
- {sentry_sdk-3.0.0a6 → sentry_sdk-3.0.0a7}/tests/test_utils.py +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
import warnings
|
|
2
3
|
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
@@ -25,6 +26,10 @@ def _init(*args: Optional[str], **kwargs: Any) -> None:
|
|
|
25
26
|
setup_scope_context_management()
|
|
26
27
|
client = sentry_sdk.Client(*args, **kwargs)
|
|
27
28
|
sentry_sdk.get_global_scope().set_client(client)
|
|
29
|
+
warnings.warn(
|
|
30
|
+
"We won't be continuing development on SDK 3.0. Please use the last stable version of the SDK to get access to the newest features and fixes. See https://github.com/getsentry/sentry-python/discussions/4955",
|
|
31
|
+
stacklevel=2,
|
|
32
|
+
)
|
|
28
33
|
_check_python_deprecations()
|
|
29
34
|
|
|
30
35
|
|
|
@@ -77,7 +77,6 @@ if TYPE_CHECKING:
|
|
|
77
77
|
"transport_compression_level": Optional[int],
|
|
78
78
|
"transport_compression_algo": Optional[CompressionAlgo],
|
|
79
79
|
"transport_num_pools": Optional[int],
|
|
80
|
-
"transport_http2": Optional[bool],
|
|
81
80
|
"transport_async": Optional[bool],
|
|
82
81
|
},
|
|
83
82
|
total=False,
|
|
@@ -971,6 +970,7 @@ class ClientConstructor:
|
|
|
971
970
|
max_stack_frames: Optional[int] = DEFAULT_MAX_STACK_FRAMES,
|
|
972
971
|
enable_logs: bool = False,
|
|
973
972
|
before_send_log: Optional[Callable[[Log, Hint], Optional[Log]]] = None,
|
|
973
|
+
http2: Optional[bool] = None,
|
|
974
974
|
) -> None:
|
|
975
975
|
"""Initialize the Sentry SDK with the given parameters. All parameters described here can be used in a call to `sentry_sdk.init()`.
|
|
976
976
|
|
|
@@ -1343,6 +1343,8 @@ class ClientConstructor:
|
|
|
1343
1343
|
This is relative to the tracing sample rate - e.g. `0.5` means 50% of sampled transactions will be
|
|
1344
1344
|
profiled.
|
|
1345
1345
|
|
|
1346
|
+
:param http2: Defaults to `True`, enables HTTP/2 support for the SDK.
|
|
1347
|
+
|
|
1346
1348
|
:param profiles_sampler:
|
|
1347
1349
|
|
|
1348
1350
|
:param profiler_mode:
|
|
@@ -1389,4 +1391,4 @@ DEFAULT_OPTIONS = _get_default_options()
|
|
|
1389
1391
|
del _get_default_options
|
|
1390
1392
|
|
|
1391
1393
|
|
|
1392
|
-
VERSION = "3.0.
|
|
1394
|
+
VERSION = "3.0.0a7"
|
|
@@ -38,7 +38,7 @@ class ThreadingIntegration(Integration):
|
|
|
38
38
|
|
|
39
39
|
try:
|
|
40
40
|
from django import VERSION as django_version # noqa: N811
|
|
41
|
-
import channels # type: ignore
|
|
41
|
+
import channels # type: ignore
|
|
42
42
|
|
|
43
43
|
channels_version = channels.__version__
|
|
44
44
|
except ImportError:
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import io
|
|
3
|
+
import logging
|
|
4
|
+
import urllib3
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
from sentry_sdk.utils import (
|
|
13
|
+
logger as sentry_logger,
|
|
14
|
+
)
|
|
15
|
+
from sentry_sdk.envelope import Envelope
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("spotlight")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
DEFAULT_SPOTLIGHT_URL = "http://localhost:8969/stream"
|
|
22
|
+
DJANGO_SPOTLIGHT_MIDDLEWARE_PATH = "sentry_sdk.spotlight.SpotlightMiddleware"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SpotlightClient:
|
|
26
|
+
def __init__(self, url: str) -> None:
|
|
27
|
+
self.url = url
|
|
28
|
+
self.http = urllib3.PoolManager()
|
|
29
|
+
self.fails = 0
|
|
30
|
+
|
|
31
|
+
def capture_envelope(self, envelope: Envelope) -> None:
|
|
32
|
+
body = io.BytesIO()
|
|
33
|
+
envelope.serialize_into(body)
|
|
34
|
+
try:
|
|
35
|
+
req = self.http.request(
|
|
36
|
+
url=self.url,
|
|
37
|
+
body=body.getvalue(),
|
|
38
|
+
method="POST",
|
|
39
|
+
headers={
|
|
40
|
+
"Content-Type": "application/x-sentry-envelope",
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
req.close()
|
|
44
|
+
self.fails = 0
|
|
45
|
+
except Exception as e:
|
|
46
|
+
if self.fails < 2:
|
|
47
|
+
sentry_logger.warning(str(e))
|
|
48
|
+
self.fails += 1
|
|
49
|
+
elif self.fails == 2:
|
|
50
|
+
self.fails += 1
|
|
51
|
+
sentry_logger.warning(
|
|
52
|
+
"Looks like Spotlight is not running, will keep trying to send events but will not log errors."
|
|
53
|
+
)
|
|
54
|
+
# omitting self.fails += 1 in the `else:` case intentionally
|
|
55
|
+
# to avoid overflowing the variable if Spotlight never becomes reachable
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def setup_spotlight(options: Dict[str, Any]) -> Optional[SpotlightClient]:
|
|
59
|
+
_handler = logging.StreamHandler(sys.stderr)
|
|
60
|
+
_handler.setFormatter(logging.Formatter(" [spotlight] %(levelname)s: %(message)s"))
|
|
61
|
+
logger.addHandler(_handler)
|
|
62
|
+
logger.setLevel(logging.INFO)
|
|
63
|
+
|
|
64
|
+
url = options.get("spotlight")
|
|
65
|
+
|
|
66
|
+
if url is True:
|
|
67
|
+
url = DEFAULT_SPOTLIGHT_URL
|
|
68
|
+
|
|
69
|
+
if not isinstance(url, str):
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
client = SpotlightClient(url)
|
|
73
|
+
logger.info("Enabled Spotlight using sidecar at %s", url)
|
|
74
|
+
|
|
75
|
+
return client
|
|
@@ -1051,7 +1051,12 @@ else:
|
|
|
1051
1051
|
def make_transport(options: Dict[str, Any]) -> Optional[Transport]:
|
|
1052
1052
|
ref_transport = options["transport"]
|
|
1053
1053
|
|
|
1054
|
-
|
|
1054
|
+
# We default to using HTTP2 transport if the user also has the required h2
|
|
1055
|
+
# library installed (through the subclass check). The reason is h2 not being
|
|
1056
|
+
# available on py3.7 which we still support.
|
|
1057
|
+
use_http2_transport = options.get("http2") is not False and not issubclass(
|
|
1058
|
+
Http2Transport, HttpTransport
|
|
1059
|
+
)
|
|
1055
1060
|
use_async_transport = options.get("_experiments", {}).get("transport_async", False)
|
|
1056
1061
|
async_integration = any(
|
|
1057
1062
|
integration.__class__.__name__ == "AsyncioIntegration"
|
|
@@ -257,8 +257,8 @@ def test_proxy(monkeypatch, testcase, http2):
|
|
|
257
257
|
|
|
258
258
|
kwargs = {}
|
|
259
259
|
|
|
260
|
-
if http2:
|
|
261
|
-
kwargs["
|
|
260
|
+
if not http2:
|
|
261
|
+
kwargs["http2"] = False
|
|
262
262
|
|
|
263
263
|
if testcase["arg_http_proxy"] is not None:
|
|
264
264
|
kwargs["http_proxy"] = testcase["arg_http_proxy"]
|
|
@@ -83,7 +83,7 @@ def mock_transaction_envelope(span_count: int) -> Envelope:
|
|
|
83
83
|
@pytest.mark.parametrize("use_pickle", (True, False))
|
|
84
84
|
@pytest.mark.parametrize("compression_level", (0, 9, None))
|
|
85
85
|
@pytest.mark.parametrize("compression_algo", ("gzip", "br", "<invalid>", None))
|
|
86
|
-
@pytest.mark.parametrize("http2", [
|
|
86
|
+
@pytest.mark.parametrize("http2", [None, False])
|
|
87
87
|
def test_transport_works(
|
|
88
88
|
capturing_server,
|
|
89
89
|
request,
|
|
@@ -106,11 +106,9 @@ def test_transport_works(
|
|
|
106
106
|
if compression_algo is not None:
|
|
107
107
|
experiments["transport_compression_algo"] = compression_algo
|
|
108
108
|
|
|
109
|
-
if http2:
|
|
110
|
-
experiments["transport_http2"] = True
|
|
111
|
-
|
|
112
109
|
client = make_client(
|
|
113
110
|
debug=debug,
|
|
111
|
+
http2=http2,
|
|
114
112
|
_experiments=experiments,
|
|
115
113
|
)
|
|
116
114
|
|
|
@@ -245,7 +243,7 @@ def test_transport_num_pools(make_client, num_pools, expected_num_pools):
|
|
|
245
243
|
if num_pools is not None:
|
|
246
244
|
_experiments["transport_num_pools"] = num_pools
|
|
247
245
|
|
|
248
|
-
client = make_client(_experiments=_experiments)
|
|
246
|
+
client = make_client(_experiments=_experiments, http2=False)
|
|
249
247
|
|
|
250
248
|
options = client.transport._get_pool_options()
|
|
251
249
|
assert options["num_pools"] == expected_num_pools
|
|
@@ -255,17 +253,13 @@ def test_transport_num_pools(make_client, num_pools, expected_num_pools):
|
|
|
255
253
|
"http2", [True, False] if sys.version_info >= (3, 8) else [False]
|
|
256
254
|
)
|
|
257
255
|
def test_two_way_ssl_authentication(make_client, http2):
|
|
258
|
-
_experiments = {}
|
|
259
|
-
if http2:
|
|
260
|
-
_experiments["transport_http2"] = True
|
|
261
|
-
|
|
262
256
|
current_dir = os.path.dirname(__file__)
|
|
263
257
|
cert_file = f"{current_dir}/test.pem"
|
|
264
258
|
key_file = f"{current_dir}/test.key"
|
|
265
259
|
client = make_client(
|
|
266
260
|
cert_file=cert_file,
|
|
267
261
|
key_file=key_file,
|
|
268
|
-
|
|
262
|
+
http2=http2,
|
|
269
263
|
)
|
|
270
264
|
options = client.transport._get_pool_options()
|
|
271
265
|
|
|
@@ -290,20 +284,20 @@ def test_socket_options(make_client):
|
|
|
290
284
|
|
|
291
285
|
|
|
292
286
|
def test_keep_alive_true(make_client):
|
|
293
|
-
client = make_client(keep_alive=True)
|
|
287
|
+
client = make_client(keep_alive=True, http2=False)
|
|
294
288
|
|
|
295
289
|
options = client.transport._get_pool_options()
|
|
296
290
|
assert options["socket_options"] == KEEP_ALIVE_SOCKET_OPTIONS
|
|
297
291
|
|
|
298
292
|
|
|
299
293
|
def test_keep_alive_on_by_default(make_client):
|
|
300
|
-
client = make_client()
|
|
294
|
+
client = make_client(http2=False)
|
|
301
295
|
options = client.transport._get_pool_options()
|
|
302
296
|
assert "socket_options" not in options
|
|
303
297
|
|
|
304
298
|
|
|
305
299
|
def test_default_timeout(make_client):
|
|
306
|
-
client = make_client()
|
|
300
|
+
client = make_client(http2=False)
|
|
307
301
|
|
|
308
302
|
options = client.transport._get_pool_options()
|
|
309
303
|
assert "timeout" in options
|
|
@@ -312,7 +306,7 @@ def test_default_timeout(make_client):
|
|
|
312
306
|
|
|
313
307
|
@pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+")
|
|
314
308
|
def test_default_timeout_http2(make_client):
|
|
315
|
-
client = make_client(
|
|
309
|
+
client = make_client()
|
|
316
310
|
|
|
317
311
|
with mock.patch(
|
|
318
312
|
"sentry_sdk.transport.httpcore.ConnectionPool.request",
|
|
@@ -335,7 +329,7 @@ def test_default_timeout_http2(make_client):
|
|
|
335
329
|
|
|
336
330
|
@pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+")
|
|
337
331
|
def test_http2_with_https_dsn(make_client):
|
|
338
|
-
client = make_client(
|
|
332
|
+
client = make_client()
|
|
339
333
|
client.transport.parsed_dsn.scheme = "https"
|
|
340
334
|
options = client.transport._get_pool_options()
|
|
341
335
|
assert options["http2"] is True
|
|
@@ -343,7 +337,7 @@ def test_http2_with_https_dsn(make_client):
|
|
|
343
337
|
|
|
344
338
|
@pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+")
|
|
345
339
|
def test_no_http2_with_http_dsn(make_client):
|
|
346
|
-
client = make_client(
|
|
340
|
+
client = make_client()
|
|
347
341
|
client.transport.parsed_dsn.scheme = "http"
|
|
348
342
|
options = client.transport._get_pool_options()
|
|
349
343
|
assert options["http2"] is False
|
|
@@ -356,19 +350,44 @@ def test_socket_options_override_keep_alive(make_client):
|
|
|
356
350
|
(socket.SOL_TCP, socket.TCP_KEEPCNT, 6),
|
|
357
351
|
]
|
|
358
352
|
|
|
359
|
-
client = make_client(socket_options=socket_options, keep_alive=False)
|
|
353
|
+
client = make_client(socket_options=socket_options, keep_alive=False, http2=False)
|
|
360
354
|
|
|
361
355
|
options = client.transport._get_pool_options()
|
|
362
356
|
assert options["socket_options"] == socket_options
|
|
363
357
|
|
|
364
358
|
|
|
359
|
+
@pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+")
|
|
360
|
+
def test_socket_options_merge_with_keep_alive_http2(make_client):
|
|
361
|
+
socket_options = [
|
|
362
|
+
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 42),
|
|
363
|
+
(socket.SOL_TCP, socket.TCP_KEEPINTVL, 42),
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
client = make_client(socket_options=socket_options)
|
|
367
|
+
|
|
368
|
+
options = client.transport._get_pool_options()
|
|
369
|
+
try:
|
|
370
|
+
assert options["socket_options"] == [
|
|
371
|
+
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 42),
|
|
372
|
+
(socket.SOL_TCP, socket.TCP_KEEPINTVL, 42),
|
|
373
|
+
(socket.SOL_TCP, socket.TCP_KEEPIDLE, 45),
|
|
374
|
+
(socket.SOL_TCP, socket.TCP_KEEPCNT, 6),
|
|
375
|
+
]
|
|
376
|
+
except AttributeError:
|
|
377
|
+
assert options["socket_options"] == [
|
|
378
|
+
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 42),
|
|
379
|
+
(socket.SOL_TCP, socket.TCP_KEEPINTVL, 42),
|
|
380
|
+
(socket.SOL_TCP, socket.TCP_KEEPCNT, 6),
|
|
381
|
+
]
|
|
382
|
+
|
|
383
|
+
|
|
365
384
|
def test_socket_options_merge_with_keep_alive(make_client):
|
|
366
385
|
socket_options = [
|
|
367
386
|
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 42),
|
|
368
387
|
(socket.SOL_TCP, socket.TCP_KEEPINTVL, 42),
|
|
369
388
|
]
|
|
370
389
|
|
|
371
|
-
client = make_client(socket_options=socket_options, keep_alive=True)
|
|
390
|
+
client = make_client(socket_options=socket_options, keep_alive=True, http2=False)
|
|
372
391
|
|
|
373
392
|
options = client.transport._get_pool_options()
|
|
374
393
|
try:
|
|
@@ -386,12 +405,23 @@ def test_socket_options_merge_with_keep_alive(make_client):
|
|
|
386
405
|
]
|
|
387
406
|
|
|
388
407
|
|
|
389
|
-
|
|
408
|
+
@pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+")
|
|
409
|
+
def test_socket_options_override_defaults_http2(make_client):
|
|
390
410
|
# If socket_options are set to [], this doesn't mean the user doesn't want
|
|
391
411
|
# any custom socket_options, but rather that they want to disable the urllib3
|
|
392
412
|
# socket option defaults, so we need to set this and not ignore it.
|
|
393
413
|
client = make_client(socket_options=[])
|
|
394
414
|
|
|
415
|
+
options = client.transport._get_pool_options()
|
|
416
|
+
assert options["socket_options"] == KEEP_ALIVE_SOCKET_OPTIONS
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def test_socket_options_override_defaults(make_client):
|
|
420
|
+
# If socket_options are set to [], this doesn't mean the user doesn't want
|
|
421
|
+
# any custom socket_options, but rather that they want to disable the urllib3
|
|
422
|
+
# socket option defaults, so we need to set this and not ignore it.
|
|
423
|
+
client = make_client(http2=False, socket_options=[])
|
|
424
|
+
|
|
395
425
|
options = client.transport._get_pool_options()
|
|
396
426
|
assert options["socket_options"] == []
|
|
397
427
|
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
import io
|
|
3
|
-
import logging
|
|
4
|
-
import os
|
|
5
|
-
import urllib.parse
|
|
6
|
-
import urllib.request
|
|
7
|
-
import urllib.error
|
|
8
|
-
import urllib3
|
|
9
|
-
import sys
|
|
10
|
-
|
|
11
|
-
from itertools import chain, product
|
|
12
|
-
|
|
13
|
-
from typing import TYPE_CHECKING
|
|
14
|
-
|
|
15
|
-
if TYPE_CHECKING:
|
|
16
|
-
from typing import Any, Callable, Dict, Optional
|
|
17
|
-
|
|
18
|
-
from sentry_sdk.utils import (
|
|
19
|
-
logger as sentry_logger,
|
|
20
|
-
env_to_bool,
|
|
21
|
-
capture_internal_exceptions,
|
|
22
|
-
)
|
|
23
|
-
from sentry_sdk.envelope import Envelope
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
logger = logging.getLogger("spotlight")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
DEFAULT_SPOTLIGHT_URL = "http://localhost:8969/stream"
|
|
30
|
-
DJANGO_SPOTLIGHT_MIDDLEWARE_PATH = "sentry_sdk.spotlight.SpotlightMiddleware"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class SpotlightClient:
|
|
34
|
-
def __init__(self, url: str) -> None:
|
|
35
|
-
self.url = url
|
|
36
|
-
self.http = urllib3.PoolManager()
|
|
37
|
-
self.fails = 0
|
|
38
|
-
|
|
39
|
-
def capture_envelope(self, envelope: Envelope) -> None:
|
|
40
|
-
body = io.BytesIO()
|
|
41
|
-
envelope.serialize_into(body)
|
|
42
|
-
try:
|
|
43
|
-
req = self.http.request(
|
|
44
|
-
url=self.url,
|
|
45
|
-
body=body.getvalue(),
|
|
46
|
-
method="POST",
|
|
47
|
-
headers={
|
|
48
|
-
"Content-Type": "application/x-sentry-envelope",
|
|
49
|
-
},
|
|
50
|
-
)
|
|
51
|
-
req.close()
|
|
52
|
-
self.fails = 0
|
|
53
|
-
except Exception as e:
|
|
54
|
-
if self.fails < 2:
|
|
55
|
-
sentry_logger.warning(str(e))
|
|
56
|
-
self.fails += 1
|
|
57
|
-
elif self.fails == 2:
|
|
58
|
-
self.fails += 1
|
|
59
|
-
sentry_logger.warning(
|
|
60
|
-
"Looks like Spotlight is not running, will keep trying to send events but will not log errors."
|
|
61
|
-
)
|
|
62
|
-
# omitting self.fails += 1 in the `else:` case intentionally
|
|
63
|
-
# to avoid overflowing the variable if Spotlight never becomes reachable
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
try:
|
|
67
|
-
from django.utils.deprecation import MiddlewareMixin
|
|
68
|
-
from django.http import HttpResponseServerError, HttpResponse, HttpRequest
|
|
69
|
-
from django.conf import settings
|
|
70
|
-
|
|
71
|
-
SPOTLIGHT_JS_ENTRY_PATH = "/assets/main.js"
|
|
72
|
-
SPOTLIGHT_JS_SNIPPET_PATTERN = (
|
|
73
|
-
"<script>window.__spotlight = {{ initOptions: {{ sidecarUrl: '{spotlight_url}', fullPage: false }} }};</script>\n"
|
|
74
|
-
'<script type="module" crossorigin src="{spotlight_js_url}"></script>\n'
|
|
75
|
-
)
|
|
76
|
-
SPOTLIGHT_ERROR_PAGE_SNIPPET = (
|
|
77
|
-
'<html><base href="{spotlight_url}">\n'
|
|
78
|
-
'<script>window.__spotlight = {{ initOptions: {{ fullPage: true, startFrom: "/errors/{event_id}" }}}};</script>\n'
|
|
79
|
-
)
|
|
80
|
-
CHARSET_PREFIX = "charset="
|
|
81
|
-
BODY_TAG_NAME = "body"
|
|
82
|
-
BODY_CLOSE_TAG_POSSIBILITIES = tuple(
|
|
83
|
-
"</{}>".format("".join(chars))
|
|
84
|
-
for chars in product(*zip(BODY_TAG_NAME.upper(), BODY_TAG_NAME.lower()))
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
class SpotlightMiddleware(MiddlewareMixin): # type: ignore[misc]
|
|
88
|
-
_spotlight_script: Optional[str] = None
|
|
89
|
-
_spotlight_url: Optional[str] = None
|
|
90
|
-
|
|
91
|
-
def __init__(self, get_response: Callable[..., HttpResponse]) -> None:
|
|
92
|
-
super().__init__(get_response)
|
|
93
|
-
|
|
94
|
-
import sentry_sdk.api
|
|
95
|
-
|
|
96
|
-
self.sentry_sdk = sentry_sdk.api
|
|
97
|
-
|
|
98
|
-
spotlight_client = self.sentry_sdk.get_client().spotlight
|
|
99
|
-
if spotlight_client is None:
|
|
100
|
-
sentry_logger.warning(
|
|
101
|
-
"Cannot find Spotlight client from SpotlightMiddleware, disabling the middleware."
|
|
102
|
-
)
|
|
103
|
-
return None
|
|
104
|
-
# Spotlight URL has a trailing `/stream` part at the end so split it off
|
|
105
|
-
self._spotlight_url = urllib.parse.urljoin(spotlight_client.url, "../")
|
|
106
|
-
|
|
107
|
-
@property
|
|
108
|
-
def spotlight_script(self) -> Optional[str]:
|
|
109
|
-
if self._spotlight_url is not None and self._spotlight_script is None:
|
|
110
|
-
try:
|
|
111
|
-
spotlight_js_url = urllib.parse.urljoin(
|
|
112
|
-
self._spotlight_url, SPOTLIGHT_JS_ENTRY_PATH
|
|
113
|
-
)
|
|
114
|
-
req = urllib.request.Request(
|
|
115
|
-
spotlight_js_url,
|
|
116
|
-
method="HEAD",
|
|
117
|
-
)
|
|
118
|
-
urllib.request.urlopen(req)
|
|
119
|
-
self._spotlight_script = SPOTLIGHT_JS_SNIPPET_PATTERN.format(
|
|
120
|
-
spotlight_url=self._spotlight_url,
|
|
121
|
-
spotlight_js_url=spotlight_js_url,
|
|
122
|
-
)
|
|
123
|
-
except urllib.error.URLError as err:
|
|
124
|
-
sentry_logger.debug(
|
|
125
|
-
"Cannot get Spotlight JS to inject at %s. SpotlightMiddleware will not be very useful.",
|
|
126
|
-
spotlight_js_url,
|
|
127
|
-
exc_info=err,
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
return self._spotlight_script
|
|
131
|
-
|
|
132
|
-
def process_response(
|
|
133
|
-
self, _request: HttpRequest, response: HttpResponse
|
|
134
|
-
) -> Optional[HttpResponse]:
|
|
135
|
-
content_type_header = tuple(
|
|
136
|
-
p.strip()
|
|
137
|
-
for p in response.headers.get("Content-Type", "").lower().split(";")
|
|
138
|
-
)
|
|
139
|
-
content_type = content_type_header[0]
|
|
140
|
-
if len(content_type_header) > 1 and content_type_header[1].startswith(
|
|
141
|
-
CHARSET_PREFIX
|
|
142
|
-
):
|
|
143
|
-
encoding = content_type_header[1][len(CHARSET_PREFIX) :]
|
|
144
|
-
else:
|
|
145
|
-
encoding = "utf-8"
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
self.spotlight_script is not None
|
|
149
|
-
and not response.streaming
|
|
150
|
-
and content_type == "text/html"
|
|
151
|
-
):
|
|
152
|
-
content_length = len(response.content)
|
|
153
|
-
injection = self.spotlight_script.encode(encoding)
|
|
154
|
-
injection_site = next(
|
|
155
|
-
(
|
|
156
|
-
idx
|
|
157
|
-
for idx in (
|
|
158
|
-
response.content.rfind(body_variant.encode(encoding))
|
|
159
|
-
for body_variant in BODY_CLOSE_TAG_POSSIBILITIES
|
|
160
|
-
)
|
|
161
|
-
if idx > -1
|
|
162
|
-
),
|
|
163
|
-
content_length,
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
# This approach works even when we don't have a `</body>` tag
|
|
167
|
-
response.content = (
|
|
168
|
-
response.content[:injection_site]
|
|
169
|
-
+ injection
|
|
170
|
-
+ response.content[injection_site:]
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
if response.has_header("Content-Length"):
|
|
174
|
-
response.headers["Content-Length"] = content_length + len(injection)
|
|
175
|
-
|
|
176
|
-
return response
|
|
177
|
-
|
|
178
|
-
def process_exception(
|
|
179
|
-
self, _request: HttpRequest, exception: Exception
|
|
180
|
-
) -> Optional[HttpResponseServerError]:
|
|
181
|
-
if not settings.DEBUG or not self._spotlight_url:
|
|
182
|
-
return None
|
|
183
|
-
|
|
184
|
-
try:
|
|
185
|
-
spotlight = (
|
|
186
|
-
urllib.request.urlopen(self._spotlight_url).read().decode("utf-8")
|
|
187
|
-
)
|
|
188
|
-
except urllib.error.URLError:
|
|
189
|
-
return None
|
|
190
|
-
else:
|
|
191
|
-
event_id = self.sentry_sdk.capture_exception(exception)
|
|
192
|
-
return HttpResponseServerError(
|
|
193
|
-
spotlight.replace(
|
|
194
|
-
"<html>",
|
|
195
|
-
SPOTLIGHT_ERROR_PAGE_SNIPPET.format(
|
|
196
|
-
spotlight_url=self._spotlight_url, event_id=event_id
|
|
197
|
-
),
|
|
198
|
-
)
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
except ImportError:
|
|
202
|
-
settings = None
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def setup_spotlight(options: Dict[str, Any]) -> Optional[SpotlightClient]:
|
|
206
|
-
_handler = logging.StreamHandler(sys.stderr)
|
|
207
|
-
_handler.setFormatter(logging.Formatter(" [spotlight] %(levelname)s: %(message)s"))
|
|
208
|
-
logger.addHandler(_handler)
|
|
209
|
-
logger.setLevel(logging.INFO)
|
|
210
|
-
|
|
211
|
-
url = options.get("spotlight")
|
|
212
|
-
|
|
213
|
-
if url is True:
|
|
214
|
-
url = DEFAULT_SPOTLIGHT_URL
|
|
215
|
-
|
|
216
|
-
if not isinstance(url, str):
|
|
217
|
-
return None
|
|
218
|
-
|
|
219
|
-
with capture_internal_exceptions():
|
|
220
|
-
if (
|
|
221
|
-
settings is not None
|
|
222
|
-
and settings.DEBUG
|
|
223
|
-
and env_to_bool(os.environ.get("SENTRY_SPOTLIGHT_ON_ERROR", "1"))
|
|
224
|
-
and env_to_bool(os.environ.get("SENTRY_SPOTLIGHT_MIDDLEWARE", "1"))
|
|
225
|
-
):
|
|
226
|
-
middleware = settings.MIDDLEWARE
|
|
227
|
-
if DJANGO_SPOTLIGHT_MIDDLEWARE_PATH not in middleware:
|
|
228
|
-
settings.MIDDLEWARE = type(middleware)(
|
|
229
|
-
chain(middleware, (DJANGO_SPOTLIGHT_MIDDLEWARE_PATH,))
|
|
230
|
-
)
|
|
231
|
-
logger.info("Enabled Spotlight integration for Django")
|
|
232
|
-
|
|
233
|
-
client = SpotlightClient(url)
|
|
234
|
-
logger.info("Enabled Spotlight using sidecar at %s", url)
|
|
235
|
-
|
|
236
|
-
return client
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|