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.
Files changed (167) hide show
  1. {stemtrace-0.3.8 → stemtrace-0.3.9}/.bumpversion.toml +1 -1
  2. {stemtrace-0.3.8 → stemtrace-0.3.9}/CHANGELOG.md +7 -1
  3. {stemtrace-0.3.8 → stemtrace-0.3.9}/PKG-INFO +77 -2
  4. {stemtrace-0.3.8 → stemtrace-0.3.9}/README.md +76 -1
  5. {stemtrace-0.3.8 → stemtrace-0.3.9}/pyproject.toml +1 -1
  6. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/__init__.py +15 -2
  7. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/core/events.py +3 -0
  8. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/config.py +8 -0
  9. stemtrace-0.3.9/src/stemtrace/library/output_capture.py +536 -0
  10. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/signals.py +54 -0
  11. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/api/routes.py +17 -4
  12. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/api/schemas.py +1 -0
  13. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/package-lock.json +2 -2
  14. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/package.json +1 -1
  15. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/api/client.ts +2 -0
  16. stemtrace-0.3.9/src/stemtrace/server/ui/frontend/src/components/TaskOutput.tsx +118 -0
  17. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/hooks/WebSocketContext.tsx +26 -8
  18. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/tasks.$taskId.tsx +4 -0
  19. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-data.ts +2 -0
  20. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/task-detail.spec.ts +219 -2
  21. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/websocket.py +3 -1
  22. stemtrace-0.3.9/tests/integration/output_worker_app.py +102 -0
  23. stemtrace-0.3.9/tests/integration/test_output_capture_worker.py +251 -0
  24. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_events.py +38 -0
  25. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_init.py +34 -0
  26. stemtrace-0.3.9/tests/unit/test_output_capture.py +1080 -0
  27. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_routes.py +40 -0
  28. stemtrace-0.3.9/tests/unit/test_signals_output.py +364 -0
  29. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_websocket.py +25 -0
  30. {stemtrace-0.3.8 → stemtrace-0.3.9}/uv.lock +1 -1
  31. {stemtrace-0.3.8 → stemtrace-0.3.9}/.dockerignore +0 -0
  32. {stemtrace-0.3.8 → stemtrace-0.3.9}/.github/dependabot.yml +0 -0
  33. {stemtrace-0.3.8 → stemtrace-0.3.9}/.github/workflows/ci.yml +0 -0
  34. {stemtrace-0.3.8 → stemtrace-0.3.9}/.github/workflows/e2e.yml +0 -0
  35. {stemtrace-0.3.8 → stemtrace-0.3.9}/.github/workflows/release.yml +0 -0
  36. {stemtrace-0.3.8 → stemtrace-0.3.9}/.gitignore +0 -0
  37. {stemtrace-0.3.8 → stemtrace-0.3.9}/.pre-commit-config.yaml +0 -0
  38. {stemtrace-0.3.8 → stemtrace-0.3.9}/.python-version +0 -0
  39. {stemtrace-0.3.8 → stemtrace-0.3.9}/CONTRIBUTING.md +0 -0
  40. {stemtrace-0.3.8 → stemtrace-0.3.9}/Dockerfile +0 -0
  41. {stemtrace-0.3.8 → stemtrace-0.3.9}/Dockerfile.e2e +0 -0
  42. {stemtrace-0.3.8 → stemtrace-0.3.9}/LICENSE +0 -0
  43. {stemtrace-0.3.8 → stemtrace-0.3.9}/Makefile +0 -0
  44. {stemtrace-0.3.8 → stemtrace-0.3.9}/build_ui.py +0 -0
  45. {stemtrace-0.3.8 → stemtrace-0.3.9}/docker-compose.e2e.rabbitmq.yml +0 -0
  46. {stemtrace-0.3.8 → stemtrace-0.3.9}/docker-compose.e2e.yml +0 -0
  47. {stemtrace-0.3.8 → stemtrace-0.3.9}/docker-compose.rabbitmq.yml +0 -0
  48. {stemtrace-0.3.8 → stemtrace-0.3.9}/docker-compose.yml +0 -0
  49. {stemtrace-0.3.8 → stemtrace-0.3.9}/docs/brand/stemtrace-mark-light.svg +0 -0
  50. {stemtrace-0.3.8 → stemtrace-0.3.9}/docs/brand/stemtrace-mark.svg +0 -0
  51. {stemtrace-0.3.8 → stemtrace-0.3.9}/docs/screenshots/task_details.png +0 -0
  52. {stemtrace-0.3.8 → stemtrace-0.3.9}/docs/screenshots/unregistered.png +0 -0
  53. {stemtrace-0.3.8 → stemtrace-0.3.9}/docs/screenshots/workflow.png +0 -0
  54. {stemtrace-0.3.8 → stemtrace-0.3.9}/examples/celery_app.py +0 -0
  55. {stemtrace-0.3.8 → stemtrace-0.3.9}/examples/fastapi_integration.py +0 -0
  56. {stemtrace-0.3.8 → stemtrace-0.3.9}/examples/with_auth.py +0 -0
  57. {stemtrace-0.3.8 → stemtrace-0.3.9}/examples/with_login.py +0 -0
  58. {stemtrace-0.3.8 → stemtrace-0.3.9}/progress.md +0 -0
  59. {stemtrace-0.3.8 → stemtrace-0.3.9}/scripts/changelog_section.py +0 -0
  60. {stemtrace-0.3.8 → stemtrace-0.3.9}/scripts/wait_for_http.py +0 -0
  61. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/core/__init__.py +0 -0
  62. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/core/exceptions.py +0 -0
  63. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/core/graph.py +0 -0
  64. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/core/ports.py +0 -0
  65. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/__init__.py +0 -0
  66. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/bootsteps.py +0 -0
  67. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/scrubbing.py +0 -0
  68. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/transports/__init__.py +0 -0
  69. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/transports/breaker.py +0 -0
  70. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/transports/memory.py +0 -0
  71. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/transports/rabbitmq.py +0 -0
  72. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/library/transports/redis.py +0 -0
  73. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/py.typed +0 -0
  74. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/__init__.py +0 -0
  75. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/__main__.py +0 -0
  76. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/api/__init__.py +0 -0
  77. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/api/websocket.py +0 -0
  78. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/consumer.py +0 -0
  79. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/__init__.py +0 -0
  80. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/auth.py +0 -0
  81. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/extension.py +0 -0
  82. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/form_auth.py +0 -0
  83. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/form_auth_middleware.py +0 -0
  84. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/login_routes.py +0 -0
  85. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/fastapi/router.py +0 -0
  86. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/store.py +0 -0
  87. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/__init__.py +0 -0
  88. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/biome.json +0 -0
  89. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/index.html +0 -0
  90. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/playwright.config.ts +0 -0
  91. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/postcss.config.js +0 -0
  92. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/api/queries.ts +0 -0
  93. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/DateRangePicker.tsx +0 -0
  94. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/Filters.tsx +0 -0
  95. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/LogoutButton.tsx +0 -0
  96. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/SessionRejectedNotice.tsx +0 -0
  97. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/SproutMark.tsx +0 -0
  98. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/TaskGraph.tsx +0 -0
  99. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/TaskList.tsx +0 -0
  100. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/TaskStateBadge.tsx +0 -0
  101. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/TaskTimeline.tsx +0 -0
  102. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/TimelineBar.tsx +0 -0
  103. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/components/Tooltip.tsx +0 -0
  104. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/hooks/useInfiniteScroll.ts +0 -0
  105. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/index.css +0 -0
  106. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/main.tsx +0 -0
  107. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/__root.tsx +0 -0
  108. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/graph.$rootId.tsx +0 -0
  109. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/graphs.tsx +0 -0
  110. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/index.tsx +0 -0
  111. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/registry.tsx +0 -0
  112. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/routes/workers.tsx +0 -0
  113. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/utils/format.ts +0 -0
  114. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/src/vite-env.d.ts +0 -0
  115. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/auth.spec.ts +0 -0
  116. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-api.ts +0 -0
  117. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/graphs.spec.ts +0 -0
  118. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/header.spec.ts +0 -0
  119. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/registry.spec.ts +0 -0
  120. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/tasks.spec.ts +0 -0
  121. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tests/workers.spec.ts +0 -0
  122. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tsconfig.json +0 -0
  123. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/tsconfig.tsbuildinfo +0 -0
  124. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/frontend/vite.config.ts +0 -0
  125. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/static.py +0 -0
  126. {stemtrace-0.3.8 → stemtrace-0.3.9}/src/stemtrace/server/ui/templates/login.html +0 -0
  127. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/__init__.py +0 -0
  128. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/conftest.py +0 -0
  129. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/e2e/__init__.py +0 -0
  130. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/e2e/conftest.py +0 -0
  131. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/e2e/tasks.py +0 -0
  132. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/e2e/test_api_e2e.py +0 -0
  133. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/e2e/test_workers_e2e.py +0 -0
  134. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/__init__.py +0 -0
  135. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_embedded_static.py +0 -0
  136. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_fastapi_integration.py +0 -0
  137. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_rabbitmq_publish.py +0 -0
  138. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_rabbitmq_transport.py +0 -0
  139. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_redis_resilience_docker.py +0 -0
  140. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_redis_transport.py +0 -0
  141. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_static_ui.py +0 -0
  142. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_wait_for_http_redirects.py +0 -0
  143. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/integration/test_workers_refresh.py +0 -0
  144. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/__init__.py +0 -0
  145. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_auth.py +0 -0
  146. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_bootsteps.py +0 -0
  147. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_build_ui_hook.py +0 -0
  148. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_changelog_section_script.py +0 -0
  149. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_cli.py +0 -0
  150. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_cli_form_login.py +0 -0
  151. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_config.py +0 -0
  152. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_consumer.py +0 -0
  153. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_examples.py +0 -0
  154. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_exceptions.py +0 -0
  155. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_favicon.py +0 -0
  156. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_form_auth.py +0 -0
  157. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_form_login.py +0 -0
  158. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_graph.py +0 -0
  159. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_publish_breaker.py +0 -0
  160. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_rabbitmq_publish.py +0 -0
  161. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_redis_resilience.py +0 -0
  162. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_scrubbing.py +0 -0
  163. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_signals.py +0 -0
  164. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_store.py +0 -0
  165. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_transports.py +0 -0
  166. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_ui_static_prefix.py +0 -0
  167. {stemtrace-0.3.8 → stemtrace-0.3.9}/tests/unit/test_wait_for_http_script.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "0.3.8"
