stemtrace 0.3.8__tar.gz → 0.3.10__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.10}/.bumpversion.toml +3 -3
- stemtrace-0.3.10/.coderabbit.yaml +92 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/.dockerignore +1 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/.github/workflows/ci.yml +10 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/.github/workflows/release.yml +7 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/.gitignore +1 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/.pre-commit-config.yaml +6 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/CHANGELOG.md +18 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/CONTRIBUTING.md +36 -3
- {stemtrace-0.3.8 → stemtrace-0.3.10}/Dockerfile +3 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/Makefile +2 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/PKG-INFO +78 -2
- {stemtrace-0.3.8 → stemtrace-0.3.10}/README.md +76 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/pyproject.toml +78 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/scripts/wait_for_http.py +0 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/__init__.py +16 -3
- stemtrace-0.3.10/src/stemtrace/_version.py +8 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/core/events.py +3 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/core/graph.py +278 -264
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/library/config.py +8 -0
- stemtrace-0.3.10/src/stemtrace/library/output_capture.py +536 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/library/scrubbing.py +3 -4
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/library/signals.py +63 -0
- stemtrace-0.3.10/src/stemtrace/library/transports/__init__.py +24 -0
- stemtrace-0.3.10/src/stemtrace/library/transports/breaker.py +5 -0
- stemtrace-0.3.10/src/stemtrace/library/transports/memory.py +5 -0
- stemtrace-0.3.10/src/stemtrace/library/transports/rabbitmq.py +29 -0
- stemtrace-0.3.10/src/stemtrace/library/transports/redis.py +23 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/__main__.py +2 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/api/routes.py +232 -129
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/api/schemas.py +1 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/consumer.py +30 -12
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/fastapi/form_auth.py +28 -10
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/fastapi/form_auth_middleware.py +54 -21
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/fastapi/router.py +4 -4
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/package-lock.json +2 -2
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/package.json +1 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/playwright.config.ts +9 -6
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/api/client.ts +2 -0
- stemtrace-0.3.10/src/stemtrace/server/ui/frontend/src/components/TaskOutput.tsx +118 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/hooks/WebSocketContext.tsx +38 -10
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/routes/tasks.$taskId.tsx +4 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-data.ts +2 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/tests/header.spec.ts +50 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/tests/task-detail.spec.ts +260 -39
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/static.py +1 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/websocket.py +4 -2
- {stemtrace-0.3.8/src/stemtrace/library → stemtrace-0.3.10/src/stemtrace}/transports/__init__.py +12 -8
- {stemtrace-0.3.8/src/stemtrace/library → stemtrace-0.3.10/src/stemtrace}/transports/rabbitmq.py +139 -96
- {stemtrace-0.3.8/src/stemtrace/library → stemtrace-0.3.10/src/stemtrace}/transports/redis.py +119 -63
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/e2e/test_api_e2e.py +1 -2
- stemtrace-0.3.10/tests/integration/output_worker_app.py +102 -0
- stemtrace-0.3.10/tests/integration/test_output_capture_worker.py +251 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/integration/test_rabbitmq_publish.py +9 -5
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/integration/test_rabbitmq_transport.py +18 -19
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/integration/test_redis_resilience_docker.py +1 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/integration/test_redis_transport.py +3 -3
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/integration/test_static_ui.py +29 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/integration/test_workers_refresh.py +1 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_consumer.py +95 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_events.py +38 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_form_auth.py +68 -2
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_form_login.py +18 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_graph.py +398 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_init.py +36 -4
- stemtrace-0.3.10/tests/unit/test_library_transports_alias.py +77 -0
- stemtrace-0.3.10/tests/unit/test_output_capture.py +1080 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_publish_breaker.py +1 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_rabbitmq_publish.py +158 -146
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_redis_resilience.py +91 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_routes.py +72 -3
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_signals.py +90 -2
- stemtrace-0.3.10/tests/unit/test_signals_output.py +364 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_store.py +1 -1
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_transports.py +112 -61
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_websocket.py +25 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/uv.lock +139 -2
- {stemtrace-0.3.8 → stemtrace-0.3.10}/.github/dependabot.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/.github/workflows/e2e.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/.python-version +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/Dockerfile.e2e +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/LICENSE +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/build_ui.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/docker-compose.e2e.rabbitmq.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/docker-compose.e2e.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/docker-compose.rabbitmq.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/docker-compose.yml +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/docs/brand/stemtrace-mark-light.svg +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/docs/brand/stemtrace-mark.svg +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/docs/screenshots/task_details.png +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/docs/screenshots/unregistered.png +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/docs/screenshots/workflow.png +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/examples/celery_app.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/examples/fastapi_integration.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/examples/with_auth.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/examples/with_login.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/progress.md +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/scripts/changelog_section.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/core/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/core/exceptions.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/core/ports.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/library/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/library/bootsteps.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/py.typed +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/api/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/api/websocket.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/fastapi/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/fastapi/auth.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/fastapi/extension.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/fastapi/login_routes.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/store.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/biome.json +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/index.html +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/postcss.config.js +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/api/queries.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/components/DateRangePicker.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/components/Filters.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/components/LogoutButton.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/components/SessionRejectedNotice.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/components/SproutMark.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/components/TaskGraph.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/components/TaskList.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/components/TaskStateBadge.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/components/TaskTimeline.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/components/TimelineBar.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/components/Tooltip.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/hooks/useInfiniteScroll.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/index.css +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/main.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/routes/__root.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/routes/graph.$rootId.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/routes/graphs.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/routes/index.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/routes/registry.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/routes/workers.tsx +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/utils/format.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/src/vite-env.d.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/tests/auth.spec.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-api.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/tests/graphs.spec.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/tests/registry.spec.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/tests/tasks.spec.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/tests/workers.spec.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/tsconfig.json +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/tsconfig.tsbuildinfo +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/frontend/vite.config.ts +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/src/stemtrace/server/ui/templates/login.html +0 -0
- {stemtrace-0.3.8/src/stemtrace/library → stemtrace-0.3.10/src/stemtrace}/transports/breaker.py +0 -0
- {stemtrace-0.3.8/src/stemtrace/library → stemtrace-0.3.10/src/stemtrace}/transports/memory.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/conftest.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/e2e/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/e2e/conftest.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/e2e/tasks.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/e2e/test_workers_e2e.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/integration/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/integration/test_embedded_static.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/integration/test_fastapi_integration.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/integration/test_wait_for_http_redirects.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/__init__.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_auth.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_bootsteps.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_build_ui_hook.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_changelog_section_script.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_cli.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_cli_form_login.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_config.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_examples.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_exceptions.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_favicon.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_scrubbing.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_ui_static_prefix.py +0 -0
- {stemtrace-0.3.8 → stemtrace-0.3.10}/tests/unit/test_wait_for_http_script.py +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[tool.bumpversion]
|
|
2
|
-
current_version = "0.3.
|
|
2
|
+
current_version = "0.3.10"
|
|
3
3
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
4
4
|
serialize = ["{major}.{minor}.{patch}"]
|
|
5
5
|
commit = true
|
|
@@ -12,9 +12,9 @@ filename = "pyproject.toml"
|
|
|
12
12
|
search = 'version = "{current_version}"'
|
|
13
13
|
replace = 'version = "{new_version}"'
|
|
14
14
|
|
|
15
|
-
# Python __version__
|
|
15
|
+
# Python __version__ (leaf module, re-exported by the package __init__.py)
|
|
16
16
|
[[tool.bumpversion.files]]
|
|
17
|
-
filename = "src/stemtrace/
|
|
17
|
+
filename = "src/stemtrace/_version.py"
|
|
18
18
|
search = '__version__ = "{current_version}"'
|
|
19
19
|
replace = '__version__ = "{new_version}"'
|
|
20
20
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
|
2
|
+
# CodeRabbit review settings. The rules behind these instructions are summarized
|
|
3
|
+
# in CONTRIBUTING.md; ruff, mypy and import-linter already run in CI, so reviews
|
|
4
|
+
# should focus on what tools can't check.
|
|
5
|
+
language: en-US
|
|
6
|
+
tone_instructions: >-
|
|
7
|
+
Be concise and specific. Skip praise and style nits that ruff or Biome already
|
|
8
|
+
enforce. Lead with correctness, boundaries and test gaps.
|
|
9
|
+
|
|
10
|
+
reviews:
|
|
11
|
+
profile: chill
|
|
12
|
+
high_level_summary: true
|
|
13
|
+
poem: false
|
|
14
|
+
in_progress_fortune: false
|
|
15
|
+
sequence_diagrams: false
|
|
16
|
+
collapse_walkthrough: true
|
|
17
|
+
path_filters:
|
|
18
|
+
- "!uv.lock"
|
|
19
|
+
- "!**/package-lock.json"
|
|
20
|
+
- "!src/stemtrace/server/ui/frontend/src/routeTree.gen.ts"
|
|
21
|
+
- "!src/stemtrace/server/ui/frontend/dist/**"
|
|
22
|
+
# Most specific paths first, the general Python entry last. The server and
|
|
23
|
+
# general globs match only *.py, so frontend files get just the frontend entry.
|
|
24
|
+
path_instructions:
|
|
25
|
+
- path: "src/stemtrace/core/**"
|
|
26
|
+
instructions: |
|
|
27
|
+
Domain layer: pure Python plus Pydantic only. Flag any I/O (network, files,
|
|
28
|
+
environment, clocks used for side effects) and any import of
|
|
29
|
+
stemtrace.library, stemtrace.server, Celery, Redis, kombu or FastAPI.
|
|
30
|
+
Events are immutable (frozen=True). Synthetic GROUP/CHORD nodes have
|
|
31
|
+
empty `events`; new graph code must handle that.
|
|
32
|
+
- path: "src/stemtrace/library/**"
|
|
33
|
+
instructions: |
|
|
34
|
+
Runs inside Celery workers. Imports core only, never stemtrace.server.
|
|
35
|
+
Publishing is fire-and-forget: it must never raise into or block a task,
|
|
36
|
+
and a failing broker must stay cheap on repeated events (circuit breaker
|
|
37
|
+
or backoff, never retry per event). Log volume must stay bounded during
|
|
38
|
+
an outage. Check shared state under prefork, threads and gevent/eventlet,
|
|
39
|
+
and fork safety of module-level locks. Per-task state belongs in a
|
|
40
|
+
ContextVar.
|
|
41
|
+
- path: "src/stemtrace/server/ui/frontend/**"
|
|
42
|
+
instructions: |
|
|
43
|
+
React 19 + TypeScript strict + TanStack Router/Query + @xyflow/react.
|
|
44
|
+
No `any`, explicit return types on exported functions, props interfaces
|
|
45
|
+
for components. routeTree.gen.ts is generated and must not be edited by
|
|
46
|
+
hand. Graph or UI behavior changes need Playwright coverage in tests/.
|
|
47
|
+
- path: "src/stemtrace/server/**/*.py"
|
|
48
|
+
instructions: |
|
|
49
|
+
Web server and consumer. Imports core only, never stemtrace.library. The
|
|
50
|
+
server is read-only toward the broker and the user's Celery app. Check
|
|
51
|
+
that API response schema changes are reflected in the frontend types and
|
|
52
|
+
tests, that auth dependencies apply to new routes, and that background
|
|
53
|
+
loops stop within a bounded time.
|
|
54
|
+
- path: "pyproject.toml"
|
|
55
|
+
instructions: |
|
|
56
|
+
Flag any new per-file-ignores entry for the design rules (C90, PLR09xx,
|
|
57
|
+
BLE, ERA, RET, PT) and any added import-linter ignore_imports entry:
|
|
58
|
+
the baseline only shrinks. New dependencies need a stated reason.
|
|
59
|
+
- path: "tests/**"
|
|
60
|
+
instructions: |
|
|
61
|
+
- A new test should fail on the old code with an assertion error that is
|
|
62
|
+
the bug, not an ImportError.
|
|
63
|
+
- Assert exact values, sequences and log counts, not `is not None`.
|
|
64
|
+
- Prefer fakes that model the real library semantics over
|
|
65
|
+
unittest.mock; a fake that always succeeds proves nothing.
|
|
66
|
+
- No real sleeps in unit tests: inject the clock and sleep function, and
|
|
67
|
+
bound loops so a regression fails instead of hanging.
|
|
68
|
+
- New `# noqa` comments need a reason after the codes; bare ones are
|
|
69
|
+
the pre-existing baseline and only shrink.
|
|
70
|
+
- Imports at the top of the file. Names follow
|
|
71
|
+
test_<action>_<condition>_<result>. Tests needing Redis or RabbitMQ
|
|
72
|
+
are marked integration; Docker-based ones e2e.
|
|
73
|
+
- path: "src/stemtrace/**/*.py"
|
|
74
|
+
instructions: |
|
|
75
|
+
- Does each new class or function hide a decision or remove work from its
|
|
76
|
+
callers? Flag pass-through wrappers and options with no current caller.
|
|
77
|
+
- Constants, timeouts and limits are defined once and imported, not copied.
|
|
78
|
+
- Every blocking call (connect, read, close, join, wait, lock) needs an
|
|
79
|
+
explicit timeout smaller than its caller's.
|
|
80
|
+
- Broad `except Exception` only at fire-and-forget boundaries, with a
|
|
81
|
+
`# noqa: BLE001 <reason>` comment and logging with context.
|
|
82
|
+
- Comments explain why or state an invariant. Comments about third-party
|
|
83
|
+
behavior should name the library version and function.
|
|
84
|
+
- Behavior claims in docstrings, README and CHANGELOG must be backed by a
|
|
85
|
+
test. Changes to user-visible behavior should be opt-in.
|
|
86
|
+
- Bare `# noqa: <codes>` comments are the baseline of code that predates
|
|
87
|
+
the lint rules; it only shrinks. Flag a newly added bare noqa: a new
|
|
88
|
+
suppression must give a reason after the codes, and a refactor
|
|
89
|
+
should delete the noqa it makes unnecessary.
|
|
90
|
+
|
|
91
|
+
chat:
|
|
92
|
+
art: false
|
|
@@ -15,6 +15,7 @@ jobs:
|
|
|
15
15
|
lint:
|
|
16
16
|
if: github.event_name == 'pull_request'
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
|
+
timeout-minutes: 10
|
|
18
19
|
env:
|
|
19
20
|
UV_PYTHON: "3.12"
|
|
20
21
|
STEMTRACE_SKIP_UI_BUILD: "1"
|
|
@@ -40,9 +41,13 @@ jobs:
|
|
|
40
41
|
- name: Check formatting
|
|
41
42
|
run: uv run --locked --no-build ruff format --check src/ tests/
|
|
42
43
|
|
|
44
|
+
- name: Check import contracts
|
|
45
|
+
run: uv run --locked --no-build lint-imports
|
|
46
|
+
|
|
43
47
|
type-check:
|
|
44
48
|
if: github.event_name == 'pull_request'
|
|
45
49
|
runs-on: ubuntu-latest
|
|
50
|
+
timeout-minutes: 10
|
|
46
51
|
env:
|
|
47
52
|
UV_PYTHON: "3.12"
|
|
48
53
|
STEMTRACE_SKIP_UI_BUILD: "1"
|
|
@@ -68,6 +73,7 @@ jobs:
|
|
|
68
73
|
test:
|
|
69
74
|
if: github.event_name == 'pull_request'
|
|
70
75
|
runs-on: ubuntu-latest
|
|
76
|
+
timeout-minutes: 15
|
|
71
77
|
env:
|
|
72
78
|
UV_PYTHON: ${{ matrix.python-version }}
|
|
73
79
|
STEMTRACE_SKIP_UI_BUILD: "1"
|
|
@@ -104,6 +110,7 @@ jobs:
|
|
|
104
110
|
audit:
|
|
105
111
|
name: Audit runtime dependencies
|
|
106
112
|
runs-on: ubuntu-latest
|
|
113
|
+
timeout-minutes: 10
|
|
107
114
|
env:
|
|
108
115
|
UV_PYTHON: "3.12"
|
|
109
116
|
steps:
|
|
@@ -128,6 +135,7 @@ jobs:
|
|
|
128
135
|
frontend:
|
|
129
136
|
if: github.event_name == 'pull_request'
|
|
130
137
|
runs-on: ubuntu-latest
|
|
138
|
+
timeout-minutes: 10
|
|
131
139
|
defaults:
|
|
132
140
|
run:
|
|
133
141
|
working-directory: src/stemtrace/server/ui/frontend
|
|
@@ -153,6 +161,7 @@ jobs:
|
|
|
153
161
|
build:
|
|
154
162
|
if: github.event_name == 'pull_request'
|
|
155
163
|
runs-on: ubuntu-latest
|
|
164
|
+
timeout-minutes: 15
|
|
156
165
|
env:
|
|
157
166
|
UV_PYTHON: "3.12"
|
|
158
167
|
needs: [lint, type-check, test, frontend]
|
|
@@ -200,6 +209,7 @@ jobs:
|
|
|
200
209
|
# actual default-branch commit after merges (or direct pushes / releases).
|
|
201
210
|
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
|
|
202
211
|
runs-on: ubuntu-latest
|
|
212
|
+
timeout-minutes: 15
|
|
203
213
|
env:
|
|
204
214
|
UV_PYTHON: "3.12"
|
|
205
215
|
STEMTRACE_SKIP_UI_BUILD: "1"
|
|
@@ -8,6 +8,7 @@ on:
|
|
|
8
8
|
jobs:
|
|
9
9
|
build:
|
|
10
10
|
runs-on: ubuntu-latest
|
|
11
|
+
timeout-minutes: 15
|
|
11
12
|
env:
|
|
12
13
|
UV_PYTHON: "3.12"
|
|
13
14
|
outputs:
|
|
@@ -87,8 +88,11 @@ jobs:
|
|
|
87
88
|
path: dist/
|
|
88
89
|
|
|
89
90
|
pypi:
|
|
90
|
-
|
|
91
|
+
# After the image: a failed or hung docker build then publishes nothing,
|
|
92
|
+
# instead of leaving PyPI ahead of GHCR and the GitHub Release.
|
|
93
|
+
needs: [build, docker]
|
|
91
94
|
runs-on: ubuntu-latest
|
|
95
|
+
timeout-minutes: 10
|
|
92
96
|
environment: pypi
|
|
93
97
|
permissions:
|
|
94
98
|
id-token: write
|
|
@@ -105,6 +109,7 @@ jobs:
|
|
|
105
109
|
docker:
|
|
106
110
|
needs: build
|
|
107
111
|
runs-on: ubuntu-latest
|
|
112
|
+
timeout-minutes: 25
|
|
108
113
|
permissions:
|
|
109
114
|
contents: read
|
|
110
115
|
packages: write
|
|
@@ -147,6 +152,7 @@ jobs:
|
|
|
147
152
|
github-release:
|
|
148
153
|
needs: [build, pypi, docker]
|
|
149
154
|
runs-on: ubuntu-latest
|
|
155
|
+
timeout-minutes: 10
|
|
150
156
|
permissions:
|
|
151
157
|
contents: write
|
|
152
158
|
steps:
|
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.10] - 2026-10-11
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Docker image: the UI is built once on the build machine's native platform instead of under emulation for each architecture, which also makes release builds faster. Building the image from source now needs BuildKit (the default since Docker 23 and in `docker compose`).
|
|
14
|
+
- Development: ruff now also enforces complexity, function-size, blind-`except` and dead-code rules, and [import-linter](https://import-linter.readthedocs.io/) checks the `core`/`library`/`server` import boundaries in `make lint` and CI (see CONTRIBUTING.md). No runtime change.
|
|
15
|
+
- The broker transports moved from `stemtrace.library.transports` to `stemtrace.transports`, so the Celery integration and the server share them without importing each other. The old module paths (`stemtrace.library.transports` and its `redis`, `rabbitmq`, `memory` and `breaker` submodules) still import the same public classes and functions but emit a `DeprecationWarning`, and will be removed in 1.0; patching module internals through the old paths (e.g. `mock.patch("stemtrace.library.transports.redis.time.sleep")`) no longer reaches the real code, so update such patches to `stemtrace.transports`. The transports' loggers are renamed to match (e.g. `stemtrace.transports.rabbitmq`); logging configured for the `stemtrace` logger is unaffected.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Redis: a stream entry whose payload is valid JSON but not an object (e.g. `42`) is now skipped with a warning; it used to end the consumer's read loop and trigger a restart.
|
|
19
|
+
|
|
20
|
+
## [0.3.9] - 2026-10-10
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- 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).
|
|
24
|
+
|
|
10
25
|
## [0.3.8] - 2026-10-10
|
|
11
26
|
|
|
12
27
|
### Fixed
|
|
@@ -181,7 +196,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
181
196
|
- **E2E test suite**: Docker API tests + Playwright browser tests
|
|
182
197
|
- **Comprehensive test suite**: 350+ Python tests, 90%+ coverage
|
|
183
198
|
|
|
184
|
-
[unreleased]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.
|
|
199
|
+
[unreleased]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.10...HEAD
|
|
200
|
+
[0.3.10]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.9...v0.3.10
|
|
201
|
+
[0.3.9]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.8...v0.3.9
|
|
185
202
|
[0.3.8]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.7...v0.3.8
|
|
186
203
|
[0.3.7]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.6...v0.3.7
|
|
187
204
|
[0.3.6]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.5...v0.3.6
|
|
@@ -37,7 +37,7 @@ make check
|
|
|
37
37
|
make check
|
|
38
38
|
|
|
39
39
|
# Individual checks
|
|
40
|
-
make lint # ruff check + format
|
|
40
|
+
make lint # ruff check + format + import contracts
|
|
41
41
|
make types # mypy --strict
|
|
42
42
|
make test # pytest with coverage
|
|
43
43
|
```
|
|
@@ -54,6 +54,38 @@ uv run ruff check --fix src/ tests/
|
|
|
54
54
|
uv run ruff format src/ tests/
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
### Lint Rules and Import Contracts
|
|
58
|
+
|
|
59
|
+
Besides style, ruff enforces design limits: McCabe complexity 10 (`C90`), at most
|
|
60
|
+
12 branches, 6 returns, 50 statements and 5 positional arguments per function
|
|
61
|
+
(`PLR09xx`; make extra parameters keyword-only), no blind `except Exception`
|
|
62
|
+
(`BLE`), no commented-out code (`ERA`), plus `RET` and `PT` style rules. A broad
|
|
63
|
+
except is accepted only at a fire-and-forget boundary, with
|
|
64
|
+
`# noqa: BLE001 <reason>` on that line.
|
|
65
|
+
|
|
66
|
+
[import-linter](https://import-linter.readthedocs.io/) checks the architecture
|
|
67
|
+
boundaries (`uv run lint-imports`, part of `make lint` and CI):
|
|
68
|
+
|
|
69
|
+
- `stemtrace.core` imports nothing from `stemtrace.library`, `stemtrace.server` or
|
|
70
|
+
`stemtrace.transports`, and no broker, Celery or web library (`celery`, `kombu`,
|
|
71
|
+
`amqp`, `redis`, `fastapi`, `starlette`, `uvicorn`, `typer`, `httpx`)
|
|
72
|
+
- `stemtrace.transports` imports nothing from `stemtrace.library` or
|
|
73
|
+
`stemtrace.server` (both of them use it)
|
|
74
|
+
- `stemtrace.library` does not import `stemtrace.server`
|
|
75
|
+
- `stemtrace.server` does not import `stemtrace.library`
|
|
76
|
+
|
|
77
|
+
**Baseline policy.** Code that predates these rules carries a per-line
|
|
78
|
+
`# noqa: <codes>` comment with no reason (added with `ruff check --add-noqa`).
|
|
79
|
+
Known import violations, if any, are listed under `ignore_imports` in
|
|
80
|
+
`pyproject.toml` (there are none at the moment). The baseline only shrinks:
|
|
81
|
+
|
|
82
|
+
- New code follows the rules. If a suppression is really needed, it names the
|
|
83
|
+
code and gives a reason: `# noqa: BLE001 fire-and-forget: <why>`. Never add a
|
|
84
|
+
bare `# noqa`.
|
|
85
|
+
- When you fix a baselined line, delete its `# noqa`. Ruff's `RUF100` fails on a
|
|
86
|
+
`# noqa` that no longer suppresses anything, and import-linter fails on an
|
|
87
|
+
`ignore_imports` entry that no longer matches, so stale entries can't linger.
|
|
88
|
+
|
|
57
89
|
### Type Checking
|
|
58
90
|
|
|
59
91
|
All code must pass `mypy --strict`:
|
|
@@ -125,8 +157,9 @@ uv lock --upgrade
|
|
|
125
157
|
### Key Principles
|
|
126
158
|
|
|
127
159
|
- **core/** - Pure Python, no external dependencies
|
|
128
|
-
- **
|
|
129
|
-
- **
|
|
160
|
+
- **transports/** - Broker adapters (Redis, RabbitMQ, memory), imports only from core/
|
|
161
|
+
- **library/** - Celery integration, imports only from core/ and transports/
|
|
162
|
+
- **server/** - Web server, imports only from core/ and transports/
|
|
130
163
|
- Never import sideways (library ↔ server)
|
|
131
164
|
|
|
132
165
|
### Code Quality
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
# Multi-stage build for smaller production image
|
|
3
3
|
|
|
4
4
|
# Stage 1: Build frontend
|
|
5
|
-
|
|
5
|
+
# The UI build output is static files, identical for every platform, so build it
|
|
6
|
+
# once on the native build platform instead of under QEMU (npm ci hung there).
|
|
7
|
+
FROM --platform=$BUILDPLATFORM node:22.23.3-alpine3.24@sha256:0a7108bf6c7bf5de370ffb1a3ed6be93d405b43ff159f681a8d18c0e2bc2e402 AS frontend-builder
|
|
6
8
|
|
|
7
9
|
WORKDIR /app/frontend
|
|
8
10
|
COPY src/stemtrace/server/ui/frontend/package*.json ./
|
|
@@ -30,6 +30,7 @@ exit(1) if missing else print('✓ All public APIs have docstrings') \
|
|
|
30
30
|
lint:
|
|
31
31
|
uv run ruff check src/ tests/
|
|
32
32
|
uv run ruff format --check src/ tests/
|
|
33
|
+
uv run lint-imports
|
|
33
34
|
|
|
34
35
|
format:
|
|
35
36
|
uv run ruff format src/ tests/
|
|
@@ -57,7 +58,7 @@ coverage:
|
|
|
57
58
|
|
|
58
59
|
# Clean build artifacts
|
|
59
60
|
clean:
|
|
60
|
-
rm -rf .pytest_cache .mypy_cache .ruff_cache htmlcov .coverage dist/
|
|
61
|
+
rm -rf .pytest_cache .mypy_cache .ruff_cache .import_linter_cache htmlcov .coverage dist/
|
|
61
62
|
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
|
62
63
|
|
|
63
64
|
# =============================================================================
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stemtrace
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.10
|
|
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
|
|
@@ -42,6 +42,7 @@ Provides-Extra: dev
|
|
|
42
42
|
Requires-Dist: build>=1.0.0; extra == 'dev'
|
|
43
43
|
Requires-Dist: bump-my-version>=0.28.0; extra == 'dev'
|
|
44
44
|
Requires-Dist: hatchling>=1.20.0; extra == 'dev'
|
|
45
|
+
Requires-Dist: import-linter>=2.0; extra == 'dev'
|
|
45
46
|
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
46
47
|
Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
|
|
47
48
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
@@ -59,7 +60,7 @@ Description-Content-Type: text/markdown
|
|
|
59
60
|
|
|
60
61
|
**Zero-infrastructure Celery task flow visualizer**
|
|
61
62
|
|
|
62
|
-
[](https://pypi.org/project/stemtrace)
|
|
63
64
|
[](https://pypi.org/project/stemtrace/)
|
|
64
65
|
[](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml)
|
|
65
66
|
[](https://codecov.io/gh/iansokolskyi/stemtrace)
|
|
@@ -83,6 +84,7 @@ Stemtrace captures your task executions as a graph — visualize parent→child
|
|
|
83
84
|
- **Task Flow Graphs** — Visualize chains, groups, and chords as interactive DAGs
|
|
84
85
|
- **Execution Timeline** — Track queued → started → retried → finished states
|
|
85
86
|
- **Arguments & Results** — Inspect inputs, outputs, and exceptions
|
|
87
|
+
- **Task Output** — See what each attempt printed and logged, next to the task (opt-in)
|
|
86
88
|
- **Retry Chains** — Understand exactly when and why retries happened
|
|
87
89
|
|
|
88
90
|
**Canvas Support**
|
|
@@ -253,6 +255,10 @@ stemtrace.init_worker(
|
|
|
253
255
|
scrub_sensitive_data=True, # Scrub passwords, API keys, etc.
|
|
254
256
|
additional_sensitive_keys=frozenset({"my_secret"}), # Add custom keys
|
|
255
257
|
safe_keys=frozenset({"public_key"}), # Never scrub these keys
|
|
258
|
+
|
|
259
|
+
# Task output (disabled by default, see "Task Output" below)
|
|
260
|
+
capture_output=False, # Capture what each attempt prints/logs
|
|
261
|
+
max_output_size=16384, # Max bytes kept per attempt (the end is kept)
|
|
256
262
|
)
|
|
257
263
|
|
|
258
264
|
# Introspection (after init)
|
|
@@ -271,6 +277,75 @@ By default, stemtrace scrubs common sensitive keys from task arguments:
|
|
|
271
277
|
|
|
272
278
|
Scrubbed values appear as `[Filtered]` in the UI.
|
|
273
279
|
|
|
280
|
+
#### Task Output (print and logging)
|
|
281
|
+
|
|
282
|
+
Turn on `capture_output` on the worker to see what each task attempt printed and
|
|
283
|
+
logged on its task page, instead of searching interleaved worker logs:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
stemtrace.init_worker(app, capture_output=True)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
from celery.utils.log import get_task_logger
|
|
291
|
+
import logging
|
|
292
|
+
|
|
293
|
+
task_logger = get_task_logger(__name__)
|
|
294
|
+
log = logging.getLogger("myapp.billing")
|
|
295
|
+
|
|
296
|
+
@app.task(bind=True, max_retries=3)
|
|
297
|
+
def charge(self, order_id):
|
|
298
|
+
print(f"charging {order_id}") # -> charging 42
|
|
299
|
+
task_logger.info("calling gateway") # -> INFO myapp.tasks: calling gateway
|
|
300
|
+
log.warning("gateway slow") # -> WARNING myapp.billing: gateway slow
|
|
301
|
+
...
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The task page shows an **Output** section with one block per attempt (latest
|
|
305
|
+
first): the output of a retried attempt is on its `RETRY` event, the final one on
|
|
306
|
+
`SUCCESS`/`FAILURE`.
|
|
307
|
+
|
|
308
|
+
- **What's captured**: `print()` to stdout and stderr, exactly as printed
|
|
309
|
+
(whether or not Celery redirects stdout to its log), and log records
|
|
310
|
+
(`logging.getLogger(...)`, `get_task_logger(...)`) emitted while the task
|
|
311
|
+
runs, in one chronological stream. Records are captured when they reach the
|
|
312
|
+
root logger or `celery.task`, or when a handler writes them to stdout/stderr
|
|
313
|
+
after stemtrace is set up. A logger with `propagate=False` whose handlers were
|
|
314
|
+
set up at worker startup (writing to the real stream or a file) isn't
|
|
315
|
+
captured. Log levels still apply to log records: one your logging config
|
|
316
|
+
filters out (e.g. `DEBUG` under `--loglevel=INFO`) isn't captured.
|
|
317
|
+
stemtrace's own logs and Celery/kombu internals are left out.
|
|
318
|
+
What the worker prints to its console or log file is unchanged (when no
|
|
319
|
+
logging handlers are configured, warnings still print as Python's
|
|
320
|
+
`logging.lastResort` would). One side effect: from the first task on, the root
|
|
321
|
+
logger has stemtrace's handler, so `logging.getLogger().hasHandlers()` is
|
|
322
|
+
`True` and a later `logging.basicConfig()` call without `force=True` does
|
|
323
|
+
nothing. Configure logging at startup (or pass `force=True`). Logging
|
|
324
|
+
handlers added while a task runs, ones that override `handle()`, or ones
|
|
325
|
+
that write to stdout/stderr in unusual ways may show a record twice or show
|
|
326
|
+
Celery/kombu lines in that task's output; handlers set up at worker startup
|
|
327
|
+
don't.
|
|
328
|
+
- **Size**: at most `max_output_size` bytes (UTF-8) per attempt. Longer output
|
|
329
|
+
keeps its end, where failures usually are, after a `[… N bytes truncated]` line.
|
|
330
|
+
Output travels with the task's terminal event, so it is stored wherever events
|
|
331
|
+
are: by the server (up to `max_nodes` tasks) and, with the Redis transport, in
|
|
332
|
+
the stemtrace stream on your broker, which keeps the last `max(ttl, 10000)`
|
|
333
|
+
events (86,400 with the default `ttl`). At 16 KB per attempt that can add up to
|
|
334
|
+
more than a GB of Redis memory on busy workers, so consider a lower
|
|
335
|
+
`max_output_size` or `ttl` there. It isn't sent over the UI's WebSocket; the
|
|
336
|
+
task page fetches it.
|
|
337
|
+
- **Pools**: each task's output is collected in a `ContextVar` (per thread, and
|
|
338
|
+
per greenlet under gevent/eventlet), so concurrent tasks never mix output.
|
|
339
|
+
Output from threads a task starts itself isn't captured.
|
|
340
|
+
- **Not captured**: anything after the task ends, and output of a task whose
|
|
341
|
+
worker process was killed (hard time limit, OOM), since no terminal event is
|
|
342
|
+
sent from that process. Capture of a failed or retried attempt stops when
|
|
343
|
+
Celery reports the failure/retry, so `on_failure`/`on_retry` output is included
|
|
344
|
+
but `after_return` output is included only on success.
|
|
345
|
+
- **Not scrubbed**: output is free text, so the key-based scrubbing above can't
|
|
346
|
+
apply. That's why it's off by default: only enable it if your tasks don't print
|
|
347
|
+
or log secrets.
|
|
348
|
+
|
|
274
349
|
### Canvas Graph Visualization
|
|
275
350
|
|
|
276
351
|
`stemtrace` automatically detects and visualizes Celery canvas constructs:
|
|
@@ -631,6 +706,7 @@ What is detected depends on the transport:
|
|
|
631
706
|
- ✅ **Full lifecycle tracking** — PENDING → RECEIVED → STARTED → SUCCESS/FAILURE
|
|
632
707
|
- ✅ **Canvas awareness** — Automatic GROUP/CHORD node visualization
|
|
633
708
|
- ✅ **Arguments & results** — View inputs, outputs, and exceptions
|
|
709
|
+
- ✅ **Task output** — What each attempt printed and logged (opt-in)
|
|
634
710
|
- ✅ **Sensitive data scrubbing** — Passwords and API keys filtered automatically
|
|
635
711
|
- ✅ **Real-time updates** — WebSocket-powered live dashboard
|
|
636
712
|
- ✅ **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
|