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