railwatch 0.9.4 → 1.0.0
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.
- checksums.yaml +4 -4
- data/AGENTS.md +37 -33
- data/CHANGELOG.md +50 -0
- data/README.md +51 -60
- data/config/routes.rb +0 -59
- data/docs/ai-and-mcp.md +9 -8
- data/docs/configuration.md +35 -66
- data/docs/faq.md +17 -25
- data/docs/getting-started.md +105 -117
- data/docs/replacing-nightwatch.md +8 -7
- data/docs/replacing-sentry.md +12 -16
- data/docs/security.md +14 -20
- data/docs/self-hosting.md +24 -22
- data/docs/testing.md +6 -14
- data/docs/troubleshooting.md +12 -46
- data/lib/generators/railwatch/install/install_generator.rb +12 -240
- data/lib/generators/railwatch/install/templates/initializer.rb.tt +10 -40
- data/lib/generators/railwatch/install/templates/post-deploy +0 -7
- data/lib/railwatch/authentication.rb +6 -5
- data/lib/railwatch/configuration.rb +21 -151
- data/lib/railwatch/engine.rb +9 -118
- data/lib/railwatch/patches.rb +0 -11
- data/lib/railwatch/reporter.rb +3 -19
- data/lib/railwatch/subscribers/process_info.rb +1 -2
- data/lib/railwatch/version.rb +1 -1
- data/lib/railwatch.rb +4 -62
- data/lib/tasks/railwatch_tasks.rake +3 -325
- data/llms.txt +23 -25
- metadata +3 -453
- data/app/channels/railwatch/environment_channel.rb +0 -62
- data/app/controllers/concerns/railwatch/telemetry_identity.rb +0 -25
- data/app/controllers/railwatch/alerts_controller.rb +0 -48
- data/app/controllers/railwatch/anomaly_rules_controller.rb +0 -31
- data/app/controllers/railwatch/attachments_controller.rb +0 -22
- data/app/controllers/railwatch/broadcasts_controller.rb +0 -15
- data/app/controllers/railwatch/cache_events_controller.rb +0 -30
- data/app/controllers/railwatch/commands_controller.rb +0 -16
- data/app/controllers/railwatch/comments_controller.rb +0 -11
- data/app/controllers/railwatch/dashboard_controller.rb +0 -70
- data/app/controllers/railwatch/deploys_controller.rb +0 -34
- data/app/controllers/railwatch/deprecations_controller.rb +0 -54
- data/app/controllers/railwatch/environment_scoped.rb +0 -81
- data/app/controllers/railwatch/exceptions_controller.rb +0 -52
- data/app/controllers/railwatch/executions_controller.rb +0 -13
- data/app/controllers/railwatch/issues_controller.rb +0 -258
- data/app/controllers/railwatch/jobs_controller.rb +0 -63
- data/app/controllers/railwatch/llm_calls_controller.rb +0 -124
- data/app/controllers/railwatch/logs_controller.rb +0 -40
- data/app/controllers/railwatch/mails_controller.rb +0 -19
- data/app/controllers/railwatch/notifications_controller.rb +0 -11
- data/app/controllers/railwatch/outgoing_requests_controller.rb +0 -19
- data/app/controllers/railwatch/overview_controller.rb +0 -38
- data/app/controllers/railwatch/people_controller.rb +0 -25
- data/app/controllers/railwatch/processes_controller.rb +0 -33
- data/app/controllers/railwatch/profiles_controller.rb +0 -65
- data/app/controllers/railwatch/queries_controller.rb +0 -59
- data/app/controllers/railwatch/releases_controller.rb +0 -75
- data/app/controllers/railwatch/requests_controller.rb +0 -37
- data/app/controllers/railwatch/saved_views_controller.rb +0 -53
- data/app/controllers/railwatch/scheduled_tasks_controller.rb +0 -45
- data/app/controllers/railwatch/spans_controller.rb +0 -47
- data/app/controllers/railwatch/storage_ops_controller.rb +0 -15
- data/app/controllers/railwatch/tenants_controller.rb +0 -44
- data/app/controllers/railwatch/thresholds_controller.rb +0 -47
- data/app/controllers/railwatch/traces_controller.rb +0 -72
- data/app/controllers/railwatch/transactions_controller.rb +0 -21
- data/app/controllers/railwatch/view_renders_controller.rb +0 -28
- data/app/controllers/railwatch/visits_controller.rb +0 -53
- data/app/frontend/components/app-content.tsx +0 -26
- data/app/frontend/components/app-logo-icon.tsx +0 -23
- data/app/frontend/components/app-logo.tsx +0 -14
- data/app/frontend/components/app-shell.tsx +0 -38
- data/app/frontend/components/app-sidebar-header.tsx +0 -54
- data/app/frontend/components/app-sidebar.tsx +0 -94
- data/app/frontend/components/app-wordmark.tsx +0 -29
- data/app/frontend/components/breadcrumbs.tsx +0 -46
- data/app/frontend/components/icon.tsx +0 -16
- data/app/frontend/components/input-error.tsx +0 -18
- data/app/frontend/components/nav-footer.tsx +0 -46
- data/app/frontend/components/nav-main.tsx +0 -41
- data/app/frontend/components/nav-user.tsx +0 -50
- data/app/frontend/components/railwatch/chart-hover.tsx +0 -34
- data/app/frontend/components/railwatch/chart-panel.tsx +0 -170
- data/app/frontend/components/railwatch/code.tsx +0 -28
- data/app/frontend/components/railwatch/command-palette.tsx +0 -132
- data/app/frontend/components/railwatch/copy-block.tsx +0 -52
- data/app/frontend/components/railwatch/copy-id.tsx +0 -49
- data/app/frontend/components/railwatch/cursor-load-more.tsx +0 -39
- data/app/frontend/components/railwatch/data-table.tsx +0 -195
- data/app/frontend/components/railwatch/digit-roll.tsx +0 -312
- data/app/frontend/components/railwatch/empty-state.tsx +0 -159
- data/app/frontend/components/railwatch/env-nav.tsx +0 -130
- data/app/frontend/components/railwatch/env-switcher.tsx +0 -145
- data/app/frontend/components/railwatch/filter-bar.tsx +0 -153
- data/app/frontend/components/railwatch/flamegraph.tsx +0 -324
- data/app/frontend/components/railwatch/frames.tsx +0 -295
- data/app/frontend/components/railwatch/json-viewer.tsx +0 -159
- data/app/frontend/components/railwatch/live-dot.tsx +0 -78
- data/app/frontend/components/railwatch/live-toggle.tsx +0 -76
- data/app/frontend/components/railwatch/metric-chart.tsx +0 -112
- data/app/frontend/components/railwatch/mobile-page-switcher.tsx +0 -89
- data/app/frontend/components/railwatch/onboarding.tsx +0 -91
- data/app/frontend/components/railwatch/origin-identity.tsx +0 -84
- data/app/frontend/components/railwatch/page-header.tsx +0 -42
- data/app/frontend/components/railwatch/percentile-picker.tsx +0 -18
- data/app/frontend/components/railwatch/relative-time.tsx +0 -18
- data/app/frontend/components/railwatch/saved-views.tsx +0 -232
- data/app/frontend/components/railwatch/segmented.tsx +0 -64
- data/app/frontend/components/railwatch/series-chart.tsx +0 -400
- data/app/frontend/components/railwatch/shortcuts-dialog.tsx +0 -89
- data/app/frontend/components/railwatch/signal-toast.tsx +0 -121
- data/app/frontend/components/railwatch/sort-header.tsx +0 -37
- data/app/frontend/components/railwatch/source-link.tsx +0 -57
- data/app/frontend/components/railwatch/sparkline-cell.tsx +0 -14
- data/app/frontend/components/railwatch/sql-block.tsx +0 -116
- data/app/frontend/components/railwatch/stat.tsx +0 -185
- data/app/frontend/components/railwatch/status-badge.tsx +0 -79
- data/app/frontend/components/railwatch/step-picker.tsx +0 -57
- data/app/frontend/components/railwatch/timeline.tsx +0 -405
- data/app/frontend/components/railwatch/utilisation-bar.tsx +0 -44
- data/app/frontend/components/railwatch/window-picker.tsx +0 -147
- data/app/frontend/components/ui/avatar.tsx +0 -51
- data/app/frontend/components/ui/badge.tsx +0 -46
- data/app/frontend/components/ui/breadcrumb.tsx +0 -109
- data/app/frontend/components/ui/button.tsx +0 -60
- data/app/frontend/components/ui/card.tsx +0 -92
- data/app/frontend/components/ui/chart.tsx +0 -374
- data/app/frontend/components/ui/checkbox.tsx +0 -32
- data/app/frontend/components/ui/collapsible.tsx +0 -31
- data/app/frontend/components/ui/command.tsx +0 -182
- data/app/frontend/components/ui/dialog.tsx +0 -158
- data/app/frontend/components/ui/dropdown-menu.tsx +0 -255
- data/app/frontend/components/ui/hover-card.tsx +0 -42
- data/app/frontend/components/ui/input.tsx +0 -21
- data/app/frontend/components/ui/label.tsx +0 -24
- data/app/frontend/components/ui/popover.tsx +0 -87
- data/app/frontend/components/ui/select.tsx +0 -185
- data/app/frontend/components/ui/separator.tsx +0 -28
- data/app/frontend/components/ui/sheet.tsx +0 -137
- data/app/frontend/components/ui/sidebar.tsx +0 -726
- data/app/frontend/components/ui/skeleton.tsx +0 -13
- data/app/frontend/components/ui/sonner.tsx +0 -41
- data/app/frontend/components/ui/table.tsx +0 -114
- data/app/frontend/components/ui/tabs.tsx +0 -91
- data/app/frontend/components/ui/textarea.tsx +0 -18
- data/app/frontend/components/ui/tooltip.tsx +0 -61
- data/app/frontend/components/user-info.tsx +0 -32
- data/app/frontend/components/user-menu-content.tsx +0 -94
- data/app/frontend/entrypoints/application.css +0 -244
- data/app/frontend/entrypoints/inertia.tsx +0 -92
- data/app/frontend/fonts/CommitMono-Bold.woff2 +0 -0
- data/app/frontend/fonts/CommitMono-Regular.woff2 +0 -0
- data/app/frontend/fonts/Roboto-Black.woff2 +0 -0
- data/app/frontend/fonts/Roboto-Bold.woff2 +0 -0
- data/app/frontend/fonts/Roboto-Medium.woff2 +0 -0
- data/app/frontend/fonts/Roboto-Regular.woff2 +0 -0
- data/app/frontend/hooks/use-appearance.tsx +0 -67
- data/app/frontend/hooks/use-clipboard.ts +0 -32
- data/app/frontend/hooks/use-flash.tsx +0 -23
- data/app/frontend/hooks/use-hotkeys.ts +0 -63
- data/app/frontend/hooks/use-initials.tsx +0 -15
- data/app/frontend/hooks/use-live.ts +0 -205
- data/app/frontend/hooks/use-mobile-navigation.ts +0 -8
- data/app/frontend/hooks/use-mobile.ts +0 -21
- data/app/frontend/hooks/use-percentile.ts +0 -24
- data/app/frontend/hooks/use-reduced-motion.ts +0 -25
- data/app/frontend/hooks/use-row-nav.ts +0 -106
- data/app/frontend/hooks/use-step.ts +0 -27
- data/app/frontend/hooks/use-ticker.ts +0 -12
- data/app/frontend/hooks/use-window.ts +0 -58
- data/app/frontend/layouts/app/app-sidebar-layout.tsx +0 -107
- data/app/frontend/layouts/app-layout.tsx +0 -21
- data/app/frontend/layouts/env-layout.tsx +0 -53
- data/app/frontend/layouts/persistent-layout.tsx +0 -18
- data/app/frontend/lib/cable.ts +0 -10
- data/app/frontend/lib/env-pages.ts +0 -236
- data/app/frontend/lib/execution-path.ts +0 -55
- data/app/frontend/lib/explain.ts +0 -50
- data/app/frontend/lib/filter.ts +0 -25
- data/app/frontend/lib/flamegraph.ts +0 -155
- data/app/frontend/lib/format.ts +0 -71
- data/app/frontend/lib/keyboard.ts +0 -18
- data/app/frontend/lib/mount.ts +0 -23
- data/app/frontend/lib/source-link.ts +0 -217
- data/app/frontend/lib/utils.ts +0 -6
- data/app/frontend/pages/alerts/index.tsx +0 -171
- data/app/frontend/pages/broadcasts/index.tsx +0 -171
- data/app/frontend/pages/cache_events/index.tsx +0 -127
- data/app/frontend/pages/commands/index.tsx +0 -143
- data/app/frontend/pages/deploys/index.tsx +0 -94
- data/app/frontend/pages/deploys/show.tsx +0 -309
- data/app/frontend/pages/deprecations/index.tsx +0 -176
- data/app/frontend/pages/exceptions/index.tsx +0 -271
- data/app/frontend/pages/executions/show.tsx +0 -821
- data/app/frontend/pages/issues/index.tsx +0 -399
- data/app/frontend/pages/issues/show.tsx +0 -1367
- data/app/frontend/pages/jobs/index.tsx +0 -381
- data/app/frontend/pages/jobs/klass.tsx +0 -215
- data/app/frontend/pages/llm_calls/index.tsx +0 -664
- data/app/frontend/pages/logs/index.tsx +0 -303
- data/app/frontend/pages/mails/index.tsx +0 -218
- data/app/frontend/pages/notifications/index.tsx +0 -161
- data/app/frontend/pages/outgoing_requests/index.tsx +0 -309
- data/app/frontend/pages/overview/show.tsx +0 -333
- data/app/frontend/pages/people/index.tsx +0 -102
- data/app/frontend/pages/people/show.tsx +0 -149
- data/app/frontend/pages/processes/index.tsx +0 -335
- data/app/frontend/pages/profiles/index.tsx +0 -110
- data/app/frontend/pages/profiles/show.tsx +0 -105
- data/app/frontend/pages/queries/index.tsx +0 -492
- data/app/frontend/pages/queries/show.tsx +0 -276
- data/app/frontend/pages/releases/index.tsx +0 -190
- data/app/frontend/pages/releases/release-health.tsx +0 -68
- data/app/frontend/pages/releases/release-path.ts +0 -25
- data/app/frontend/pages/releases/show.tsx +0 -262
- data/app/frontend/pages/requests/index.tsx +0 -235
- data/app/frontend/pages/requests/route.tsx +0 -159
- data/app/frontend/pages/scheduled_tasks/index.tsx +0 -220
- data/app/frontend/pages/spans/index.tsx +0 -229
- data/app/frontend/pages/spans/show.tsx +0 -220
- data/app/frontend/pages/storage_ops/index.tsx +0 -150
- data/app/frontend/pages/tenants/index.tsx +0 -236
- data/app/frontend/pages/tenants/show.tsx +0 -477
- data/app/frontend/pages/tenants/tenant-path.ts +0 -26
- data/app/frontend/pages/thresholds/index.tsx +0 -414
- data/app/frontend/pages/traces/show.tsx +0 -208
- data/app/frontend/pages/transactions/index.tsx +0 -220
- data/app/frontend/pages/view_renders/index.tsx +0 -241
- data/app/frontend/pages/visits/index.tsx +0 -321
- data/app/frontend/public/icon.png +0 -0
- data/app/frontend/public/icon.svg +0 -5
- data/app/frontend/routes/index.d.ts +0 -2136
- data/app/frontend/routes/index.js +0 -2112
- data/app/frontend/types/globals.d.ts +0 -9
- data/app/frontend/types/index.ts +0 -288
- data/app/frontend/types/vite-env.d.ts +0 -9
- data/app/helpers/railwatch/assets_helper.rb +0 -52
- data/app/jobs/railwatch/anomaly_scan_job.rb +0 -11
- data/app/jobs/railwatch/application_job.rb +0 -7
- data/app/jobs/railwatch/auto_resolve_issues_job.rb +0 -19
- data/app/jobs/railwatch/check_scheduled_tasks_job.rb +0 -113
- data/app/jobs/railwatch/detect_anomalies_job.rb +0 -171
- data/app/jobs/railwatch/detect_performance_issues_job.rb +0 -95
- data/app/jobs/railwatch/group_exceptions_job.rb +0 -91
- data/app/jobs/railwatch/optimize_telemetry_job.rb +0 -23
- data/app/jobs/railwatch/performance_scan_job.rb +0 -11
- data/app/jobs/railwatch/prune_telemetry_job.rb +0 -129
- data/app/jobs/railwatch/release_health_rollup_job.rb +0 -64
- data/app/jobs/railwatch/rollup_catchup_job.rb +0 -21
- data/app/jobs/railwatch/rollup_job.rb +0 -132
- data/app/jobs/railwatch/scheduled_task_scan_job.rb +0 -11
- data/app/models/concerns/railwatch/detection_snapshotting.rb +0 -20
- data/app/models/railwatch/alert.rb +0 -229
- data/app/models/railwatch/alert_rule.rb +0 -121
- data/app/models/railwatch/anomaly_rule.rb +0 -51
- data/app/models/railwatch/application.rb +0 -44
- data/app/models/railwatch/application_record.rb +0 -33
- data/app/models/railwatch/comment.rb +0 -36
- data/app/models/railwatch/deploy.rb +0 -67
- data/app/models/railwatch/environment.rb +0 -68
- data/app/models/railwatch/execution_presenter.rb +0 -185
- data/app/models/railwatch/filter_query.rb +0 -184
- data/app/models/railwatch/followup_receipt.rb +0 -27
- data/app/models/railwatch/ingest/batch.rb +0 -355
- data/app/models/railwatch/ingest/mapper.rb +0 -585
- data/app/models/railwatch/ingest/payload.rb +0 -96
- data/app/models/railwatch/ingest/rollup_absorber.rb +0 -170
- data/app/models/railwatch/ingest/writer.rb +0 -137
- data/app/models/railwatch/issue.rb +0 -277
- data/app/models/railwatch/issue_activity.rb +0 -23
- data/app/models/railwatch/issue_detection_presenter.rb +0 -309
- data/app/models/railwatch/issue_detection_snapshot.rb +0 -77
- data/app/models/railwatch/maintenance_task.rb +0 -53
- data/app/models/railwatch/saved_view.rb +0 -63
- data/app/models/railwatch/telemetry/aggregations.rb +0 -251
- data/app/models/railwatch/telemetry/attachment.rb +0 -31
- data/app/models/railwatch/telemetry/bounded_gzip.rb +0 -101
- data/app/models/railwatch/telemetry/broadcast.rb +0 -13
- data/app/models/railwatch/telemetry/cache_event.rb +0 -16
- data/app/models/railwatch/telemetry/child.rb +0 -53
- data/app/models/railwatch/telemetry/cursor_page.rb +0 -99
- data/app/models/railwatch/telemetry/deprecation.rb +0 -13
- data/app/models/railwatch/telemetry/enqueued_job.rb +0 -13
- data/app/models/railwatch/telemetry/exception.rb +0 -28
- data/app/models/railwatch/telemetry/execution.rb +0 -152
- data/app/models/railwatch/telemetry/export_delivery.rb +0 -32
- data/app/models/railwatch/telemetry/export_destination.rb +0 -66
- data/app/models/railwatch/telemetry/health_sample.rb +0 -72
- data/app/models/railwatch/telemetry/ingest_batch.rb +0 -31
- data/app/models/railwatch/telemetry/llm_call.rb +0 -37
- data/app/models/railwatch/telemetry/log.rb +0 -91
- data/app/models/railwatch/telemetry/mail.rb +0 -13
- data/app/models/railwatch/telemetry/n_plus_one.rb +0 -92
- data/app/models/railwatch/telemetry/notification.rb +0 -13
- data/app/models/railwatch/telemetry/outgoing_request.rb +0 -13
- data/app/models/railwatch/telemetry/person.rb +0 -50
- data/app/models/railwatch/telemetry/process.rb +0 -10
- data/app/models/railwatch/telemetry/profile.rb +0 -37
- data/app/models/railwatch/telemetry/query.rb +0 -57
- data/app/models/railwatch/telemetry/query_shape.rb +0 -45
- data/app/models/railwatch/telemetry/release_health.rb +0 -69
- data/app/models/railwatch/telemetry/rollup.rb +0 -157
- data/app/models/railwatch/telemetry/session.rb +0 -24
- data/app/models/railwatch/telemetry/span.rb +0 -19
- data/app/models/railwatch/telemetry/storage_op.rb +0 -13
- data/app/models/railwatch/telemetry/tenant.rb +0 -240
- data/app/models/railwatch/telemetry/transaction.rb +0 -13
- data/app/models/railwatch/telemetry/view_render.rb +0 -13
- data/app/models/railwatch/telemetry/visit.rb +0 -24
- data/app/models/railwatch/telemetry_record.rb +0 -107
- data/app/models/railwatch/threshold.rb +0 -66
- data/app/models/railwatch/user.rb +0 -47
- data/app/models/railwatch/viewer.rb +0 -13
- data/app/models/railwatch/window.rb +0 -66
- data/app/views/layouts/railwatch/dashboard.html.erb +0 -25
- data/db/railwatch_migrate/20260916000000_create_railwatch_tables.rb +0 -151
- data/db/railwatch_migrate/20260917000000_create_railwatch_maintenance_tasks.rb +0 -18
- data/db/railwatch_migrate/20260917120000_create_railwatch_followup_receipts.rb +0 -20
- data/db/railwatch_migrate/20260918120000_widen_host_user_ids.rb +0 -71
- data/db/railwatch_telemetry_migrate/20260903000000_enable_incremental_vacuum.rb +0 -62
- data/db/railwatch_telemetry_migrate/20260903000001_create_telemetry.rb +0 -481
- data/db/railwatch_telemetry_migrate/20260903000002_rename_tenant_to_app_tenant.rb +0 -14
- data/db/railwatch_telemetry_migrate/20260903000003_add_statement_count_to_transactions.rb +0 -9
- data/db/railwatch_telemetry_migrate/20260903000004_add_role_and_channel.rb +0 -10
- data/db/railwatch_telemetry_migrate/20260903000005_add_locals_to_exceptions.rb +0 -9
- data/db/railwatch_telemetry_migrate/20260903000006_add_spans_health_vitals_and_fts.rb +0 -82
- data/db/railwatch_telemetry_migrate/20260903000007_rename_span_attributes_to_payload.rb +0 -10
- data/db/railwatch_telemetry_migrate/20260903000008_add_profiles_and_attachments.rb +0 -60
- data/db/railwatch_telemetry_migrate/20260903000009_add_truncated_to_attachments.rb +0 -9
- data/db/railwatch_telemetry_migrate/20260903000010_create_sessions_and_release_health.rb +0 -51
- data/db/railwatch_telemetry_migrate/20260903000011_add_fingerprint_to_exceptions.rb +0 -11
- data/db/railwatch_telemetry_migrate/20260904000012_add_failed_to_broadcasts.rb +0 -10
- data/db/railwatch_telemetry_migrate/20260904010000_add_filter_cursor_indexes.rb +0 -37
- data/db/railwatch_telemetry_migrate/20260904020000_add_n_plus_ones_execution_id_index.rb +0 -11
- data/db/railwatch_telemetry_migrate/20260904120000_create_query_shapes.rb +0 -15
- data/db/railwatch_telemetry_migrate/20260906120000_add_backpressure_factor_to_ingest_batches.rb +0 -9
- data/db/railwatch_telemetry_migrate/20260907000000_rename_lantern_version_on_processes.rb +0 -10
- data/db/railwatch_telemetry_migrate/20260913000000_rename_nightrail_version_on_processes.rb +0 -9
- data/db/railwatch_telemetry_migrate/20260914000000_drop_orphan_durable_ingest_tables.rb +0 -18
- data/db/railwatch_telemetry_migrate/20260914010000_drop_orphan_durable_ingest_columns.rb +0 -25
- data/db/railwatch_telemetry_migrate/20260915000000_create_llm_calls.rb +0 -55
- data/db/railwatch_telemetry_migrate/20260915120000_add_detail_to_llm_calls.rb +0 -21
- data/db/railwatch_telemetry_migrate/20260915200000_add_explained_index_to_queries.rb +0 -16
- data/db/railwatch_telemetry_migrate/20260915210000_add_slowest_index_to_queries.rb +0 -18
- data/db/railwatch_telemetry_migrate/20260917010000_add_batch_ledger_to_ingest_batches.rb +0 -16
- data/db/railwatch_telemetry_migrate/20260919000100_create_export_queue.rb +0 -146
- data/db/railwatch_telemetry_migrate/20260922000000_add_covering_indexes_for_dashboard_aggregates.rb +0 -34
- data/db/railwatch_telemetry_migrate/20260923000000_add_people_index.rb +0 -15
- data/db/railwatch_telemetry_migrate/20260925000000_add_tenant_summary_index.rb +0 -17
- data/db/railwatch_telemetry_migrate/20261008000000_add_in_transaction_to_outgoing_requests.rb +0 -10
- data/docs/embedded.md +0 -503
- data/lib/puma/plugin/railwatch.rb +0 -215
- data/lib/railwatch/dashboard_assets.rb +0 -45
- data/lib/railwatch/embedded.rb +0 -50
- data/lib/railwatch/export/client.rb +0 -124
- data/lib/railwatch/export/lease.rb +0 -56
- data/lib/railwatch/export/outbox.rb +0 -361
- data/lib/railwatch/export/policy.rb +0 -79
- data/lib/railwatch/export/sender.rb +0 -130
- data/lib/railwatch/ingest_request_body_limit.rb +0 -10
- data/lib/railwatch/maintenance.rb +0 -194
- data/lib/railwatch/patches/migration_busy_timeout.rb +0 -73
- data/lib/railwatch/transport/local.rb +0 -78
- data/lib/railwatch/transport/socket.rb +0 -183
- data/lib/railwatch/writer.rb +0 -370
- data/public/railwatch/assets/CommitMono-Bold-D6h61ieg.woff2 +0 -0
- data/public/railwatch/assets/CommitMono-Regular-zr8w7Obm.woff2 +0 -0
- data/public/railwatch/assets/Roboto-Black-auA4GeOK.woff2 +0 -0
- data/public/railwatch/assets/Roboto-Bold-CJLnO8j1.woff2 +0 -0
- data/public/railwatch/assets/Roboto-Medium-Cm2bwKpj.woff2 +0 -0
- data/public/railwatch/assets/Roboto-Regular-Chaq1-PV.woff2 +0 -0
- data/public/railwatch/assets/app-layout-by7G3sa1.js +0 -41
- data/public/railwatch/assets/application-Cc5v_Vk9.css +0 -1
- data/public/railwatch/assets/arrow-up-DCZ7emux.js +0 -1
- data/public/railwatch/assets/braces-CykT5_5u.js +0 -1
- data/public/railwatch/assets/card-C-PPSA0g.js +0 -1
- data/public/railwatch/assets/chart-Cov79YC5.js +0 -39
- data/public/railwatch/assets/chart-hover-DdK4E-2h.js +0 -1
- data/public/railwatch/assets/chart-panel-BPaYRXJB.js +0 -1
- data/public/railwatch/assets/code-BYjC4YZ_.js +0 -1
- data/public/railwatch/assets/copy-id-LPfoj9cC.js +0 -1
- data/public/railwatch/assets/cursor-load-more-DSdhgpGl.js +0 -1
- data/public/railwatch/assets/data-table-CQ_YRWQs.js +0 -1
- data/public/railwatch/assets/env-layout-CdMWbUkm.js +0 -1
- data/public/railwatch/assets/execution-path-7MYt-Qbn.js +0 -1
- data/public/railwatch/assets/filter-bar-CrzpQ_vD.js +0 -1
- data/public/railwatch/assets/flamegraph-BIFEdTAg.js +0 -2
- data/public/railwatch/assets/format-B9SDkrWj.js +0 -1
- data/public/railwatch/assets/frames-C5t-x4f0.js +0 -1
- data/public/railwatch/assets/index--NQ7us-L.js +0 -1
- data/public/railwatch/assets/index-2kgYe_99.js +0 -1
- data/public/railwatch/assets/index-4YEPHxtO.js +0 -1
- data/public/railwatch/assets/index-AOCC1GDJ.js +0 -1
- data/public/railwatch/assets/index-BEo-u0eO.js +0 -1
- data/public/railwatch/assets/index-BJ1V3w2N.js +0 -1
- data/public/railwatch/assets/index-BPoHsBKb.js +0 -1
- data/public/railwatch/assets/index-BZzyGUM3.js +0 -1
- data/public/railwatch/assets/index-BgVq7yKL.js +0 -1
- data/public/railwatch/assets/index-BqFGEZvW.js +0 -1
- data/public/railwatch/assets/index-ByuHJ1CH.js +0 -1
- data/public/railwatch/assets/index-C2j50AL2.js +0 -1
- data/public/railwatch/assets/index-CLEdTkvW.js +0 -1
- data/public/railwatch/assets/index-CWgq8qBu.js +0 -1
- data/public/railwatch/assets/index-CXQ7_oVj.js +0 -2
- data/public/railwatch/assets/index-CmyYrjS2.js +0 -1
- data/public/railwatch/assets/index-Cn9_HjDo.js +0 -1
- data/public/railwatch/assets/index-CsMAm9UK.js +0 -1
- data/public/railwatch/assets/index-D0Kk_oGY.js +0 -1
- data/public/railwatch/assets/index-DC9ZptJh.js +0 -1
- data/public/railwatch/assets/index-DNzg7jmV.js +0 -1
- data/public/railwatch/assets/index-DQ8uJO5f.js +0 -1
- data/public/railwatch/assets/index-Deq14BxB.js +0 -1
- data/public/railwatch/assets/index-IaV3Blzp.js +0 -1
- data/public/railwatch/assets/index-TYNWcJQs.js +0 -1
- data/public/railwatch/assets/index-WonH2fcZ.js +0 -1
- data/public/railwatch/assets/index-WxWUaH_d.js +0 -1
- data/public/railwatch/assets/index-mUNrl4ky.js +0 -1
- data/public/railwatch/assets/inertia-DfNRARuA.js +0 -97
- data/public/railwatch/assets/json-viewer-DMsF5glp.js +0 -1
- data/public/railwatch/assets/klass-CmHYk5rz.js +0 -1
- data/public/railwatch/assets/origin-identity-1R3bqa3R.js +0 -1
- data/public/railwatch/assets/percentile-picker-73z5PAps.js +0 -1
- data/public/railwatch/assets/relative-time-Dsc_cCdX.js +0 -1
- data/public/railwatch/assets/release-health-BV5OaOWd.js +0 -1
- data/public/railwatch/assets/route-CWh58C7K.js +0 -1
- data/public/railwatch/assets/segmented-DB4zh-Am.js +0 -1
- data/public/railwatch/assets/select-DBKjFlOX.js +0 -1
- data/public/railwatch/assets/series-chart-BaiDG795.js +0 -1
- data/public/railwatch/assets/show-B4ICCFGh.js +0 -1
- data/public/railwatch/assets/show-B5lOU9Gv.js +0 -1
- data/public/railwatch/assets/show-BYPB74t4.js +0 -2
- data/public/railwatch/assets/show-C7A0m2M8.js +0 -1
- data/public/railwatch/assets/show-DT5X1zqE.js +0 -1
- data/public/railwatch/assets/show-D_NBdXsn.js +0 -2
- data/public/railwatch/assets/show-DgPCnRg8.js +0 -1
- data/public/railwatch/assets/show-Yl69HF0x.js +0 -1
- data/public/railwatch/assets/show-_QxuUQCm.js +0 -3
- data/public/railwatch/assets/show-g__jnN36.js +0 -1
- data/public/railwatch/assets/show-l4ztfjl9.js +0 -1
- data/public/railwatch/assets/sort-header-BcG8ulEH.js +0 -1
- data/public/railwatch/assets/source-link-B2183i2-.js +0 -1
- data/public/railwatch/assets/sparkline-cell-B47ipscB.js +0 -1
- data/public/railwatch/assets/stat-DT2hhsqS.js +0 -1
- data/public/railwatch/assets/status-badge-CmbRKlY7.js +0 -1
- data/public/railwatch/assets/tenant-path-C4rNGauE.js +0 -1
- data/public/railwatch/assets/use-clipboard-oTNeP4ax.js +0 -1
- data/public/railwatch/assets/use-live-CLZZj245.js +0 -1
- data/public/railwatch/icon.png +0 -0
- data/public/railwatch/icon.svg +0 -5
- data/public/railwatch/manifest.json +0 -1245
- data/public/railwatch/rails-vite.json +0 -1
- /data/{app/frontend/lib → lib/generators/railwatch/install/templates}/railwatch.ts +0 -0
data/docs/embedded.md
DELETED
|
@@ -1,503 +0,0 @@
|
|
|
1
|
-
# Embedded mode: the dashboard inside your app
|
|
2
|
-
|
|
3
|
-
This is what the installer sets up by default. Railwatch keeps every
|
|
4
|
-
record in your own application and serves the full dashboard at
|
|
5
|
-
`/railwatch`, with no token and no cloud. The gem's reporter, buffer and
|
|
6
|
-
sampling are the same as with the cloud; the only difference is where a
|
|
7
|
-
batch ends up. In embedded mode it is written into a SQLite database
|
|
8
|
-
your app owns, and the dashboard reads it back from there.
|
|
9
|
-
|
|
10
|
-
Use it when one server runs the app. Telemetry lands in a file next to
|
|
11
|
-
your other SQLite databases, so several servers would each see only
|
|
12
|
-
their own slice. For more than one server, or one place for many apps,
|
|
13
|
-
add Railwatch Cloud: export alongside embedded, or the cloud on its own
|
|
14
|
-
([Getting started](getting-started.md#railwatch-cloud-instead)).
|
|
15
|
-
|
|
16
|
-
## Three ways to run it
|
|
17
|
-
|
|
18
|
-
Same gem, same install, same records. The only question is where they end
|
|
19
|
-
up:
|
|
20
|
-
|
|
21
|
-
| | Records live | Dashboard |
|
|
22
|
-
|---|---|---|
|
|
23
|
-
| **Embedded** | your app's SQLite files | `/railwatch` in your app |
|
|
24
|
-
| **Cloud** | Railwatch Cloud | the hosted one |
|
|
25
|
-
| **Both** | your app's files, *and* Railwatch Cloud | either |
|
|
26
|
-
|
|
27
|
-
Embedded is `c.transport = :local`, which is what the installer writes
|
|
28
|
-
unless you ask it for the cloud (`--cloud`, or any token or URL option).
|
|
29
|
-
With no initializer setting it, the runtime default is `:http`, the
|
|
30
|
-
cloud. "Both" is embedded plus export, which needs a Railwatch Cloud
|
|
31
|
-
environment token. The default embedded install has none, so set one
|
|
32
|
-
first (`bin/rails railwatch:token` prints where to create it), plus the
|
|
33
|
-
ingest URL if you self-host:
|
|
34
|
-
|
|
35
|
-
```sh
|
|
36
|
-
RAILWATCH_TOKEN=rw_...
|
|
37
|
-
RAILWATCH_INGEST_URL=https://telemetry.example.com # only when self-hosting
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
With a token in place, export is one more line:
|
|
41
|
-
|
|
42
|
-
```ruby
|
|
43
|
-
c.export_enabled = true # or RAILWATCH_EXPORT_ENABLED=true
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
It reuses `RAILWATCH_TOKEN` and `RAILWATCH_INGEST_URL`
|
|
47
|
-
(`RAILWATCH_EXPORT_TOKEN` and `RAILWATCH_EXPORT_URL` override them), so
|
|
48
|
-
an install that was pointed at the cloud and moved to embedded needs
|
|
49
|
-
nothing else. Every record captured locally is mirrored, so the hosted
|
|
50
|
-
dashboard is as complete as a cloud-only install's, and `/railwatch`
|
|
51
|
-
keeps working.
|
|
52
|
-
|
|
53
|
-
It is off unless you set that flag. A token being present is not consent:
|
|
54
|
-
an embedded install that has one configured still sends nothing.
|
|
55
|
-
`railwatch:doctor` says nothing about export until you ask for it, and
|
|
56
|
-
fails if you ask for it and it cannot work. `railwatch:export:status`
|
|
57
|
-
shows what is queued. The queue is durable, in the telemetry database,
|
|
58
|
-
and bounded: 256 MiB (`RAILWATCH_EXPORT_MAX_BYTES`), 100,000 deliveries
|
|
59
|
-
(`RAILWATCH_EXPORT_MAX_DELIVERIES`), and a day's age
|
|
60
|
-
(`RAILWATCH_EXPORT_MAX_AGE_SECONDS`, at most seven days).
|
|
61
|
-
|
|
62
|
-
## Railwatch Cloud runs the same models
|
|
63
|
-
|
|
64
|
-
The hosted platform is this engine's telemetry layer plus what hosting adds
|
|
65
|
-
(accounts, tokens, quotas, alert routing). It uses `Railwatch::Telemetry::*`,
|
|
66
|
-
`Railwatch::Ingest::Batch`, `Railwatch::Telemetry::Aggregations` and the rest
|
|
67
|
-
directly, with two seams: it tenants `Railwatch::TelemetryRecord` so each
|
|
68
|
-
monitored environment gets its own database, and its `Environment` is an
|
|
69
|
-
account-scoped Active Record row rather than the embedded singleton. Anything
|
|
70
|
-
those models need from an environment is the surface `Railwatch::Environment`
|
|
71
|
-
documents: `id`, `slug`, `name`, `with_telemetry`, and the display attributes.
|
|
72
|
-
|
|
73
|
-
## Install
|
|
74
|
-
|
|
75
|
-
```sh
|
|
76
|
-
bundle add railwatch
|
|
77
|
-
bin/rails generate railwatch:install
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Restart the app and open `/railwatch`; in development it is open with no
|
|
81
|
-
password (see [Authentication](#authentication) for production).
|
|
82
|
-
`bin/rails railwatch:doctor` checks the wiring. When the `sqlite3` gem
|
|
83
|
-
is already in the bundle, the generator creates and migrates both
|
|
84
|
-
databases itself; when it is not, see the next section. After that,
|
|
85
|
-
`bin/rails db:prepare`, which a deploy already runs, migrates them after
|
|
86
|
-
every gem update.
|
|
87
|
-
|
|
88
|
-
The engine needs Active Job (its grouping and scan jobs are Active Job
|
|
89
|
-
classes even though embedded mode calls them directly) and loads it
|
|
90
|
-
itself. Action Cable is optional: with it the dashboard updates live,
|
|
91
|
-
without it (`rails new --minimal`) the pages refresh on navigation.
|
|
92
|
-
|
|
93
|
-
## Your application's own database
|
|
94
|
-
|
|
95
|
-
Embedded mode does not care what your application runs on. The two
|
|
96
|
-
databases it adds are SQLite files either way, so the generated entries
|
|
97
|
-
name `adapter: sqlite3` themselves rather than inheriting your default
|
|
98
|
-
block, and they need no `&default` anchor to exist.
|
|
99
|
-
|
|
100
|
-
On a PostgreSQL or MySQL app without the `sqlite3` gem, the install
|
|
101
|
-
takes two more commands:
|
|
102
|
-
|
|
103
|
-
```sh
|
|
104
|
-
bin/rails generate railwatch:install # adds gem "sqlite3", writes the config
|
|
105
|
-
bundle install
|
|
106
|
-
bin/rails db:prepare # creates the two SQLite files
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Verified end to end on both. On a PostgreSQL app and on a MySQL app, the
|
|
110
|
-
application's own four databases stay where they were, Railwatch's two are
|
|
111
|
-
files under `storage/`, and neither server gains a single Railwatch table.
|
|
112
|
-
|
|
113
|
-
What the embedded install writes, on top of what every install writes
|
|
114
|
-
(listed in [Getting started](getting-started.md#what-the-generator-writes)):
|
|
115
|
-
|
|
116
|
-
- `config/initializers/railwatch.rb` with `c.transport = :local` and the
|
|
117
|
-
dashboard's own paths excluded from request capture.
|
|
118
|
-
- Two databases in every environment of `config/database.yml`:
|
|
119
|
-
`railwatch` (issues, comments, saved views, thresholds, deploys:
|
|
120
|
-
small, permanent) and `railwatch_telemetry` (everything the app
|
|
121
|
-
reports: written continuously, pruned nightly). A flat
|
|
122
|
-
`development:` entry is nested under `primary:` first, since named
|
|
123
|
-
databases need that form. Each entry's `migrations_paths` points into
|
|
124
|
-
the gem, so `db:prepare` builds the tables from the gem's own
|
|
125
|
-
migrations and nothing is copied into `db/`.
|
|
126
|
-
- `plugin :railwatch` at the end of `config/puma.rb`, which forks the
|
|
127
|
-
[writer process](#the-writer-process).
|
|
128
|
-
- `mount Railwatch::Engine, at: "/railwatch"`, as always.
|
|
129
|
-
|
|
130
|
-
Nothing touches your primary database.
|
|
131
|
-
|
|
132
|
-
## Upgrading
|
|
133
|
-
|
|
134
|
-
The engine's tables migrate the way Active Storage's do: the migrations
|
|
135
|
-
live in the gem and each database's `migrations_paths` points at them.
|
|
136
|
-
After `bundle update railwatch`, run `bin/rails db:prepare` (or
|
|
137
|
-
`db:migrate`) and whatever is new applies; a deploy that already runs
|
|
138
|
-
one of those needs nothing extra. `bin/rails railwatch:doctor` reports
|
|
139
|
-
pending migrations for both databases. Tables in the `railwatch` file
|
|
140
|
-
are prefixed `railwatch_`; the telemetry file's tables are the hosted
|
|
141
|
-
platform's schema and are unprefixed.
|
|
142
|
-
|
|
143
|
-
## What you get
|
|
144
|
-
|
|
145
|
-
Every page of Railwatch Cloud: requests, jobs, scheduled tasks,
|
|
146
|
-
commands, queries, spans, exceptions, logs, cache, mail, notifications,
|
|
147
|
-
broadcasts, outgoing requests, LLM calls, storage, views, transactions,
|
|
148
|
-
deprecations, processes, releases, deploys, users, tenants, visits,
|
|
149
|
-
thresholds; issues with comments, assignment, merge and split; saved
|
|
150
|
-
views; alert rules and the alert log. Live updates arrive over Action
|
|
151
|
-
Cable when the app has it. The dashboard is a prebuilt bundle shipped
|
|
152
|
-
inside the gem, so the app needs no Node, no Vite and no asset pipeline
|
|
153
|
-
integration.
|
|
154
|
-
|
|
155
|
-
Not in embedded mode: accounts and members (the operator is whoever your
|
|
156
|
-
app lets through), integrations (alerts are recorded, not delivered to
|
|
157
|
-
Slack, email, webhooks or Linear), and the
|
|
158
|
-
[MCP server](ai-and-mcp.md). Those are Railwatch Cloud's, and
|
|
159
|
-
[export](#three-ways-to-run-it) gets them without giving up the local
|
|
160
|
-
dashboard.
|
|
161
|
-
|
|
162
|
-
## Authentication
|
|
163
|
-
|
|
164
|
-
The dashboard shows every query, log line and exception your app
|
|
165
|
-
produced, so it works the way Mission Control Jobs does: **HTTP Basic
|
|
166
|
-
authentication is on by default, and closed until credentials exist**.
|
|
167
|
-
With none configured every dashboard request is 401, the app logs a
|
|
168
|
-
warning at boot, and `railwatch:doctor` says so. Set them with
|
|
169
|
-
|
|
170
|
-
```sh
|
|
171
|
-
bin/rails railwatch:authentication:configure
|
|
172
|
-
RAILS_ENV=production bin/rails railwatch:authentication:configure
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
The one exception is development. There, with Basic on and no
|
|
176
|
-
credentials set, the dashboard is open, so a first run needs no password
|
|
177
|
-
step; Rails shows full error pages in development for the same reason.
|
|
178
|
-
Set credentials there too and development asks for them like everywhere
|
|
179
|
-
else. Test, staging and production are closed until you do.
|
|
180
|
-
|
|
181
|
-
`railwatch:authentication:configure` writes them to that environment's Rails credentials:
|
|
182
|
-
|
|
183
|
-
```yml
|
|
184
|
-
railwatch:
|
|
185
|
-
http_basic_auth_user: ops
|
|
186
|
-
http_basic_auth_password: secret
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
`RAILWATCH_HTTP_BASIC_AUTH_USER` and `RAILWATCH_HTTP_BASIC_AUTH_PASSWORD`,
|
|
190
|
-
or `c.http_basic_auth_user =` / `c.http_basic_auth_password =` in the
|
|
191
|
-
initializer, do the same. The live-update channel checks the same
|
|
192
|
-
credentials (the browser sends them on the WebSocket handshake).
|
|
193
|
-
|
|
194
|
-
### Your own authentication
|
|
195
|
-
|
|
196
|
-
Two ways, both from Mission Control's playbook. Either lets an admin of
|
|
197
|
-
your app in with no second password. Turn Basic off when you use one,
|
|
198
|
-
or both gates apply.
|
|
199
|
-
|
|
200
|
-
A base controller. Every dashboard controller inherits from it, so its
|
|
201
|
-
`before_action` runs first:
|
|
202
|
-
|
|
203
|
-
```ruby
|
|
204
|
-
c.http_basic_auth_enabled = false
|
|
205
|
-
c.base_controller_class = "AdminController" # requires an admin, or redirects to sign-in
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
Your controller's code runs inside the engine, whose route helpers take
|
|
209
|
-
precedence; reach your app's with `main_app.root_path`. The controller does
|
|
210
|
-
not run for Action Cable subscriptions: live updates stay closed unless
|
|
211
|
-
`dashboard_user` also authorizes the connection (as below). Return `nil` or
|
|
212
|
-
`false` for unauthorized users. The resolver names the operator on pages;
|
|
213
|
-
keep the controller or mount constraint as the page authorization gate.
|
|
214
|
-
|
|
215
|
-
Or a routes constraint, which keeps the engine out of it entirely (for
|
|
216
|
-
example with the sessions Rails' authentication generator creates):
|
|
217
|
-
|
|
218
|
-
```ruby
|
|
219
|
-
# config/routes.rb
|
|
220
|
-
constraints ->(request) { Session.find_by(id: request.cookie_jar.signed[:session_id])&.user&.admin? } do
|
|
221
|
-
mount Railwatch::Engine, at: "/railwatch"
|
|
222
|
-
end
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
Requests that fail the constraint never reach the engine. A constraint is
|
|
226
|
-
invisible to the gem, though, so say that the engine's own gate is off on
|
|
227
|
-
purpose:
|
|
228
|
-
|
|
229
|
-
```ruby
|
|
230
|
-
c.http_basic_auth_enabled = false
|
|
231
|
-
# The mount constraint does not cover /cable. Authorize live updates too:
|
|
232
|
-
c.dashboard_user = ->(request) {
|
|
233
|
-
user = Session.find_by(id: request.cookie_jar.signed[:session_id])&.user
|
|
234
|
-
{ id: user.id, name: user.name } if user&.admin?
|
|
235
|
-
}
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### Deliberately public
|
|
239
|
-
|
|
240
|
-
A dashboard on a private network or behind a VPN can be open, and saying
|
|
241
|
-
so is a setting rather than an omission:
|
|
242
|
-
|
|
243
|
-
```ruby
|
|
244
|
-
c.http_basic_auth_enabled = false
|
|
245
|
-
c.dashboard_open = true
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
With Basic off and none of `base_controller_class`, `dashboard_user` or
|
|
249
|
-
`dashboard_open` set, the gem cannot tell a deliberate choice from a
|
|
250
|
-
forgotten one. It serves the dashboard (a routes constraint it cannot see
|
|
251
|
-
is a legitimate answer) but logs a warning at every boot outside
|
|
252
|
-
development, `railwatch:doctor` reports the gate as undeclared, and live
|
|
253
|
-
updates are refused. A custom base controller declares the page gate only;
|
|
254
|
-
live updates still require their own authorization as described below.
|
|
255
|
-
|
|
256
|
-
### Live updates and `/cable`
|
|
257
|
-
|
|
258
|
-
Action Cable runs on your application's own `/cable` endpoint, not under
|
|
259
|
-
the engine's mount, so a routes constraint around `/railwatch` does not
|
|
260
|
-
cover it and a base controller cannot reach it. The live-update channel
|
|
261
|
-
therefore follows what you declared: HTTP Basic credentials are checked
|
|
262
|
-
from the WebSocket handshake, a `dashboard_user` resolver is consulted,
|
|
263
|
-
or `dashboard_open` explicitly permits public live updates. A custom
|
|
264
|
-
`base_controller_class` authorizes pages only and never permits a Cable
|
|
265
|
-
subscription by itself. The gate is rechecked before each live update;
|
|
266
|
-
revocation removes the subscription and notifies the client. The channel carries an ingest ping (a
|
|
267
|
-
timestamp and per-type counts) and never telemetry records.
|
|
268
|
-
|
|
269
|
-
### Naming the operator, and what an id may be
|
|
270
|
-
|
|
271
|
-
Comments, saved views and issue activity record who did them. Give the
|
|
272
|
-
initializer a resolver and the dashboard shows that person instead of a
|
|
273
|
-
single "Operator":
|
|
274
|
-
|
|
275
|
-
```ruby
|
|
276
|
-
c.dashboard_user = ->(request) do
|
|
277
|
-
user = User.find_by(id: request.session[:user_id])
|
|
278
|
-
user && { id: user.id, name: user.name, email: user.email }
|
|
279
|
-
end
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
The `id` may be anything your application already uses: an integer, a
|
|
283
|
-
UUID, a ULID, an email. It is stored as an opaque string and handed back
|
|
284
|
-
to you; nothing joins on it and nothing parses it, because the engine has
|
|
285
|
-
no user table to check it against. Comments, saved views, issue activity
|
|
286
|
-
and assignment all key off whatever you return, so a person keeps their
|
|
287
|
-
own views and their name on their own comments however you identify them.
|
|
288
|
-
|
|
289
|
-
On a page, returning `nil` does not refuse the request: the page renders
|
|
290
|
-
with the default "Operator". With HTTP Basic off, `nil` refuses the
|
|
291
|
-
live-update subscription. Pages are gated by HTTP Basic, your
|
|
292
|
-
`base_controller_class`, or a mount constraint, never by this resolver.
|
|
293
|
-
|
|
294
|
-
## The writer process
|
|
295
|
-
|
|
296
|
-
Puma forks one Railwatch writer from its master when `config/puma.rb`
|
|
297
|
-
carries the plugin (the embedded install adds it):
|
|
298
|
-
|
|
299
|
-
```ruby
|
|
300
|
-
plugin :railwatch
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
Leave it unconditional: `bundle exec puma` reads this file before it
|
|
304
|
-
loads the app, so `if defined?(Railwatch)` would be false there and no
|
|
305
|
-
writer would start. The plugin does nothing when Railwatch is off or not
|
|
306
|
-
in embedded mode.
|
|
307
|
-
|
|
308
|
-
Every web worker keeps its reporter thread, but instead of writing
|
|
309
|
-
SQLite it hands each batch to the writer over a Unix socket
|
|
310
|
-
(`tmp/sockets/railwatch-writer.sock`, `RAILWATCH_WRITER_SOCKET`). The
|
|
311
|
-
socket's directory is created mode 0700 and the socket 0600, so only
|
|
312
|
-
the app's own user can reach it; keep it that way if you move it. Linux
|
|
313
|
-
caps the whole path at 108 bytes, so an app checked out deep in the
|
|
314
|
-
filesystem should point this at a directory of its own under
|
|
315
|
-
`/run/user/$UID` or `/tmp` (not a bare file in `/tmp`). Single and
|
|
316
|
-
cluster mode alike: a default Rails 8 app runs Puma with no workers, and
|
|
317
|
-
its batches come off its request threads just the same. The
|
|
318
|
-
writer maps the records, writes both databases, folds the rollups,
|
|
319
|
-
groups exceptions into issues and runs the maintenance clock below. It
|
|
320
|
-
is the only process that ever holds the telemetry database's write lock,
|
|
321
|
-
and its Ruby interpreter is its own, so none of that work is ever
|
|
322
|
-
interleaved with a request. Same shape as Solid Queue's
|
|
323
|
-
`solid_queue_mode :fork`: it exits when Puma does, and Puma restarts it
|
|
324
|
-
if it dies. While it is down the reporter keeps batches in memory, with
|
|
325
|
-
the same byte ceiling and backoff as the HTTP transport, and every
|
|
326
|
-
batch carries an id the writer records inside the write transaction, so
|
|
327
|
-
a batch delivered twice is written once.
|
|
328
|
-
|
|
329
|
-
The writer judges its own health. A single batch write that runs past
|
|
330
|
-
sixty seconds is a stuck writer, not a slow one (a lock that never
|
|
331
|
-
clears, a lost connection), and the process exits so Puma restarts it;
|
|
332
|
-
the batch is retained on the worker and written by the new writer. The
|
|
333
|
-
doctor reports both whether the socket answers and when the last batch
|
|
334
|
-
was actually written, since a process that is alive and a process that
|
|
335
|
-
is doing its job are different questions.
|
|
336
|
-
|
|
337
|
-
Whether a writer is expected decides what a missing one means. Puma
|
|
338
|
-
workers under the plugin expect one: a socket that is absent or not
|
|
339
|
-
answering is a writer that is starting or restarting, and they retain
|
|
340
|
-
batches and retry for as long as it takes. A process with no plugin
|
|
341
|
-
(`bin/rails runner`, a Solid Queue worker, a `rails server` without it,
|
|
342
|
-
the test suite) expects none, says so once under `RAILWATCH_DEBUG`, and
|
|
343
|
-
writes its batches in-process instead. That fallback is provisional: the
|
|
344
|
-
socket is tried again every 30 seconds, so a process that started before
|
|
345
|
-
the writer did hands the work back as soon as one is listening. What
|
|
346
|
-
changes is which process pays for the write, not whether the write
|
|
347
|
-
happens.
|
|
348
|
-
|
|
349
|
-
Retention while a writer is away is bounded, not infinite. A writer that
|
|
350
|
-
is restarting is back in seconds; one that is missing for a minute (an
|
|
351
|
-
unwritable socket directory, a fork that keeps failing) is treated as
|
|
352
|
-
absent and the worker writes its own batches again, still re-checking, so
|
|
353
|
-
the records are kept rather than retained to the reporter's retry cap. A
|
|
354
|
-
batch that does exhaust that ladder is counted as dropped and reported
|
|
355
|
-
through `Railwatch.on_unrecoverable`, so loss is never silent. A Puma
|
|
356
|
-
phased restart stops the writer and starts a fresh one once the new
|
|
357
|
-
workers are up.
|
|
358
|
-
|
|
359
|
-
Stopping the writer is bounded. Puma sends it TERM and waits up to
|
|
360
|
-
`c.shutdown_timeout` (2 seconds) for it to exit, the same allowance it
|
|
361
|
-
gives its own reporter, then kills it. A writer killed mid-batch loses
|
|
362
|
-
nothing: the transaction rolls back and the worker retries that batch by
|
|
363
|
-
id against the next writer, so waiting longer for its drain would buy no
|
|
364
|
-
data. The bound is what keeps Puma's exit short when the writer is wedged
|
|
365
|
-
in a SQLite write or on a full disk. It counts against the container's
|
|
366
|
-
stop grace (Docker's default is 10 seconds; Kamal's `stop_timeout` sets
|
|
367
|
-
it), and `RAILWATCH_SHUTDOWN_TIMEOUT` raises it for an app whose grace
|
|
368
|
-
allows more.
|
|
369
|
-
|
|
370
|
-
## Maintenance
|
|
371
|
-
|
|
372
|
-
Railwatch needs no job worker and nothing in `config/recurring.yml`.
|
|
373
|
-
The work that keeps the dashboard current happens in two places:
|
|
374
|
-
|
|
375
|
-
- **As each batch lands.** The writer writes the batch, folds its rows
|
|
376
|
-
into the hour's rollups, and groups any exceptions into issues, all
|
|
377
|
-
before it picks up the next batch. Counts, percentiles and the issues
|
|
378
|
-
list move with every batch.
|
|
379
|
-
- **On Railwatch's own clock.** The writer runs a `railwatch-maintenance`
|
|
380
|
-
thread that wakes every 30 seconds. Without a writer, every web and
|
|
381
|
-
worker process runs one, and one process at a time runs each task,
|
|
382
|
-
claimed through a lease row in the `railwatch` database, so a Puma
|
|
383
|
-
cluster and a Solid Queue worker on the same server do not all prune
|
|
384
|
-
at once.
|
|
385
|
-
|
|
386
|
-
| Task | Cadence | What it does |
|
|
387
|
-
| --- | --- | --- |
|
|
388
|
-
| drain follow-ups | every minute | Finishes the exception grouping of any batch whose process died right after the batch committed |
|
|
389
|
-
| release health | every minute | Hourly crash-free aggregates for the current and previous hour |
|
|
390
|
-
| rollup reconcile | hourly | Recomputes the previous hour's rollups from raw rows, for records that arrived after their hour closed |
|
|
391
|
-
| performance scan | every 5 minutes | Threshold breaches become issues |
|
|
392
|
-
| anomaly scan | every 5 minutes | Anomaly rules, when any are enabled |
|
|
393
|
-
| scheduled tasks | every 10 minutes | Missed and late scheduled tasks |
|
|
394
|
-
| auto-resolve | daily | Resolves issues quiet for 14 days |
|
|
395
|
-
| prune | daily | Deletes telemetry older than `retention_days`, returns the freed pages to the filesystem when incremental auto-vacuum is on (see below), then `ANALYZE` |
|
|
396
|
-
|
|
397
|
-
Because the clock lives in the web process, it keeps running when the
|
|
398
|
-
job worker is down, which is exactly when "scheduled task X missed its
|
|
399
|
-
run" needs to be raised. With Solid Queue, the issue also says which of
|
|
400
|
-
three things happened: the scheduler never enqueued the run, it was
|
|
401
|
-
enqueued but no worker is running, or a worker is alive and it is
|
|
402
|
-
waiting behind a backlog.
|
|
403
|
-
|
|
404
|
-
Every batch is written exactly once. The reporter gives each batch an
|
|
405
|
-
id before its first attempt and the write records it in the same
|
|
406
|
-
transaction as the rows, so a write that fails (the file locked by a
|
|
407
|
-
backup, say) is retried with backoff and a retry of a batch that did
|
|
408
|
-
commit is a no-op. `bin/rails railwatch:doctor` reports the last
|
|
409
|
-
tick. If you installed a pre-release that added `Railwatch::*` entries to
|
|
410
|
-
`config/recurring.yml`, remove them; the doctor says so too.
|
|
411
|
-
|
|
412
|
-
## Settings
|
|
413
|
-
|
|
414
|
-
```ruby
|
|
415
|
-
Railwatch.configure do |c|
|
|
416
|
-
c.transport = :local # RAILWATCH_TRANSPORT=local
|
|
417
|
-
c.issue_prefix = "SHOP" # RAILWATCH_ISSUE_PREFIX; keys like SHOP-12
|
|
418
|
-
c.repository_url = "https://github.com/you/shop" # RAILWATCH_REPOSITORY_URL
|
|
419
|
-
c.retention_days = 7 # RAILWATCH_RETENTION_DAYS
|
|
420
|
-
c.http_basic_auth_enabled = true # RAILWATCH_HTTP_BASIC_AUTH_ENABLED; credentials from Rails credentials or env
|
|
421
|
-
c.base_controller_class = "ActionController::Base" # RAILWATCH_BASE_CONTROLLER_CLASS
|
|
422
|
-
c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN; true makes it public on purpose
|
|
423
|
-
c.dashboard_user = ->(request) { ... }
|
|
424
|
-
end
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
Every other option (sampling, redaction, ignored record types) applies
|
|
428
|
-
unchanged. The default samples every execution; set `c.sample` lower on
|
|
429
|
-
a busy app.
|
|
430
|
-
|
|
431
|
-
## Deploys
|
|
432
|
-
|
|
433
|
-
`bin/rails railwatch:deploy` records the marker in the app's own
|
|
434
|
-
database instead of posting it. The Kamal post-deploy hook runs on the
|
|
435
|
-
deployer, which does not read your initializer, so it only knows the
|
|
436
|
-
install is embedded when `RAILWATCH_TRANSPORT=local` is set in the
|
|
437
|
-
deployer's environment. Then it runs `railwatch:deploy` inside the
|
|
438
|
-
primary container.
|
|
439
|
-
|
|
440
|
-
## Storage and overhead
|
|
441
|
-
|
|
442
|
-
Telemetry is written by the reporter thread in batches, never on a
|
|
443
|
-
request. The cost on the request path is the same instrumentation the
|
|
444
|
-
cloud transport pays; the write itself adds under a millisecond at the
|
|
445
|
-
99th percentile at a hundred requests per second on a single Puma. The
|
|
446
|
-
telemetry database grows with traffic and sampling and is pruned to
|
|
447
|
-
`retention_days`; the `railwatch` database stays small. Both are plain
|
|
448
|
-
SQLite files in `storage/`, so Litestream or a volume snapshot covers
|
|
449
|
-
them.
|
|
450
|
-
|
|
451
|
-
### Giving the disk back
|
|
452
|
-
|
|
453
|
-
Deleting rows does not shrink a SQLite file on its own. The pages go on
|
|
454
|
-
the database's freelist, where later inserts reuse them, and the file
|
|
455
|
-
stays whatever size it reached. So pruning alone keeps the *contents*
|
|
456
|
-
bounded and lets the *file* grow forever.
|
|
457
|
-
|
|
458
|
-
Railwatch creates its telemetry database in SQLite's
|
|
459
|
-
`auto_vacuum=incremental` mode, and the nightly prune runs `PRAGMA
|
|
460
|
-
incremental_vacuum` after its deletes, which hands those pages back to
|
|
461
|
-
the filesystem. It is bounded -- 2,000 pages a slice, 25 slices, about
|
|
462
|
-
200MB a night at SQLite's 4K default page size -- so a large backlog
|
|
463
|
-
drains over successive nights rather than holding the write lock through
|
|
464
|
-
one enormous reclaim. Nothing to configure.
|
|
465
|
-
|
|
466
|
-
```
|
|
467
|
-
$ bin/rails railwatch:vacuum:status
|
|
468
|
-
Railwatch telemetry database
|
|
469
|
-
file /rails/storage/production_railwatch_telemetry.sqlite3
|
|
470
|
-
size 1.42 GB
|
|
471
|
-
mode auto_vacuum=incremental
|
|
472
|
-
free 312 pages (1.22 MB) on the freelist
|
|
473
|
-
|
|
474
|
-
The nightly prune returns up to 195 MB a night on its own.
|
|
475
|
-
`bin/rails railwatch:vacuum` returns all 1.22 MB now.
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
**Installs created before 0.3.5 report `auto_vacuum=none`, and pruning
|
|
479
|
-
cannot return their space.** SQLite can only set the mode on a database
|
|
480
|
-
that is still empty; on one that has data the only route is a full
|
|
481
|
-
`VACUUM`, which rewrites the entire file with the write lock held.
|
|
482
|
-
Railwatch will not do that to a running application behind your back, so
|
|
483
|
-
upgrading leaves the mode alone and the conversion is a task you run
|
|
484
|
-
when you can spare the lock:
|
|
485
|
-
|
|
486
|
-
```
|
|
487
|
-
$ bin/rails railwatch:vacuum
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
It prints the file, its size and its freelist first, then says how long
|
|
491
|
-
the `VACUUM` should take and that it needs about the file's own size in
|
|
492
|
-
free disk for the temporary copy. Telemetry written while it runs waits
|
|
493
|
-
for it and the dashboard is frozen for the duration, so pick a quiet
|
|
494
|
-
moment. Once converted, the nightly prune keeps up on its own and you
|
|
495
|
-
never need to run it again.
|
|
496
|
-
|
|
497
|
-
## Switching to the cloud later
|
|
498
|
-
|
|
499
|
-
To keep the local dashboard and add the cloud, turn on
|
|
500
|
-
[export](#three-ways-to-run-it). To move to the cloud entirely, set a
|
|
501
|
-
token and drop `c.transport = :local` (or set
|
|
502
|
-
`RAILWATCH_TRANSPORT=http`). The local databases can stay; the dashboard
|
|
503
|
-
at `/railwatch` keeps reading what is there until it is pruned.
|