2
+ current_version = "0.3.9"
3
3
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
4
4
  serialize = ["{major}.{minor}.{patch}"]
5
5
  commit = true
@@ -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.8...HEAD
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.8
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
- [![PyPI version](https://img.shields.io/badge/pypi-v0.3.8-darklime)](https://pypi.org/project/stemtrace)
62
+ [![PyPI version](https://img.shields.io/badge/pypi-v0.3.9-darklime)](https://pypi.org/project/stemtrace)
63
63
  [![Python](https://img.shields.io/pypi/pyversions/stemtrace.svg)](https://pypi.org/project/stemtrace/)
64
64
  [![CI](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml/badge.svg)](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml)
65
65
  [![codecov](https://codecov.io/gh/iansokolskyi/stemtrace/graph/badge.svg)](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
- [![PyPI version](https://img.shields.io/badge/pypi-v0.3.8-darklime)](https://pypi.org/project/stemtrace)
7
+ [![PyPI version](https://img.shields.io/badge/pypi-v0.3.9-darklime)](https://pypi.org/project/stemtrace)
8
8
  [![Python](https://img.shields.io/pypi/pyversions/stemtrace.svg)](https://pypi.org/project/stemtrace/)
9
9
  [![CI](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml/badge.svg)](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml)
10
10
  [![codecov](https://codecov.io/gh/iansokolskyi/stemtrace/graph/badge.svg)](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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stemtrace"
3
- version = "0.3.8"
3
+ version = "0.3.9"
4
4
  description = "A lightweight Celery task flow visualizer"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -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.8"
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