lumonox 0.2.5__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.
- lumonox-0.2.5/.env.example +126 -0
- lumonox-0.2.5/.gitignore +73 -0
- lumonox-0.2.5/ALERT_DELIVERY_RUNBOOK.md +59 -0
- lumonox-0.2.5/PKG-INFO +20 -0
- lumonox-0.2.5/README.md +138 -0
- lumonox-0.2.5/alembic.ini +39 -0
- lumonox-0.2.5/pyproject.toml +39 -0
- lumonox-0.2.5/scripts/backfill_events_to_duckdb.py +83 -0
- lumonox-0.2.5/scripts/debug_retention_vacuum.py +97 -0
- lumonox-0.2.5/scripts/manual_dashboard_test.py +236 -0
- lumonox-0.2.5/scripts/package_wheel.sh +24 -0
- lumonox-0.2.5/src/lumonox_backend/__init__.py +5 -0
- lumonox-0.2.5/src/lumonox_backend/alembic/env.py +61 -0
- lumonox-0.2.5/src/lumonox_backend/alembic/script.py.mako +25 -0
- lumonox-0.2.5/src/lumonox_backend/alembic/versions/initial.py +732 -0
- lumonox-0.2.5/src/lumonox_backend/alerts.py +31 -0
- lumonox-0.2.5/src/lumonox_backend/api/__init__.py +3 -0
- lumonox-0.2.5/src/lumonox_backend/api/router.py +12 -0
- lumonox-0.2.5/src/lumonox_backend/api/routes/__init__.py +1 -0
- lumonox-0.2.5/src/lumonox_backend/api/routes/health.py +491 -0
- lumonox-0.2.5/src/lumonox_backend/app.py +62 -0
- lumonox-0.2.5/src/lumonox_backend/auth/__init__.py +59 -0
- lumonox-0.2.5/src/lumonox_backend/auth/api_keys.py +136 -0
- lumonox-0.2.5/src/lumonox_backend/auth/dashboard.py +637 -0
- lumonox-0.2.5/src/lumonox_backend/auth/dashboard_security.py +104 -0
- lumonox-0.2.5/src/lumonox_backend/auth/rbac.py +101 -0
- lumonox-0.2.5/src/lumonox_backend/commercial/__init__.py +1 -0
- lumonox-0.2.5/src/lumonox_backend/commercial/plan_limits.py +34 -0
- lumonox-0.2.5/src/lumonox_backend/config.py +21 -0
- lumonox-0.2.5/src/lumonox_backend/core/__init__.py +7 -0
- lumonox-0.2.5/src/lumonox_backend/core/config.py +1018 -0
- lumonox-0.2.5/src/lumonox_backend/core/config_env.py +92 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/__init__.py +42 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/duckdb_queries.py +630 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/duckdb_query_utils.py +37 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/error_grouping.py +132 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/event_scope.py +13 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/log_query.py +182 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/messages.py +20 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/overview_derived_widgets.py +164 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/params.py +18 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/parsing.py +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/payload_limits.py +14 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/query_snapshot_cache.py +300 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/read_rate_limit.py +89 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/repositories/__init__.py +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/repositories/project_ui.py +81 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/__init__.py +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/alert_routes.py +413 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/auth_routes.py +548 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/bookmark_routes.py +152 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/diagnosis.py +339 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/error_groups.py +411 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/job_events.py +178 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/log_query_routes.py +247 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/oidc_routes.py +270 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/organization_routes.py +284 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/overview.py +659 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/query_bundle.py +602 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/query_explorer.py +148 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/requests_routes.py +217 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/traces.py +285 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/ui_settings.py +402 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/websockets.py +123 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/routes/widgets.py +99 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/serializers.py +84 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/static_export_mount.py +119 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/studio_nav_pages.py +27 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/studio_showcase.py +321 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/time_window.py +59 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/widget_layout.py +143 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard/widget_points_selection.py +50 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/404/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/404.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/__next.__PAGE__.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/__next._full.txt +20 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0-27aiizw1ccj.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0-ofl9r~na2aa.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0._d-i80mn1in.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/00-csyj9a6t-x.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/00eadyl74e5fh.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/00ywsx1nh925h.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/019m0vkkx-5er.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/01qk1etdnj3x8.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/01xlw8hd842-c.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/02s99uve0dntn.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/03k~wtmusmela.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/03~g8xwvee~1x.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/03~yq9q893hmn.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/04otm4_fgokbm.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/056s9zs336jxm.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/059hv_g8zqixk.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/05vemp8yxojjb.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/06fhfy8eo--2j.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/077munl7atdzy.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/07_sztjw2zw03.css +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/07o5~zvw9clgj.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/07uz2g0_38qia.js +4 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/08tnvfuv3_5o9.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0b7arnzv89h-j.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0be78tz0a5au1.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0cimpi4vr37_t.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0f.~cy-4z32-5.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0fi6roa6ocwc9.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0gz_3mtiwcabp.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0ilwwu75d3vfp.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0im_i0ss6zxet.js +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0ka051yepewro.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0n~dq4kpx9xxx.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0p6~n93_~j4rn.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0pl2cmnllqj7b.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0qn5stkkf4xy1.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0rdmkomuytin5.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0spq~zna~mrc6.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0tqco-emg.3ff.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0umzvrml~lr94.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0v.c9j1.kuiv~.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0ze4gu236oq96.js +31 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/10.6-2w1agbgg.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/11.ke-gavch7..js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/138xf6ysy85-9.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/14s3dpmeh6ra3.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/15bxb1da~ccz9.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/16rwsqira6kf6.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/17p96j1ofg3lm.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/180zy~f3la25~.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/turbopack-0xgsc5fkkqcj~.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/media/icon.0f8r8fb1xdir..svg +21 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/uVYYN_m9Y6LX0BhAeduWk/_buildManifest.js +11 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/uVYYN_m9Y6LX0BhAeduWk/_clientMiddlewareManifest.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/uVYYN_m9Y6LX0BhAeduWk/_ssgManifest.js +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/__next._full.txt +18 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/__next._not-found.__PAGE__.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/__next._not-found.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/index.txt +18 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next.!KG1haW4p.alerts.__PAGE__.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next.!KG1haW4p.alerts.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next._full.txt +26 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/index.txt +26 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next._full.txt +20 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next.auth.magic-link.__PAGE__.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next.auth.magic-link.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next.auth.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/index.txt +20 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next.!KG1haW4p.bookmarks.__PAGE__.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next.!KG1haW4p.bookmarks.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next._full.txt +26 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/index.txt +26 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next.!KG1haW4p.dashboard.__PAGE__.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next.!KG1haW4p.dashboard.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next._full.txt +28 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/index.txt +28 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next.!KG1haW4p.diagnosis.__PAGE__.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next.!KG1haW4p.diagnosis.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next._full.txt +25 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/index.txt +25 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/icon.svg +21 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/index.txt +20 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next.!KG1haW4p.logs.__PAGE__.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next.!KG1haW4p.logs.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next._full.txt +23 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/index.txt +23 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next.!KG1haW4p.onboarding.__PAGE__.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next.!KG1haW4p.onboarding.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next._full.txt +27 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/index.txt +27 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next.!KG1haW4p.query-explorer.__PAGE__.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next.!KG1haW4p.query-explorer.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next._full.txt +26 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/index.txt +26 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next.!KG1haW4p.requests.__PAGE__.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next.!KG1haW4p.requests.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next._full.txt +26 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/index.txt +26 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next.!KG1haW4p.settings.__PAGE__.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next.!KG1haW4p.settings.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next._full.txt +26 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/index.txt +26 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next.!KG1haW4p.traces.__PAGE__.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next.!KG1haW4p.traces.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next._full.txt +26 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/index.txt +26 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next.!KG1haW4p.w.$d$pageId.__PAGE__.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next.!KG1haW4p.w.$d$pageId.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next.!KG1haW4p.w.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next._full.txt +27 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/index.txt +27 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next.!KG1haW4p.widgets.__PAGE__.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next.!KG1haW4p.widgets.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next._full.txt +24 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/index.txt +24 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next.!KG1haW4p.widgets-showcase.__PAGE__.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next.!KG1haW4p.widgets-showcase.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next._full.txt +24 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/index.txt +24 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next.!KG1haW4p.txt +9 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next.!KG1haW4p.widgets-showroom.__PAGE__.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next.!KG1haW4p.widgets-showroom.txt +5 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next._full.txt +24 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next._head.txt +6 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next._index.txt +7 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next._tree.txt +2 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/index.html +1 -0
- lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/index.txt +24 -0
- lumonox-0.2.5/src/lumonox_backend/database/__init__.py +19 -0
- lumonox-0.2.5/src/lumonox_backend/database/migrations.py +95 -0
- lumonox-0.2.5/src/lumonox_backend/database/session.py +99 -0
- lumonox-0.2.5/src/lumonox_backend/ingestion/__init__.py +1 -0
- lumonox-0.2.5/src/lumonox_backend/ingestion/body_size.py +138 -0
- lumonox-0.2.5/src/lumonox_backend/ingestion/exclude_lumonox.py +60 -0
- lumonox-0.2.5/src/lumonox_backend/ingestion/limits.py +62 -0
- lumonox-0.2.5/src/lumonox_backend/ingestion/otlp_traces.py +242 -0
- lumonox-0.2.5/src/lumonox_backend/ingestion/scenario_events.py +307 -0
- lumonox-0.2.5/src/lumonox_backend/jobs/__init__.py +719 -0
- lumonox-0.2.5/src/lumonox_backend/jobs/__main__.py +8 -0
- lumonox-0.2.5/src/lumonox_backend/lifespan.py +382 -0
- lumonox-0.2.5/src/lumonox_backend/main.py +5 -0
- lumonox-0.2.5/src/lumonox_backend/maintenance/__init__.py +8 -0
- lumonox-0.2.5/src/lumonox_backend/maintenance/retention.py +310 -0
- lumonox-0.2.5/src/lumonox_backend/maintenance/retention_constants.py +6 -0
- lumonox-0.2.5/src/lumonox_backend/maintenance/retention_deletes.py +236 -0
- lumonox-0.2.5/src/lumonox_backend/maintenance/retention_duckdb.py +179 -0
- lumonox-0.2.5/src/lumonox_backend/maintenance/retention_sqlite.py +230 -0
- lumonox-0.2.5/src/lumonox_backend/metrics/__init__.py +57 -0
- lumonox-0.2.5/src/lumonox_backend/middleware/__init__.py +1 -0
- lumonox-0.2.5/src/lumonox_backend/middleware/dashboard_origin.py +63 -0
- lumonox-0.2.5/src/lumonox_backend/middleware/gzip_request_body.py +164 -0
- lumonox-0.2.5/src/lumonox_backend/models/__init__.py +55 -0
- lumonox-0.2.5/src/lumonox_backend/models/orm.py +620 -0
- lumonox-0.2.5/src/lumonox_backend/realtime/__init__.py +285 -0
- lumonox-0.2.5/src/lumonox_backend/realtime/bus.py +209 -0
- lumonox-0.2.5/src/lumonox_backend/realtime/dashboard_snapshot_reconcile.py +84 -0
- lumonox-0.2.5/src/lumonox_backend/realtime/dashboard_snapshot_store.py +118 -0
- lumonox-0.2.5/src/lumonox_backend/realtime/dashboard_ws_tick.py +80 -0
- lumonox-0.2.5/src/lumonox_backend/repositories/__init__.py +1 -0
- lumonox-0.2.5/src/lumonox_backend/repositories/aggregates.py +120 -0
- lumonox-0.2.5/src/lumonox_backend/repositories/alert_dispatches.py +39 -0
- lumonox-0.2.5/src/lumonox_backend/repositories/alert_settings.py +40 -0
- lumonox-0.2.5/src/lumonox_backend/repositories/dashboard_widgets.py +172 -0
- lumonox-0.2.5/src/lumonox_backend/repositories/events.py +100 -0
- lumonox-0.2.5/src/lumonox_backend/repositories/ingest_reliability.py +339 -0
- lumonox-0.2.5/src/lumonox_backend/repositories/metric_bucket_overview.py +81 -0
- lumonox-0.2.5/src/lumonox_backend/repositories/runtime_controls.py +116 -0
- lumonox-0.2.5/src/lumonox_backend/routes/__init__.py +1 -0
- lumonox-0.2.5/src/lumonox_backend/routes/dev_scenarios.py +118 -0
- lumonox-0.2.5/src/lumonox_backend/routes/ingest.py +521 -0
- lumonox-0.2.5/src/lumonox_backend/routes/rum.py +68 -0
- lumonox-0.2.5/src/lumonox_backend/schemas/__init__.py +180 -0
- lumonox-0.2.5/src/lumonox_backend/schemas/dashboard.py +777 -0
- lumonox-0.2.5/src/lumonox_backend/schemas/dashboard_overview_models.py +227 -0
- lumonox-0.2.5/src/lumonox_backend/schemas/ingest.py +57 -0
- lumonox-0.2.5/src/lumonox_backend/schemas/rum.py +48 -0
- lumonox-0.2.5/src/lumonox_backend/services/__init__.py +1 -0
- lumonox-0.2.5/src/lumonox_backend/services/aggregate_delta_codec.py +103 -0
- lumonox-0.2.5/src/lumonox_backend/services/alert_delivery.py +610 -0
- lumonox-0.2.5/src/lumonox_backend/services/alert_service.py +249 -0
- lumonox-0.2.5/src/lumonox_backend/services/duckdb_async.py +155 -0
- lumonox-0.2.5/src/lumonox_backend/services/event_plane_compactor.py +468 -0
- lumonox-0.2.5/src/lumonox_backend/services/event_plane_compactor_worker.py +110 -0
- lumonox-0.2.5/src/lumonox_backend/services/event_plane_manifest.py +208 -0
- lumonox-0.2.5/src/lumonox_backend/services/event_plane_parity.py +233 -0
- lumonox-0.2.5/src/lumonox_backend/services/event_plane_read_path.py +55 -0
- lumonox-0.2.5/src/lumonox_backend/services/event_plane_shards.py +434 -0
- lumonox-0.2.5/src/lumonox_backend/services/event_store.py +1001 -0
- lumonox-0.2.5/src/lumonox_backend/services/event_store_utils.py +31 -0
- lumonox-0.2.5/src/lumonox_backend/services/infrastructure_metrics.py +146 -0
- lumonox-0.2.5/src/lumonox_backend/services/ingest_aggregate_worker.py +143 -0
- lumonox-0.2.5/src/lumonox_backend/services/ingest_service.py +507 -0
- lumonox-0.2.5/src/lumonox_backend/services/ingest_sql_tail_codec.py +131 -0
- lumonox-0.2.5/src/lumonox_backend/services/ingest_widgets.py +308 -0
- lumonox-0.2.5/src/lumonox_backend/services/parquet_exporter.py +220 -0
- lumonox-0.2.5/src/lumonox_backend/services/parquet_lifecycle.py +231 -0
- lumonox-0.2.5/src/lumonox_backend/services/parquet_object_storage.py +393 -0
- lumonox-0.2.5/src/lumonox_backend/services/project_activity.py +41 -0
- lumonox-0.2.5/tests/conftest.py +98 -0
- lumonox-0.2.5/tests/db_reset.py +85 -0
- lumonox-0.2.5/tests/test_alert_window_semantics_parity.py +253 -0
- lumonox-0.2.5/tests/test_alerts.py +363 -0
- lumonox-0.2.5/tests/test_app_health.py +331 -0
- lumonox-0.2.5/tests/test_backend_jobs.py +431 -0
- lumonox-0.2.5/tests/test_commercial_plan_limits.py +11 -0
- lumonox-0.2.5/tests/test_dashboard.py +1582 -0
- lumonox-0.2.5/tests/test_dashboard_auth.py +1230 -0
- lumonox-0.2.5/tests/test_dashboard_query_bundle.py +158 -0
- lumonox-0.2.5/tests/test_dashboard_query_snapshot_cache.py +159 -0
- lumonox-0.2.5/tests/test_dashboard_read_rate_limit.py +84 -0
- lumonox-0.2.5/tests/test_dashboard_rum.py +52 -0
- lumonox-0.2.5/tests/test_dashboard_snapshot_reconcile.py +65 -0
- lumonox-0.2.5/tests/test_dashboard_snapshot_store.py +56 -0
- lumonox-0.2.5/tests/test_database_url_redaction.py +17 -0
- lumonox-0.2.5/tests/test_deployment_settings.py +316 -0
- lumonox-0.2.5/tests/test_dev_scenarios.py +129 -0
- lumonox-0.2.5/tests/test_drill_catalog_docs.py +25 -0
- lumonox-0.2.5/tests/test_dx_docs_parity.py +39 -0
- lumonox-0.2.5/tests/test_event_plane_compactor.py +589 -0
- lumonox-0.2.5/tests/test_event_plane_compactor_worker.py +57 -0
- lumonox-0.2.5/tests/test_event_plane_config.py +113 -0
- lumonox-0.2.5/tests/test_event_plane_manifest.py +124 -0
- lumonox-0.2.5/tests/test_event_plane_parity.py +221 -0
- lumonox-0.2.5/tests/test_event_plane_read_path.py +97 -0
- lumonox-0.2.5/tests/test_event_plane_shards.py +242 -0
- lumonox-0.2.5/tests/test_event_store_duckdb.py +515 -0
- lumonox-0.2.5/tests/test_event_store_duckdb_path_normalization.py +79 -0
- lumonox-0.2.5/tests/test_gzip_request_body_middleware.py +94 -0
- lumonox-0.2.5/tests/test_ingest.py +1121 -0
- lumonox-0.2.5/tests/test_ingest_body_size.py +129 -0
- lumonox-0.2.5/tests/test_ingest_event_plane_shadow.py +200 -0
- lumonox-0.2.5/tests/test_ingest_sql_tail_codec.py +145 -0
- lumonox-0.2.5/tests/test_ingest_widget_points.py +38 -0
- lumonox-0.2.5/tests/test_integration_flow.py +312 -0
- lumonox-0.2.5/tests/test_overview_derived_widgets.py +125 -0
- lumonox-0.2.5/tests/test_overview_metric_bucket_merge.py +80 -0
- lumonox-0.2.5/tests/test_parquet_config_paths.py +33 -0
- lumonox-0.2.5/tests/test_parquet_exporter.py +319 -0
- lumonox-0.2.5/tests/test_parquet_lifecycle.py +304 -0
- lumonox-0.2.5/tests/test_parquet_object_storage.py +382 -0
- lumonox-0.2.5/tests/test_query_explorer_validation.py +35 -0
- lumonox-0.2.5/tests/test_realtime_bus.py +103 -0
- lumonox-0.2.5/tests/test_release_gates_manifest.py +46 -0
- lumonox-0.2.5/tests/test_retention.py +708 -0
- lumonox-0.2.5/tests/test_retention_pressure.py +129 -0
- lumonox-0.2.5/tests/test_runtime_controls.py +84 -0
- lumonox-0.2.5/tests/test_sqlite_local_defaults.py +142 -0
- lumonox-0.2.5/tests/test_studio_nav_and_showcase.py +63 -0
- lumonox-0.2.5/tests/test_widget_layout.py +41 -0
- lumonox-0.2.5/tests/test_widget_points_selection.py +55 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Copy to backend/.env and fill in values (never commit backend/.env).
|
|
2
|
+
# Synthetic stack: scripts/run_synthetic_stack.sh sources this file before
|
|
3
|
+
# `npm --prefix frontend run build`, so set NEXT_PUBLIC_* here first.
|
|
4
|
+
#
|
|
5
|
+
# Local / uvicorn: the backend loads this file automatically when it exists
|
|
6
|
+
# next to this package (repo ``backend/.env``) or set LUMONOX_BACKEND_DOTENV to an absolute path.
|
|
7
|
+
|
|
8
|
+
# Relational DB (projects, keys, dashboard metadata). Path is anchored to repo root / LUMONOX_DATA_DIR
|
|
9
|
+
# (see normalize_database_url); keep alongside DuckDB under .lumonox/.
|
|
10
|
+
DATABASE_URL=sqlite+aiosqlite:///./.lumonox/lumonox.db
|
|
11
|
+
# Multi-replica production: run migrations once as a deploy step, then set to false on API replicas.
|
|
12
|
+
# DATABASE_RUN_MIGRATIONS_ON_STARTUP=false
|
|
13
|
+
|
|
14
|
+
# Raw HTTP events (local / synthetic stack)
|
|
15
|
+
LUMONOX_EVENT_STORE=duckdb
|
|
16
|
+
LUMONOX_EVENT_PLANE_MODE=duckdb_single_writer
|
|
17
|
+
# Relative paths are anchored to the repo root (parent of backend/), not process cwd.
|
|
18
|
+
# Prefer an absolute path in production. Override anchor with LUMONOX_DATA_DIR if needed.
|
|
19
|
+
# Relative paths anchor to data root (LUMONOX_DATA_DIR / LUMONOX_PROJECT_ROOT, else monorepo root).
|
|
20
|
+
# Prefer an absolute path in production. See resolve_lumonox_data_root in core/config.py.
|
|
21
|
+
LUMONOX_DUCKDB_PATH=.lumonox/events.duckdb
|
|
22
|
+
# Plan B (duckdb_log_shards): append-only shard storage root.
|
|
23
|
+
# Relative paths are anchored to data root (same as LUMONOX_DUCKDB_PATH).
|
|
24
|
+
LUMONOX_EVENT_PLANE_SHARDS_PATH=.lumonox/events-log
|
|
25
|
+
# Plan B compactor snapshot output root.
|
|
26
|
+
LUMONOX_EVENT_PLANE_SNAPSHOTS_PATH=.lumonox/events-duckdb
|
|
27
|
+
# Optional phase-1 cold export (DuckDB -> partitioned Parquet):
|
|
28
|
+
# LUMONOX_PARQUET_EXPORT_ENABLED=true
|
|
29
|
+
# LUMONOX_PARQUET_EXPORT_ROOT=.lumonox/parquet/events
|
|
30
|
+
# LUMONOX_PARQUET_EXPORT_INTERVAL_SECONDS=300
|
|
31
|
+
# LUMONOX_PARQUET_EXPORT_WINDOW_SECONDS=900
|
|
32
|
+
# Optional phase-2 hybrid read path (DuckDB hot + Parquet cold):
|
|
33
|
+
# LUMONOX_PARQUET_QUERY_ENABLED=true
|
|
34
|
+
# LUMONOX_PARQUET_HOT_WINDOW_HOURS=24
|
|
35
|
+
# Optional phase-3 lifecycle (compaction + retention + readability verification):
|
|
36
|
+
# LUMONOX_PARQUET_LIFECYCLE_ENABLED=true
|
|
37
|
+
# LUMONOX_PARQUET_LIFECYCLE_INTERVAL_SECONDS=3600
|
|
38
|
+
# LUMONOX_PARQUET_LIFECYCLE_RETENTION_DAYS=90
|
|
39
|
+
# LUMONOX_PARQUET_LIFECYCLE_DRY_RUN=false
|
|
40
|
+
# LUMONOX_PARQUET_LIFECYCLE_COMPACTION_MIN_FILES=4
|
|
41
|
+
# LUMONOX_PARQUET_LIFECYCLE_VERIFY_SAMPLE_SIZE=5
|
|
42
|
+
# Optional phase-4 object storage snapshots + DR restore:
|
|
43
|
+
# LUMONOX_PARQUET_OBJECT_STORAGE_ENABLED=true
|
|
44
|
+
# s3:// URIs need boto3 (Dockerfile installs backend[parquet-s3]; locally: pip install -e "./backend[parquet-s3]").
|
|
45
|
+
# LUMONOX_PARQUET_OBJECT_STORAGE_URI=s3://my-bucket/lumonox
|
|
46
|
+
# LUMONOX_PARQUET_OBJECT_STORAGE_PREFIX=parquet-events
|
|
47
|
+
# LUMONOX_PARQUET_OBJECT_STORAGE_INTERVAL_SECONDS=900
|
|
48
|
+
# LUMONOX_PARQUET_OBJECT_STORAGE_VERIFY_UPLOAD=true
|
|
49
|
+
# LUMONOX_PARQUET_OBJECT_STORAGE_ENDPOINT_URL=https://s3.us-east-1.amazonaws.com
|
|
50
|
+
# LUMONOX_PARQUET_OBJECT_STORAGE_REGION=us-east-1
|
|
51
|
+
# LUMONOX_PARQUET_OBJECT_STORAGE_ACCESS_KEY_ID=
|
|
52
|
+
# LUMONOX_PARQUET_OBJECT_STORAGE_SECRET_ACCESS_KEY=
|
|
53
|
+
# LUMONOX_PARQUET_OBJECT_STORAGE_SESSION_TOKEN=
|
|
54
|
+
# LUMONOX_PARQUET_OBJECT_STORAGE_RESTORE_ROOT=.lumonox/parquet/restore
|
|
55
|
+
# LUMONOX_PARQUET_OBJECT_STORAGE_RESTORE_MANIFEST_KEY=
|
|
56
|
+
# Optional Plan B knobs (kept conservative by default):
|
|
57
|
+
# LUMONOX_SHARD_MAX_BYTES=134217728
|
|
58
|
+
# LUMONOX_SHARD_MAX_AGE_SECONDS=300
|
|
59
|
+
# LUMONOX_COMPACTOR_INTERVAL_SECONDS=60
|
|
60
|
+
# LUMONOX_COMPACTOR_MAX_CONCURRENCY=1
|
|
61
|
+
# LUMONOX_COMPACTOR_MAX_SHARDS_PER_RUN=1024
|
|
62
|
+
# LUMONOX_COMPACTOR_PUBLISH_TIMEOUT_SECONDS=60
|
|
63
|
+
# LUMONOX_SNAPSHOT_RETENTION_COUNT=3
|
|
64
|
+
# LUMONOX_EVENT_PLANE_BACKPRESSURE_MIN_FREE_BYTES=536870912
|
|
65
|
+
# LUMONOX_EVENT_PLANE_BACKPRESSURE_MIN_FREE_PERCENT=5
|
|
66
|
+
# LUMONOX_EVENT_PLANE_BACKPRESSURE_MAX_PENDING_SHARDS=20000
|
|
67
|
+
# LUMONOX_DATA_DIR=/srv/lumonox
|
|
68
|
+
|
|
69
|
+
# Scheduler defaults:
|
|
70
|
+
# - Local default SQLite filename (.lumonox/lumonox.db) auto-enables scheduler when unset.
|
|
71
|
+
# - Non-default SQLite/Postgres should set JOBS_ENABLE_SCHEDULER=true explicitly (or run external cron jobs).
|
|
72
|
+
# JOBS_ENABLE_SCHEDULER=true
|
|
73
|
+
|
|
74
|
+
# Production topology baseline (recommended explicit values):
|
|
75
|
+
# LUMONOX_ENV=production
|
|
76
|
+
# LUMONOX_EVENT_STORE=duckdb
|
|
77
|
+
# LUMONOX_EVENT_PLANE_MODE=duckdb_single_writer
|
|
78
|
+
# LUMONOX_DUCKDB_SINGLE_WRITER_PROFILE=true # required ack for single-writer DuckDB in production
|
|
79
|
+
# JOBS_ENABLE_SCHEDULER=true
|
|
80
|
+
# DASHBOARD_AUTH_ENABLED=true
|
|
81
|
+
# DASHBOARD_ENFORCE_ORIGIN_FOR_MUTATIONS=true
|
|
82
|
+
# DASHBOARD_REALTIME_BUS_BACKEND=postgres_notify # set "none" for single-replica + sticky WS
|
|
83
|
+
# DATABASE_RUN_MIGRATIONS_ON_STARTUP=false # run one-shot migration before scaling replicas
|
|
84
|
+
|
|
85
|
+
# Dashboard read/query protection for expensive endpoints (set 0 to disable):
|
|
86
|
+
# DASHBOARD_READ_RATE_LIMIT_REQUESTS_PER_WINDOW=120
|
|
87
|
+
# DASHBOARD_READ_RATE_LIMIT_WINDOW_SECONDS=60
|
|
88
|
+
# Optional cross-replica WS propagation on Postgres:
|
|
89
|
+
# DASHBOARD_REALTIME_BUS_BACKEND=postgres_notify
|
|
90
|
+
# DASHBOARD_REALTIME_BUS_CHANNEL=lumonox_dashboard_realtime
|
|
91
|
+
# Optional realtime snapshot + WS delta protocol (fallback remains POST /dashboard/query):
|
|
92
|
+
# LUMONOX_DASHBOARD_REALTIME_ENABLED=false
|
|
93
|
+
# LUMONOX_DASHBOARD_REALTIME_WS_ENABLED=false
|
|
94
|
+
# LUMONOX_DASHBOARD_REALTIME_SNAPSHOT_MAX_PROJECTS=512
|
|
95
|
+
# LUMONOX_DASHBOARD_REALTIME_SNAPSHOT_TTL_SECONDS=900
|
|
96
|
+
# LUMONOX_DASHBOARD_REALTIME_MAX_DELTA_QUEUE_PER_PROJECT=32
|
|
97
|
+
# LUMONOX_DASHBOARD_REALTIME_SNAPSHOT_RECONCILE_INTERVAL_SECONDS=15
|
|
98
|
+
# LUMONOX_DASHBOARD_REALTIME_SNAPSHOT_MAX_DRIFT_VERSIONS=25
|
|
99
|
+
# LUMONOX_DASHBOARD_QUERY_SNAPSHOT_REFRESH_SECONDS=30
|
|
100
|
+
# LUMONOX_DASHBOARD_WS_LIVE_TICK_SECONDS=2
|
|
101
|
+
# NEXT_PUBLIC_LUMONOX_LIVE_DELTA_REFRESH_THROTTLE_MS=800
|
|
102
|
+
# NEXT_PUBLIC_LUMONOX_DASHBOARD_REALTIME_WS_ENABLED=false
|
|
103
|
+
|
|
104
|
+
# Optional dashboard frontend RUM ingest guardrails (`POST /lumonox/rum`):
|
|
105
|
+
# DASHBOARD_RUM_MAX_REQUEST_BYTES=8192
|
|
106
|
+
# DASHBOARD_RUM_LOG_PAYLOADS=false
|
|
107
|
+
|
|
108
|
+
# Browser origins allowed for credentialed dashboard calls (include every host:port you open the UI on).
|
|
109
|
+
# Default in code also allows 8000/8000; override if you use other ports.
|
|
110
|
+
# CORS_ALLOW_ORIGINS=http://127.0.0.1:8000,http://localhost:8000,http://127.0.0.1:8000,http://localhost:8000
|
|
111
|
+
|
|
112
|
+
# Dashboard magic link — use the SAME email you type on the sign-in screen (or use DASHBOARD_ALLOWED_EMAIL_DOMAINS).
|
|
113
|
+
# For local dev without email delivery, set DASHBOARD_AUTH_MAGIC_LINK_DEV_EXPOSE_TOKEN=true to get a link in the UI.
|
|
114
|
+
DASHBOARD_AUTH_ALLOWED_EMAIL=you@example.com
|
|
115
|
+
ALERT_EMAIL_PROVIDER=file
|
|
116
|
+
# Relative paths are anchored to the project/data root (see resolve_lumonox_data_root), not cwd.
|
|
117
|
+
ALERT_EMAIL_FILE_OUTBOX_DIR=./.lumonox/emails
|
|
118
|
+
ALERT_EMAIL_FROM=alerts@example.com
|
|
119
|
+
# DASHBOARD_AUTH_MAGIC_LINK_DEV_EXPOSE_TOKEN=true
|
|
120
|
+
|
|
121
|
+
# Optional: static UI env bundle — often repo-root .env.lumonox for Next builds
|
|
122
|
+
# NEXT_PUBLIC_LUMONOX_API_BASE_URL=/lumonox
|
|
123
|
+
# NEXT_PUBLIC_LUMONOX_API_KEY=…
|
|
124
|
+
|
|
125
|
+
# Optional: custom .env.lumonox path (default ./.env.lumonox)
|
|
126
|
+
# LUMONOX_ENV_FILE=/path/to/.env.lumonox
|
lumonox-0.2.5/.gitignore
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
/lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
MANIFEST
|
|
23
|
+
.venv/
|
|
24
|
+
venv/
|
|
25
|
+
ENV/
|
|
26
|
+
|
|
27
|
+
# uv
|
|
28
|
+
.uv/
|
|
29
|
+
|
|
30
|
+
# Testing / coverage
|
|
31
|
+
.pytest_cache/
|
|
32
|
+
.coverage
|
|
33
|
+
htmlcov/
|
|
34
|
+
.mypy_cache/
|
|
35
|
+
.ruff_cache/
|
|
36
|
+
|
|
37
|
+
# Node / Next.js (frontend)
|
|
38
|
+
node_modules/
|
|
39
|
+
.next/
|
|
40
|
+
out/
|
|
41
|
+
.turbo/
|
|
42
|
+
*.tsbuildinfo
|
|
43
|
+
|
|
44
|
+
# OS / editors
|
|
45
|
+
.DS_Store
|
|
46
|
+
.idea/
|
|
47
|
+
.vscode/
|
|
48
|
+
*.swp
|
|
49
|
+
*.swo
|
|
50
|
+
|
|
51
|
+
# Environment
|
|
52
|
+
.env
|
|
53
|
+
.env.*
|
|
54
|
+
!.env.example
|
|
55
|
+
!.env.lumonox.example
|
|
56
|
+
|
|
57
|
+
# Local SQLite (dev)
|
|
58
|
+
*.db
|
|
59
|
+
|
|
60
|
+
# Logs
|
|
61
|
+
*.log
|
|
62
|
+
|
|
63
|
+
.lumonox/
|
|
64
|
+
# Legacy local data dir name (pre-rename checkouts); never commit.
|
|
65
|
+
.autopulse/
|
|
66
|
+
backend/.autopulse/
|
|
67
|
+
sdk/.autopulse/
|
|
68
|
+
|
|
69
|
+
# Local stray copies of dashboard static export (canonical build is frontend/out)
|
|
70
|
+
sdk/src/lumonox/ui/
|
|
71
|
+
|
|
72
|
+
# Hatch sdist staging for shipped wheels (never commit baked export under src)
|
|
73
|
+
backend/src/lumonox_backend/dashboard_static/
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Alert Delivery Verification Runbook
|
|
2
|
+
|
|
3
|
+
This runbook validates that alert delivery is configured and observable from the dashboard.
|
|
4
|
+
|
|
5
|
+
## 1) Configure sender mode
|
|
6
|
+
|
|
7
|
+
Set one of the minimal sender configurations:
|
|
8
|
+
|
|
9
|
+
- Email provider (recommended first):
|
|
10
|
+
- `ALERT_SENDER_MODE=email`
|
|
11
|
+
- `ALERT_EMAIL_PROVIDER=resend` (or `postmark`)
|
|
12
|
+
- `ALERT_EMAIL_API_KEY=...`
|
|
13
|
+
- `ALERT_EMAIL_FROM=alerts@example.com`
|
|
14
|
+
- Email (zero-config dev outbox; writes `.eml` locally):
|
|
15
|
+
- `ALERT_SENDER_MODE=email`
|
|
16
|
+
- `ALERT_EMAIL_PROVIDER=file`
|
|
17
|
+
- optional: `ALERT_EMAIL_FILE_OUTBOX_DIR=./.lumonox/emails`
|
|
18
|
+
- optional: `ALERT_EMAIL_FROM=alerts@localhost`
|
|
19
|
+
- Email (no SaaS, requires local MTA present):
|
|
20
|
+
- `ALERT_SENDER_MODE=email`
|
|
21
|
+
- `ALERT_EMAIL_PROVIDER=sendmail`
|
|
22
|
+
- optional: `ALERT_SENDMAIL_PATH=/usr/sbin/sendmail`
|
|
23
|
+
- Email (no SaaS, requires SMTP server reachable):
|
|
24
|
+
- `ALERT_SENDER_MODE=email`
|
|
25
|
+
- `ALERT_EMAIL_PROVIDER=smtp`
|
|
26
|
+
- `ALERT_EMAIL_SMTP_HOST=127.0.0.1` (or your SMTP host)
|
|
27
|
+
- optional: `ALERT_EMAIL_SMTP_PORT=25`
|
|
28
|
+
- optional: `ALERT_EMAIL_SMTP_USE_TLS=true`
|
|
29
|
+
- optional: `ALERT_EMAIL_SMTP_USERNAME=...`
|
|
30
|
+
- optional: `ALERT_EMAIL_SMTP_PASSWORD=...`
|
|
31
|
+
- Slack webhook:
|
|
32
|
+
- `ALERT_SENDER_MODE=slack`
|
|
33
|
+
- `ALERT_SLACK_WEBHOOK_URL=...`
|
|
34
|
+
- Discord webhook:
|
|
35
|
+
- `ALERT_SENDER_MODE=discord`
|
|
36
|
+
- `ALERT_DISCORD_WEBHOOK_URL=...`
|
|
37
|
+
- Multi-channel:
|
|
38
|
+
- `ALERT_SENDER_MODE=composite`
|
|
39
|
+
- combine email + Slack and/or Discord vars above.
|
|
40
|
+
|
|
41
|
+
## 2) Trigger one evaluation pass
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv run python -m lumonox_backend.jobs alerts-once
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The command prints the number of successfully sent alerts in that run.
|
|
48
|
+
|
|
49
|
+
## 3) Validate dispatch observability
|
|
50
|
+
|
|
51
|
+
Open the dashboard Alerts page and verify dispatch rows include:
|
|
52
|
+
|
|
53
|
+
- `status` (`sent`, `failed`, or `skipped`)
|
|
54
|
+
- `reason_code` for failures/skips
|
|
55
|
+
- `attempt_count`
|
|
56
|
+
- `delivered_at` (for successful sends)
|
|
57
|
+
- `provider_message_id` when available
|
|
58
|
+
|
|
59
|
+
Use the **Failed only** filter to quickly review actionable delivery failures.
|
lumonox-0.2.5/PKG-INFO
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lumonox
|
|
3
|
+
Version: 0.2.5
|
|
4
|
+
Summary: Lumonox backend ingestion API
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: aiosqlite>=0.22.1
|
|
7
|
+
Requires-Dist: alembic>=1.14.0
|
|
8
|
+
Requires-Dist: asyncpg>=0.30.0
|
|
9
|
+
Requires-Dist: duckdb>=1.1.3
|
|
10
|
+
Requires-Dist: fastapi>=0.115.0
|
|
11
|
+
Requires-Dist: greenlet>=3.0.0
|
|
12
|
+
Requires-Dist: httpx>=0.27.0
|
|
13
|
+
Requires-Dist: psutil>=6.0.0
|
|
14
|
+
Requires-Dist: psycopg[binary]>=3.3.3
|
|
15
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
16
|
+
Requires-Dist: sqlalchemy>=2.0.36
|
|
17
|
+
Requires-Dist: uvicorn>=0.32.0
|
|
18
|
+
Requires-Dist: websockets>=12.0
|
|
19
|
+
Provides-Extra: parquet-s3
|
|
20
|
+
Requires-Dist: boto3>=1.34.0; extra == 'parquet-s3'
|
lumonox-0.2.5/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Lumonox Backend
|
|
2
|
+
|
|
3
|
+
FastAPI backend for ingest, dashboard APIs, auth/session flows, alerts, retention jobs, and realtime updates.
|
|
4
|
+
|
|
5
|
+
Python **import name** is **`lumonox_backend`**. The **distribution / PyPI project name** is **`lumonox`** (API + pre-built dashboard static export bundled in the wheel). Use **`lumonox`** / **`lumonox-sdk`** for installs from PyPI.
|
|
6
|
+
|
|
7
|
+
## What lives here
|
|
8
|
+
|
|
9
|
+
- Ingest API: `POST /ingest` with API-key authentication.
|
|
10
|
+
- Dashboard API: overview, requests, error groups, diagnosis, alerts, settings, log query, organizations.
|
|
11
|
+
- Dashboard auth: magic-link sign-in, cookie sessions, tenant bootstrap endpoint.
|
|
12
|
+
- Background jobs: alert evaluation and retention cleanup.
|
|
13
|
+
- Internal ops endpoints: health/ready and service metrics.
|
|
14
|
+
|
|
15
|
+
## Install outside the monorepo
|
|
16
|
+
|
|
17
|
+
**One line (PyPI, after trusted publishing is enabled):**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install lumonox
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
uv add lumonox
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**One line (Git — always works; pin `main` to a tag or SHA in production):**
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uv add "lumonox @ git+https://github.com/sintimaski/lumonox.git@main#subdirectory=backend"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install "lumonox @ git+https://github.com/sintimaski/lumonox.git@main#subdirectory=backend"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**API + Fast instrumented app in one line:** use the SDK extra (see [sdk/README.md](../sdk/README.md)):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install "lumonox-sdk[stack]"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If Python **3.14** fails to resolve wheels, try **3.12 or 3.13**.
|
|
44
|
+
|
|
45
|
+
### Wheel build (bundled dashboard)
|
|
46
|
+
|
|
47
|
+
The **`lumonox`** sdist/wheel ships the Next static export under **`lumonox_backend/dashboard_static/`** (mounted at **`/lumonox/ui/`** when **`LUMONOX_FRONTEND_STATIC_DIR`** is unset and **`index.html`** exists). Build **`frontend/out`** first (see **`scripts/run_synthetic_stack.sh`** for **`NEXT_PUBLIC_*`** defaults):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
./backend/scripts/package_wheel.sh
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Run locally
|
|
54
|
+
|
|
55
|
+
From repository root:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv sync --group dev
|
|
59
|
+
# Loads backend/.env so retention/cap settings are actually applied.
|
|
60
|
+
uv run uvicorn lumonox_backend.main:app --env-file backend/.env --log-level info
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Backend defaults to `http://localhost:8000`.
|
|
64
|
+
|
|
65
|
+
## Key environment variables
|
|
66
|
+
|
|
67
|
+
- `DATABASE_URL` (default SQLite file: `.lumonox/lumonox.db` under the repo root—same directory tree as DuckDB; see `normalize_database_url` in `core/config.py`)
|
|
68
|
+
- `LUMONOX_EVENT_STORE` (`duckdb` default; set `sqlite` to force legacy SQL event reads)
|
|
69
|
+
- `LUMONOX_DUCKDB_PATH` (DuckDB event store file; relative values anchor under `LUMONOX_DATA_DIR` / `LUMONOX_PROJECT_ROOT`, else monorepo root—see `normalize_event_store_duckdb_path` / `resolve_lumonox_data_root` in `core/config.py`; use absolute paths in production if you prefer)
|
|
70
|
+
- `LUMONOX_DATA_DIR` / `LUMONOX_PROJECT_ROOT` (optional; pins the root for relative DuckDB paths and keeps ingest/dashboard/CLI on one file regardless of shell cwd)
|
|
71
|
+
- `CORS_ALLOW_ORIGINS`
|
|
72
|
+
- `DASHBOARD_AUTH_ALLOWED_EMAIL`
|
|
73
|
+
- `DASHBOARD_AUTH_ALLOW_API_KEY_FALLBACK` (disabled by default; enable only for controlled non-browser flows)
|
|
74
|
+
- `INGEST_MAX_REQUEST_BYTES`
|
|
75
|
+
- `INGEST_RATE_LIMIT_REQUESTS_PER_WINDOW`
|
|
76
|
+
- `INGEST_RATE_LIMIT_WINDOW_SECONDS`
|
|
77
|
+
- `INGEST_DISTRIBUTED_RATE_LIMIT_ENABLED` (enables DB-backed distributed limiter)
|
|
78
|
+
- `INGEST_ASYNC_AGGREGATE_ENABLED` (keeps ingest hot path raw-write-first)
|
|
79
|
+
- `INGEST_ASYNC_AGGREGATE_QUEUE_MAX_SIZE`
|
|
80
|
+
- `INGEST_DROP_LUMONOX_TRAFFIC_FROM_DB` (drops `/lumonox/*`, `/dashboard/*`, and `/ingest` events before persistence)
|
|
81
|
+
- `JOBS_ENABLE_SCHEDULER`
|
|
82
|
+
- `JOBS_RETENTION_INTERVAL_SECONDS` (minimum **5**; periodic `run_retention_cleanup_once` when scheduler or retention-only loop runs)
|
|
83
|
+
- `JOBS_SCHEDULER_LEASE_ENABLED` (prevents duplicate periodic job execution across instances)
|
|
84
|
+
- `JOBS_SCHEDULER_LEASE_TTL_SECONDS`
|
|
85
|
+
- `LUMONOX_ENV_FILE` (optional path to the `.env.lumonox` bundle for local static UI builds; default `./.env.lumonox` at process cwd)
|
|
86
|
+
- `LUMONOX_SQLITE_MAX_DB_FILE_MB` (max SQLite log-store file size in MB; applies to DuckDB or SQLite when capped. For SQLite it includes main + `-wal` + `-shm`; deprecated alias `LUMONOX_EMBEDDED_MAX_DB_SIZE_MB`; default **512** on dev default SQLite filenames when unset)
|
|
87
|
+
- `LUMONOX_RETENTION_PRESSURE_POLL_SECONDS` / `LUMONOX_RETENTION_PRESSURE_MIN_INTERVAL_SECONDS` (SQLite pressure poll; see `core/config.py`)
|
|
88
|
+
|
|
89
|
+
Parquet **object storage** with `LUMONOX_PARQUET_OBJECT_STORAGE_URI=s3://...` needs **`boto3`**. Install the extra from this directory (`uv pip install -e ".[parquet-s3]"`) or add `boto3` to your environment. `file://` URIs do not use `boto3`.
|
|
90
|
+
|
|
91
|
+
See `backend/src/lumonox_backend/core/config.py` and `backend/.env.example` for the complete list and defaults.
|
|
92
|
+
|
|
93
|
+
**Production:** startup applies `validate_deployment_settings` for `LUMONOX_ENV=production`. Follow `docs/ops/PRODUCTION_DEPLOYMENT.md` for enforced HTTPS ingest, internal metrics token, CORS, dashboard session/magic-link TTL, OIDC/magic-link URL schemes, and related constraints. Automated checks: `backend/tests/test_deployment_settings.py`.
|
|
94
|
+
|
|
95
|
+
## Testing (backend)
|
|
96
|
+
|
|
97
|
+
- Unit-style deployment guardrails: `uv run pytest backend/tests/test_deployment_settings.py`
|
|
98
|
+
- Integration tests under `backend/tests/` use `BACKEND_TEST_DATABASE_URL` when set; otherwise `uv run pytest` uses an **isolated session SQLite file** under pytest’s basetemp (see `backend/tests/conftest.py`). Override explicitly, e.g. `export BACKEND_TEST_DATABASE_URL=sqlite+aiosqlite:////tmp/lx-test.db`, when you want a fixed path or Postgres.
|
|
99
|
+
- Ingest idempotency replay parity is required on Postgres (CI enforced). Local equivalent:
|
|
100
|
+
`export BACKEND_TEST_DATABASE_URL=postgresql+asyncpg://lumonox:lumonox@127.0.0.1:5432/lumonox_ci && uv run pytest backend/tests/test_ingest.py::test_ingest_idempotency_key_replays_accepted_without_duplicate_events -q`
|
|
101
|
+
- Backend CI-equivalent one-command gate (same backend checks split across `python-sqlite` + `python-postgres` CI jobs):
|
|
102
|
+
`export BACKEND_TEST_DATABASE_URL=postgresql+asyncpg://lumonox:lumonox@127.0.0.1:5432/lumonox_ci && make check-python-ci`
|
|
103
|
+
|
|
104
|
+
## Retention scheduling (FastAPI-optional)
|
|
105
|
+
|
|
106
|
+
The portable unit of work is **one retention pass** (SQLite caps, time windows, aggregates trim):
|
|
107
|
+
|
|
108
|
+
| How | Command / API |
|
|
109
|
+
|-----|-----------------|
|
|
110
|
+
| **CLI (any OS, no web server)** | `cd backend && uv run python -m lumonox_backend.jobs retention-once` |
|
|
111
|
+
| **Sync Python (cron, Django command, systemd `ExecStart`)** | `from lumonox_backend.jobs import run_retention_sync` then `run_retention_sync()` after setting `DATABASE_URL` in the environment |
|
|
112
|
+
| **In-process (FastAPI)** | Lifespan starts the scheduler or retention-only loop; optional SQLite pressure poll |
|
|
113
|
+
|
|
114
|
+
For **Linux production** or **Django** (no FastAPI event loop), prefer **cron** or **systemd timers** calling the CLI or `run_retention_sync()` on the interval you want, and set `JOBS_ENABLE_SCHEDULER=false` on the API so you do not double-run retention in-process and from cron.
|
|
115
|
+
|
|
116
|
+
## Event store migration helpers
|
|
117
|
+
|
|
118
|
+
- Backfill SQL `events` rows into the DuckDB event store:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
cd backend
|
|
122
|
+
uv run python scripts/backfill_events_to_duckdb.py --batch-size 1000
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Example cron every five minutes:
|
|
126
|
+
|
|
127
|
+
```cron
|
|
128
|
+
*/5 * * * * cd /path/to/lumonox/backend && /path/to/uv run python -m lumonox_backend.jobs retention-once >>/var/log/lumonox-retention.log 2>&1
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Operational runbooks
|
|
132
|
+
|
|
133
|
+
- Alert delivery setup and verification: `backend/ALERT_DELIVERY_RUNBOOK.md`
|
|
134
|
+
- Release/incident drill gates: `docs/runbooks/PHASE5_RELEASE_CHECKLIST.md` and `docs/runbooks/PHASE5_INCIDENT_DRILLS.md`
|
|
135
|
+
|
|
136
|
+
## Scope and constraints
|
|
137
|
+
|
|
138
|
+
Product/architecture source of truth remains `DEVELOPMENT.md`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[alembic]
|
|
2
|
+
script_location = src/lumonox_backend/alembic
|
|
3
|
+
path_separator = os
|
|
4
|
+
prepend_sys_path = ./src
|
|
5
|
+
# Overridden at runtime by alembic/env.py from get_settings() (loads backend/.env).
|
|
6
|
+
# Default app DB is SQLite under .lumonox/; Postgres requires DATABASE_URL in the environment.
|
|
7
|
+
sqlalchemy.url = sqlite:///./.lumonox/lumonox.db
|
|
8
|
+
|
|
9
|
+
[loggers]
|
|
10
|
+
keys = root,sqlalchemy,alembic
|
|
11
|
+
|
|
12
|
+
[handlers]
|
|
13
|
+
keys = console
|
|
14
|
+
|
|
15
|
+
[formatters]
|
|
16
|
+
keys = generic
|
|
17
|
+
|
|
18
|
+
[logger_root]
|
|
19
|
+
level = WARN
|
|
20
|
+
handlers = console
|
|
21
|
+
|
|
22
|
+
[logger_sqlalchemy]
|
|
23
|
+
level = WARN
|
|
24
|
+
handlers =
|
|
25
|
+
qualname = sqlalchemy.engine
|
|
26
|
+
|
|
27
|
+
[logger_alembic]
|
|
28
|
+
level = INFO
|
|
29
|
+
handlers =
|
|
30
|
+
qualname = alembic
|
|
31
|
+
|
|
32
|
+
[handler_console]
|
|
33
|
+
class = StreamHandler
|
|
34
|
+
args = (sys.stderr,)
|
|
35
|
+
level = NOTSET
|
|
36
|
+
formatter = generic
|
|
37
|
+
|
|
38
|
+
[formatter_generic]
|
|
39
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "lumonox"
|
|
3
|
+
version = "0.2.5"
|
|
4
|
+
description = "Lumonox backend ingestion API"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"fastapi>=0.115.0",
|
|
8
|
+
"python-dotenv>=1.0.0",
|
|
9
|
+
"uvicorn>=0.32.0",
|
|
10
|
+
"websockets>=12.0",
|
|
11
|
+
"sqlalchemy>=2.0.36",
|
|
12
|
+
"greenlet>=3.0.0",
|
|
13
|
+
"asyncpg>=0.30.0",
|
|
14
|
+
"psycopg[binary]>=3.3.3",
|
|
15
|
+
"alembic>=1.14.0",
|
|
16
|
+
"aiosqlite>=0.22.1",
|
|
17
|
+
"duckdb>=1.1.3",
|
|
18
|
+
"psutil>=6.0.0",
|
|
19
|
+
"httpx>=0.27.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
# Parquet object storage with ``s3://`` URIs (``parquet_object_storage``); not needed for ``file://``.
|
|
24
|
+
parquet-s3 = [
|
|
25
|
+
"boto3>=1.34.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["hatchling>=1.26.0"]
|
|
30
|
+
build-backend = "hatchling.build"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.sdist]
|
|
33
|
+
# Ship the Next static export inside the extracted tree used for wheels (nested under ``packages``)—avoids editable-install
|
|
34
|
+
# breakage from wheel-only forced paths while keeping ``frontend/out`` out of Git.
|
|
35
|
+
[tool.hatch.build.targets.sdist.force-include]
|
|
36
|
+
"../frontend/out" = "src/lumonox_backend/dashboard_static"
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.targets.wheel]
|
|
39
|
+
packages = ["src/lumonox_backend"]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import asyncio
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import select
|
|
8
|
+
|
|
9
|
+
from lumonox_backend.database import AsyncSessionLocal
|
|
10
|
+
from lumonox_backend.models import Event
|
|
11
|
+
from lumonox_backend.services.duckdb_async import run_duckdb_write_sync
|
|
12
|
+
from lumonox_backend.services.event_store import get_duckdb_event_store
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def run_backfill(*, batch_size: int, since: datetime | None) -> int:
|
|
16
|
+
store = get_duckdb_event_store()
|
|
17
|
+
copied = 0
|
|
18
|
+
last_id = 0
|
|
19
|
+
while True:
|
|
20
|
+
async with AsyncSessionLocal() as session:
|
|
21
|
+
query = (
|
|
22
|
+
select(Event).where(Event.id > last_id).order_by(Event.id.asc()).limit(batch_size)
|
|
23
|
+
)
|
|
24
|
+
if since is not None:
|
|
25
|
+
query = query.where(Event.received_at >= since)
|
|
26
|
+
rows = (await session.scalars(query)).all()
|
|
27
|
+
if not rows:
|
|
28
|
+
break
|
|
29
|
+
payload = [
|
|
30
|
+
{
|
|
31
|
+
"project_id": row.project_id,
|
|
32
|
+
"timestamp": row.timestamp,
|
|
33
|
+
"received_at": row.received_at,
|
|
34
|
+
"sdk_version": row.sdk_version,
|
|
35
|
+
"type": row.type,
|
|
36
|
+
"service_name": row.service_name,
|
|
37
|
+
"environment": row.environment,
|
|
38
|
+
"method": row.method,
|
|
39
|
+
"path": row.path,
|
|
40
|
+
"status_code": row.status_code,
|
|
41
|
+
"latency_ms": row.latency_ms,
|
|
42
|
+
"payload": row.payload,
|
|
43
|
+
"request_id": row.request_id,
|
|
44
|
+
}
|
|
45
|
+
for row in rows
|
|
46
|
+
]
|
|
47
|
+
await run_duckdb_write_sync(store.insert_rows, payload)
|
|
48
|
+
copied += len(rows)
|
|
49
|
+
last_id = int(rows[-1].id)
|
|
50
|
+
return copied
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def parse_args() -> argparse.Namespace:
|
|
54
|
+
parser = argparse.ArgumentParser(description="Backfill SQL events into DuckDB event store")
|
|
55
|
+
parser.add_argument("--batch-size", type=int, default=1000)
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"--since",
|
|
58
|
+
type=str,
|
|
59
|
+
default="",
|
|
60
|
+
help="Optional ISO timestamp, e.g. 2026-04-01T00:00:00Z",
|
|
61
|
+
)
|
|
62
|
+
return parser.parse_args()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _parse_since(raw: str) -> datetime | None:
|
|
66
|
+
value = raw.strip()
|
|
67
|
+
if not value:
|
|
68
|
+
return None
|
|
69
|
+
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
70
|
+
if parsed.tzinfo is None:
|
|
71
|
+
return parsed.replace(tzinfo=UTC)
|
|
72
|
+
return parsed.astimezone(UTC)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
args = parse_args()
|
|
77
|
+
total = asyncio.run(
|
|
78
|
+
run_backfill(
|
|
79
|
+
batch_size=max(1, int(args.batch_size)),
|
|
80
|
+
since=_parse_since(args.since),
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
print(f"backfilled_events={total}")
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import asyncio
|
|
5
|
+
import sqlite3
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from sqlalchemy import func, select
|
|
10
|
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
|
11
|
+
|
|
12
|
+
from lumonox_backend.core.config import get_settings
|
|
13
|
+
from lumonox_backend.database import get_engine
|
|
14
|
+
from lumonox_backend.jobs import run_retention_once
|
|
15
|
+
from lumonox_backend.maintenance.retention import (
|
|
16
|
+
_resolve_sqlite_db_path,
|
|
17
|
+
_sqlite_db_disk_footprint_bytes,
|
|
18
|
+
sqlite_retention_pressure_pending,
|
|
19
|
+
)
|
|
20
|
+
from lumonox_backend.models import Event
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _fmt_mb(value: int) -> str:
|
|
24
|
+
return f"{value / (1024 * 1024):.2f}MB"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _sqlite_vacuum_probe(db_path: Path) -> tuple[bool, str]:
|
|
28
|
+
try:
|
|
29
|
+
with sqlite3.connect(str(db_path), timeout=2.0) as connection:
|
|
30
|
+
connection.execute("PRAGMA busy_timeout=2000")
|
|
31
|
+
connection.execute("PRAGMA wal_checkpoint(TRUNCATE)")
|
|
32
|
+
connection.execute("VACUUM")
|
|
33
|
+
return True, "ok"
|
|
34
|
+
except sqlite3.OperationalError as exc:
|
|
35
|
+
return False, str(exc)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def _run(iterations: int, sleep_seconds: float, dispose_engine_after: bool) -> None:
|
|
39
|
+
settings = get_settings()
|
|
40
|
+
db_path = _resolve_sqlite_db_path(settings.database_url)
|
|
41
|
+
if db_path is None:
|
|
42
|
+
raise SystemExit(f"DATABASE_URL is not file-backed sqlite: {settings.database_url}")
|
|
43
|
+
|
|
44
|
+
engine = get_engine(settings.database_url)
|
|
45
|
+
session_maker = async_sessionmaker(bind=engine, expire_on_commit=False, class_=AsyncSession)
|
|
46
|
+
print(f"db={db_path}")
|
|
47
|
+
print(
|
|
48
|
+
f"cap_mb={settings.sqlite_max_db_file_mb} "
|
|
49
|
+
f"interval={settings.jobs_retention_interval_seconds} "
|
|
50
|
+
f"pressure_poll={settings.retention_pressure_poll_seconds}"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
for i in range(iterations):
|
|
54
|
+
async with session_maker() as session:
|
|
55
|
+
pending = await sqlite_retention_pressure_pending(session, settings)
|
|
56
|
+
event_count = int((await session.execute(select(func.count(Event.id)))).scalar_one())
|
|
57
|
+
before = _sqlite_db_disk_footprint_bytes(db_path)
|
|
58
|
+
start = time.monotonic()
|
|
59
|
+
deleted = await run_retention_once(
|
|
60
|
+
settings=settings, dispose_engine_after=dispose_engine_after
|
|
61
|
+
)
|
|
62
|
+
elapsed_ms = int((time.monotonic() - start) * 1000)
|
|
63
|
+
after = _sqlite_db_disk_footprint_bytes(db_path)
|
|
64
|
+
probe_ok, probe_msg = _sqlite_vacuum_probe(db_path)
|
|
65
|
+
print(
|
|
66
|
+
f"[{i + 1}/{iterations}] pending={pending} events={event_count} deleted={deleted} "
|
|
67
|
+
f"before={_fmt_mb(before)} after={_fmt_mb(after)} elapsed_ms={elapsed_ms} "
|
|
68
|
+
f"vacuum_probe={'ok' if probe_ok else 'locked'} ({probe_msg})"
|
|
69
|
+
)
|
|
70
|
+
if i < iterations - 1:
|
|
71
|
+
await asyncio.sleep(sleep_seconds)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def main() -> int:
|
|
75
|
+
parser = argparse.ArgumentParser(description="Debug scheduled retention + VACUUM behavior.")
|
|
76
|
+
parser.add_argument(
|
|
77
|
+
"--iterations", type=int, default=8, help="How many retention passes to run."
|
|
78
|
+
)
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--sleep",
|
|
81
|
+
type=float,
|
|
82
|
+
default=2.0,
|
|
83
|
+
help="Sleep seconds between passes.",
|
|
84
|
+
)
|
|
85
|
+
parser.add_argument(
|
|
86
|
+
"--dispose-engine-after",
|
|
87
|
+
action=argparse.BooleanOptionalAction,
|
|
88
|
+
default=True,
|
|
89
|
+
help="Dispose shared async engine before VACUUM (default: true).",
|
|
90
|
+
)
|
|
91
|
+
args = parser.parse_args()
|
|
92
|
+
asyncio.run(_run(args.iterations, args.sleep, args.dispose_engine_after))
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if __name__ == "__main__":
|
|
97
|
+
raise SystemExit(main())
|