railwatch 0.9.3 → 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 +66 -0
- data/README.md +51 -60
- data/config/routes.rb +0 -59
- data/docs/ai-and-mcp.md +9 -8
- data/docs/configuration.md +57 -71
- 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/buffer.rb +9 -0
- 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 +92 -21
- data/lib/railwatch/sampler.rb +10 -2
- data/lib/railwatch/subscribers/process_info.rb +1 -2
- data/lib/railwatch/version.rb +1 -1
- data/lib/railwatch.rb +8 -64
- 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/lib/railwatch/sampler.rb
CHANGED
|
@@ -8,10 +8,18 @@ module Railwatch
|
|
|
8
8
|
module_function
|
|
9
9
|
|
|
10
10
|
def decide(kind)
|
|
11
|
-
|
|
11
|
+
configured = Railwatch.config.sample_rate(kind)
|
|
12
|
+
rate = configured / Railwatch.reporter.backpressure_factor
|
|
12
13
|
return true if rate >= 1.0
|
|
13
14
|
return false if rate <= 0.0
|
|
14
|
-
|
|
15
|
+
|
|
16
|
+
roll = Random.rand
|
|
17
|
+
return true if roll < rate
|
|
18
|
+
|
|
19
|
+
# Kept by the configured rate, lost to backpressure: counted, so the
|
|
20
|
+
# reporter can say what an ingest refusal cost once it is over.
|
|
21
|
+
Railwatch.reporter.note_sampled_out if roll < configured
|
|
22
|
+
false
|
|
15
23
|
end
|
|
16
24
|
end
|
|
17
25
|
end
|
|
@@ -89,8 +89,7 @@ module Railwatch
|
|
|
89
89
|
# $PROGRAM_NAME after boot and would otherwise turn the supervisor,
|
|
90
90
|
# dispatcher, and scheduler into "web" on every health sample.
|
|
91
91
|
def role
|
|
92
|
-
if defined?(
|
|
93
|
-
elsif defined?(::SolidQueue) && ($PROGRAM_NAME.include?("jobs") || $PROGRAM_NAME.start_with?("solid-queue-") || ARGV.first.to_s.start_with?("solid_queue:")) then "worker"
|
|
92
|
+
if defined?(::SolidQueue) && ($PROGRAM_NAME.include?("jobs") || $PROGRAM_NAME.start_with?("solid-queue-") || ARGV.first.to_s.start_with?("solid_queue:")) then "worker"
|
|
94
93
|
elsif defined?(::Rails::Console) then "console"
|
|
95
94
|
elsif $PROGRAM_NAME.end_with?("rake") then "command"
|
|
96
95
|
elsif defined?(::Puma) then "web"
|
data/lib/railwatch/version.rb
CHANGED
data/lib/railwatch.rb
CHANGED
|
@@ -21,19 +21,9 @@ require "railwatch/current"
|
|
|
21
21
|
require "railwatch/record"
|
|
22
22
|
require "railwatch/buffer"
|
|
23
23
|
require "railwatch/transport/wire_encoder"
|
|
24
|
-
require "railwatch/export/policy"
|
|
25
|
-
require "railwatch/export/lease"
|
|
26
|
-
require "railwatch/export/outbox"
|
|
27
|
-
require "railwatch/export/client"
|
|
28
|
-
require "railwatch/export/sender"
|
|
29
24
|
require "railwatch/transport/http"
|
|
30
|
-
require "railwatch/transport/local"
|
|
31
|
-
require "railwatch/transport/socket"
|
|
32
|
-
require "railwatch/writer"
|
|
33
25
|
require "railwatch/authentication"
|
|
34
26
|
require "railwatch/json_compat"
|
|
35
|
-
require "railwatch/embedded"
|
|
36
|
-
require "railwatch/ingest_request_body_limit"
|
|
37
27
|
require "railwatch/reporter"
|
|
38
28
|
require "railwatch/sampler"
|
|
39
29
|
require "railwatch/redactor"
|
|
@@ -69,7 +59,7 @@ module Railwatch
|
|
|
69
59
|
end
|
|
70
60
|
|
|
71
61
|
def reporter
|
|
72
|
-
@reporter ||= Reporter.new(config
|
|
62
|
+
@reporter ||= Reporter.new(config)
|
|
73
63
|
end
|
|
74
64
|
|
|
75
65
|
# Called once config/initializers has run, so a reporter built before
|
|
@@ -87,16 +77,6 @@ module Railwatch
|
|
|
87
77
|
@reporter&.adopt_final_config!
|
|
88
78
|
end
|
|
89
79
|
|
|
90
|
-
# Embedded mode: a Puma worker hands its batches to the writer process
|
|
91
|
-
# over the socket; the writer itself, and any process when no socket is
|
|
92
|
-
# configured, writes them straight into SQLite. nil means HTTP.
|
|
93
|
-
def local_transport
|
|
94
|
-
return nil unless config.local?
|
|
95
|
-
return Transport::Local.new(config) if Writer.running? || config.writer_socket_path.nil?
|
|
96
|
-
|
|
97
|
-
Transport::Socket.new(config)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
80
|
def redactor
|
|
101
81
|
@redactor ||= Redactor.new(config)
|
|
102
82
|
end
|
|
@@ -124,9 +104,7 @@ module Railwatch
|
|
|
124
104
|
Subscribers::Users.restart_after_fork!
|
|
125
105
|
Subscribers::ProcessInfo.restart_after_fork!
|
|
126
106
|
Health.restart_after_fork!
|
|
127
|
-
Export::Sender.restart_after_fork!
|
|
128
107
|
Sessions.restart_after_fork!
|
|
129
|
-
Maintenance.restart_after_fork!
|
|
130
108
|
end
|
|
131
109
|
|
|
132
110
|
# --- execution lifecycle -------------------------------------------------
|
|
@@ -355,8 +333,10 @@ module Railwatch
|
|
|
355
333
|
# the gem recovered, and a subscriber that raises once is noise in
|
|
356
334
|
# someone else's cron output. A DeliveryError is different. It is raised
|
|
357
335
|
# only when records are already gone -- a batch dropped after the retry
|
|
358
|
-
# ladder, one the receiver permanently refused,
|
|
359
|
-
# when at_exit's bounded shutdown ran out of time
|
|
336
|
+
# ladder, one the receiver permanently refused, the ones still unsent
|
|
337
|
+
# when at_exit's bounded shutdown ran out of time, or the executions an
|
|
338
|
+
# ingest refusal sampled out (said when it starts, counted when it
|
|
339
|
+
# ends) -- and since 0.3.7 that
|
|
360
340
|
# shutdown is the only delivery a rake task or runner gets. Behind
|
|
361
341
|
# RAILWATCH_DEBUG that loss was invisible: a cron job whose exception
|
|
362
342
|
# never reached the platform looked exactly like one that had nothing to
|
|
@@ -617,45 +597,16 @@ module Railwatch
|
|
|
617
597
|
end
|
|
618
598
|
|
|
619
599
|
require "railwatch/subscribers"
|
|
620
|
-
require "railwatch/dashboard_assets"
|
|
621
600
|
|
|
622
601
|
module Railwatch
|
|
623
|
-
#
|
|
624
|
-
#
|
|
625
|
-
|
|
626
|
-
# every gem update, from the gem's own history, the way Active Storage
|
|
627
|
-
# migrates its tables. Nothing is copied into the app.
|
|
628
|
-
def self.migrations_path(database)
|
|
629
|
-
File.expand_path("../db/#{database}_migrate", __dir__)
|
|
630
|
-
end
|
|
631
|
-
|
|
632
|
-
# Where links the models build (a trace from an execution page, a saved
|
|
633
|
-
# view's page) resolve. The engine's own routes serve the embedded
|
|
634
|
-
# dashboard; a host that mounts these pages on its own routes, as
|
|
635
|
-
# Railwatch Cloud does, points this at them.
|
|
636
|
-
def self.url_helpers
|
|
637
|
-
@url_helpers || Engine.routes.url_helpers
|
|
638
|
-
end
|
|
639
|
-
|
|
640
|
-
def self.url_helpers=(helpers)
|
|
641
|
-
@url_helpers = helpers
|
|
642
|
-
end
|
|
643
|
-
|
|
644
|
-
# The one browser client, relative to the gem root. The dashboard bundle
|
|
645
|
-
# is built from it and the install generator copies it into a host.
|
|
646
|
-
BROWSER_CLIENT = "app/frontend/lib/railwatch.ts"
|
|
602
|
+
# The Inertia browser client, relative to the gem root. The install
|
|
603
|
+
# generator copies it into a host's app/frontend/lib.
|
|
604
|
+
BROWSER_CLIENT = "lib/generators/railwatch/install/templates/railwatch.ts"
|
|
647
605
|
|
|
648
606
|
def self.browser_client_path
|
|
649
607
|
File.expand_path("../#{BROWSER_CLIENT}", __dir__)
|
|
650
608
|
end
|
|
651
609
|
|
|
652
|
-
# The dashboard's frontend source. Railwatch Cloud builds its bundle from
|
|
653
|
-
# this directory plus its own hosting pages, so there is one copy of every
|
|
654
|
-
# dashboard page and component.
|
|
655
|
-
def self.frontend_path
|
|
656
|
-
File.expand_path("../app/frontend", __dir__)
|
|
657
|
-
end
|
|
658
|
-
|
|
659
610
|
# One realistic wire record per record type, exactly as the gem's own
|
|
660
611
|
# subscribers emit it, generated by spec/railwatch/wire_fixtures_spec.rb.
|
|
661
612
|
# A receiver tests its mapper against these instead of hand-writing what
|
|
@@ -667,12 +618,5 @@ module Railwatch
|
|
|
667
618
|
def self.wire_fixtures
|
|
668
619
|
@wire_fixtures ||= JSON.parse(File.read(wire_fixtures_path)).freeze
|
|
669
620
|
end
|
|
670
|
-
|
|
671
|
-
# Raised when the engine's models are used in an app whose database.yml
|
|
672
|
-
# has no entry for them. They must never fall back to the host's primary
|
|
673
|
-
# connection: the telemetry tables are unprefixed (`sessions`, `visits`,
|
|
674
|
-
# `people`, `notifications`, `logs`), so a query there would read, and a
|
|
675
|
-
# write would corrupt, the application's own tables.
|
|
676
|
-
class DatabaseNotConfigured < StandardError; end
|
|
677
621
|
end
|
|
678
622
|
require "railwatch/engine" if defined?(Rails::Engine)
|
|
@@ -28,130 +28,6 @@ module Railwatch
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
# What the embedded telemetry database is costing on disk, and the one-time
|
|
32
|
-
# conversion that lets pruning give that cost back.
|
|
33
|
-
#
|
|
34
|
-
# Deleting rows does not shrink a SQLite file. The pages go on the freelist
|
|
35
|
-
# and are reused by later inserts, and the only way back to the filesystem
|
|
36
|
-
# is auto_vacuum=incremental plus PRAGMA incremental_vacuum -- which
|
|
37
|
-
# PruneTelemetryJob now runs after every prune. A database can only enter
|
|
38
|
-
# that mode while it is still empty (EnableIncrementalVacuum does that for
|
|
39
|
-
# every telemetry database created since it shipped) or through a full
|
|
40
|
-
# VACUUM, which is what this offers to an older one.
|
|
41
|
-
class TelemetryDisk
|
|
42
|
-
# Deliberately pessimistic, and said out loud as an estimate: VACUUM
|
|
43
|
-
# rewrites the whole file, and what that costs is the operator's disk,
|
|
44
|
-
# not ours to know.
|
|
45
|
-
VACUUM_BYTES_PER_SECOND = 50 * 1024 * 1024
|
|
46
|
-
|
|
47
|
-
# nil, having said why, for an install with no telemetry database to
|
|
48
|
-
# talk about.
|
|
49
|
-
def self.open
|
|
50
|
-
unless Railwatch.config.local?
|
|
51
|
-
puts "Railwatch reports over HTTP here (transport = :http), so this app has no telemetry database; nothing to vacuum."
|
|
52
|
-
return nil
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
environment = Railwatch::Environment.current
|
|
56
|
-
return new(environment) if environment.with_telemetry { Railwatch::TelemetryRecord.sqlite? }
|
|
57
|
-
|
|
58
|
-
puts "The railwatch_telemetry database is not SQLite; auto_vacuum does not apply."
|
|
59
|
-
nil
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def initialize(environment)
|
|
63
|
-
@environment = environment
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def report
|
|
67
|
-
with_telemetry do
|
|
68
|
-
<<~TEXT.chomp
|
|
69
|
-
Railwatch telemetry database
|
|
70
|
-
file #{path}
|
|
71
|
-
size #{human(file_bytes)}#{" + #{human(wal_bytes)} WAL" if wal_bytes.positive?}
|
|
72
|
-
mode auto_vacuum=#{mode}
|
|
73
|
-
free #{freelist} pages (#{human(freelist * page_size)}) on the freelist
|
|
74
|
-
TEXT
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def advice
|
|
79
|
-
with_telemetry do
|
|
80
|
-
if mode != :incremental
|
|
81
|
-
"\nPruning cannot return space to the filesystem in this mode: every page it frees stays in this file.\n" \
|
|
82
|
-
"`bin/rails railwatch:vacuum` converts the database to incremental auto-vacuum with a full VACUUM. " \
|
|
83
|
-
"That rewrites all #{human(file_bytes)}, needs about that much free disk for the temporary copy, and " \
|
|
84
|
-
"holds the write lock for roughly #{estimate}. Telemetry written while it runs waits for it."
|
|
85
|
-
elsif freelist.positive?
|
|
86
|
-
"\nThe nightly prune returns up to #{human(Railwatch::PruneTelemetryJob::VACUUM_PAGES_PER_SLICE * Railwatch::PruneTelemetryJob::VACUUM_SLICES * page_size)} " \
|
|
87
|
-
"a night on its own. `bin/rails railwatch:vacuum` returns all #{human(freelist * page_size)} now."
|
|
88
|
-
else
|
|
89
|
-
"\nNothing on the freelist: pruning is already returning this database's space as it goes."
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# The whole point of asking explicitly, so this one is not bounded the way
|
|
95
|
-
# the nightly prune is.
|
|
96
|
-
def reclaim!
|
|
97
|
-
with_telemetry do
|
|
98
|
-
before = disk_bytes
|
|
99
|
-
if mode == :incremental
|
|
100
|
-
Railwatch::TelemetryRecord.incremental_vacuum
|
|
101
|
-
else
|
|
102
|
-
puts "\nConverting to incremental auto-vacuum (full VACUUM, roughly #{estimate})..."
|
|
103
|
-
connection.execute("PRAGMA auto_vacuum = incremental")
|
|
104
|
-
connection.execute("VACUUM")
|
|
105
|
-
end
|
|
106
|
-
# A checkpoint that cannot finish says so in its result rather than
|
|
107
|
-
# raising: a reader still on an older snapshot holds the WAL open.
|
|
108
|
-
# Counting only the main file would then report the WAL's bytes as
|
|
109
|
-
# returned while they are still on the disk.
|
|
110
|
-
busy = checkpoint_busy?
|
|
111
|
-
[
|
|
112
|
-
"\nauto_vacuum=#{mode}, #{human(disk_bytes)} on disk including the WAL " \
|
|
113
|
-
"(#{human(before - disk_bytes)} returned), #{freelist} pages left on the freelist.",
|
|
114
|
-
busy ? "The WAL could not be truncated yet -- something is still reading it. " \
|
|
115
|
-
"Its bytes come back at the next checkpoint; run this again if you want to watch it." : nil
|
|
116
|
-
].compact.join("\n")
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
private
|
|
121
|
-
def with_telemetry(&) = @environment.with_telemetry(&)
|
|
122
|
-
|
|
123
|
-
def connection = Railwatch::TelemetryRecord.connection
|
|
124
|
-
|
|
125
|
-
def mode = Railwatch::TelemetryRecord.auto_vacuum_mode
|
|
126
|
-
|
|
127
|
-
def freelist = Railwatch::TelemetryRecord.freelist_pages
|
|
128
|
-
|
|
129
|
-
def page_size = Railwatch::TelemetryRecord.page_size
|
|
130
|
-
|
|
131
|
-
def path = Rails.root.join(Railwatch::TelemetryRecord.connection_db_config.database.to_s)
|
|
132
|
-
|
|
133
|
-
def file_bytes = File.exist?(path) ? File.size(path) : 0
|
|
134
|
-
|
|
135
|
-
# What this database actually occupies. In WAL mode the pages a VACUUM
|
|
136
|
-
# frees are not off the disk until the WAL is checkpointed, so the main
|
|
137
|
-
# file alone understates it -- and, right after a vacuum, flatters it.
|
|
138
|
-
def disk_bytes = file_bytes + wal_bytes
|
|
139
|
-
|
|
140
|
-
# `PRAGMA wal_checkpoint` answers with [busy, log_pages, checkpointed];
|
|
141
|
-
# a non-zero first column means it gave up rather than failed.
|
|
142
|
-
def checkpoint_busy?
|
|
143
|
-
connection.select_rows("PRAGMA wal_checkpoint(TRUNCATE)").dig(0, 0).to_i != 0
|
|
144
|
-
rescue StandardError
|
|
145
|
-
false
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def wal_bytes = File.exist?("#{path}-wal") ? File.size("#{path}-wal") : 0
|
|
149
|
-
|
|
150
|
-
def estimate = ActiveSupport::Duration.build([ (file_bytes / VACUUM_BYTES_PER_SECOND.to_f).ceil, 1 ].max).inspect
|
|
151
|
-
|
|
152
|
-
def human(bytes) = ActiveSupport::NumberHelper.number_to_human_size(bytes)
|
|
153
|
-
end
|
|
154
|
-
|
|
155
31
|
# Where this install's platform lives, derived from config.ingest_url:
|
|
156
32
|
# railwatch:token and railwatch:mcp point at the same host the gem already
|
|
157
33
|
# ships to, so a self-hosted app never gets told to visit railwatch.rebulk.com.
|
|
@@ -189,10 +65,6 @@ namespace :railwatch do
|
|
|
189
65
|
|
|
190
66
|
desc "Check that the app can reach Railwatch with the configured token"
|
|
191
67
|
task status: :environment do
|
|
192
|
-
if Railwatch.config.local?
|
|
193
|
-
puts "Railwatch OK: embedded (telemetry in this app's railwatch_telemetry database; dashboard at /railwatch)"
|
|
194
|
-
next
|
|
195
|
-
end
|
|
196
68
|
unless Railwatch.config.token.present?
|
|
197
69
|
abort "RAILWATCH_TOKEN is not set"
|
|
198
70
|
end
|
|
@@ -214,146 +86,6 @@ namespace :railwatch do
|
|
|
214
86
|
ok
|
|
215
87
|
end
|
|
216
88
|
|
|
217
|
-
if config.local?
|
|
218
|
-
# Embedded: no token and no ingest host. What can go wrong instead is
|
|
219
|
-
# the two databases the engine writes to and the jobs that derive
|
|
220
|
-
# rollups and issues from them.
|
|
221
|
-
check.call(true, "transport", "local (telemetry stays in this app; dashboard at the engine mount)")
|
|
222
|
-
# Mirroring is opt-in, so silence means "not asked for". Asked for and
|
|
223
|
-
# not working is the case worth failing on: the operator believes their
|
|
224
|
-
# telemetry is leaving the box and it is not.
|
|
225
|
-
if config.export_enabled
|
|
226
|
-
problem = config.export_problem
|
|
227
|
-
check.call(problem.nil?, "export", problem || "mirroring to #{config.resolved_export_url}", fatal: true)
|
|
228
|
-
if problem.nil?
|
|
229
|
-
status = Railwatch::Export::Outbox.new(config, Railwatch::Environment.current)
|
|
230
|
-
.then { |outbox| Railwatch::Environment.current.with_telemetry { outbox.status } }
|
|
231
|
-
check.call(status[:state] == "ready", "export destination",
|
|
232
|
-
status[:state] == "ready" ? "#{status[:queued_deliveries]} queued (#{status[:queued_bytes]} bytes), producer #{status[:producer_id]}" : "#{status[:state]}: #{status[:reason]} (bin/rails railwatch:export:rebind to clear)")
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
%w[railwatch railwatch_telemetry].each do |name|
|
|
236
|
-
configured = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name)
|
|
237
|
-
check.call(!configured.nil?, "#{name} database",
|
|
238
|
-
configured ? configured.database : "not in config/database.yml (bin/rails generate railwatch:install)",
|
|
239
|
-
fatal: true)
|
|
240
|
-
end
|
|
241
|
-
{ "railwatch_telemetry" => Railwatch::TelemetryRecord, "railwatch" => Railwatch::ApplicationRecord }.each do |name, base|
|
|
242
|
-
pending = begin
|
|
243
|
-
base.connection_pool.migration_context.open.pending_migrations.map(&:version)
|
|
244
|
-
rescue StandardError => e
|
|
245
|
-
e.message
|
|
246
|
-
end
|
|
247
|
-
check.call(pending == [], "#{name} migrations",
|
|
248
|
-
case pending
|
|
249
|
-
when [] then "up to date"
|
|
250
|
-
when Array then "#{pending.size} pending (bin/rails db:prepare)"
|
|
251
|
-
else "cannot check: #{pending}"
|
|
252
|
-
end, fatal: true)
|
|
253
|
-
end
|
|
254
|
-
# An install created before 0.3.5 is in auto_vacuum=none, where the
|
|
255
|
-
# nightly prune's reclaim is a silent no-op and the file only grows.
|
|
256
|
-
# Nothing converts it on its own, because that needs a full VACUUM with
|
|
257
|
-
# the write lock held -- so the check people actually run is where it
|
|
258
|
-
# has to be said, rather than leaving them to notice the disk.
|
|
259
|
-
if Railwatch.config.local? && Railwatch::TelemetryRecord.sqlite?
|
|
260
|
-
vacuum_mode = begin
|
|
261
|
-
Railwatch::Environment.current.with_telemetry { Railwatch::TelemetryRecord.auto_vacuum_mode }
|
|
262
|
-
rescue StandardError => e
|
|
263
|
-
e.message
|
|
264
|
-
end
|
|
265
|
-
check.call(vacuum_mode == :incremental, "telemetry disk",
|
|
266
|
-
case vacuum_mode
|
|
267
|
-
when :incremental then "auto_vacuum=incremental; the nightly prune returns freed pages"
|
|
268
|
-
when Symbol then "auto_vacuum=#{vacuum_mode}: pruned pages stay in the file " \
|
|
269
|
-
"(bin/rails railwatch:vacuum:status)"
|
|
270
|
-
else "cannot check: #{vacuum_mode}"
|
|
271
|
-
end)
|
|
272
|
-
end
|
|
273
|
-
# The maintenance clock runs in web and worker processes, not in this
|
|
274
|
-
# rake process, so what can be checked here is whether one has ticked.
|
|
275
|
-
last_tick = begin
|
|
276
|
-
Railwatch::MaintenanceTask.last_tick_at
|
|
277
|
-
rescue StandardError
|
|
278
|
-
nil
|
|
279
|
-
end
|
|
280
|
-
check.call(last_tick && last_tick > 10.minutes.ago, "maintenance",
|
|
281
|
-
if last_tick
|
|
282
|
-
"last tick #{ActiveSupport::Duration.build((Time.current - last_tick).round).inspect} ago"
|
|
283
|
-
else
|
|
284
|
-
"no tick recorded yet (runs inside the app's web and worker processes, not in rake)"
|
|
285
|
-
end)
|
|
286
|
-
socket_path = config.writer_socket_path
|
|
287
|
-
if socket_path && !Railwatch::Writer.usable_path?(socket_path)
|
|
288
|
-
check.call(false, "writer process",
|
|
289
|
-
"socket path is #{socket_path.bytesize} bytes, over Linux's #{Railwatch::Writer::MAX_SOCKET_PATH}; " \
|
|
290
|
-
"set RAILWATCH_WRITER_SOCKET to a shorter path (e.g. /tmp/#{Rails.application.class.module_parent_name.parameterize}-railwatch.sock)")
|
|
291
|
-
elsif socket_path
|
|
292
|
-
listening = Railwatch::Writer.listening?(socket_path)
|
|
293
|
-
puma_rb = Rails.root.join("config/puma.rb")
|
|
294
|
-
plugged = puma_rb.exist? && puma_rb.read.include?("plugin :railwatch")
|
|
295
|
-
# Not listening is only fine when the app is not running: this task
|
|
296
|
-
# is its own process and cannot tell, so it says which case it is
|
|
297
|
-
# looking at instead of calling a missing writer a pass.
|
|
298
|
-
check.call(listening, "writer process",
|
|
299
|
-
if listening
|
|
300
|
-
"listening at #{socket_path}"
|
|
301
|
-
elsif plugged
|
|
302
|
-
"not listening at #{socket_path} right now. Expected if the app is stopped; if it is serving, " \
|
|
303
|
-
"the writer died or `plugin :railwatch` never activated (check the Puma log)"
|
|
304
|
-
else
|
|
305
|
-
"not configured: add `plugin :railwatch` to config/puma.rb so batches are written outside the " \
|
|
306
|
-
"processes serving requests"
|
|
307
|
-
end)
|
|
308
|
-
# A socket that answers is a process that is alive; the ledger is
|
|
309
|
-
# what says it is doing its job. Nothing written in a while with the
|
|
310
|
-
# app serving traffic is a writer that is stuck or a worker that is
|
|
311
|
-
# not reaching it.
|
|
312
|
-
if listening
|
|
313
|
-
last_write = begin
|
|
314
|
-
Railwatch::Environment.current.with_telemetry { Railwatch::Telemetry::IngestBatch.maximum(:received_at) }
|
|
315
|
-
rescue StandardError
|
|
316
|
-
nil
|
|
317
|
-
end
|
|
318
|
-
check.call(last_write && last_write > 5.minutes.ago, "last write",
|
|
319
|
-
if last_write
|
|
320
|
-
"#{ActiveSupport::Duration.build((Time.current - last_write).round).inspect} ago"
|
|
321
|
-
else
|
|
322
|
-
"no batch written yet"
|
|
323
|
-
end)
|
|
324
|
-
end
|
|
325
|
-
end
|
|
326
|
-
# HTTP Basic is on and closed until credentials exist; a deploy that
|
|
327
|
-
# forgot gets a 401, not a public page, and this says so first.
|
|
328
|
-
gate = config.dashboard_gate
|
|
329
|
-
check.call(gate != :undeclared && !(gate == :basic && !config.http_basic_auth_configured? && !config.http_basic_auth_waived?),
|
|
330
|
-
"dashboard access",
|
|
331
|
-
case gate
|
|
332
|
-
when :basic
|
|
333
|
-
if config.http_basic_auth_configured?
|
|
334
|
-
"HTTP Basic, user #{config.http_basic_auth_user}"
|
|
335
|
-
elsif config.http_basic_auth_waived?
|
|
336
|
-
"open in development. Production stays closed until you run " \
|
|
337
|
-
"`RAILS_ENV=production bin/rails railwatch:authentication:configure`"
|
|
338
|
-
else
|
|
339
|
-
"closed: HTTP Basic is on with no credentials, so every dashboard request is 401. " \
|
|
340
|
-
"Run `bin/rails railwatch:authentication:configure`"
|
|
341
|
-
end
|
|
342
|
-
when :controller then "your own: c.base_controller_class = #{config.base_controller_class}"
|
|
343
|
-
when :resolver then "your own: c.dashboard_user decides, and live updates follow it"
|
|
344
|
-
when :open then "deliberately open: anyone who can reach the mount can read it (c.dashboard_open)"
|
|
345
|
-
else
|
|
346
|
-
"undeclared: HTTP Basic is off and no base_controller_class, dashboard_user or dashboard_open " \
|
|
347
|
-
"is set. If a routes constraint gates the mount, say so with `c.dashboard_open = true` " \
|
|
348
|
-
"(it also enables live updates); otherwise the dashboard is public"
|
|
349
|
-
end)
|
|
350
|
-
check.call(!Railwatch::JsonCompat.broken?, "json compatibility",
|
|
351
|
-
Railwatch::JsonCompat.broken? ? Railwatch::JsonCompat.advice : "json #{Railwatch::JsonCompat.json_version} decodes on Rails #{Rails.version}")
|
|
352
|
-
recurring = Rails.root.join("config/recurring.yml")
|
|
353
|
-
leftover = recurring.exist? && recurring.read.include?("Railwatch::")
|
|
354
|
-
check.call(!leftover, "recurring.yml",
|
|
355
|
-
leftover ? "Railwatch::* jobs listed in config/recurring.yml are no longer needed in embedded mode; remove them" : "no Railwatch entries (none needed)")
|
|
356
|
-
else
|
|
357
89
|
token = config.token.to_s
|
|
358
90
|
check.call(!token.empty?, "token",
|
|
359
91
|
token.empty? ? "RAILWATCH_TOKEN is not set" : Railwatch::SecretSafety.token_preview(token),
|
|
@@ -380,7 +112,6 @@ namespace :railwatch do
|
|
|
380
112
|
|
|
381
113
|
check.call(Railwatch::Transport::Http.new(config).ping, "ingest reachable",
|
|
382
114
|
"GET #{URI.join(config.ingest_url, '/ingest/ping')}", fatal: true)
|
|
383
|
-
end
|
|
384
115
|
|
|
385
116
|
middleware = Rails.application.middleware.map(&:name)
|
|
386
117
|
position = middleware.index("Railwatch::Middleware::Request")
|
|
@@ -447,47 +178,6 @@ namespace :railwatch do
|
|
|
447
178
|
puts "\nRailwatch is wired up."
|
|
448
179
|
end
|
|
449
180
|
|
|
450
|
-
# Disk. `railwatch:vacuum:status` only reads; `railwatch:vacuum` is the
|
|
451
|
-
# one-time conversion a database created before EnableIncrementalVacuum
|
|
452
|
-
# needs, and it is a rake task rather than anything automatic because it
|
|
453
|
-
# rewrites the whole file with the write lock held.
|
|
454
|
-
desc "Report the telemetry database's size, free pages, and whether pruning can return them to the filesystem"
|
|
455
|
-
task "vacuum:status" => :environment do
|
|
456
|
-
disk = Railwatch::TelemetryDisk.open or next
|
|
457
|
-
puts disk.report
|
|
458
|
-
puts disk.advice
|
|
459
|
-
end
|
|
460
|
-
|
|
461
|
-
desc "Reclaim disk from the telemetry database. Converts it to incremental auto-vacuum if needed -- a full VACUUM, which locks the file"
|
|
462
|
-
task vacuum: :environment do
|
|
463
|
-
disk = Railwatch::TelemetryDisk.open or next
|
|
464
|
-
puts disk.report
|
|
465
|
-
puts disk.reclaim!
|
|
466
|
-
end
|
|
467
|
-
|
|
468
|
-
namespace :export do
|
|
469
|
-
desc "Show what the export queue is holding and whether it can send"
|
|
470
|
-
task status: :environment do
|
|
471
|
-
env = Railwatch::Environment.current
|
|
472
|
-
status = env.with_telemetry { Railwatch::Export::Outbox.new(Railwatch.config, env).status }
|
|
473
|
-
status.each { |key, value| puts "#{key}: #{value}" }
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
desc "Clear a credential block, abandoning work admitted under the old token"
|
|
477
|
-
task rebind: :environment do
|
|
478
|
-
env = Railwatch::Environment.current
|
|
479
|
-
discarded = env.with_telemetry { Railwatch::Export::Outbox.new(Railwatch.config, env).rebind! }
|
|
480
|
-
puts discarded ? "rebound; #{discarded} queued deliveries abandoned" : "export is not configured"
|
|
481
|
-
end
|
|
482
|
-
|
|
483
|
-
desc "Abandon everything queued for export without contacting the receiver"
|
|
484
|
-
task discard: :environment do
|
|
485
|
-
env = Railwatch::Environment.current
|
|
486
|
-
count = env.with_telemetry { Railwatch::Export::Outbox.new(Railwatch.config, env).discard_all! }
|
|
487
|
-
puts "#{count} queued deliveries abandoned"
|
|
488
|
-
end
|
|
489
|
-
end
|
|
490
|
-
|
|
491
181
|
desc "Print where to create an ingest token for this app's Railwatch platform"
|
|
492
182
|
task token: :environment do
|
|
493
183
|
base = Railwatch::Endpoints.new(Railwatch.config)
|
|
@@ -583,9 +273,10 @@ namespace :railwatch do
|
|
|
583
273
|
|
|
584
274
|
# The same shape as `mission_control:jobs:authentication:configure`: asks
|
|
585
275
|
# for a user and password and writes them to the current environment's
|
|
586
|
-
# credentials under `railwatch:`, which the engine reads at boot
|
|
276
|
+
# credentials under `railwatch:`, which the engine reads at boot and
|
|
277
|
+
# railwatch-server's dashboard proxy checks.
|
|
587
278
|
# RAILS_ENV=production bin/rails railwatch:authentication:configure
|
|
588
|
-
desc "Configure HTTP Basic authentication for
|
|
279
|
+
desc "Configure HTTP Basic authentication for railwatch-server's dashboard (writes Rails credentials)"
|
|
589
280
|
task "authentication:configure" => :environment do
|
|
590
281
|
Railwatch::Authentication.configure
|
|
591
282
|
end
|
|
@@ -593,19 +284,6 @@ namespace :railwatch do
|
|
|
593
284
|
desc "Send deploy metadata to Railwatch: rake railwatch:deploy[ref,name,url]"
|
|
594
285
|
task :deploy, [ :ref, :name, :url ] => :environment do |_t, args|
|
|
595
286
|
deploy = Railwatch.config.deploy or abort "RAILWATCH_DEPLOY (or KAMAL_VERSION) is not set"
|
|
596
|
-
if Railwatch.config.local?
|
|
597
|
-
# Embedded: the deploy marker is a row in this app's railwatch database.
|
|
598
|
-
row = Railwatch::Environment.current.deploys.find_or_initialize_by(deploy: deploy.to_s.first(128))
|
|
599
|
-
row.assign_attributes(ref: (args[:ref] || `git rev-parse HEAD 2>/dev/null`.strip).presence&.first(128),
|
|
600
|
-
name: args[:name].presence&.first(255), url: args[:url].presence&.first(1024),
|
|
601
|
-
server: Railwatch.config.server, deployed_at: Time.current,
|
|
602
|
-
commits: Railwatch::DeployMetadata.commits,
|
|
603
|
-
detail: { performer: ENV["KAMAL_PERFORMER"], destination: ENV["KAMAL_DESTINATION"],
|
|
604
|
-
service: ENV["KAMAL_SERVICE"] }.compact)
|
|
605
|
-
row.save!
|
|
606
|
-
puts "Deploy #{deploy} recorded"
|
|
607
|
-
next
|
|
608
|
-
end
|
|
609
287
|
abort "Plain HTTP ingest is disabled; use HTTPS or set RAILWATCH_ALLOW_HTTP=true" unless Railwatch.config.ingest_url_allowed?
|
|
610
288
|
uri = URI.join(Railwatch.config.ingest_url, "/ingest/deploys")
|
|
611
289
|
req = Net::HTTP::Post.new(uri)
|
data/llms.txt
CHANGED
|
@@ -1,44 +1,42 @@
|
|
|
1
1
|
# Railwatch
|
|
2
2
|
|
|
3
|
-
> Railwatch is a Ruby gem that instruments a Rails application end
|
|
4
|
-
> requests, jobs, scheduled tasks, rake/runner commands, database
|
|
5
|
-
> N+1s, exceptions, cache, mail, notifications, broadcasts,
|
|
6
|
-
> Active Storage, view renders, RubyLLM calls, and logs — links
|
|
7
|
-
> them into a single execution tree by `execution_id`/`trace_id`,
|
|
8
|
-
> them from a background thread
|
|
9
|
-
> the default)
|
|
10
|
-
> separate
|
|
11
|
-
>
|
|
12
|
-
>
|
|
13
|
-
>
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
>
|
|
17
|
-
>
|
|
18
|
-
>
|
|
19
|
-
> and the Inertia browser client where the app has them, and wires the test
|
|
3
|
+
> Railwatch is a Ruby gem (the agent) that instruments a Rails application end
|
|
4
|
+
> to end — requests, jobs, scheduled tasks, rake/runner commands, database
|
|
5
|
+
> queries and N+1s, exceptions, cache, mail, notifications, broadcasts,
|
|
6
|
+
> outgoing HTTP, Active Storage, view renders, RubyLLM calls, and logs — links
|
|
7
|
+
> every one of them into a single execution tree by `execution_id`/`trace_id`,
|
|
8
|
+
> and sends them from a background thread over HTTPS to a Railwatch server:
|
|
9
|
+
> Railwatch Cloud (the default), a self-hosted railwatch-rs, or the one the
|
|
10
|
+
> separate `railwatch-server` gem runs next to Puma for an embedded dashboard
|
|
11
|
+
> at `/railwatch`. It replaces a separate APM and a separate error tracker
|
|
12
|
+
> with one gem and one configuration block, costs under a millisecond of CPU
|
|
13
|
+
> per request, and never writes to the application's primary database.
|
|
14
|
+
> Install is `bundle add railwatch` followed by
|
|
15
|
+
> `bin/rails generate railwatch:install` (`--prompt-token`, `--url` for a
|
|
16
|
+
> self-hosted server, `--kamal-secrets`), which writes the initializer,
|
|
17
|
+
> mounts the engine for the browser beacon, adds a Kamal post-deploy hook and
|
|
18
|
+
> the Inertia browser client where the app has them, and wires the test
|
|
20
19
|
> matchers. `bin/rails railwatch:doctor` prints a ✓/✗ line for every piece.
|
|
21
20
|
|
|
22
21
|
## Docs
|
|
23
22
|
|
|
24
|
-
- [README](README.md): what Railwatch is,
|
|
25
|
-
- [Getting started](docs/getting-started.md): the
|
|
26
|
-
- [
|
|
27
|
-
- [Configuration](docs/configuration.md): every configuration option and its `RAILWATCH_*` environment variable, plus the full public facade, redaction, rejection, transport and buffering behaviour, and the rake tasks.
|
|
23
|
+
- [README](README.md): what Railwatch is, where telemetry goes (Cloud, self-hosted, railwatch-server), the install, and where each document fits.
|
|
24
|
+
- [Getting started](docs/getting-started.md): the install and where its token comes from, embedded with the `railwatch-server` gem, moving from 0.x embedded mode, the three optional lines, and deploying under Kamal, Docker/Heroku/Render, or no deploy tool at all.
|
|
25
|
+
- [Configuration](docs/configuration.md): every configuration option and its `RAILWATCH_*` environment variable, plus the full public facade, redaction, rejection, transport and buffering behaviour, the dashboard gate railwatch-server uses, and the rake tasks.
|
|
28
26
|
- [Record types](docs/records.md): every record type the gem ships and every attribute on it, including RubyLLM calls (tokens, cost, finish reason, tool calls, workflows), sourced from the code that builds it.
|
|
29
27
|
- [Testing](docs/testing.md): the RSpec and Minitest matchers (`have_railwatch_queries`, `have_railwatch_n_plus_one`, `record_railwatch_span`, `record_railwatch_exception`, `have_railwatch_outgoing_requests`, `have_railwatch_http_in_transaction`) and a CI performance-gate recipe.
|
|
30
28
|
- [Production source maps](docs/source-maps.md): hidden Vite maps, private upload before publishing assets, safe opt-in deletion, resolved browser stacks and default issue grouping.
|
|
31
|
-
- [AI assistants and MCP](docs/ai-and-mcp.md):
|
|
29
|
+
- [AI assistants and MCP](docs/ai-and-mcp.md): the server's MCP endpoint, how to get a token, paste-ready client configuration for Claude Code, Claude Desktop, Cursor, VS Code, and Zed, and every tool, prompt, and resource the server exposes.
|
|
32
30
|
- [Replacing Sentry](docs/replacing-sentry.md): a step-by-step migration — removing the gems, porting each option, rewriting each call site, breadcrumbs, spans, profiling, attachments, `before_send`, fingerprints, and release health.
|
|
33
31
|
- [Coming from Laravel Nightwatch](docs/replacing-nightwatch.md): the record-type mapping, what "execution" means in Railwatch, sampling parity, and the facade method names in Ruby.
|
|
34
|
-
- [Self-hosting](docs/self-hosting.md): pointing the gem at a self-hosted
|
|
35
|
-
- [Troubleshooting](docs/troubleshooting.md): every `railwatch:doctor` line
|
|
32
|
+
- [Self-hosting](docs/self-hosting.md): pointing the gem at a self-hosted railwatch-rs, creating a token there, and checking the connection.
|
|
33
|
+
- [Troubleshooting](docs/troubleshooting.md): every `railwatch:doctor` line and the failure modes behind them — no records, doubled scheduled tasks, WebMock in specs, Puma fork, tail-sampling memory, missing profiler, missing deploy marker, Kamal hook, log search, empty tenants.
|
|
36
34
|
- [Security](docs/security.md): transport, capture defaults, the browser beacon, token handling, and application responsibilities.
|
|
37
35
|
- [FAQ](docs/faq.md): overhead numbers and how they are measured, retention, what is redacted by default versus opt-in, SQLite, and what happens when the platform is unreachable.
|
|
38
36
|
|
|
39
37
|
## For coding agents
|
|
40
38
|
|
|
41
|
-
- [AGENTS.md](AGENTS.md): how to install and use Railwatch from inside a Rails app — the facade methods, the spec matchers, `railwatch:doctor`, and the
|
|
39
|
+
- [AGENTS.md](AGENTS.md): how to install and use Railwatch from inside a Rails app — the facade methods, the spec matchers, `railwatch:doctor`, and the MCP hookup.
|
|
42
40
|
|
|
43
41
|
## Optional
|
|
44
42
|
|