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.
Files changed (452) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +37 -33
  3. data/CHANGELOG.md +50 -0
  4. data/README.md +51 -60
  5. data/config/routes.rb +0 -59
  6. data/docs/ai-and-mcp.md +9 -8
  7. data/docs/configuration.md +35 -66
  8. data/docs/faq.md +17 -25
  9. data/docs/getting-started.md +105 -117
  10. data/docs/replacing-nightwatch.md +8 -7
  11. data/docs/replacing-sentry.md +12 -16
  12. data/docs/security.md +14 -20
  13. data/docs/self-hosting.md +24 -22
  14. data/docs/testing.md +6 -14
  15. data/docs/troubleshooting.md +12 -46
  16. data/lib/generators/railwatch/install/install_generator.rb +12 -240
  17. data/lib/generators/railwatch/install/templates/initializer.rb.tt +10 -40
  18. data/lib/generators/railwatch/install/templates/post-deploy +0 -7
  19. data/lib/railwatch/authentication.rb +6 -5
  20. data/lib/railwatch/configuration.rb +21 -151
  21. data/lib/railwatch/engine.rb +9 -118
  22. data/lib/railwatch/patches.rb +0 -11
  23. data/lib/railwatch/reporter.rb +3 -19
  24. data/lib/railwatch/subscribers/process_info.rb +1 -2
  25. data/lib/railwatch/version.rb +1 -1
  26. data/lib/railwatch.rb +4 -62
  27. data/lib/tasks/railwatch_tasks.rake +3 -325
  28. data/llms.txt +23 -25
  29. metadata +3 -453
  30. data/app/channels/railwatch/environment_channel.rb +0 -62
  31. data/app/controllers/concerns/railwatch/telemetry_identity.rb +0 -25
  32. data/app/controllers/railwatch/alerts_controller.rb +0 -48
  33. data/app/controllers/railwatch/anomaly_rules_controller.rb +0 -31
  34. data/app/controllers/railwatch/attachments_controller.rb +0 -22
  35. data/app/controllers/railwatch/broadcasts_controller.rb +0 -15
  36. data/app/controllers/railwatch/cache_events_controller.rb +0 -30
  37. data/app/controllers/railwatch/commands_controller.rb +0 -16
  38. data/app/controllers/railwatch/comments_controller.rb +0 -11
  39. data/app/controllers/railwatch/dashboard_controller.rb +0 -70
  40. data/app/controllers/railwatch/deploys_controller.rb +0 -34
  41. data/app/controllers/railwatch/deprecations_controller.rb +0 -54
  42. data/app/controllers/railwatch/environment_scoped.rb +0 -81
  43. data/app/controllers/railwatch/exceptions_controller.rb +0 -52
  44. data/app/controllers/railwatch/executions_controller.rb +0 -13
  45. data/app/controllers/railwatch/issues_controller.rb +0 -258
  46. data/app/controllers/railwatch/jobs_controller.rb +0 -63
  47. data/app/controllers/railwatch/llm_calls_controller.rb +0 -124
  48. data/app/controllers/railwatch/logs_controller.rb +0 -40
  49. data/app/controllers/railwatch/mails_controller.rb +0 -19
  50. data/app/controllers/railwatch/notifications_controller.rb +0 -11
  51. data/app/controllers/railwatch/outgoing_requests_controller.rb +0 -19
  52. data/app/controllers/railwatch/overview_controller.rb +0 -38
  53. data/app/controllers/railwatch/people_controller.rb +0 -25
  54. data/app/controllers/railwatch/processes_controller.rb +0 -33
  55. data/app/controllers/railwatch/profiles_controller.rb +0 -65
  56. data/app/controllers/railwatch/queries_controller.rb +0 -59
  57. data/app/controllers/railwatch/releases_controller.rb +0 -75
  58. data/app/controllers/railwatch/requests_controller.rb +0 -37
  59. data/app/controllers/railwatch/saved_views_controller.rb +0 -53
  60. data/app/controllers/railwatch/scheduled_tasks_controller.rb +0 -45
  61. data/app/controllers/railwatch/spans_controller.rb +0 -47
  62. data/app/controllers/railwatch/storage_ops_controller.rb +0 -15
  63. data/app/controllers/railwatch/tenants_controller.rb +0 -44
  64. data/app/controllers/railwatch/thresholds_controller.rb +0 -47
  65. data/app/controllers/railwatch/traces_controller.rb +0 -72
  66. data/app/controllers/railwatch/transactions_controller.rb +0 -21
  67. data/app/controllers/railwatch/view_renders_controller.rb +0 -28
  68. data/app/controllers/railwatch/visits_controller.rb +0 -53
  69. data/app/frontend/components/app-content.tsx +0 -26
  70. data/app/frontend/components/app-logo-icon.tsx +0 -23
  71. data/app/frontend/components/app-logo.tsx +0 -14
  72. data/app/frontend/components/app-shell.tsx +0 -38
  73. data/app/frontend/components/app-sidebar-header.tsx +0 -54
  74. data/app/frontend/components/app-sidebar.tsx +0 -94
  75. data/app/frontend/components/app-wordmark.tsx +0 -29
  76. data/app/frontend/components/breadcrumbs.tsx +0 -46
  77. data/app/frontend/components/icon.tsx +0 -16
  78. data/app/frontend/components/input-error.tsx +0 -18
  79. data/app/frontend/components/nav-footer.tsx +0 -46
  80. data/app/frontend/components/nav-main.tsx +0 -41
  81. data/app/frontend/components/nav-user.tsx +0 -50
  82. data/app/frontend/components/railwatch/chart-hover.tsx +0 -34
  83. data/app/frontend/components/railwatch/chart-panel.tsx +0 -170
  84. data/app/frontend/components/railwatch/code.tsx +0 -28
  85. data/app/frontend/components/railwatch/command-palette.tsx +0 -132
  86. data/app/frontend/components/railwatch/copy-block.tsx +0 -52
  87. data/app/frontend/components/railwatch/copy-id.tsx +0 -49
  88. data/app/frontend/components/railwatch/cursor-load-more.tsx +0 -39
  89. data/app/frontend/components/railwatch/data-table.tsx +0 -195
  90. data/app/frontend/components/railwatch/digit-roll.tsx +0 -312
  91. data/app/frontend/components/railwatch/empty-state.tsx +0 -159
  92. data/app/frontend/components/railwatch/env-nav.tsx +0 -130
  93. data/app/frontend/components/railwatch/env-switcher.tsx +0 -145
  94. data/app/frontend/components/railwatch/filter-bar.tsx +0 -153
  95. data/app/frontend/components/railwatch/flamegraph.tsx +0 -324
  96. data/app/frontend/components/railwatch/frames.tsx +0 -295
  97. data/app/frontend/components/railwatch/json-viewer.tsx +0 -159
  98. data/app/frontend/components/railwatch/live-dot.tsx +0 -78
  99. data/app/frontend/components/railwatch/live-toggle.tsx +0 -76
  100. data/app/frontend/components/railwatch/metric-chart.tsx +0 -112
  101. data/app/frontend/components/railwatch/mobile-page-switcher.tsx +0 -89
  102. data/app/frontend/components/railwatch/onboarding.tsx +0 -91
  103. data/app/frontend/components/railwatch/origin-identity.tsx +0 -84
  104. data/app/frontend/components/railwatch/page-header.tsx +0 -42
  105. data/app/frontend/components/railwatch/percentile-picker.tsx +0 -18
  106. data/app/frontend/components/railwatch/relative-time.tsx +0 -18
  107. data/app/frontend/components/railwatch/saved-views.tsx +0 -232
  108. data/app/frontend/components/railwatch/segmented.tsx +0 -64
  109. data/app/frontend/components/railwatch/series-chart.tsx +0 -400
  110. data/app/frontend/components/railwatch/shortcuts-dialog.tsx +0 -89
  111. data/app/frontend/components/railwatch/signal-toast.tsx +0 -121
  112. data/app/frontend/components/railwatch/sort-header.tsx +0 -37
  113. data/app/frontend/components/railwatch/source-link.tsx +0 -57
  114. data/app/frontend/components/railwatch/sparkline-cell.tsx +0 -14
  115. data/app/frontend/components/railwatch/sql-block.tsx +0 -116
  116. data/app/frontend/components/railwatch/stat.tsx +0 -185
  117. data/app/frontend/components/railwatch/status-badge.tsx +0 -79
  118. data/app/frontend/components/railwatch/step-picker.tsx +0 -57
  119. data/app/frontend/components/railwatch/timeline.tsx +0 -405
  120. data/app/frontend/components/railwatch/utilisation-bar.tsx +0 -44
  121. data/app/frontend/components/railwatch/window-picker.tsx +0 -147
  122. data/app/frontend/components/ui/avatar.tsx +0 -51
  123. data/app/frontend/components/ui/badge.tsx +0 -46
  124. data/app/frontend/components/ui/breadcrumb.tsx +0 -109
  125. data/app/frontend/components/ui/button.tsx +0 -60
  126. data/app/frontend/components/ui/card.tsx +0 -92
  127. data/app/frontend/components/ui/chart.tsx +0 -374
  128. data/app/frontend/components/ui/checkbox.tsx +0 -32
  129. data/app/frontend/components/ui/collapsible.tsx +0 -31
  130. data/app/frontend/components/ui/command.tsx +0 -182
  131. data/app/frontend/components/ui/dialog.tsx +0 -158
  132. data/app/frontend/components/ui/dropdown-menu.tsx +0 -255
  133. data/app/frontend/components/ui/hover-card.tsx +0 -42
  134. data/app/frontend/components/ui/input.tsx +0 -21
  135. data/app/frontend/components/ui/label.tsx +0 -24
  136. data/app/frontend/components/ui/popover.tsx +0 -87
  137. data/app/frontend/components/ui/select.tsx +0 -185
  138. data/app/frontend/components/ui/separator.tsx +0 -28
  139. data/app/frontend/components/ui/sheet.tsx +0 -137
  140. data/app/frontend/components/ui/sidebar.tsx +0 -726
  141. data/app/frontend/components/ui/skeleton.tsx +0 -13
  142. data/app/frontend/components/ui/sonner.tsx +0 -41
  143. data/app/frontend/components/ui/table.tsx +0 -114
  144. data/app/frontend/components/ui/tabs.tsx +0 -91
  145. data/app/frontend/components/ui/textarea.tsx +0 -18
  146. data/app/frontend/components/ui/tooltip.tsx +0 -61
  147. data/app/frontend/components/user-info.tsx +0 -32
  148. data/app/frontend/components/user-menu-content.tsx +0 -94
  149. data/app/frontend/entrypoints/application.css +0 -244
  150. data/app/frontend/entrypoints/inertia.tsx +0 -92
  151. data/app/frontend/fonts/CommitMono-Bold.woff2 +0 -0
  152. data/app/frontend/fonts/CommitMono-Regular.woff2 +0 -0
  153. data/app/frontend/fonts/Roboto-Black.woff2 +0 -0
  154. data/app/frontend/fonts/Roboto-Bold.woff2 +0 -0
  155. data/app/frontend/fonts/Roboto-Medium.woff2 +0 -0
  156. data/app/frontend/fonts/Roboto-Regular.woff2 +0 -0
  157. data/app/frontend/hooks/use-appearance.tsx +0 -67
  158. data/app/frontend/hooks/use-clipboard.ts +0 -32
  159. data/app/frontend/hooks/use-flash.tsx +0 -23
  160. data/app/frontend/hooks/use-hotkeys.ts +0 -63
  161. data/app/frontend/hooks/use-initials.tsx +0 -15
  162. data/app/frontend/hooks/use-live.ts +0 -205
  163. data/app/frontend/hooks/use-mobile-navigation.ts +0 -8
  164. data/app/frontend/hooks/use-mobile.ts +0 -21
  165. data/app/frontend/hooks/use-percentile.ts +0 -24
  166. data/app/frontend/hooks/use-reduced-motion.ts +0 -25
  167. data/app/frontend/hooks/use-row-nav.ts +0 -106
  168. data/app/frontend/hooks/use-step.ts +0 -27
  169. data/app/frontend/hooks/use-ticker.ts +0 -12
  170. data/app/frontend/hooks/use-window.ts +0 -58
  171. data/app/frontend/layouts/app/app-sidebar-layout.tsx +0 -107
  172. data/app/frontend/layouts/app-layout.tsx +0 -21
  173. data/app/frontend/layouts/env-layout.tsx +0 -53
  174. data/app/frontend/layouts/persistent-layout.tsx +0 -18
  175. data/app/frontend/lib/cable.ts +0 -10
  176. data/app/frontend/lib/env-pages.ts +0 -236
  177. data/app/frontend/lib/execution-path.ts +0 -55
  178. data/app/frontend/lib/explain.ts +0 -50
  179. data/app/frontend/lib/filter.ts +0 -25
  180. data/app/frontend/lib/flamegraph.ts +0 -155
  181. data/app/frontend/lib/format.ts +0 -71
  182. data/app/frontend/lib/keyboard.ts +0 -18
  183. data/app/frontend/lib/mount.ts +0 -23
  184. data/app/frontend/lib/source-link.ts +0 -217
  185. data/app/frontend/lib/utils.ts +0 -6
  186. data/app/frontend/pages/alerts/index.tsx +0 -171
  187. data/app/frontend/pages/broadcasts/index.tsx +0 -171
  188. data/app/frontend/pages/cache_events/index.tsx +0 -127
  189. data/app/frontend/pages/commands/index.tsx +0 -143
  190. data/app/frontend/pages/deploys/index.tsx +0 -94
  191. data/app/frontend/pages/deploys/show.tsx +0 -309
  192. data/app/frontend/pages/deprecations/index.tsx +0 -176
  193. data/app/frontend/pages/exceptions/index.tsx +0 -271
  194. data/app/frontend/pages/executions/show.tsx +0 -821
  195. data/app/frontend/pages/issues/index.tsx +0 -399
  196. data/app/frontend/pages/issues/show.tsx +0 -1367
  197. data/app/frontend/pages/jobs/index.tsx +0 -381
  198. data/app/frontend/pages/jobs/klass.tsx +0 -215
  199. data/app/frontend/pages/llm_calls/index.tsx +0 -664
  200. data/app/frontend/pages/logs/index.tsx +0 -303
  201. data/app/frontend/pages/mails/index.tsx +0 -218
  202. data/app/frontend/pages/notifications/index.tsx +0 -161
  203. data/app/frontend/pages/outgoing_requests/index.tsx +0 -309
  204. data/app/frontend/pages/overview/show.tsx +0 -333
  205. data/app/frontend/pages/people/index.tsx +0 -102
  206. data/app/frontend/pages/people/show.tsx +0 -149
  207. data/app/frontend/pages/processes/index.tsx +0 -335
  208. data/app/frontend/pages/profiles/index.tsx +0 -110
  209. data/app/frontend/pages/profiles/show.tsx +0 -105
  210. data/app/frontend/pages/queries/index.tsx +0 -492
  211. data/app/frontend/pages/queries/show.tsx +0 -276
  212. data/app/frontend/pages/releases/index.tsx +0 -190
  213. data/app/frontend/pages/releases/release-health.tsx +0 -68
  214. data/app/frontend/pages/releases/release-path.ts +0 -25
  215. data/app/frontend/pages/releases/show.tsx +0 -262
  216. data/app/frontend/pages/requests/index.tsx +0 -235
  217. data/app/frontend/pages/requests/route.tsx +0 -159
  218. data/app/frontend/pages/scheduled_tasks/index.tsx +0 -220
  219. data/app/frontend/pages/spans/index.tsx +0 -229
  220. data/app/frontend/pages/spans/show.tsx +0 -220
  221. data/app/frontend/pages/storage_ops/index.tsx +0 -150
  222. data/app/frontend/pages/tenants/index.tsx +0 -236
  223. data/app/frontend/pages/tenants/show.tsx +0 -477
  224. data/app/frontend/pages/tenants/tenant-path.ts +0 -26
  225. data/app/frontend/pages/thresholds/index.tsx +0 -414
  226. data/app/frontend/pages/traces/show.tsx +0 -208
  227. data/app/frontend/pages/transactions/index.tsx +0 -220
  228. data/app/frontend/pages/view_renders/index.tsx +0 -241
  229. data/app/frontend/pages/visits/index.tsx +0 -321
  230. data/app/frontend/public/icon.png +0 -0
  231. data/app/frontend/public/icon.svg +0 -5
  232. data/app/frontend/routes/index.d.ts +0 -2136
  233. data/app/frontend/routes/index.js +0 -2112
  234. data/app/frontend/types/globals.d.ts +0 -9
  235. data/app/frontend/types/index.ts +0 -288
  236. data/app/frontend/types/vite-env.d.ts +0 -9
  237. data/app/helpers/railwatch/assets_helper.rb +0 -52
  238. data/app/jobs/railwatch/anomaly_scan_job.rb +0 -11
  239. data/app/jobs/railwatch/application_job.rb +0 -7
  240. data/app/jobs/railwatch/auto_resolve_issues_job.rb +0 -19
  241. data/app/jobs/railwatch/check_scheduled_tasks_job.rb +0 -113
  242. data/app/jobs/railwatch/detect_anomalies_job.rb +0 -171
  243. data/app/jobs/railwatch/detect_performance_issues_job.rb +0 -95
  244. data/app/jobs/railwatch/group_exceptions_job.rb +0 -91
  245. data/app/jobs/railwatch/optimize_telemetry_job.rb +0 -23
  246. data/app/jobs/railwatch/performance_scan_job.rb +0 -11
  247. data/app/jobs/railwatch/prune_telemetry_job.rb +0 -129
  248. data/app/jobs/railwatch/release_health_rollup_job.rb +0 -64
  249. data/app/jobs/railwatch/rollup_catchup_job.rb +0 -21
  250. data/app/jobs/railwatch/rollup_job.rb +0 -132
  251. data/app/jobs/railwatch/scheduled_task_scan_job.rb +0 -11
  252. data/app/models/concerns/railwatch/detection_snapshotting.rb +0 -20
  253. data/app/models/railwatch/alert.rb +0 -229
  254. data/app/models/railwatch/alert_rule.rb +0 -121
  255. data/app/models/railwatch/anomaly_rule.rb +0 -51
  256. data/app/models/railwatch/application.rb +0 -44
  257. data/app/models/railwatch/application_record.rb +0 -33
  258. data/app/models/railwatch/comment.rb +0 -36
  259. data/app/models/railwatch/deploy.rb +0 -67
  260. data/app/models/railwatch/environment.rb +0 -68
  261. data/app/models/railwatch/execution_presenter.rb +0 -185
  262. data/app/models/railwatch/filter_query.rb +0 -184
  263. data/app/models/railwatch/followup_receipt.rb +0 -27
  264. data/app/models/railwatch/ingest/batch.rb +0 -355
  265. data/app/models/railwatch/ingest/mapper.rb +0 -585
  266. data/app/models/railwatch/ingest/payload.rb +0 -96
  267. data/app/models/railwatch/ingest/rollup_absorber.rb +0 -170
  268. data/app/models/railwatch/ingest/writer.rb +0 -137
  269. data/app/models/railwatch/issue.rb +0 -277
  270. data/app/models/railwatch/issue_activity.rb +0 -23
  271. data/app/models/railwatch/issue_detection_presenter.rb +0 -309
  272. data/app/models/railwatch/issue_detection_snapshot.rb +0 -77
  273. data/app/models/railwatch/maintenance_task.rb +0 -53
  274. data/app/models/railwatch/saved_view.rb +0 -63
  275. data/app/models/railwatch/telemetry/aggregations.rb +0 -251
  276. data/app/models/railwatch/telemetry/attachment.rb +0 -31
  277. data/app/models/railwatch/telemetry/bounded_gzip.rb +0 -101
  278. data/app/models/railwatch/telemetry/broadcast.rb +0 -13
  279. data/app/models/railwatch/telemetry/cache_event.rb +0 -16
  280. data/app/models/railwatch/telemetry/child.rb +0 -53
  281. data/app/models/railwatch/telemetry/cursor_page.rb +0 -99
  282. data/app/models/railwatch/telemetry/deprecation.rb +0 -13
  283. data/app/models/railwatch/telemetry/enqueued_job.rb +0 -13
  284. data/app/models/railwatch/telemetry/exception.rb +0 -28
  285. data/app/models/railwatch/telemetry/execution.rb +0 -152
  286. data/app/models/railwatch/telemetry/export_delivery.rb +0 -32
  287. data/app/models/railwatch/telemetry/export_destination.rb +0 -66
  288. data/app/models/railwatch/telemetry/health_sample.rb +0 -72
  289. data/app/models/railwatch/telemetry/ingest_batch.rb +0 -31
  290. data/app/models/railwatch/telemetry/llm_call.rb +0 -37
  291. data/app/models/railwatch/telemetry/log.rb +0 -91
  292. data/app/models/railwatch/telemetry/mail.rb +0 -13
  293. data/app/models/railwatch/telemetry/n_plus_one.rb +0 -92
  294. data/app/models/railwatch/telemetry/notification.rb +0 -13
  295. data/app/models/railwatch/telemetry/outgoing_request.rb +0 -13
  296. data/app/models/railwatch/telemetry/person.rb +0 -50
  297. data/app/models/railwatch/telemetry/process.rb +0 -10
  298. data/app/models/railwatch/telemetry/profile.rb +0 -37
  299. data/app/models/railwatch/telemetry/query.rb +0 -57
  300. data/app/models/railwatch/telemetry/query_shape.rb +0 -45
  301. data/app/models/railwatch/telemetry/release_health.rb +0 -69
  302. data/app/models/railwatch/telemetry/rollup.rb +0 -157
  303. data/app/models/railwatch/telemetry/session.rb +0 -24
  304. data/app/models/railwatch/telemetry/span.rb +0 -19
  305. data/app/models/railwatch/telemetry/storage_op.rb +0 -13
  306. data/app/models/railwatch/telemetry/tenant.rb +0 -240
  307. data/app/models/railwatch/telemetry/transaction.rb +0 -13
  308. data/app/models/railwatch/telemetry/view_render.rb +0 -13
  309. data/app/models/railwatch/telemetry/visit.rb +0 -24
  310. data/app/models/railwatch/telemetry_record.rb +0 -107
  311. data/app/models/railwatch/threshold.rb +0 -66
  312. data/app/models/railwatch/user.rb +0 -47
  313. data/app/models/railwatch/viewer.rb +0 -13
  314. data/app/models/railwatch/window.rb +0 -66
  315. data/app/views/layouts/railwatch/dashboard.html.erb +0 -25
  316. data/db/railwatch_migrate/20260916000000_create_railwatch_tables.rb +0 -151
  317. data/db/railwatch_migrate/20260917000000_create_railwatch_maintenance_tasks.rb +0 -18
  318. data/db/railwatch_migrate/20260917120000_create_railwatch_followup_receipts.rb +0 -20
  319. data/db/railwatch_migrate/20260918120000_widen_host_user_ids.rb +0 -71
  320. data/db/railwatch_telemetry_migrate/20260903000000_enable_incremental_vacuum.rb +0 -62
  321. data/db/railwatch_telemetry_migrate/20260903000001_create_telemetry.rb +0 -481
  322. data/db/railwatch_telemetry_migrate/20260903000002_rename_tenant_to_app_tenant.rb +0 -14
  323. data/db/railwatch_telemetry_migrate/20260903000003_add_statement_count_to_transactions.rb +0 -9
  324. data/db/railwatch_telemetry_migrate/20260903000004_add_role_and_channel.rb +0 -10
  325. data/db/railwatch_telemetry_migrate/20260903000005_add_locals_to_exceptions.rb +0 -9
  326. data/db/railwatch_telemetry_migrate/20260903000006_add_spans_health_vitals_and_fts.rb +0 -82
  327. data/db/railwatch_telemetry_migrate/20260903000007_rename_span_attributes_to_payload.rb +0 -10
  328. data/db/railwatch_telemetry_migrate/20260903000008_add_profiles_and_attachments.rb +0 -60
  329. data/db/railwatch_telemetry_migrate/20260903000009_add_truncated_to_attachments.rb +0 -9
  330. data/db/railwatch_telemetry_migrate/20260903000010_create_sessions_and_release_health.rb +0 -51
  331. data/db/railwatch_telemetry_migrate/20260903000011_add_fingerprint_to_exceptions.rb +0 -11
  332. data/db/railwatch_telemetry_migrate/20260904000012_add_failed_to_broadcasts.rb +0 -10
  333. data/db/railwatch_telemetry_migrate/20260904010000_add_filter_cursor_indexes.rb +0 -37
  334. data/db/railwatch_telemetry_migrate/20260904020000_add_n_plus_ones_execution_id_index.rb +0 -11
  335. data/db/railwatch_telemetry_migrate/20260904120000_create_query_shapes.rb +0 -15
  336. data/db/railwatch_telemetry_migrate/20260906120000_add_backpressure_factor_to_ingest_batches.rb +0 -9
  337. data/db/railwatch_telemetry_migrate/20260907000000_rename_lantern_version_on_processes.rb +0 -10
  338. data/db/railwatch_telemetry_migrate/20260913000000_rename_nightrail_version_on_processes.rb +0 -9
  339. data/db/railwatch_telemetry_migrate/20260914000000_drop_orphan_durable_ingest_tables.rb +0 -18
  340. data/db/railwatch_telemetry_migrate/20260914010000_drop_orphan_durable_ingest_columns.rb +0 -25
  341. data/db/railwatch_telemetry_migrate/20260915000000_create_llm_calls.rb +0 -55
  342. data/db/railwatch_telemetry_migrate/20260915120000_add_detail_to_llm_calls.rb +0 -21
  343. data/db/railwatch_telemetry_migrate/20260915200000_add_explained_index_to_queries.rb +0 -16
  344. data/db/railwatch_telemetry_migrate/20260915210000_add_slowest_index_to_queries.rb +0 -18
  345. data/db/railwatch_telemetry_migrate/20260917010000_add_batch_ledger_to_ingest_batches.rb +0 -16
  346. data/db/railwatch_telemetry_migrate/20260919000100_create_export_queue.rb +0 -146
  347. data/db/railwatch_telemetry_migrate/20260922000000_add_covering_indexes_for_dashboard_aggregates.rb +0 -34
  348. data/db/railwatch_telemetry_migrate/20260923000000_add_people_index.rb +0 -15
  349. data/db/railwatch_telemetry_migrate/20260925000000_add_tenant_summary_index.rb +0 -17
  350. data/db/railwatch_telemetry_migrate/20261008000000_add_in_transaction_to_outgoing_requests.rb +0 -10
  351. data/docs/embedded.md +0 -503
  352. data/lib/puma/plugin/railwatch.rb +0 -215
  353. data/lib/railwatch/dashboard_assets.rb +0 -45
  354. data/lib/railwatch/embedded.rb +0 -50
  355. data/lib/railwatch/export/client.rb +0 -124
  356. data/lib/railwatch/export/lease.rb +0 -56
  357. data/lib/railwatch/export/outbox.rb +0 -361
  358. data/lib/railwatch/export/policy.rb +0 -79
  359. data/lib/railwatch/export/sender.rb +0 -130
  360. data/lib/railwatch/ingest_request_body_limit.rb +0 -10
  361. data/lib/railwatch/maintenance.rb +0 -194
  362. data/lib/railwatch/patches/migration_busy_timeout.rb +0 -73
  363. data/lib/railwatch/transport/local.rb +0 -78
  364. data/lib/railwatch/transport/socket.rb +0 -183
  365. data/lib/railwatch/writer.rb +0 -370
  366. data/public/railwatch/assets/CommitMono-Bold-D6h61ieg.woff2 +0 -0
  367. data/public/railwatch/assets/CommitMono-Regular-zr8w7Obm.woff2 +0 -0
  368. data/public/railwatch/assets/Roboto-Black-auA4GeOK.woff2 +0 -0
  369. data/public/railwatch/assets/Roboto-Bold-CJLnO8j1.woff2 +0 -0
  370. data/public/railwatch/assets/Roboto-Medium-Cm2bwKpj.woff2 +0 -0
  371. data/public/railwatch/assets/Roboto-Regular-Chaq1-PV.woff2 +0 -0
  372. data/public/railwatch/assets/app-layout-by7G3sa1.js +0 -41
  373. data/public/railwatch/assets/application-Cc5v_Vk9.css +0 -1
  374. data/public/railwatch/assets/arrow-up-DCZ7emux.js +0 -1
  375. data/public/railwatch/assets/braces-CykT5_5u.js +0 -1
  376. data/public/railwatch/assets/card-C-PPSA0g.js +0 -1
  377. data/public/railwatch/assets/chart-Cov79YC5.js +0 -39
  378. data/public/railwatch/assets/chart-hover-DdK4E-2h.js +0 -1
  379. data/public/railwatch/assets/chart-panel-BPaYRXJB.js +0 -1
  380. data/public/railwatch/assets/code-BYjC4YZ_.js +0 -1
  381. data/public/railwatch/assets/copy-id-LPfoj9cC.js +0 -1
  382. data/public/railwatch/assets/cursor-load-more-DSdhgpGl.js +0 -1
  383. data/public/railwatch/assets/data-table-CQ_YRWQs.js +0 -1
  384. data/public/railwatch/assets/env-layout-CdMWbUkm.js +0 -1
  385. data/public/railwatch/assets/execution-path-7MYt-Qbn.js +0 -1
  386. data/public/railwatch/assets/filter-bar-CrzpQ_vD.js +0 -1
  387. data/public/railwatch/assets/flamegraph-BIFEdTAg.js +0 -2
  388. data/public/railwatch/assets/format-B9SDkrWj.js +0 -1
  389. data/public/railwatch/assets/frames-C5t-x4f0.js +0 -1
  390. data/public/railwatch/assets/index--NQ7us-L.js +0 -1
  391. data/public/railwatch/assets/index-2kgYe_99.js +0 -1
  392. data/public/railwatch/assets/index-4YEPHxtO.js +0 -1
  393. data/public/railwatch/assets/index-AOCC1GDJ.js +0 -1
  394. data/public/railwatch/assets/index-BEo-u0eO.js +0 -1
  395. data/public/railwatch/assets/index-BJ1V3w2N.js +0 -1
  396. data/public/railwatch/assets/index-BPoHsBKb.js +0 -1
  397. data/public/railwatch/assets/index-BZzyGUM3.js +0 -1
  398. data/public/railwatch/assets/index-BgVq7yKL.js +0 -1
  399. data/public/railwatch/assets/index-BqFGEZvW.js +0 -1
  400. data/public/railwatch/assets/index-ByuHJ1CH.js +0 -1
  401. data/public/railwatch/assets/index-C2j50AL2.js +0 -1
  402. data/public/railwatch/assets/index-CLEdTkvW.js +0 -1
  403. data/public/railwatch/assets/index-CWgq8qBu.js +0 -1
  404. data/public/railwatch/assets/index-CXQ7_oVj.js +0 -2
  405. data/public/railwatch/assets/index-CmyYrjS2.js +0 -1
  406. data/public/railwatch/assets/index-Cn9_HjDo.js +0 -1
  407. data/public/railwatch/assets/index-CsMAm9UK.js +0 -1
  408. data/public/railwatch/assets/index-D0Kk_oGY.js +0 -1
  409. data/public/railwatch/assets/index-DC9ZptJh.js +0 -1
  410. data/public/railwatch/assets/index-DNzg7jmV.js +0 -1
  411. data/public/railwatch/assets/index-DQ8uJO5f.js +0 -1
  412. data/public/railwatch/assets/index-Deq14BxB.js +0 -1
  413. data/public/railwatch/assets/index-IaV3Blzp.js +0 -1
  414. data/public/railwatch/assets/index-TYNWcJQs.js +0 -1
  415. data/public/railwatch/assets/index-WonH2fcZ.js +0 -1
  416. data/public/railwatch/assets/index-WxWUaH_d.js +0 -1
  417. data/public/railwatch/assets/index-mUNrl4ky.js +0 -1
  418. data/public/railwatch/assets/inertia-DfNRARuA.js +0 -97
  419. data/public/railwatch/assets/json-viewer-DMsF5glp.js +0 -1
  420. data/public/railwatch/assets/klass-CmHYk5rz.js +0 -1
  421. data/public/railwatch/assets/origin-identity-1R3bqa3R.js +0 -1
  422. data/public/railwatch/assets/percentile-picker-73z5PAps.js +0 -1
  423. data/public/railwatch/assets/relative-time-Dsc_cCdX.js +0 -1
  424. data/public/railwatch/assets/release-health-BV5OaOWd.js +0 -1
  425. data/public/railwatch/assets/route-CWh58C7K.js +0 -1
  426. data/public/railwatch/assets/segmented-DB4zh-Am.js +0 -1
  427. data/public/railwatch/assets/select-DBKjFlOX.js +0 -1
  428. data/public/railwatch/assets/series-chart-BaiDG795.js +0 -1
  429. data/public/railwatch/assets/show-B4ICCFGh.js +0 -1
  430. data/public/railwatch/assets/show-B5lOU9Gv.js +0 -1
  431. data/public/railwatch/assets/show-BYPB74t4.js +0 -2
  432. data/public/railwatch/assets/show-C7A0m2M8.js +0 -1
  433. data/public/railwatch/assets/show-DT5X1zqE.js +0 -1
  434. data/public/railwatch/assets/show-D_NBdXsn.js +0 -2
  435. data/public/railwatch/assets/show-DgPCnRg8.js +0 -1
  436. data/public/railwatch/assets/show-Yl69HF0x.js +0 -1
  437. data/public/railwatch/assets/show-_QxuUQCm.js +0 -3
  438. data/public/railwatch/assets/show-g__jnN36.js +0 -1
  439. data/public/railwatch/assets/show-l4ztfjl9.js +0 -1
  440. data/public/railwatch/assets/sort-header-BcG8ulEH.js +0 -1
  441. data/public/railwatch/assets/source-link-B2183i2-.js +0 -1
  442. data/public/railwatch/assets/sparkline-cell-B47ipscB.js +0 -1
  443. data/public/railwatch/assets/stat-DT2hhsqS.js +0 -1
  444. data/public/railwatch/assets/status-badge-CmbRKlY7.js +0 -1
  445. data/public/railwatch/assets/tenant-path-C4rNGauE.js +0 -1
  446. data/public/railwatch/assets/use-clipboard-oTNeP4ax.js +0 -1
  447. data/public/railwatch/assets/use-live-CLZZj245.js +0 -1
  448. data/public/railwatch/icon.png +0 -0
  449. data/public/railwatch/icon.svg +0 -5
  450. data/public/railwatch/manifest.json +0 -1245
  451. data/public/railwatch/rails-vite.json +0 -1
  452. /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.