django-lambda-tasks 0.9.2__tar.gz → 0.11.0__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.
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/steering/product.md +19 -23
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/steering/structure.md +4 -4
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/steering/tech.md +1 -18
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/PKG-INFO +27 -13
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/README.md +26 -0
- django_lambda_tasks-0.11.0/example/example_app/tasks.py +31 -0
- django_lambda_tasks-0.11.0/example/example_app/urls.py +7 -0
- django_lambda_tasks-0.11.0/example/example_app/views.py +17 -0
- django_lambda_tasks-0.11.0/example/example_project/sentry.py +50 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/example_project/settings.py +7 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/handler.py +0 -19
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/models.py +138 -118
- django_lambda_tasks-0.11.0/lambda_tasks/sentry.py +143 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/tasks.py +5 -1
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/pyproject.toml +2 -18
- django_lambda_tasks-0.11.0/tests/test_batch_worker.py +77 -0
- django_lambda_tasks-0.11.0/tests/test_sentry.py +448 -0
- django_lambda_tasks-0.9.2/example/example_app/tasks.py +0 -18
- django_lambda_tasks-0.9.2/example/example_app/urls.py +0 -6
- django_lambda_tasks-0.9.2/example/example_app/views.py +0 -8
- django_lambda_tasks-0.9.2/lambda_tasks/tracing.py +0 -325
- django_lambda_tasks-0.9.2/tests/test_batch_worker.py +0 -138
- django_lambda_tasks-0.9.2/tests/test_tracing.py +0 -539
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.github/dependabot.yml +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.github/workflows/ci.yml +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.github/workflows/release.yml +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.gitignore +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/async-local-execution/.config.kiro +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/async-local-execution/design.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/async-local-execution/requirements.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/async-local-execution/tasks.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/deferred-task-enqueue/.config.kiro +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/deferred-task-enqueue/design.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/deferred-task-enqueue/requirements.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/deferred-task-enqueue/tasks.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/eager-mode-example-app/.config.kiro +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/eager-mode-example-app/design.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/eager-mode-example-app/requirements.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/eager-mode-example-app/tasks.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ignore-errors-decorator-option/.config.kiro +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ignore-errors-decorator-option/design.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ignore-errors-decorator-option/requirements.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ignore-errors-decorator-option/tasks.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/import-string-task-resolution/.config.kiro +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/import-string-task-resolution/design.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/import-string-task-resolution/requirements.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/import-string-task-resolution/tasks.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/retry-delay/.config.kiro +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/retry-delay/design.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/retry-delay/requirements.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/retry-delay/tasks.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks/.config.kiro +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks/design.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks/requirements.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks/tasks.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks-bugfix/.config.kiro +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks-bugfix/bugfix.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks-bugfix/design.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks-bugfix/tasks.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/singleton-task/.config.kiro +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/singleton-task/design.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/singleton-task/requirements.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/singleton-task/tasks.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ssm-environment-loader/.config.kiro +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ssm-environment-loader/design.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ssm-environment-loader/requirements.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ssm-environment-loader/tasks.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/task-retry/.config.kiro +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/task-retry/design.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/task-retry/requirements.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/task-retry/tasks.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.pre-commit-config.yaml +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.vscode/settings.json +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/docker-compose.yml +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/README.md +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/docker-compose.yml +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/example_app/__init__.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/example_app/apps.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/example_project/__init__.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/example_project/urls.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/example_project/wsgi.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/manage.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/__init__.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/admin.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/apps.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/checks.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/decorators.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/environment_loader.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/local_executor.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/logging.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/migrations/0001_initial.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/migrations/0002_alter_taskrecord_status.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/migrations/0003_taskrecord_taskrecord_kwargs_trgm.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/migrations/0004_remove_taskrecord_taskrecord_kwargs_trgm_and_more.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/migrations/__init__.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/secret_loader.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/settings.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/timeouts.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/conftest.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/settings.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_admin.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_batch.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_checks.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_decorator.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_decorators.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_deferred_enqueue.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_environment_loader.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_handler.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_kwargs_only.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_local_executor.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_logging.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_memory_limit.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_models.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_noop_execution.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_secret_loader.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_serializer.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_settings.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_tasks.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_timeout_validation.py +0 -0
- {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_timeouts.py +0 -0
|
@@ -31,7 +31,6 @@ Key modules:
|
|
|
31
31
|
- `models.py` — `TaskRecord` (Django ORM), `SQSLambdaTaskMessage` (SQS schema + execution), `SQSLambdaTask` (routing + SQS publish, Batch submit, or local pool submit)
|
|
32
32
|
- `local_executor.py` — `ProcessPoolExecutor`-based async local execution for development
|
|
33
33
|
- `handler.py` — AWS Lambda entry point + AWS Batch container entry point (`main()`)
|
|
34
|
-
- `tracing.py` — optional OpenTelemetry tracing (import-time setup, direct X-Ray OTLP export, SigV4-signed)
|
|
35
34
|
- `logging.py` — `task_logger` for invocation-scoped log output
|
|
36
35
|
- `settings.py` — lazy `LambdaTasksSettings` reading from Django settings, queue type helpers
|
|
37
36
|
|
|
@@ -128,7 +127,7 @@ class SQSLambdaTaskMessage(BaseModel):
|
|
|
128
127
|
|
|
129
128
|
## Execution
|
|
130
129
|
|
|
131
|
-
`SQSLambdaTaskMessage.execute_immediately(*, message_id: str)` in `models.py
|
|
130
|
+
`SQSLambdaTaskMessage.execute_immediately(*, message_id: str)` in `models.py` sets the `task_logger` `message_id` and enters `task_scope(...)` (Sentry transaction name + tags), then delegates the run to the private `_run(*, message_id)` method which:
|
|
132
131
|
1. Checks for an existing `TaskRecord` with the same `pk` (`message_id`) via `get_or_create`
|
|
133
132
|
2. If a record already exists (any status), logs and returns immediately — duplicate deliveries are silently skipped
|
|
134
133
|
3. Resolves timeouts: decorator default → settings defaults (soft=270s, hard=300s), validated against queue max (900 for SQS, 3600 for Batch)
|
|
@@ -159,7 +158,6 @@ class SQSLambdaTaskMessage(BaseModel):
|
|
|
159
158
|
- Reads `LAMBDA_TASKS_MESSAGE` env var (the serialized task JSON, set by `submit_batch_job` via container overrides)
|
|
160
159
|
- Uses `AWS_BATCH_JOB_ID` as the `message_id` (falls back to UUID4 if not set)
|
|
161
160
|
- Constructs a synthetic SQS event and delegates to `handler()` — reuses the full cold-start and execution path
|
|
162
|
-
- Before returning, force-flushes and shuts down the global `TracerProvider` (if OTEL tracing is configured) so short-lived Batch jobs do not drop buffered spans
|
|
163
161
|
- Returns exit code 0 on success, 1 on failure
|
|
164
162
|
|
|
165
163
|
## Environment Loader
|
|
@@ -299,26 +297,6 @@ Relying on `atexit` alone loses this race in applications with a heavy shutdown
|
|
|
299
297
|
|
|
300
298
|
The checks are registered via `AppConfig.ready()` and tagged with `deploy=True` so they only fire during `--deploy` checks.
|
|
301
299
|
|
|
302
|
-
## OpenTelemetry Tracing (optional)
|
|
303
|
-
|
|
304
|
-
`lambda_tasks/tracing.py` adds OpenTelemetry tracing to the Lambda and Batch execution paths without the managed ADOT Lambda layer — the OTEL packages installed in the application image are the single source of instrumentation (reproducible-build friendly).
|
|
305
|
-
|
|
306
|
-
Setup lives in the handler module, **not** Django settings. `AwsLambdaInstrumentor` must wrap the handler named by `_HANDLER`/`ORIG_HANDLER` **before** the runtime invokes it, which is earlier than the cold-start `django.setup()` inside `handler()`. So `lambda_tasks.handler` calls `configure_tracing()` and `instrument_lambda_handler()` at **import time**, and the setup is driven purely by standard OTEL environment variables (settings are not configured yet at import time).
|
|
307
|
-
|
|
308
|
-
Behaviour:
|
|
309
|
-
- Gated on `OTEL_EXPORTER_OTLP_ENDPOINT` (or the per-signal `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`). Unset → everything is a no-op (local dev, tests, un-opted-in deployments).
|
|
310
|
-
- Builds a `TracerProvider` with an OTLP/HTTP span exporter via a `BatchSpanProcessor`. Spans go **directly** to the AWS X-Ray OTLP traces endpoint (`https://xray.<region>.amazonaws.com/v1/traces`) — no collector, no daemon, no Lambda layer.
|
|
311
|
-
- The exporter's `requests` session SigV4-signs each export with the ambient AWS credentials (task / execution role) via `botocore` — no API key to manage. Unsigned when no credentials are resolvable.
|
|
312
|
-
- Uses the AWS X-Ray id generator (`AwsXRayIdGenerator`) so the X-Ray backend accepts the trace IDs, and the X-Ray propagator (`AwsXRayLambdaPropagator` on Lambda, else `AwsXRayPropagator`). The X-Ray packages (`opentelemetry-sdk-extension-aws`, `opentelemetry-propagator-aws-xray`) are part of the `otel` extra.
|
|
313
|
-
- Library instrumentation: after the provider and propagator are installed, `configure_tracing()` calls `_instrument_libraries()`, which attaches the instrumentors for the libraries the tasks use — **psycopg 3** (SQL queries), **botocore** (every boto3 / aioboto3 call), **requests** and **httpx** (outbound HTTP), and **redis** (cache ops via `django_redis` → `redis-py`). Each instrumentor is wrapped independently and is best-effort: a missing or failing one is logged and skipped, never raised, so one broken package cannot stop the others or take down the worker. These are process-global and idempotent, and emit spans into the global provider (so it must exist first). Django request instrumentation (`DjangoInstrumentor`) is **not** installed here — a task has no Django request; the API (ECS) consumer installs that itself. sqlcommenter (`enable_commenter=True` on the psycopg instrumentor) is left off because it modifies the SQL text sent to PostgreSQL.
|
|
314
|
-
- Exporter self-trace suppression: the OTLP exporter POSTs spans over `requests` to the X-Ray endpoint, so the `requests` instrumentor is configured with `excluded_urls="xray\\..*\\.amazonaws\\.com"` to avoid tracing its own export calls (self-referential noise). The instrumentor also honours the `OTEL_PYTHON_REQUESTS_EXCLUDED_URLS` env var for per-environment overrides. botocore is not used by the exporter, so it needs no exclusion.
|
|
315
|
-
- The OpenTelemetry / `requests` imports are **deferred into the functions** (module scope imports only `os`), so the OTEL packages remain a genuinely optional extra: importing `lambda_tasks.handler` with them uninstalled is a no-op, not an `ImportError`. The disabled path (endpoint unset) never reaches any OTEL import.
|
|
316
|
-
- Lambda path: also calls `AwsLambdaInstrumentor().instrument()` (only when `AWS_LAMBDA_FUNCTION_NAME` is set). Batch path (`main()`): the instrumentor is skipped (no handler to wrap); the provider alone is configured.
|
|
317
|
-
- Flush on shutdown: `main()` calls `TracerProvider.force_flush()` / `shutdown()` before the one-shot process exits so short-lived Batch jobs do not drop buffered spans. The Lambda path needs no manual flush — `AwsLambdaInstrumentor` force-flushes per invocation.
|
|
318
|
-
- Best-effort and observable: tracing must never take down the worker. `configure_tracing()` and `instrument_lambda_handler()` catch setup failures (missing OTEL packages, provider build errors) and log a warning instead of raising — important because they run at the handler module's import time. The `lambda_tasks.tracing` logger also surfaces otherwise-silent conditions at `INFO`/`WARNING`: tracing configured / already configured / disabled, handler instrumented or skipped (not on Lambda), no resolvable AWS region, and no resolvable AWS credentials (which would make exports unsigned and rejected by X-Ray).
|
|
319
|
-
|
|
320
|
-
`configure_tracing()` returns the installed provider (or `None` when disabled or already configured — it is idempotent). Install the OTEL packages via the `otel` extra; the consuming application sets `OTEL_EXPORTER_OTLP_ENDPOINT` and declares the X-Ray packages it wants.
|
|
321
|
-
|
|
322
300
|
## Logging
|
|
323
301
|
|
|
324
302
|
Import `task_logger` to emit log records that are automatically prefixed with the active `message_id`:
|
|
@@ -335,6 +313,24 @@ def my_task(*, user_id: int) -> None:
|
|
|
335
313
|
|
|
336
314
|
`task_logger` is a `LoggerAdapter` wrapping the `lambda_tasks.task` logger. The executor sets the `message_id` before each task runs and clears it in a `finally` block. Using your own `logging.getLogger(__name__)` is fine — those records just won't carry the prefix.
|
|
337
315
|
|
|
316
|
+
## Sentry
|
|
317
|
+
|
|
318
|
+
The library does not call `sentry_sdk.init(...)` — the host application owns Sentry setup (typically `DjangoIntegration` + `AwsLambdaIntegration`). The problem those integrations leave is attribution: on Lambda, `AwsLambdaIntegration` names every event's transaction after the Lambda function, so all tasks share one transaction (and a single invocation-level transaction is shared across a batch of SQS records); on AWS Batch there is no request and no Lambda integration, so the transaction and URL are `"--"` and `traces_sample_rate` produces no trace for the task body. Either way you cannot tell which task ran or raised.
|
|
319
|
+
|
|
320
|
+
`lambda_tasks.sentry.task_scope(*, task_name, message_id, n_retries)` fixes this. `execute_immediately` wraps the task body in it, which — when a Sentry client is active — opens an isolated `sentry_sdk.new_scope()` **and a per-task root span/transaction** named after the task. This gives one trace per task on both Lambda and Batch. It records, both on the span and as scope tags:
|
|
321
|
+
|
|
322
|
+
- transaction/span **name** = the fully-qualified task name (e.g. `myapp.tasks.my_task`)
|
|
323
|
+
- `lambda_task` = task name
|
|
324
|
+
- `lambda_task.message_id` = the `TaskRecord` primary key
|
|
325
|
+
- `lambda_task.n_retries` = the retry counter
|
|
326
|
+
- `lambda_task.runtime` = the physical execution environment, read from the AWS environment: `AWS_LAMBDA_FUNCTION_NAME` on Lambda or `AWS_BATCH_JQ_NAME` (the Batch job queue name) on Batch; omitted when neither is present
|
|
327
|
+
|
|
328
|
+
The scope covers the success, retry, and failure paths, so task-logic exceptions (which the executor records as `FAILED` and does not re-raise to the handler) still carry the task name when captured by Sentry. The scope tags are kept in addition to span metadata so error events are attributed even when the performance span is not sampled.
|
|
329
|
+
|
|
330
|
+
**SDK version compatibility:** `task_scope` is future-proof across the sentry-sdk 2.x→3.0 boundary. On 2.x it uses `start_transaction` + `set_tag` (a root span must be a transaction to be sent); on 3.0 (stream mode, where transactions are removed and tags no longer propagate to spans) it uses `start_span` + `set_attribute`. The correct API is detected at runtime via `hasattr`, so no code change is needed when the host upgrades the SDK.
|
|
331
|
+
|
|
332
|
+
`task_scope` is a no-op when `sentry_sdk` is not installed or no client is initialised, so Sentry is not a dependency of the library. `sentry-sdk` is a dev dependency only, for the test suite. No custom Sentry integration is needed — per-task tracing at the one execution entry point covers both Lambda and Batch.
|
|
333
|
+
|
|
338
334
|
## Built-in Tasks
|
|
339
335
|
|
|
340
336
|
`lambda_tasks.tasks` provides maintenance tasks that ship with the library:
|
|
@@ -16,6 +16,7 @@ django-lambda-tasks/
|
|
|
16
16
|
│ ├── decorators.py # LambdaTaskWrapper, @lambda_task
|
|
17
17
|
│ ├── handler.py # AWS Lambda entry point + AWS Batch container entry point (main)
|
|
18
18
|
│ ├── logging.py # task_logger — invocation-scoped LoggerAdapter
|
|
19
|
+
│ ├── sentry.py # task_scope — per-task Sentry transaction name + tags
|
|
19
20
|
│ ├── models.py # TaskRecord, SQSLambdaTaskMessage, SQSLambdaTask
|
|
20
21
|
│ ├── settings.py # LambdaTasksSettings, constants, queue type helpers
|
|
21
22
|
│ ├── secret_loader.py # Resolves LAMBDA_TASKS_SECRET_* env vars at cold start
|
|
@@ -23,7 +24,6 @@ django-lambda-tasks/
|
|
|
23
24
|
│ ├── local_executor.py # ProcessPoolExecutor for async local task execution
|
|
24
25
|
│ ├── tasks.py # Built-in tasks (cleanup_task_records, submit_batch_job)
|
|
25
26
|
│ ├── timeouts.py # TimeoutContext implementation
|
|
26
|
-
│ ├── tracing.py # Optional OpenTelemetry tracing (import-time setup, X-Ray OTLP export)
|
|
27
27
|
│ └── migrations/ # Django migrations for TaskRecord
|
|
28
28
|
├── tests/ # pytest test suite
|
|
29
29
|
│ ├── conftest.py
|
|
@@ -42,13 +42,13 @@ django-lambda-tasks/
|
|
|
42
42
|
## Module Responsibilities
|
|
43
43
|
|
|
44
44
|
- `decorators.py` — defines `LambdaTaskWrapper` and `@lambda_task` decorator factory; enforces kwargs-only at decoration time; validates timeouts (> 0, soft < hard) at decoration time; upper-bound timeout validation deferred to execution time based on queue type
|
|
45
|
-
- `models.py` — `TaskRecord` (Django ORM), `SQSLambdaTaskMessage` (Pydantic, SQS schema + execution logic), `SQSLambdaTask` (Pydantic, holds message + routing; `_execute()` inspects queue config to publish to SQS, submit to Batch via `submit_batch_job`, execute eagerly, or submit to the local process pool; `execute_on_commit()` registers `_execute` with `transaction.on_commit`)
|
|
45
|
+
- `models.py` — `TaskRecord` (Django ORM), `SQSLambdaTaskMessage` (Pydantic, SQS schema + execution logic; `execute_immediately` owns the logging + Sentry context and delegates the run to a private `_run` method), `SQSLambdaTask` (Pydantic, holds message + routing; `_execute()` inspects queue config to publish to SQS, submit to Batch via `submit_batch_job`, execute eagerly, or submit to the local process pool; `execute_on_commit()` registers `_execute` with `transaction.on_commit`)
|
|
46
46
|
- `local_executor.py` — `ProcessPoolExecutor`-based async local execution; `get_pool()` lazily creates a module-level pool; `submit_task()` fire-and-forget submission; `_execute_in_worker()` deserializes and runs the task in a child process; `_pool_initializer()` calls `django.setup()` per worker (and sets `SIGINT` to `SIG_IGN` so workers ignore Ctrl+C); `_install_shutdown_handlers()` installs main-thread `SIGINT`/`SIGTERM` handlers that release the pool before chaining to the previous handler
|
|
47
|
-
- `handler.py` — Lambda entry point (`handler(event, context)`); cold-start init (memory limit → temporary log handler → `resolve_environment()` → `resolve_secrets_into_env()` → handler removed → conditional `django.setup()`) runs inside the handler on first invocation, guarded by `_cold_start_done` sentinel; calls `db.close_old_connections()` before each SQS record to handle stale connections on warm starts; processes SQS records independently; returns `batchItemFailures`.
|
|
48
|
-
- `tracing.py` — optional OpenTelemetry tracing, driven purely by standard OTEL env vars (no Django settings) so the package stays generic; no-op unless `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) is set. All OpenTelemetry / `requests` imports are **deferred into the functions** (only `os` is imported at module scope) so the OTEL packages are a genuinely optional extra — importing `lambda_tasks.handler` without them is a no-op, not an `ImportError`. `configure_tracing()` installs a global `TracerProvider` using the X-Ray id generator (`AwsXRayIdGenerator`) with an OTLP/HTTP exporter whose `requests` session SigV4-signs each export to the AWS X-Ray traces endpoint (unsigned when no credentials resolve), and sets the X-Ray propagator (`AwsXRayLambdaPropagator` on Lambda, else `AwsXRayPropagator`), then calls `_instrument_libraries()` to attach the library instrumentors (psycopg 3, botocore, requests, httpx, redis) — each wrapped independently and best-effort (a missing or failing instrumentor is logged, not raised), and the `requests` instrumentor is given an `excluded_urls` regex for the X-Ray OTLP endpoint so the exporter's own export POSTs are not self-traced. `instrument_lambda_handler()` calls `AwsLambdaInstrumentor().instrument()` only when tracing is enabled and `AWS_LAMBDA_FUNCTION_NAME` is set (Lambda path); skipped on the Batch path. `flush_tracing()` force-flushes and shuts down the provider (gated; used by the Batch entry point)
|
|
47
|
+
- `handler.py` — Lambda entry point (`handler(event, context)`); cold-start init (memory limit → temporary log handler → `resolve_environment()` → `resolve_secrets_into_env()` → handler removed → conditional `django.setup()`) runs inside the handler on first invocation, guarded by `_cold_start_done` sentinel; calls `db.close_old_connections()` before each SQS record to handle stale connections on warm starts; processes SQS records independently; returns `batchItemFailures`. Also provides `main()` as the AWS Batch container entry point (`python -m lambda_tasks.handler`) — reads `LAMBDA_TASKS_MESSAGE` env var, constructs a synthetic SQS event, and delegates to `handler()`
|
|
49
48
|
- `environment_loader.py` — loads env vars from a Secrets Manager secret at cold start; validates flat JSON format; idempotent via `_loaded` sentinel
|
|
50
49
|
- `secret_loader.py` — resolves `LAMBDA_TASKS_SECRET_*` env vars from Secrets Manager before Django starts; validates format, detects conflicts, batches API calls; idempotent via `_loaded` sentinel
|
|
51
50
|
- `logging.py` — `task_logger` singleton; `message_id` set/cleared around each task execution
|
|
51
|
+
- `sentry.py` — `task_scope` context manager; opens an isolated `new_scope` and a per-task root span/transaction named after the task, so both errors and performance traces are attributed per task (one trace per task on Lambda and Batch, not one invocation-level transaction shared across an SQS batch). Records `lambda_task` / `lambda_task.message_id` / `lambda_task.n_retries` / `lambda_task.runtime` as span metadata and scope tags; `lambda_task.runtime` is the execution environment read from `AWS_LAMBDA_FUNCTION_NAME` (Lambda) or `AWS_BATCH_JQ_NAME` (Batch). Future-proof across the SDK 2.x→3.0 boundary: it uses `start_transaction`+`set_tag` on 2.x and `start_span`+`set_attribute` on 3.0 (stream mode), detected at runtime via `hasattr`. A no-op when `sentry_sdk` is absent or uninitialised, so the library has no hard dependency on Sentry (`sentry-sdk` is a dev dependency only). Wrapped around the task body in `SQSLambdaTaskMessage.execute_immediately`
|
|
52
52
|
- `settings.py` — constants (`MAX_DELAY`, `MAX_TIMEOUT`, `MAX_BATCH_TIMEOUT`); `_is_sqs_queue()` and `_is_batch_queue()` helpers; `LambdaTasksSettings` instantiated fresh per use (reads live Django settings); `queue_max_timeout()` method returns 900 or 3600 based on queue type
|
|
53
53
|
- `admin.py` — Django admin registration for `TaskRecord`
|
|
54
54
|
- `apps.py` — Django `AppConfig`; `ready()` imports `checks` to register deployment checks and installs the local-executor shutdown signal handlers when `LOCAL_WORKERS > 0`
|
|
@@ -17,30 +17,13 @@ inclusion: always
|
|
|
17
17
|
| `boto3` | `>= 1.42.69` | AWS SDK — SQS publishing, Lambda integration |
|
|
18
18
|
| `pydantic` | `>= 2.12.5` | Task argument serialization via `SQSLambdaTaskMessage` model |
|
|
19
19
|
|
|
20
|
-
## Optional Dependencies
|
|
21
|
-
|
|
22
|
-
Installed via the `otel` extra (`pip install django-lambda-tasks[otel]`). Enables OpenTelemetry tracing in `lambda_tasks/tracing.py`; the library stays generic without them. SigV4 signing reuses `botocore` (already a boto3 transitive dependency) — no extra signing package.
|
|
23
|
-
|
|
24
|
-
| Package | Purpose |
|
|
25
|
-
|---|---|
|
|
26
|
-
| `opentelemetry-sdk` | `TracerProvider`, `BatchSpanProcessor` |
|
|
27
|
-
| `opentelemetry-exporter-otlp-proto-http` | OTLP/HTTP span exporter (no gRPC, no meta-package) |
|
|
28
|
-
| `opentelemetry-instrumentation-aws-lambda` | Wraps the Lambda handler (`AwsLambdaInstrumentor`) |
|
|
29
|
-
| `opentelemetry-sdk-extension-aws` | X-Ray id generator (`AwsXRayIdGenerator`) |
|
|
30
|
-
| `opentelemetry-propagator-aws-xray` | X-Ray trace propagator |
|
|
31
|
-
| `opentelemetry-instrumentation-psycopg` | SQL query spans (psycopg 3) |
|
|
32
|
-
| `opentelemetry-instrumentation-botocore` | AWS SDK spans — every boto3 / aioboto3 call |
|
|
33
|
-
| `opentelemetry-instrumentation-requests` | Outbound HTTP spans via `requests` |
|
|
34
|
-
| `opentelemetry-instrumentation-httpx` | Outbound HTTP spans via `httpx` |
|
|
35
|
-
| `opentelemetry-instrumentation-redis` | Cache operation spans (`django_redis` → `redis-py`) |
|
|
36
|
-
| `requests` | OTLP/HTTP transport session (SigV4-signed via `botocore`) |
|
|
37
|
-
|
|
38
20
|
## Dev Dependencies
|
|
39
21
|
|
|
40
22
|
| Package | Version | Purpose |
|
|
41
23
|
|---|---|---|
|
|
42
24
|
| `pytest` | `>= 9.0.2` | Test runner |
|
|
43
25
|
| `hypothesis` | present | Property-based testing (`.hypothesis/` directory exists) |
|
|
26
|
+
| `sentry-sdk` | present | Testing the `task_scope` Sentry integration; not a runtime dependency |
|
|
44
27
|
|
|
45
28
|
## Package Manager
|
|
46
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: django-lambda-tasks
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: Run async tasks in a lambda function
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: awslambdaric
|
|
@@ -8,18 +8,6 @@ Requires-Dist: boto3
|
|
|
8
8
|
Requires-Dist: django
|
|
9
9
|
Requires-Dist: pydantic
|
|
10
10
|
Requires-Dist: redis
|
|
11
|
-
Provides-Extra: otel
|
|
12
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-http; extra == 'otel'
|
|
13
|
-
Requires-Dist: opentelemetry-instrumentation-aws-lambda; extra == 'otel'
|
|
14
|
-
Requires-Dist: opentelemetry-instrumentation-botocore; extra == 'otel'
|
|
15
|
-
Requires-Dist: opentelemetry-instrumentation-httpx; extra == 'otel'
|
|
16
|
-
Requires-Dist: opentelemetry-instrumentation-psycopg; extra == 'otel'
|
|
17
|
-
Requires-Dist: opentelemetry-instrumentation-redis; extra == 'otel'
|
|
18
|
-
Requires-Dist: opentelemetry-instrumentation-requests; extra == 'otel'
|
|
19
|
-
Requires-Dist: opentelemetry-propagator-aws-xray; extra == 'otel'
|
|
20
|
-
Requires-Dist: opentelemetry-sdk; extra == 'otel'
|
|
21
|
-
Requires-Dist: opentelemetry-sdk-extension-aws; extra == 'otel'
|
|
22
|
-
Requires-Dist: requests; extra == 'otel'
|
|
23
11
|
Description-Content-Type: text/markdown
|
|
24
12
|
|
|
25
13
|
# Django Lambda Tasks
|
|
@@ -456,6 +444,32 @@ fields @timestamp, @message
|
|
|
456
444
|
| sort @timestamp asc
|
|
457
445
|
```
|
|
458
446
|
|
|
447
|
+
### Sentry
|
|
448
|
+
|
|
449
|
+
If your application uses Sentry, tasks are automatically attributed to their task name. Without this, Sentry's `AwsLambdaIntegration` names every event's transaction after the Lambda function (so all tasks share one transaction), and on AWS Batch there is no transaction at all (it shows as `"--"`) — so you cannot tell which task raised an error.
|
|
450
|
+
|
|
451
|
+
You do **not** need to add a custom integration or change your `sentry_sdk.init(...)`. Keep your existing setup:
|
|
452
|
+
|
|
453
|
+
```python
|
|
454
|
+
sentry_sdk.init(
|
|
455
|
+
dsn=SENTRY_DSN,
|
|
456
|
+
integrations=[DjangoIntegration(), AwsLambdaIntegration()],
|
|
457
|
+
# ...
|
|
458
|
+
)
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
When a task runs, `execute_immediately()` opens an isolated Sentry scope **and a per-task trace** (a root span/transaction named after the task). With `traces_sample_rate` set, you get one trace per task on both Lambda and Batch — rather than a single invocation-level transaction shared across a batch of SQS records. It sets, on both the span and the scope:
|
|
462
|
+
|
|
463
|
+
- the **transaction/span name** to the fully-qualified task name (e.g. `myapp.tasks.send_welcome_email`), overriding the Lambda function name on Lambda and replacing `"--"` on Batch
|
|
464
|
+
- `lambda_task` (task name), `lambda_task.message_id`, and `lambda_task.n_retries`
|
|
465
|
+
- `lambda_task.runtime` — the physical execution environment, derived from the AWS environment: the Lambda function name (`AWS_LAMBDA_FUNCTION_NAME`) on Lambda, or the Batch job queue name (`AWS_BATCH_JQ_NAME`) on Batch. Omitted when neither is set (e.g. local/eager execution).
|
|
466
|
+
|
|
467
|
+
This covers task failures too: the executor records failures as `FAILED` `TaskRecord`s rather than re-raising, so the trace wraps the task body to ensure Sentry error events still carry the task name.
|
|
468
|
+
|
|
469
|
+
The integration is future-proof across the sentry-sdk 2.x→3.0 boundary: it uses `start_transaction`/`set_tag` on 2.x and `start_span`/`set_attribute` on 3.0 (stream mode), chosen automatically at runtime — no change needed when you upgrade the SDK.
|
|
470
|
+
|
|
471
|
+
`sentry_sdk` is not a dependency of this library — if it is not installed or not initialised, the enrichment is a silent no-op.
|
|
472
|
+
|
|
459
473
|
---
|
|
460
474
|
|
|
461
475
|
## Automatic retries
|
|
@@ -432,6 +432,32 @@ fields @timestamp, @message
|
|
|
432
432
|
| sort @timestamp asc
|
|
433
433
|
```
|
|
434
434
|
|
|
435
|
+
### Sentry
|
|
436
|
+
|
|
437
|
+
If your application uses Sentry, tasks are automatically attributed to their task name. Without this, Sentry's `AwsLambdaIntegration` names every event's transaction after the Lambda function (so all tasks share one transaction), and on AWS Batch there is no transaction at all (it shows as `"--"`) — so you cannot tell which task raised an error.
|
|
438
|
+
|
|
439
|
+
You do **not** need to add a custom integration or change your `sentry_sdk.init(...)`. Keep your existing setup:
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
sentry_sdk.init(
|
|
443
|
+
dsn=SENTRY_DSN,
|
|
444
|
+
integrations=[DjangoIntegration(), AwsLambdaIntegration()],
|
|
445
|
+
# ...
|
|
446
|
+
)
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
When a task runs, `execute_immediately()` opens an isolated Sentry scope **and a per-task trace** (a root span/transaction named after the task). With `traces_sample_rate` set, you get one trace per task on both Lambda and Batch — rather than a single invocation-level transaction shared across a batch of SQS records. It sets, on both the span and the scope:
|
|
450
|
+
|
|
451
|
+
- the **transaction/span name** to the fully-qualified task name (e.g. `myapp.tasks.send_welcome_email`), overriding the Lambda function name on Lambda and replacing `"--"` on Batch
|
|
452
|
+
- `lambda_task` (task name), `lambda_task.message_id`, and `lambda_task.n_retries`
|
|
453
|
+
- `lambda_task.runtime` — the physical execution environment, derived from the AWS environment: the Lambda function name (`AWS_LAMBDA_FUNCTION_NAME`) on Lambda, or the Batch job queue name (`AWS_BATCH_JQ_NAME`) on Batch. Omitted when neither is set (e.g. local/eager execution).
|
|
454
|
+
|
|
455
|
+
This covers task failures too: the executor records failures as `FAILED` `TaskRecord`s rather than re-raising, so the trace wraps the task body to ensure Sentry error events still carry the task name.
|
|
456
|
+
|
|
457
|
+
The integration is future-proof across the sentry-sdk 2.x→3.0 boundary: it uses `start_transaction`/`set_tag` on 2.x and `start_span`/`set_attribute` on 3.0 (stream mode), chosen automatically at runtime — no change needed when you upgrade the SDK.
|
|
458
|
+
|
|
459
|
+
`sentry_sdk` is not a dependency of this library — if it is not installed or not initialised, the enrichment is a silent no-op.
|
|
460
|
+
|
|
435
461
|
---
|
|
436
462
|
|
|
437
463
|
## Automatic retries
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from time import sleep
|
|
2
|
+
|
|
3
|
+
from lambda_tasks.decorators import lambda_task
|
|
4
|
+
from lambda_tasks.logging import task_logger
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@lambda_task()
|
|
8
|
+
def greet(*, name: str) -> str:
|
|
9
|
+
"""A successful task — produces one per-task Sentry transaction named
|
|
10
|
+
``example_app.tasks.greet``."""
|
|
11
|
+
task_logger.info(f"Running greet with: {name=}")
|
|
12
|
+
|
|
13
|
+
for ii in range(3):
|
|
14
|
+
task_logger.info(f"Sleeping: {ii=}")
|
|
15
|
+
sleep(1)
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
raise RuntimeError("testing")
|
|
19
|
+
except Exception:
|
|
20
|
+
task_logger.error("Raising", exc_info=True)
|
|
21
|
+
|
|
22
|
+
return f"Hello, {name}!"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@lambda_task()
|
|
26
|
+
def boom(*, name: str) -> str:
|
|
27
|
+
"""A failing task — the executor records it as a FAILED TaskRecord and the
|
|
28
|
+
error is reported to Sentry attributed to ``example_app.tasks.boom`` (not
|
|
29
|
+
the Lambda function name), inside its own per-task trace."""
|
|
30
|
+
task_logger.info(f"Running boom with: {name=}")
|
|
31
|
+
raise RuntimeError(f"boom for {name}")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from django.http import HttpRequest, HttpResponse
|
|
2
|
+
from example_app.tasks import boom, greet
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def trigger(request: HttpRequest) -> HttpResponse:
|
|
6
|
+
"""Enqueue the successful task — produces a per-task Sentry transaction."""
|
|
7
|
+
name = request.GET.get("name", "world")
|
|
8
|
+
greet.execute_on_commit(name=name)
|
|
9
|
+
return HttpResponse(f"greet task triggered for: {name}")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def trigger_error(request: HttpRequest) -> HttpResponse:
|
|
13
|
+
"""Enqueue the failing task — produces a Sentry error attributed to the
|
|
14
|
+
task name, inside its own per-task trace."""
|
|
15
|
+
name = request.GET.get("name", "world")
|
|
16
|
+
boom.execute_on_commit(name=name)
|
|
17
|
+
return HttpResponse(f"boom task triggered for: {name}")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Optional Sentry initialisation for the example app.
|
|
2
|
+
|
|
3
|
+
Demonstrates how a host application wires Sentry so that
|
|
4
|
+
``django-lambda-tasks`` can attribute per-task traces and errors. This mirrors
|
|
5
|
+
a typical production ``sentry_sdk.init(...)`` (Django + AWS Lambda integrations
|
|
6
|
+
and tracing), but is driven entirely by environment variables so the example
|
|
7
|
+
runs with or without Sentry configured.
|
|
8
|
+
|
|
9
|
+
``init_sentry()`` is called from ``settings.py`` at import time. Because the
|
|
10
|
+
local process-pool workers call ``django.setup()`` (which imports settings) in
|
|
11
|
+
each child process, this runs in the web process *and* in every task worker —
|
|
12
|
+
which is exactly what is needed for the per-task trace to be captured where the
|
|
13
|
+
task actually executes.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def init_sentry() -> None:
|
|
21
|
+
"""Initialise Sentry if ``SENTRY_DSN`` is set; otherwise do nothing.
|
|
22
|
+
|
|
23
|
+
Idempotent enough for the example's purposes: ``sentry_sdk.init`` is safe to
|
|
24
|
+
call once per process, and this is only invoked from settings import.
|
|
25
|
+
"""
|
|
26
|
+
dsn = os.environ.get("SENTRY_DSN")
|
|
27
|
+
if not dsn:
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
import sentry_sdk
|
|
31
|
+
from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration
|
|
32
|
+
from sentry_sdk.integrations.django import DjangoIntegration
|
|
33
|
+
from sentry_sdk.integrations.logging import LoggingIntegration
|
|
34
|
+
|
|
35
|
+
sentry_sdk.init(
|
|
36
|
+
dsn=dsn,
|
|
37
|
+
integrations=[
|
|
38
|
+
DjangoIntegration(),
|
|
39
|
+
AwsLambdaIntegration(),
|
|
40
|
+
# On sentry-sdk 2.x the logging integration defaulted to
|
|
41
|
+
# event_level=ERROR, so logger.error(...) calls (e.g. task_logger)
|
|
42
|
+
# became Sentry events automatically. On 3.0 the default is None, so
|
|
43
|
+
# error logs no longer surface as events. Set it explicitly so
|
|
44
|
+
# task_logger.error output appears in Sentry on both SDK versions.
|
|
45
|
+
LoggingIntegration(event_level=logging.ERROR),
|
|
46
|
+
],
|
|
47
|
+
# Sample every transaction so the per-task trace is always sent while
|
|
48
|
+
# you are testing. Lower this in a real deployment.
|
|
49
|
+
traces_sample_rate=float(os.environ.get("SENTRY_TRACES_SAMPLE_RATE", "1.0")),
|
|
50
|
+
)
|
{django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/example_project/settings.py
RENAMED
|
@@ -88,3 +88,10 @@ LOGGING = {
|
|
|
88
88
|
"level": "INFO",
|
|
89
89
|
},
|
|
90
90
|
}
|
|
91
|
+
|
|
92
|
+
# Optional Sentry integration — set SENTRY_DSN to enable. Initialised here (at
|
|
93
|
+
# settings import) so it also runs inside each local process-pool worker, which
|
|
94
|
+
# imports settings via django.setup(). See example_project/sentry.py.
|
|
95
|
+
from example_project.sentry import init_sentry # noqa: E402
|
|
96
|
+
|
|
97
|
+
init_sentry()
|
|
@@ -26,11 +26,6 @@ from django.apps import apps
|
|
|
26
26
|
|
|
27
27
|
from lambda_tasks.environment_loader import resolve_environment
|
|
28
28
|
from lambda_tasks.secret_loader import resolve_secrets_into_env
|
|
29
|
-
from lambda_tasks.tracing import (
|
|
30
|
-
configure_tracing,
|
|
31
|
-
flush_tracing,
|
|
32
|
-
instrument_lambda_handler,
|
|
33
|
-
)
|
|
34
29
|
|
|
35
30
|
logger = logging.getLogger(__name__)
|
|
36
31
|
|
|
@@ -174,25 +169,11 @@ def main() -> int:
|
|
|
174
169
|
context=None,
|
|
175
170
|
)
|
|
176
171
|
|
|
177
|
-
# Flush any buffered spans before the one-shot process exits (no-op unless
|
|
178
|
-
# tracing is enabled — this never imports OTEL otherwise).
|
|
179
|
-
flush_tracing()
|
|
180
|
-
|
|
181
172
|
if result["batchItemFailures"]:
|
|
182
173
|
return 1
|
|
183
174
|
else:
|
|
184
175
|
return 0
|
|
185
176
|
|
|
186
177
|
|
|
187
|
-
# Initialise tracing at import time so the Lambda instrumentor can wrap the
|
|
188
|
-
# handler before the runtime invokes it (which is earlier than the cold-start
|
|
189
|
-
# django.setup() inside handler()). This MUST run after `handler` and `main`
|
|
190
|
-
# are defined: wrapt resolves the handler by attribute lookup on this module,
|
|
191
|
-
# and wrapping a name that does not exist yet raises AttributeError. No-op
|
|
192
|
-
# unless OTEL_EXPORTER_OTLP_ENDPOINT is set.
|
|
193
|
-
configure_tracing()
|
|
194
|
-
instrument_lambda_handler()
|
|
195
|
-
|
|
196
|
-
|
|
197
178
|
if __name__ == "__main__":
|
|
198
179
|
sys.exit(main())
|