stemtrace 0.3.8__tar.gz → 0.3.9__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.
- {stemtrace-0.3.8 → stemtrace-0.3.9}/.bumpversion.toml +1 -1
- {stemtrace-0.3.8 → stemtrace-0.3.9}/CHANGELOG.md +7 -1
- {stemtrace-0.3.8 → stemtrace-0.3.9}/PKG-INFO +77 -2
- {stemtrace-0.3.8 → stemtrace-0.3.9}/README.md +76 -1
- {stemtrace-0.3.8 → stemtrace-0.3.9}/pyproject.toml +1 -1
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/__init__.py +15 -2
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/core/events.py +3 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/config.py +8 -0
- stemtrace-0.3.9/src/stemtrace/library/output_capture.py +536 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/signals.py +54 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/api/routes.py +17 -4
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/api/schemas.py +1 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/package-lock.json +2 -2
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/package.json +1 -1
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/api/client.ts +2 -0
- stemtrace-0.3.9/src/stemtrace/server/ui/frontend/src/components/TaskOutput.tsx +118 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/hooks/WebSocketContext.tsx +26 -8
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/tasks.$taskId.tsx +4 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-data.ts +2 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/task-detail.spec.ts +219 -2
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/websocket.py +3 -1
- stemtrace-0.3.9/tests/integration/output_worker_app.py +102 -0
- stemtrace-0.3.9/tests/integration/test_output_capture_worker.py +251 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_events.py +38 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_init.py +34 -0
- stemtrace-0.3.9/tests/unit/test_output_capture.py +1080 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_routes.py +40 -0
- stemtrace-0.3.9/tests/unit/test_signals_output.py +364 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_websocket.py +25 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/uv.lock +1 -1
- {stemtrace-0.3.8 → stemtrace-0.3.9}/.dockerignore +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/.github/dependabot.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/.github/workflows/ci.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/.github/workflows/e2e.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/.github/workflows/release.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/.gitignore +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/.pre-commit-config.yaml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/.python-version +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/CONTRIBUTING.md +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/Dockerfile +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/Dockerfile.e2e +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/LICENSE +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/Makefile +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/build_ui.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/docker-compose.e2e.rabbitmq.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/docker-compose.e2e.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/docker-compose.rabbitmq.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/docker-compose.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/docs/brand/stemtrace-mark-light.svg +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/docs/brand/stemtrace-mark.svg +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/docs/screenshots/task_details.png +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/docs/screenshots/unregistered.png +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/docs/screenshots/workflow.png +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/examples/celery_app.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/examples/fastapi_integration.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/examples/with_auth.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/examples/with_login.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/progress.md +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/scripts/changelog_section.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/scripts/wait_for_http.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/core/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/core/exceptions.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/core/graph.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/core/ports.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/bootsteps.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/scrubbing.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/transports/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/transports/breaker.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/transports/memory.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/transports/rabbitmq.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/transports/redis.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/py.typed +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/__main__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/api/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/api/websocket.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/consumer.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/auth.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/extension.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/form_auth.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/form_auth_middleware.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/login_routes.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/router.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/store.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/biome.json +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/index.html +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/playwright.config.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/postcss.config.js +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/api/queries.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/DateRangePicker.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/Filters.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/LogoutButton.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/SessionRejectedNotice.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/SproutMark.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/TaskGraph.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/TaskList.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/TaskStateBadge.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/TaskTimeline.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/TimelineBar.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/Tooltip.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/hooks/useInfiniteScroll.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/index.css +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/main.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/__root.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/graph.$rootId.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/graphs.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/index.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/registry.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/workers.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/utils/format.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/vite-env.d.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/auth.spec.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-api.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/graphs.spec.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/header.spec.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/registry.spec.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/tasks.spec.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/workers.spec.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tsconfig.json +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tsconfig.tsbuildinfo +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/vite.config.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/static.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/templates/login.html +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/conftest.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/e2e/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/e2e/conftest.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/e2e/tasks.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/e2e/test_api_e2e.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/e2e/test_workers_e2e.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_embedded_static.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_fastapi_integration.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_rabbitmq_publish.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_rabbitmq_transport.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_redis_resilience_docker.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_redis_transport.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_static_ui.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_wait_for_http_redirects.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_workers_refresh.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_auth.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_bootsteps.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_build_ui_hook.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_changelog_section_script.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_cli.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_cli_form_login.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_config.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_consumer.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_examples.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_exceptions.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_favicon.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_form_auth.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_form_login.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_graph.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_publish_breaker.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_rabbitmq_publish.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_redis_resilience.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_scrubbing.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_signals.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_store.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_transports.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_ui_static_prefix.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_wait_for_http_script.py +0 -0
|
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.9] - 2026-10-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Task output: with `init_worker(app, capture_output=True)`, what each task attempt prints and logs is shown on its task page, one block per attempt (latest first). Off by default because output isn't scrubbed; each attempt keeps the last `max_output_size` bytes (default 16 KB). Works with prefork, thread and gevent pools (thanks @JonatanMartens for the original implementation — #49; requested by @ohadmata — #47).
|
|
14
|
+
|
|
10
15
|
## [0.3.8] - 2026-10-10
|
|
11
16
|
|
|
12
17
|
### Fixed
|
|
@@ -181,7 +186,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
181
186
|
- **E2E test suite**: Docker API tests + Playwright browser tests
|
|
182
187
|
- **Comprehensive test suite**: 350+ Python tests, 90%+ coverage
|
|
183
188
|
|
|
184
|
-
[unreleased]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.
|
|
189
|
+
[unreleased]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.9...HEAD
|
|
190
|
+
[0.3.9]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.8...v0.3.9
|
|
185
191
|
[0.3.8]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.7...v0.3.8
|
|
186
192
|
[0.3.7]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.6...v0.3.7
|
|
187
193
|
[0.3.6]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.5...v0.3.6
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stemtrace
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
4
4
|
Summary: A lightweight Celery task flow visualizer
|
|
5
5
|
Project-URL: Homepage, https://github.com/iansokolskyi/stemtrace
|
|
6
6
|
Project-URL: Documentation, https://github.com/iansokolskyi/stemtrace#readme
|
|
@@ -59,7 +59,7 @@ Description-Content-Type: text/markdown
|
|
|
59
59
|
|
|
60
60
|
**Zero-infrastructure Celery task flow visualizer**
|
|
61
61
|
|
|
62
|
-
[](https://pypi.org/project/stemtrace)
|
|
63
63
|
[](https://pypi.org/project/stemtrace/)
|
|
64
64
|
[](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml)
|
|
65
65
|
[](https://codecov.io/gh/iansokolskyi/stemtrace)
|
|
@@ -83,6 +83,7 @@ Stemtrace captures your task executions as a graph — visualize parent→child
|
|
|
83
83
|
- **Task Flow Graphs** — Visualize chains, groups, and chords as interactive DAGs
|
|
84
84
|
- **Execution Timeline** — Track queued → started → retried → finished states
|
|
85
85
|
- **Arguments & Results** — Inspect inputs, outputs, and exceptions
|
|
86
|
+
- **Task Output** — See what each attempt printed and logged, next to the task (opt-in)
|
|
86
87
|
- **Retry Chains** — Understand exactly when and why retries happened
|
|
87
88
|
|
|
88
89
|
**Canvas Support**
|
|
@@ -253,6 +254,10 @@ stemtrace.init_worker(
|
|
|
253
254
|
scrub_sensitive_data=True, # Scrub passwords, API keys, etc.
|
|
254
255
|
additional_sensitive_keys=frozenset({"my_secret"}), # Add custom keys
|
|
255
256
|
safe_keys=frozenset({"public_key"}), # Never scrub these keys
|
|
257
|
+
|
|
258
|
+
# Task output (disabled by default, see "Task Output" below)
|
|
259
|
+
capture_output=False, # Capture what each attempt prints/logs
|
|
260
|
+
max_output_size=16384, # Max bytes kept per attempt (the end is kept)
|
|
256
261
|
)
|
|
257
262
|
|
|
258
263
|
# Introspection (after init)
|
|
@@ -271,6 +276,75 @@ By default, stemtrace scrubs common sensitive keys from task arguments:
|
|
|
271
276
|
|
|
272
277
|
Scrubbed values appear as `[Filtered]` in the UI.
|
|
273
278
|
|
|
279
|
+
#### Task Output (print and logging)
|
|
280
|
+
|
|
281
|
+
Turn on `capture_output` on the worker to see what each task attempt printed and
|
|
282
|
+
logged on its task page, instead of searching interleaved worker logs:
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
stemtrace.init_worker(app, capture_output=True)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
from celery.utils.log import get_task_logger
|
|
290
|
+
import logging
|
|
291
|
+
|
|
292
|
+
task_logger = get_task_logger(__name__)
|
|
293
|
+
log = logging.getLogger("myapp.billing")
|
|
294
|
+
|
|
295
|
+
@app.task(bind=True, max_retries=3)
|
|
296
|
+
def charge(self, order_id):
|
|
297
|
+
print(f"charging {order_id}") # -> charging 42
|
|
298
|
+
task_logger.info("calling gateway") # -> INFO myapp.tasks: calling gateway
|
|
299
|
+
log.warning("gateway slow") # -> WARNING myapp.billing: gateway slow
|
|
300
|
+
...
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The task page shows an **Output** section with one block per attempt (latest
|
|
304
|
+
first): the output of a retried attempt is on its `RETRY` event, the final one on
|
|
305
|
+
`SUCCESS`/`FAILURE`.
|
|
306
|
+
|
|
307
|
+
- **What's captured**: `print()` to stdout and stderr, exactly as printed
|
|
308
|
+
(whether or not Celery redirects stdout to its log), and log records
|
|
309
|
+
(`logging.getLogger(...)`, `get_task_logger(...)`) emitted while the task
|
|
310
|
+
runs, in one chronological stream. Records are captured when they reach the
|
|
311
|
+
root logger or `celery.task`, or when a handler writes them to stdout/stderr
|
|
312
|
+
after stemtrace is set up. A logger with `propagate=False` whose handlers were
|
|
313
|
+
set up at worker startup (writing to the real stream or a file) isn't
|
|
314
|
+
captured. Log levels still apply to log records: one your logging config
|
|
315
|
+
filters out (e.g. `DEBUG` under `--loglevel=INFO`) isn't captured.
|
|
316
|
+
stemtrace's own logs and Celery/kombu internals are left out.
|
|
317
|
+
What the worker prints to its console or log file is unchanged (when no
|
|
318
|
+
logging handlers are configured, warnings still print as Python's
|
|
319
|
+
`logging.lastResort` would). One side effect: from the first task on, the root
|
|
320
|
+
logger has stemtrace's handler, so `logging.getLogger().hasHandlers()` is
|
|
321
|
+
`True` and a later `logging.basicConfig()` call without `force=True` does
|
|
322
|
+
nothing. Configure logging at startup (or pass `force=True`). Logging
|
|
323
|
+
handlers added while a task runs, ones that override `handle()`, or ones
|
|
324
|
+
that write to stdout/stderr in unusual ways may show a record twice or show
|
|
325
|
+
Celery/kombu lines in that task's output; handlers set up at worker startup
|
|
326
|
+
don't.
|
|
327
|
+
- **Size**: at most `max_output_size` bytes (UTF-8) per attempt. Longer output
|
|
328
|
+
keeps its end, where failures usually are, after a `[… N bytes truncated]` line.
|
|
329
|
+
Output travels with the task's terminal event, so it is stored wherever events
|
|
330
|
+
are: by the server (up to `max_nodes` tasks) and, with the Redis transport, in
|
|
331
|
+
the stemtrace stream on your broker, which keeps the last `max(ttl, 10000)`
|
|
332
|
+
events (86,400 with the default `ttl`). At 16 KB per attempt that can add up to
|
|
333
|
+
more than a GB of Redis memory on busy workers, so consider a lower
|
|
334
|
+
`max_output_size` or `ttl` there. It isn't sent over the UI's WebSocket; the
|
|
335
|
+
task page fetches it.
|
|
336
|
+
- **Pools**: each task's output is collected in a `ContextVar` (per thread, and
|
|
337
|
+
per greenlet under gevent/eventlet), so concurrent tasks never mix output.
|
|
338
|
+
Output from threads a task starts itself isn't captured.
|
|
339
|
+
- **Not captured**: anything after the task ends, and output of a task whose
|
|
340
|
+
worker process was killed (hard time limit, OOM), since no terminal event is
|
|
341
|
+
sent from that process. Capture of a failed or retried attempt stops when
|
|
342
|
+
Celery reports the failure/retry, so `on_failure`/`on_retry` output is included
|
|
343
|
+
but `after_return` output is included only on success.
|
|
344
|
+
- **Not scrubbed**: output is free text, so the key-based scrubbing above can't
|
|
345
|
+
apply. That's why it's off by default: only enable it if your tasks don't print
|
|
346
|
+
or log secrets.
|
|
347
|
+
|
|
274
348
|
### Canvas Graph Visualization
|
|
275
349
|
|
|
276
350
|
`stemtrace` automatically detects and visualizes Celery canvas constructs:
|
|
@@ -631,6 +705,7 @@ What is detected depends on the transport:
|
|
|
631
705
|
- ✅ **Full lifecycle tracking** — PENDING → RECEIVED → STARTED → SUCCESS/FAILURE
|
|
632
706
|
- ✅ **Canvas awareness** — Automatic GROUP/CHORD node visualization
|
|
633
707
|
- ✅ **Arguments & results** — View inputs, outputs, and exceptions
|
|
708
|
+
- ✅ **Task output** — What each attempt printed and logged (opt-in)
|
|
634
709
|
- ✅ **Sensitive data scrubbing** — Passwords and API keys filtered automatically
|
|
635
710
|
- ✅ **Real-time updates** — WebSocket-powered live dashboard
|
|
636
711
|
- ✅ **FastAPI integration** — Mount into your existing app
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Zero-infrastructure Celery task flow visualizer**
|
|
6
6
|
|
|
7
|
-
[](https://pypi.org/project/stemtrace)
|
|
8
8
|
[](https://pypi.org/project/stemtrace/)
|
|
9
9
|
[](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml)
|
|
10
10
|
[](https://codecov.io/gh/iansokolskyi/stemtrace)
|
|
@@ -28,6 +28,7 @@ Stemtrace captures your task executions as a graph — visualize parent→child
|
|
|
28
28
|
- **Task Flow Graphs** — Visualize chains, groups, and chords as interactive DAGs
|
|
29
29
|
- **Execution Timeline** — Track queued → started → retried → finished states
|
|
30
30
|
- **Arguments & Results** — Inspect inputs, outputs, and exceptions
|
|
31
|
+
- **Task Output** — See what each attempt printed and logged, next to the task (opt-in)
|
|
31
32
|
- **Retry Chains** — Understand exactly when and why retries happened
|
|
32
33
|
|
|
33
34
|
**Canvas Support**
|
|
@@ -198,6 +199,10 @@ stemtrace.init_worker(
|
|
|
198
199
|
scrub_sensitive_data=True, # Scrub passwords, API keys, etc.
|
|
199
200
|
additional_sensitive_keys=frozenset({"my_secret"}), # Add custom keys
|
|
200
201
|
safe_keys=frozenset({"public_key"}), # Never scrub these keys
|
|
202
|
+
|
|
203
|
+
# Task output (disabled by default, see "Task Output" below)
|
|
204
|
+
capture_output=False, # Capture what each attempt prints/logs
|
|
205
|
+
max_output_size=16384, # Max bytes kept per attempt (the end is kept)
|
|
201
206
|
)
|
|
202
207
|
|
|
203
208
|
# Introspection (after init)
|
|
@@ -216,6 +221,75 @@ By default, stemtrace scrubs common sensitive keys from task arguments:
|
|
|
216
221
|
|
|
217
222
|
Scrubbed values appear as `[Filtered]` in the UI.
|
|
218
223
|
|
|
224
|
+
#### Task Output (print and logging)
|
|
225
|
+
|
|
226
|
+
Turn on `capture_output` on the worker to see what each task attempt printed and
|
|
227
|
+
logged on its task page, instead of searching interleaved worker logs:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
stemtrace.init_worker(app, capture_output=True)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from celery.utils.log import get_task_logger
|
|
235
|
+
import logging
|
|
236
|
+
|
|
237
|
+
task_logger = get_task_logger(__name__)
|
|
238
|
+
log = logging.getLogger("myapp.billing")
|
|
239
|
+
|
|
240
|
+
@app.task(bind=True, max_retries=3)
|
|
241
|
+
def charge(self, order_id):
|
|
242
|
+
print(f"charging {order_id}") # -> charging 42
|
|
243
|
+
task_logger.info("calling gateway") # -> INFO myapp.tasks: calling gateway
|
|
244
|
+
log.warning("gateway slow") # -> WARNING myapp.billing: gateway slow
|
|
245
|
+
...
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The task page shows an **Output** section with one block per attempt (latest
|
|
249
|
+
first): the output of a retried attempt is on its `RETRY` event, the final one on
|
|
250
|
+
`SUCCESS`/`FAILURE`.
|
|
251
|
+
|
|
252
|
+
- **What's captured**: `print()` to stdout and stderr, exactly as printed
|
|
253
|
+
(whether or not Celery redirects stdout to its log), and log records
|
|
254
|
+
(`logging.getLogger(...)`, `get_task_logger(...)`) emitted while the task
|
|
255
|
+
runs, in one chronological stream. Records are captured when they reach the
|
|
256
|
+
root logger or `celery.task`, or when a handler writes them to stdout/stderr
|
|
257
|
+
after stemtrace is set up. A logger with `propagate=False` whose handlers were
|
|
258
|
+
set up at worker startup (writing to the real stream or a file) isn't
|
|
259
|
+
captured. Log levels still apply to log records: one your logging config
|
|
260
|
+
filters out (e.g. `DEBUG` under `--loglevel=INFO`) isn't captured.
|
|
261
|
+
stemtrace's own logs and Celery/kombu internals are left out.
|
|
262
|
+
What the worker prints to its console or log file is unchanged (when no
|
|
263
|
+
logging handlers are configured, warnings still print as Python's
|
|
264
|
+
`logging.lastResort` would). One side effect: from the first task on, the root
|
|
265
|
+
logger has stemtrace's handler, so `logging.getLogger().hasHandlers()` is
|
|
266
|
+
`True` and a later `logging.basicConfig()` call without `force=True` does
|
|
267
|
+
nothing. Configure logging at startup (or pass `force=True`). Logging
|
|
268
|
+
handlers added while a task runs, ones that override `handle()`, or ones
|
|
269
|
+
that write to stdout/stderr in unusual ways may show a record twice or show
|
|
270
|
+
Celery/kombu lines in that task's output; handlers set up at worker startup
|
|
271
|
+
don't.
|
|
272
|
+
- **Size**: at most `max_output_size` bytes (UTF-8) per attempt. Longer output
|
|
273
|
+
keeps its end, where failures usually are, after a `[… N bytes truncated]` line.
|
|
274
|
+
Output travels with the task's terminal event, so it is stored wherever events
|
|
275
|
+
are: by the server (up to `max_nodes` tasks) and, with the Redis transport, in
|
|
276
|
+
the stemtrace stream on your broker, which keeps the last `max(ttl, 10000)`
|
|
277
|
+
events (86,400 with the default `ttl`). At 16 KB per attempt that can add up to
|
|
278
|
+
more than a GB of Redis memory on busy workers, so consider a lower
|
|
279
|
+
`max_output_size` or `ttl` there. It isn't sent over the UI's WebSocket; the
|
|
280
|
+
task page fetches it.
|
|
281
|
+
- **Pools**: each task's output is collected in a `ContextVar` (per thread, and
|
|
282
|
+
per greenlet under gevent/eventlet), so concurrent tasks never mix output.
|
|
283
|
+
Output from threads a task starts itself isn't captured.
|
|
284
|
+
- **Not captured**: anything after the task ends, and output of a task whose
|
|
285
|
+
worker process was killed (hard time limit, OOM), since no terminal event is
|
|
286
|
+
sent from that process. Capture of a failed or retried attempt stops when
|
|
287
|
+
Celery reports the failure/retry, so `on_failure`/`on_retry` output is included
|
|
288
|
+
but `after_return` output is included only on success.
|
|
289
|
+
- **Not scrubbed**: output is free text, so the key-based scrubbing above can't
|
|
290
|
+
apply. That's why it's off by default: only enable it if your tasks don't print
|
|
291
|
+
or log secrets.
|
|
292
|
+
|
|
219
293
|
### Canvas Graph Visualization
|
|
220
294
|
|
|
221
295
|
`stemtrace` automatically detects and visualizes Celery canvas constructs:
|
|
@@ -576,6 +650,7 @@ What is detected depends on the transport:
|
|
|
576
650
|
- ✅ **Full lifecycle tracking** — PENDING → RECEIVED → STARTED → SUCCESS/FAILURE
|
|
577
651
|
- ✅ **Canvas awareness** — Automatic GROUP/CHORD node visualization
|
|
578
652
|
- ✅ **Arguments & results** — View inputs, outputs, and exceptions
|
|
653
|
+
- ✅ **Task output** — What each attempt printed and logged (opt-in)
|
|
579
654
|
- ✅ **Sensitive data scrubbing** — Passwords and API keys filtered automatically
|
|
580
655
|
- ✅ **Real-time updates** — WebSocket-powered live dashboard
|
|
581
656
|
- ✅ **FastAPI integration** — Mount into your existing app
|
|
@@ -43,7 +43,7 @@ if TYPE_CHECKING:
|
|
|
43
43
|
from celery import Celery
|
|
44
44
|
from fastapi import FastAPI
|
|
45
45
|
|
|
46
|
-
__version__ = "0.3.
|
|
46
|
+
__version__ = "0.3.9"
|
|
47
47
|
__all__ = [
|
|
48
48
|
"ConfigurationError",
|
|
49
49
|
"StemtraceConfig",
|
|
@@ -106,6 +106,8 @@ def init_worker(
|
|
|
106
106
|
safe_keys: frozenset[str] | None = None,
|
|
107
107
|
max_data_size: int = 10240,
|
|
108
108
|
node_alias_from_arguments: str | None = None,
|
|
109
|
+
capture_output: bool = False,
|
|
110
|
+
max_output_size: int = 16384,
|
|
109
111
|
) -> None:
|
|
110
112
|
"""Initialize stemtrace for Celery worker instrumentation.
|
|
111
113
|
|
|
@@ -127,9 +129,14 @@ def init_worker(
|
|
|
127
129
|
A non-None value emits a ``DeprecationWarning`` (and a one-time
|
|
128
130
|
log warning, since Celery workers hide deprecation warnings); the
|
|
129
131
|
parameter will be removed in a future major release.
|
|
132
|
+
capture_output: Capture what each task attempt prints and logs and show
|
|
133
|
+
it in the UI (default: False). Output is not scrubbed.
|
|
134
|
+
max_output_size: Maximum UTF-8 bytes of output kept per attempt; the
|
|
135
|
+
end is kept (default: 16KB).
|
|
130
136
|
|
|
131
137
|
Raises:
|
|
132
|
-
ConfigurationError: If no broker URL can be determined
|
|
138
|
+
ConfigurationError: If no broker URL can be determined, or
|
|
139
|
+
max_output_size isn't positive.
|
|
133
140
|
"""
|
|
134
141
|
global _transport, _node_alias_deprecation_logged
|
|
135
142
|
|
|
@@ -147,6 +154,10 @@ def init_worker(
|
|
|
147
154
|
"No broker URL available. Either pass transport_url or configure "
|
|
148
155
|
"Celery's broker_url."
|
|
149
156
|
)
|
|
157
|
+
if max_output_size <= 0:
|
|
158
|
+
raise ConfigurationError(
|
|
159
|
+
f"max_output_size must be a positive number of bytes, got {max_output_size}."
|
|
160
|
+
)
|
|
150
161
|
|
|
151
162
|
config = StemtraceConfig(
|
|
152
163
|
transport_url=url,
|
|
@@ -159,6 +170,8 @@ def init_worker(
|
|
|
159
170
|
safe_keys=safe_keys or frozenset(),
|
|
160
171
|
max_data_size=max_data_size,
|
|
161
172
|
node_alias_from_arguments=node_alias_from_arguments,
|
|
173
|
+
capture_output=capture_output,
|
|
174
|
+
max_output_size=max_output_size,
|
|
162
175
|
)
|
|
163
176
|
set_config(config)
|
|
164
177
|
|
|
@@ -74,6 +74,8 @@ class TaskEvent(BaseModel):
|
|
|
74
74
|
result: Return value of the task (SUCCESS state only).
|
|
75
75
|
exception: Exception message (FAILURE/RETRY states).
|
|
76
76
|
traceback: Full traceback string (FAILURE/RETRY states).
|
|
77
|
+
output: What the attempt printed and logged, in order (SUCCESS,
|
|
78
|
+
FAILURE and RETRY events, when output capture is enabled).
|
|
77
79
|
"""
|
|
78
80
|
|
|
79
81
|
model_config = ConfigDict(frozen=True)
|
|
@@ -96,6 +98,7 @@ class TaskEvent(BaseModel):
|
|
|
96
98
|
result: Any | None = None
|
|
97
99
|
exception: str | None = None
|
|
98
100
|
traceback: str | None = None
|
|
101
|
+
output: str | None = None
|
|
99
102
|
|
|
100
103
|
|
|
101
104
|
class WorkerEvent(BaseModel):
|
|
@@ -20,6 +20,10 @@ class StemtraceConfig(BaseModel):
|
|
|
20
20
|
backward compatibility only; node aliases are resolved by the
|
|
21
21
|
server (``init_app()`` / ``stemtrace server
|
|
22
22
|
--node-alias-from-arguments``).
|
|
23
|
+
capture_output: Whether to capture what each task attempt prints and
|
|
24
|
+
logs and attach it to the attempt's terminal event.
|
|
25
|
+
max_output_size: Maximum UTF-8 bytes of output kept per attempt (the
|
|
26
|
+
end of the output is kept).
|
|
23
27
|
"""
|
|
24
28
|
|
|
25
29
|
model_config = ConfigDict(frozen=True)
|
|
@@ -41,6 +45,10 @@ class StemtraceConfig(BaseModel):
|
|
|
41
45
|
# Deprecated: unused on the worker side (resolved by the server)
|
|
42
46
|
node_alias_from_arguments: str | None = None
|
|
43
47
|
|
|
48
|
+
# Output capture options (disabled by default)
|
|
49
|
+
capture_output: bool = False
|
|
50
|
+
max_output_size: int = Field(default=16384, gt=0) # 16 KB
|
|
51
|
+
|
|
44
52
|
|
|
45
53
|
_config: StemtraceConfig | None = None
|
|
46
54
|
|