railwatch 0.5.1 → 0.6.1

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 (154) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +45 -25
  3. data/CHANGELOG.md +147 -0
  4. data/README.md +50 -31
  5. data/app/controllers/railwatch/dashboard_controller.rb +1 -0
  6. data/app/jobs/railwatch/rollup_job.rb +3 -1
  7. data/app/models/railwatch/application_record.rb +2 -2
  8. data/app/models/railwatch/ingest/rollup_absorber.rb +1 -1
  9. data/app/models/railwatch/telemetry_record.rb +2 -2
  10. data/docs/ai-and-mcp.md +9 -3
  11. data/docs/configuration.md +97 -40
  12. data/docs/embedded.md +96 -43
  13. data/docs/faq.md +28 -15
  14. data/docs/getting-started.md +121 -42
  15. data/docs/records.md +13 -10
  16. data/docs/replacing-nightwatch.md +16 -14
  17. data/docs/replacing-sentry.md +19 -10
  18. data/docs/security.md +24 -3
  19. data/docs/self-hosting.md +9 -1
  20. data/docs/testing.md +14 -4
  21. data/docs/troubleshooting.md +72 -29
  22. data/lib/generators/railwatch/install/install_generator.rb +32 -16
  23. data/lib/generators/railwatch/install/templates/initializer.rb.tt +6 -6
  24. data/lib/puma/plugin/railwatch.rb +48 -3
  25. data/lib/railwatch/configuration.rb +16 -1
  26. data/lib/railwatch/engine.rb +14 -3
  27. data/lib/railwatch/reporter.rb +52 -16
  28. data/lib/railwatch/transport/http.rb +4 -9
  29. data/lib/railwatch/version.rb +1 -1
  30. data/lib/railwatch.rb +23 -1
  31. data/lib/tasks/railwatch_tasks.rake +11 -4
  32. data/llms.txt +21 -14
  33. data/public/railwatch/assets/{app-layout-DDyQa72H.js → app-layout-Zc0v-hYh.js} +1 -1
  34. data/public/railwatch/assets/{app-wordmark-o9CODKP0.js → app-wordmark-BA_60AVb.js} +1 -1
  35. data/public/railwatch/assets/{appearance-BwuCXabr.js → appearance-CcfP9tZ7.js} +1 -1
  36. data/public/railwatch/assets/application-BrN3Sz94.css +1 -0
  37. data/public/railwatch/assets/{arrow-up-C6PxDiY3.js → arrow-up-CLQ-7heQ.js} +1 -1
  38. data/public/railwatch/assets/{auth-layout-BRt8MGFD.js → auth-layout-C05gEBIQ.js} +1 -1
  39. data/public/railwatch/assets/{badge-CAxXV8za.js → badge-DRae8XwK.js} +1 -1
  40. data/public/railwatch/assets/{braces-DgomTCNf.js → braces-rSYydpLY.js} +1 -1
  41. data/public/railwatch/assets/{card-cAtqCxWl.js → card-DPjFKfen.js} +1 -1
  42. data/public/railwatch/assets/{chart-BBeBkkNa.js → chart-DWh7l8yM.js} +1 -1
  43. data/public/railwatch/assets/{chart-hover-B1M9jc0y.js → chart-hover-CfoZUY4J.js} +1 -1
  44. data/public/railwatch/assets/{chart-panel-DUQTz_C8.js → chart-panel-CC49WTQL.js} +1 -1
  45. data/public/railwatch/assets/{checkbox-CmhMHWZO.js → checkbox-DPkLUiwM.js} +1 -1
  46. data/public/railwatch/assets/{code-DESvxyTj.js → code-CLmYS6FU.js} +1 -1
  47. data/public/railwatch/assets/{copy-block-BkSU5832.js → copy-block-CGcXxp8J.js} +1 -1
  48. data/public/railwatch/assets/{copy-id-D03GhN9F.js → copy-id-vBYHQwxg.js} +1 -1
  49. data/public/railwatch/assets/{cursor-load-more-CRyuMeQb.js → cursor-load-more-Ddwxe5dZ.js} +1 -1
  50. data/public/railwatch/assets/{data-table-BIlt7Rtm.js → data-table-CzKTEE-O.js} +1 -1
  51. data/public/railwatch/assets/{edit-O0NSBWxo.js → edit-B1kmWkzd.js} +1 -1
  52. data/public/railwatch/assets/{edit-DJ0D0wHN.js → edit-D9cx4pbG.js} +1 -1
  53. data/public/railwatch/assets/{edit-Bb6MKoe4.js → edit-yv6j9p-V.js} +1 -1
  54. data/public/railwatch/assets/{empty-state-C38il627.js → empty-state-CW4wclK_.js} +1 -1
  55. data/public/railwatch/assets/{env-layout-REF7OM4q.js → env-layout-Kz7wks1x.js} +1 -1
  56. data/public/railwatch/assets/{execution-path-FYLq1TwC.js → execution-path-FAuIzOXB.js} +1 -1
  57. data/public/railwatch/assets/{filter-bar-CYog9Alp.js → filter-bar-CJDjWFib.js} +1 -1
  58. data/public/railwatch/assets/{flamegraph-DSs69foN.js → flamegraph-EaGkP2NT.js} +1 -1
  59. data/public/railwatch/assets/{frames-BUi2J5Mk.js → frames-zZuIaNH9.js} +1 -1
  60. data/public/railwatch/assets/{google-sign-in-button-BQiIKFdd.js → google-sign-in-button-2_zgbgVy.js} +1 -1
  61. data/public/railwatch/assets/{index-ZOGOB8SA.js → index-B49SWz7K.js} +1 -1
  62. data/public/railwatch/assets/{index-DqTFTP8p.js → index-BHlY4wKe.js} +1 -1
  63. data/public/railwatch/assets/{index-DrcKVG2f.js → index-BZpPtyFY.js} +1 -1
  64. data/public/railwatch/assets/{index-Dh4IRLFI.js → index-BeVK6jCL.js} +1 -1
  65. data/public/railwatch/assets/{index-umIAl-pL.js → index-BfbSo01U.js} +1 -1
  66. data/public/railwatch/assets/{index-BoUBioBP.js → index-BfgncAv6.js} +1 -1
  67. data/public/railwatch/assets/{index-C3A_9imx.js → index-BhNszK1k.js} +1 -1
  68. data/public/railwatch/assets/{index-tpz-OGUP.js → index-Bu01uWvw.js} +1 -1
  69. data/public/railwatch/assets/{index-CGs4m_fa.js → index-C1s_hK3p.js} +1 -1
  70. data/public/railwatch/assets/{index-ZSZg9rtq.js → index-C8Cggnbw.js} +1 -1
  71. data/public/railwatch/assets/{index-so4lRrRq.js → index-CBip6V4z.js} +1 -1
  72. data/public/railwatch/assets/{index-DvjY3dPD.js → index-CMJGss5R.js} +1 -1
  73. data/public/railwatch/assets/{index-DtHmuB9Q.js → index-CRo3yK20.js} +1 -1
  74. data/public/railwatch/assets/{index-r0tSIplE.js → index-CWB_p2J8.js} +1 -1
  75. data/public/railwatch/assets/{index-CICUIFHL.js → index-CWHpndGc.js} +1 -1
  76. data/public/railwatch/assets/{index-CFFpnzIS.js → index-Ca_S4Sc3.js} +1 -1
  77. data/public/railwatch/assets/{index-DSvlZVWG.js → index-CanPDDOa.js} +1 -1
  78. data/public/railwatch/assets/{index-CrZ3vHDL.js → index-Cie90Yat.js} +1 -1
  79. data/public/railwatch/assets/{index-FhUaPPab.js → index-CiepQ_pR.js} +1 -1
  80. data/public/railwatch/assets/{index-C_upSl_k.js → index-Cm1uCIGN.js} +1 -1
  81. data/public/railwatch/assets/{index-sTYvcbkh.js → index-CzitnnSC.js} +1 -1
  82. data/public/railwatch/assets/{index-C7OtLq_3.js → index-DEUfClv3.js} +1 -1
  83. data/public/railwatch/assets/index-DJKwo-mI.js +1 -0
  84. data/public/railwatch/assets/{index-DDI_Zx5V.js → index-DK6y0YHp.js} +1 -1
  85. data/public/railwatch/assets/{index-C-PmdhXA.js → index-DMNPpLH9.js} +1 -1
  86. data/public/railwatch/assets/{index-BaR1U9An.js → index-DZO1mSfX.js} +1 -1
  87. data/public/railwatch/assets/{index-CsoN51vW.js → index-Db5wj3M5.js} +1 -1
  88. data/public/railwatch/assets/{index-BiiyMcA0.js → index-Dcy5WktB.js} +1 -1
  89. data/public/railwatch/assets/{index-8-hnAhOD.js → index-DvG0-7Lx.js} +1 -1
  90. data/public/railwatch/assets/{index-QpTtwFwu.js → index-Dvj1wuka.js} +1 -1
  91. data/public/railwatch/assets/{index-DW2CBbxU.js → index-KyZX46qX.js} +1 -1
  92. data/public/railwatch/assets/{index-BeOh2t_S.js → index-Ze-KP-sl.js} +1 -1
  93. data/public/railwatch/assets/{index-CiPo4Gob.js → index-mhRbWLBM.js} +1 -1
  94. data/public/railwatch/assets/{index-CpkI015n.js → index-x099JL5f.js} +1 -1
  95. data/public/railwatch/assets/{index-JdCVBrw8.js → index-x28zb_nf.js} +1 -1
  96. data/public/railwatch/assets/{inertia-DLew8ZNx.js → inertia-Cuyz2ZHO.js} +2 -2
  97. data/public/railwatch/assets/{input-error-cvM6_Jht.js → input-error-hog6gGxg.js} +1 -1
  98. data/public/railwatch/assets/{json-viewer-D922McGi.js → json-viewer-DH2W9HXf.js} +1 -1
  99. data/public/railwatch/assets/{klass-CrwICqN8.js → klass-DHbDelLk.js} +1 -1
  100. data/public/railwatch/assets/{label-GWl7I6sf.js → label-DdCBgiUn.js} +1 -1
  101. data/public/railwatch/assets/{layout-0ZAnD3zl.js → layout-Cueyl7c5.js} +1 -1
  102. data/public/railwatch/assets/{live-dot-D1n_BreY.js → live-dot-BZgYTYdt.js} +1 -1
  103. data/public/railwatch/assets/{nav-DPxr1NNC.js → nav-BSSGObDZ.js} +1 -1
  104. data/public/railwatch/assets/{new-D-ZzUK9a.js → new-B8FSb8Bl.js} +1 -1
  105. data/public/railwatch/assets/{new-DEVkYv-z.js → new-C39_v2Ll.js} +1 -1
  106. data/public/railwatch/assets/{new-Cdl6pqST.js → new-DVOPwaC5.js} +1 -1
  107. data/public/railwatch/assets/{new-DHAHDrN7.js → new-Dd40nvJR.js} +1 -1
  108. data/public/railwatch/assets/{new-Dz4lZf1L.js → new-SxnYe1SE.js} +1 -1
  109. data/public/railwatch/assets/{new-GMrRFurX.js → new-eP5vKD3Y.js} +1 -1
  110. data/public/railwatch/assets/onboarding-CUpZl5KB.js +1 -0
  111. data/public/railwatch/assets/{origin-identity-Bk9yHWZ1.js → origin-identity-BYt2uiuo.js} +1 -1
  112. data/public/railwatch/assets/{percentile-picker-DfSx9yJO.js → percentile-picker-CeQgllxD.js} +1 -1
  113. data/public/railwatch/assets/{relative-time-CjIjb8Lg.js → relative-time-D5UbF4oO.js} +1 -1
  114. data/public/railwatch/assets/{release-health-4b3tivEf.js → release-health-uP-GF3_V.js} +1 -1
  115. data/public/railwatch/assets/{route-C_5BUtHK.js → route-DQAY8JEr.js} +1 -1
  116. data/public/railwatch/assets/{segmented-BgbT3wZa.js → segmented-BeIe4uqk.js} +1 -1
  117. data/public/railwatch/assets/{select-DmunxCKE.js → select-2R16457Z.js} +1 -1
  118. data/public/railwatch/assets/{separator-BXzEdZ_8.js → separator-D5I0UCB5.js} +1 -1
  119. data/public/railwatch/assets/series-chart-gnrzhmM6.js +1 -0
  120. data/public/railwatch/assets/show-2BkeNRUC.js +2 -0
  121. data/public/railwatch/assets/{show-mU38uGTg.js → show-B0X1hRQH.js} +1 -1
  122. data/public/railwatch/assets/{show-CAl7xcex.js → show-BKUV5l5q.js} +1 -1
  123. data/public/railwatch/assets/{show-DnR1Dnjd.js → show-BQJD_CsF.js} +1 -1
  124. data/public/railwatch/assets/{show-Dn-GwFZL.js → show-BhmD6Sxp.js} +1 -1
  125. data/public/railwatch/assets/{show-Y74rM0VT.js → show-C3KcnVo2.js} +1 -1
  126. data/public/railwatch/assets/{show-Dily73Xk.js → show-C95cHd06.js} +1 -1
  127. data/public/railwatch/assets/{show-DXs4deaC.js → show-CdB4uVQS.js} +1 -1
  128. data/public/railwatch/assets/{show-DI8IhNUH.js → show-CoCqcVIp.js} +1 -1
  129. data/public/railwatch/assets/{show-vQ4bndYD.js → show-D2LqjEsU.js} +1 -1
  130. data/public/railwatch/assets/{show-DcpTFiLi.js → show-DNpnKyTh.js} +1 -1
  131. data/public/railwatch/assets/{show-B2zLAW83.js → show-DOlTxbig.js} +1 -1
  132. data/public/railwatch/assets/{show-SvLOcPrx.js → show-DQCkttL8.js} +1 -1
  133. data/public/railwatch/assets/{show-BLpWUHWD.js → show-DcxesipC.js} +1 -1
  134. data/public/railwatch/assets/{show-DYskfl3-.js → show-IGJoc_X0.js} +1 -1
  135. data/public/railwatch/assets/{show-DSP9Cq_C.js → show-YsNpqbcI.js} +1 -1
  136. data/public/railwatch/assets/{show-DlRVS18-.js → show-qNV6H8SH.js} +1 -1
  137. data/public/railwatch/assets/{sort-header-Dcq9bzmo.js → sort-header-mEJUM3Av.js} +1 -1
  138. data/public/railwatch/assets/{sparkline-cell-BON3qQUB.js → sparkline-cell-Dwg7awwh.js} +1 -1
  139. data/public/railwatch/assets/{stat-DFEyFxkO.js → stat-ZtxGU8lE.js} +1 -1
  140. data/public/railwatch/assets/{status-badge-BaUKP7Yo.js → status-badge-CH5P-Xkj.js} +1 -1
  141. data/public/railwatch/assets/{tenant-path-DPZPc985.js → tenant-path-CQoP-BeF.js} +1 -1
  142. data/public/railwatch/assets/{text-link-BO77t9Xk.js → text-link-DHJ8BXx5.js} +1 -1
  143. data/public/railwatch/assets/{textarea-DTqrCiV0.js → textarea-BeQtQyl5.js} +1 -1
  144. data/public/railwatch/assets/{timeline-D5rJ0es2.js → timeline-CaKQVu48.js} +1 -1
  145. data/public/railwatch/assets/{transition-DMIrZVth.js → transition-ksDpqhKJ.js} +1 -1
  146. data/public/railwatch/assets/{use-clipboard-ByoUGQqA.js → use-clipboard-DNefo-ky.js} +1 -1
  147. data/public/railwatch/assets/{use-live-D7xKz2ma.js → use-live-DMTuhKfB.js} +1 -1
  148. data/public/railwatch/manifest.json +1286 -1286
  149. metadata +116 -116
  150. data/public/railwatch/assets/application-B7h1MIhi.css +0 -1
  151. data/public/railwatch/assets/index-CFRLPs4J.js +0 -1
  152. data/public/railwatch/assets/onboarding-D1vwaHYT.js +0 -1
  153. data/public/railwatch/assets/series-chart-Xf49v9cv.js +0 -1
  154. data/public/railwatch/assets/show-SHwZjXb7.js +0 -2
@@ -1,27 +1,100 @@
1
1
  # Getting started
2
2
 
3
- Five minutes from `bundle add` to a request on the dashboard, on a
4
- Rails 8 app. Everything below is the gem's own generator and rake tasks;
5
- nothing else has to be wired by hand.
3
+ Two commands on a Rails 8 app, then a dashboard at `/railwatch`.
4
+ Everything below is the gem's own generator and rake tasks; nothing else
5
+ has to be wired by hand.
6
6
 
7
- Running one server and want the dashboard inside the app itself, with
8
- no token and no cloud? That is `bin/rails generate railwatch:install
9
- --local`; see [Embedded mode](embedded.md). The rest of this page is
10
- the cloud install.
11
-
12
- ## 1. Add the gem
7
+ ## Install
13
8
 
14
9
  ```sh
15
10
  bundle add railwatch
11
+ bin/rails generate railwatch:install
16
12
  ```
17
13
 
18
14
  The gem, its Ruby namespace, and its require path share one name:
19
15
  `railwatch`, `Railwatch::*`, `require "railwatch"`.
20
16
 
21
- ## 2. Run the installer
17
+ With no flags the install is [embedded](embedded.md): telemetry stays in
18
+ two SQLite databases the app owns, Puma forks one writer process for
19
+ them (`plugin :railwatch` in `config/puma.rb`), and the dashboard is
20
+ served from the app. No token, no cloud, no job worker. The generator
21
+ creates and migrates both databases before it returns. On a PostgreSQL
22
+ or MySQL app without the `sqlite3` gem it adds the gem instead; run
23
+ `bundle install` and `bin/rails db:prepare` to finish.
24
+
25
+ Restart the app and open `/railwatch`. In development it is open; every
26
+ other environment answers 401 until you give it a password:
22
27
 
23
28
  ```sh
24
- bin/rails generate railwatch:install
29
+ RAILS_ENV=production bin/rails railwatch:authentication:configure
30
+ ```
31
+
32
+ [Embedded mode](embedded.md#authentication) covers using your own
33
+ admin authentication instead.
34
+
35
+ ### What the generator writes
36
+
37
+ In every mode:
38
+
39
+ - `config/initializers/railwatch.rb`, with every option commented out at
40
+ its default (embedded: `c.transport = :local`).
41
+ - `mount Railwatch::Engine, at: "/railwatch"` in `config/routes.rb`: the
42
+ embedded dashboard and the beacon endpoint the browser client posts to.
43
+ - `.kamal/hooks/post-deploy` — only if `config/deploy.yml` already
44
+ exists.
45
+ - `app/frontend/lib/railwatch.ts` plus the `startRailwatch()` call in your
46
+ Inertia entrypoint — only if `app/frontend/` exists. If it can't find
47
+ an entrypoint it prints the two lines to add.
48
+ - `require "railwatch/rspec"` in `spec/rails_helper.rb`, or
49
+ `require "railwatch/minitest"` in `test/test_helper.rb`.
50
+
51
+ Embedded adds the two databases to `config/database.yml` and the Puma
52
+ plugin line; see [Embedded mode](embedded.md#your-applications-own-database).
53
+
54
+ ### Check the wiring
55
+
56
+ ```sh
57
+ bin/rails railwatch:doctor
58
+ ```
59
+
60
+ With the app running, the embedded checklist starts like this:
61
+
62
+ ```
63
+ ✓ transport: local (telemetry stays in this app; dashboard at the engine mount)
64
+ ✓ railwatch database: storage/development_railwatch.sqlite3
65
+ ✓ railwatch_telemetry database: storage/development_railwatch_telemetry.sqlite3
66
+ ✓ railwatch_telemetry migrations: up to date
67
+ ✓ railwatch migrations: up to date
68
+ ✓ telemetry disk: auto_vacuum=incremental; the nightly prune returns freed pages
69
+ ✓ writer process: listening at /app/tmp/sockets/railwatch-writer.sock
70
+ ✓ last write: 12 seconds ago
71
+ ✓ dashboard access: open in development. Production stays closed until you run `RAILS_ENV=production bin/rails railwatch:authentication:configure`
72
+ ✓ request middleware: Railwatch::Middleware::Request at position 0
73
+ ✓ engine mounted: POST /railwatch/beacon -> railwatch/beacon#create
74
+ ```
75
+
76
+ With the app stopped, `writer process` and `maintenance` are `✗`, and say
77
+ so. Every line and what to do about a `✗` is in
78
+ [`troubleshooting.md`](troubleshooting.md).
79
+
80
+ ### Adding Railwatch Cloud
81
+
82
+ Railwatch Cloud delivers alerts to Slack, email, webhooks or Linear
83
+ (embedded mode only records them), serves the [MCP server](ai-and-mcp.md)
84
+ to your AI assistant, and puts many apps and servers in one place. An
85
+ embedded install can mirror every record to it and keep `/railwatch`:
86
+ set a token ([below](#where-the-token-comes-from)) and
87
+ `c.export_enabled = true`. See
88
+ [Embedded mode](embedded.md#three-ways-to-run-it).
89
+
90
+ ## Railwatch Cloud instead
91
+
92
+ To send telemetry only to Railwatch Cloud, with no local databases, run
93
+ the same generator with `--cloud`:
94
+
95
+ ```sh
96
+ bundle add railwatch
97
+ bin/rails generate railwatch:install --cloud
25
98
  ```
26
99
 
27
100
  With the token already in hand, let the generator read it without placing the
@@ -36,6 +109,10 @@ bin/rails generate railwatch:install \
36
109
 
37
110
  - `--prompt-token` reads without echo. `--token-stdin` is available for a
38
111
  secret-manager pipe; an already exported `RAILWATCH_TOKEN` is also detected.
112
+ - Any of `--prompt-token`, `--token-stdin`, `--url=` and `--kamal-secrets`
113
+ means the cloud, so none of them needs `--cloud` as well. An exported
114
+ `RAILWATCH_TOKEN` on its own does not: without one of these flags the
115
+ install is embedded.
39
116
  - A token is written to `.env` only when Git confirms that `.env` is ignored.
40
117
  A tracked or non-ignored dotenv file is refused; use Rails credentials, a
41
118
  deployment secret manager, or add `.env` to `.gitignore` first. Token values
@@ -47,24 +124,12 @@ bin/rails generate railwatch:install \
47
124
  `config/deploy.yml`, which is the pair of edits Kamal needs to pass a
48
125
  secret through to the containers.
49
126
 
50
- What the generator writes, in every case:
51
-
52
- - `config/initializers/railwatch.rb`, with every option commented out at
53
- its default.
54
- - `mount Railwatch::Engine, at: "/railwatch"` in `config/routes.rb` (the
55
- beacon endpoint the browser client posts to).
56
- - `.kamal/hooks/post-deploy` — only if `config/deploy.yml` already
57
- exists.
58
- - `app/frontend/lib/railwatch.ts` plus the `startRailwatch()` call in your
59
- Inertia entrypoint — only if `app/frontend/` exists. If it can't find
60
- an entrypoint it prints the two lines to add.
61
- - `require "railwatch/rspec"` in `spec/rails_helper.rb`, or
62
- `require "railwatch/minitest"` in `test/test_helper.rb`.
63
-
64
- It finishes by running `railwatch:doctor` for you, so the install either
127
+ It writes the same files as the embedded install
128
+ ([above](#what-the-generator-writes)) minus the databases and the Puma
129
+ plugin, and finishes by running `railwatch:doctor`, so the install either
65
130
  ends in a clean checklist or tells you what is still missing.
66
131
 
67
- ## 3. Where the token comes from
132
+ ### Where the token comes from
68
133
 
69
134
  In Railwatch Cloud, create an application, then an environment inside it
70
135
  (`production`, `staging`, one token each). The token is shown once, on
@@ -76,12 +141,12 @@ update `RAILWATCH_TOKEN`.
76
141
  bin/rails railwatch:token # prints the URL to create/copy a token
77
142
  ```
78
143
 
79
- A token looks like `rw_` followed by 40 characters. The gem is
80
- completely inert without one: `Railwatch.enabled?` is `config.enabled &&
81
- token.present?`, so an app with no token installs no subscribers and
82
- ships nothing.
144
+ A token looks like `rw_` followed by 40 characters. A cloud install is
145
+ inert without one: with `transport = :http`, `Railwatch.enabled?` is
146
+ `config.enabled && token.present?`, so an app with no token installs no
147
+ subscribers and ships nothing.
83
148
 
84
- ## 4. Check the wiring
149
+ ### Check the cloud wiring
85
150
 
86
151
  ```sh
87
152
  bin/rails railwatch:doctor
@@ -89,12 +154,14 @@ bin/rails railwatch:doctor
89
154
 
90
155
  ```
91
156
  ✓ token: rw_9Qv... (43 chars)
157
+ ✓ token storage: no tracked plaintext Railwatch token found
92
158
  ✓ ingest url: https://railwatch.rebulk.com
159
+ ✓ ingest transport security: HTTPS with certificate verification
93
160
  ✓ ingest reachable: GET https://railwatch.rebulk.com/ingest/ping
94
161
  ✓ request middleware: Railwatch::Middleware::Request at position 0
95
162
  ✓ engine mounted: POST /railwatch/beacon -> railwatch/beacon#create
96
163
  ✓ deploy: 8f31c0a42e91 (from GIT_REV)
97
- ✓ sample rates: requests=1.0 jobs=1.0 commands=1.0 scheduled_tasks=1.0 exceptions=1.0
164
+ ✓ sample rates: requests=1.0 jobs=1.0 commands=1.0 scheduled_tasks=1.0 channels=1.0 exceptions=1.0
98
165
  ✓ ignored record types: none
99
166
  ```
100
167
 
@@ -102,13 +169,12 @@ It exits non-zero on three lines: `token` (missing), `token storage`
102
169
  (a plaintext token in a tracked file), and `ingest reachable`. The rest
103
170
  of the checklist is informational. Run it after the token is in place:
104
171
  `ingest reachable` sends the token, so a missing or wrong token makes
105
- the platform answer 401 and that line is `✗` as well. Every line and
106
- what to do about a `✗` is in [`troubleshooting.md`](troubleshooting.md).
172
+ the platform answer 401 and that line is `✗` as well.
107
173
 
108
174
  `bin/rails railwatch:status` is the one-line version: it pings
109
175
  `{ingest_url}/ingest/ping` and prints the ingest URL, deploy, and server.
110
176
 
111
- ## 5. Make one request
177
+ ## Make one request
112
178
 
113
179
  ```sh
114
180
  bin/rails server
@@ -117,9 +183,10 @@ curl http://localhost:3000/
117
183
 
118
184
  Records are batched in-process and flushed every `flush_interval`
119
185
  (2 seconds by default) or every 500 records, whichever comes first, so
120
- the request shows up on the environment's **Requests** page a couple of
121
- seconds after you make it — with its queries, cache reads, view renders,
122
- and log lines already attached to it.
186
+ the request shows up on the **Requests** page (`/railwatch`, or the
187
+ environment in Railwatch Cloud) a couple of seconds after you make it —
188
+ with its queries, cache reads, view renders, and log lines already
189
+ attached to it.
123
190
 
124
191
  ## Three optional lines
125
192
 
@@ -187,8 +254,8 @@ newest issues, deploy markers), Requests (routes table and per-request
187
254
  waterfall of every child record), Jobs, Scheduled tasks, Commands,
188
255
  Exceptions, Queries (slow list and N+1 list with the app line that
189
256
  issued them), Spans, Profiles, Transactions, View renders, Cache, Mail,
190
- Notifications, Broadcasts, Outgoing requests, Storage, Logs,
191
- Deprecations.
257
+ Notifications, Broadcasts, Outgoing requests, LLM (RubyLLM calls with
258
+ tokens, cost, cut-offs and tool calls), Storage, Logs, Deprecations.
192
259
 
193
260
  **Monitoring** — Visits (Inertia page-visit timing and web vitals),
194
261
  Users, Tenants, Deploys, Releases (crash-free session and user rates),
@@ -199,6 +266,15 @@ up, on the account.
199
266
 
200
267
  ## Deploying
201
268
 
269
+ An embedded install deploys like the rest of the app: the two databases
270
+ are files under `storage/`, which the Rails 8 Kamal template already
271
+ mounts as a volume, and the Rails 8 Docker entrypoint's
272
+ `bin/rails db:prepare` migrates them. Give the
273
+ dashboard a password first ([Install](#install)). For deploy markers
274
+ from the Kamal hook, set `RAILWATCH_TRANSPORT=local` in the deployer's
275
+ environment ([Embedded mode](embedded.md#deploys)). The token steps
276
+ below are for a cloud install or export.
277
+
202
278
  ### Kamal
203
279
 
204
280
  Two edits, both of which `--kamal-secrets` makes for you:
@@ -276,9 +352,12 @@ one — the deploy is still recorded.
276
352
  `sentry-rails`.
277
353
  - [`replacing-nightwatch.md`](replacing-nightwatch.md) — for people
278
354
  coming from Laravel.
355
+ - [`embedded.md`](embedded.md) — authentication, the writer process,
356
+ maintenance, disk, and export to the cloud.
279
357
  - [`self-hosting.md`](self-hosting.md) — pointing the gem at your own
280
358
  platform install.
281
359
  - [`troubleshooting.md`](troubleshooting.md) — every `railwatch:doctor`
282
360
  line and what a failure means.
283
361
  - [`faq.md`](faq.md) — overhead, retention, PII, unreachable platform.
284
- - [`ai-and-mcp.md`](ai-and-mcp.md) — asking an AI assistant what broke.
362
+ - [`ai-and-mcp.md`](ai-and-mcp.md) — asking an AI assistant what broke
363
+ (Railwatch Cloud).
data/docs/records.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Record types
2
2
 
3
3
  Every record Railwatch ships is a flat hash. See `lib/railwatch/record.rb`.
4
- This lists all 27, field by field, sourced from the subscriber or patch
4
+ This lists all 28, field by field, sourced from the subscriber or patch
5
5
  that builds each one. Field names below are the hash keys as sent over
6
6
  the wire: symbols in Ruby, strings in the gzip NDJSON payload.
7
7
 
@@ -207,7 +207,7 @@ vs `#execute`. `db:migrate` and other tasks in
207
207
  | `name` | Task name, or `"runner"`. |
208
208
  | `command` | Full invocation, e.g. `"rake db:seed[foo]"` or `"rails runner SomeScript.run"`. |
209
209
  | `exit_code` | 0 on success, `SystemExit`'s status, or 1 on an unhandled exception, clamped to 0-255. |
210
- | `interactive` | `true` on a `bin/rails runner` an engineer typed or piped (`-`, inline code, or a `.rb` file under `config.interactive_runner_paths`); absent otherwise. Such a run ships this record — with its `exit_code` and `exception_preview` — but its exception is not reported. A deployed script (`rails runner script/nightly.rb`), a rake task, and a job are never interactive. See [Console and runner sessions](replacing-sentry.md#console-and-runner-sessions). |
210
+ | `interactive` | `true` on a `bin/rails runner` an engineer typed or piped (`-`, inline code, or a `.rb` file under `config.interactive_runner_paths`); absent otherwise. Such a run ships this record — with its `exit_code` and `exception_preview` — but its exception is not reported. A deployed script (`rails runner script/nightly.rb`), a rake task, and a job are never interactive. See [Console and runner sessions](replacing-sentry.md#12-console-and-runner-sessions). |
211
211
 
212
212
  ### `channel_action`
213
213
 
@@ -526,14 +526,17 @@ adds the `workflow_*` fields. `cost_nanos` is null rather than zero
526
526
  whenever RubyLLM reported no cost or the model registry could not price
527
527
  it — an unpriced call is not a free one.
528
528
 
529
- **Concurrent tool calls are not recorded.** RubyLLM's opt-in
530
- `tool_concurrency` (`:threads` or `:fibers`) runs each tool in a fresh
531
- thread or fiber. `Railwatch::Current` is backed by
532
- `ActiveSupport::IsolatedExecutionState`, which a new thread does not
533
- inherit, so the `tool_call.ruby_llm` event fires with no execution to
534
- attach to and the record is dropped rather than misattributed. This
535
- affects every Railwatch subscriber in an app-spawned thread, not just this
536
- one. Tool concurrency is off by default; with it off, tool calls are
529
+ **Tool calls run on their own threads are not recorded.** RubyLLM's opt-in
530
+ `tool_concurrency: :threads` runs each tool in a fresh thread.
531
+ `Railwatch::Current` is backed by `ActiveSupport::IsolatedExecutionState`,
532
+ which a new thread does not inherit, so the `tool_call.ruby_llm` event
533
+ fires with no execution to attach to and the record is dropped rather
534
+ than misattributed. This affects every Railwatch subscriber in an
535
+ app-spawned thread, not just this one. `:fibers` depends on Rails'
536
+ isolation level: under the default, `:thread`, fibers share their
537
+ thread's state and the tool calls are recorded; with
538
+ `config.active_support.isolation_level = :fiber` they are dropped the same
539
+ way. Tool concurrency is off by default; with it off, tool calls are
537
540
  recorded normally. The model calls themselves are unaffected either way,
538
541
  so cost is always complete.
539
542
 
@@ -2,10 +2,12 @@
2
2
 
3
3
  Railwatch is the same product shape for Rails: one package instruments the
4
4
  framework end to end, records are grouped under the execution that
5
- produced them, and a hosted platform turns them into routes, jobs,
6
- queries, issues, and alerts. If you know Nightwatch, you already know
7
- how to read Railwatch — this page maps the vocabulary and points out the
8
- three places the Rails answer is genuinely different.
5
+ produced them, and a dashboard turns them into routes, jobs, queries,
6
+ issues, and alerts. Unlike Nightwatch, that dashboard runs inside your
7
+ app by default ([embedded mode](embedded.md)); Railwatch Cloud is the
8
+ hosted option. If you know Nightwatch, you already know how to read
9
+ Railwatch — this page maps the vocabulary and points out the three
10
+ places the Rails answer is genuinely different.
9
11
 
10
12
  Railwatch is an independent product and is not affiliated with Laravel or
11
13
  Laravel Nightwatch.
@@ -18,10 +20,10 @@ separate daemon POSTs them.
18
20
 
19
21
  Puma and Solid Queue workers *are* long-lived, so Railwatch skips that
20
22
  tier. A single reporter thread per process holds a bounded buffer
21
- (default 5,000 records, oldest dropped and counted), and gzip-NDJSON
22
- POSTs batches to the platform every 2 seconds or every 500 records. There
23
- is no daemon to install, supervise, or forget to restart, and nothing
24
- between the app and the ingest URL.
23
+ (16 MiB and 10,000 records by default, oldest dropped and counted) and
24
+ flushes batches every 2 seconds or every 500 records: to the embedded
25
+ writer process Puma forks, or as gzip-NDJSON POSTs to Railwatch Cloud.
26
+ There is no daemon to install, supervise, or forget to restart.
25
27
 
26
28
  The thread is re-armed after `fork`, so clustered Puma workers and
27
29
  forked Solid Queue workers each get their own with no `on_worker_boot`
@@ -50,7 +52,7 @@ Nightwatch's types, and what they're called here:
50
52
  | `queued-job` | `enqueued_job` | The enqueue side, in the execution that enqueued it. |
51
53
  | `log` | `log` | Lines at or above `log_level`, plus Rails 8.1 structured `Rails.event` events. |
52
54
  | `user` | `user` | Resolved once per user per process-hour, not once per request. |
53
- | deployment (`nightwatch:deploy`) | `Deploy` on the platform | Posted by `railwatch:deploy` or the Kamal `post-deploy` hook, with up to 50 commits so the platform can diff what shipped. |
55
+ | deployment (`nightwatch:deploy`) | `Deploy` on the dashboard | Recorded by `railwatch:deploy` or the Kamal `post-deploy` hook, with up to 50 commits when Git history is at hand, so the dashboard can diff what shipped. |
54
56
  | request `stages` | parent `stages` | Same idea, Rails boundaries: `middleware_before`, `action`, `render`, `middleware_after`, `body`. Laravel's `bootstrap` has no equivalent in a warm process — Railwatch reports boot time once per process as a `process` record instead. |
55
57
 
56
58
  And the types with no Nightwatch counterpart at all: `storage_op` (Active
@@ -59,7 +61,7 @@ Storage), `view_render`, `span` (your own timed blocks), `attachment`,
59
61
  `session` (release health), `process`, `health` (Puma pool, Active Record
60
62
  pool, Solid Queue backlog), and `profile` (sampled stack profiles).
61
63
 
62
- Field-by-field detail for all 26 is in [`records.md`](records.md).
64
+ Field-by-field detail for all 28 is in [`records.md`](records.md).
63
65
 
64
66
  ## What "execution" means
65
67
 
@@ -76,7 +78,7 @@ it is propagated across services as a W3C `traceparent` on outgoing HTTP
76
78
  — an inbound `traceparent` is adopted, so a trace spans services rather
77
79
  than stopping at the process boundary.
78
80
 
79
- Practically: on the platform you never look at a query in isolation. You
81
+ Practically: on the dashboard you never look at a query in isolation. You
80
82
  open the request, and the query is in its waterfall with everything else
81
83
  that execution did.
82
84
 
@@ -182,7 +184,7 @@ every setting still has an env var — `NIGHTWATCH_*` becomes `RAILWATCH_*`.
182
184
  | `nightwatch:agent` | Nothing — the reporter thread lives in the app process. |
183
185
  | `nightwatch:status` | `bin/rails railwatch:status` |
184
186
  | `nightwatch:deploy {deploy} --ref --name --url` | `bin/rails railwatch:deploy[ref,name,url]`, or the generated `.kamal/hooks/post-deploy` |
185
- | — | `bin/rails railwatch:doctor`, which checks the whole install and exits non-zero if the token or the ingest host is wrong |
187
+ | — | `bin/rails railwatch:doctor`, which checks the whole install and exits non-zero if the embedded databases, the token, or the ingest host is wrong |
186
188
 
187
189
  ## The three things worth knowing about Rails
188
190
 
@@ -211,6 +213,6 @@ See [`testing.md`](testing.md).
211
213
 
212
214
  ## Next
213
215
 
214
- - [`getting-started.md`](getting-started.md) — install, in five minutes.
216
+ - [`getting-started.md`](getting-started.md) — the two-command install.
215
217
  - [`configuration.md`](configuration.md) — every option and env var.
216
- - [`records.md`](records.md) — all 26 record types, field by field.
218
+ - [`records.md`](records.md) — all 28 record types, field by field.
@@ -8,7 +8,10 @@ depends on a conditional row.
8
8
 
9
9
  Install Railwatch first ([`getting-started.md`](getting-started.md)); you
10
10
  can run both for a day if you want to compare, since neither knows about
11
- the other.
11
+ the other. The default install is embedded, with the dashboard at
12
+ `/railwatch` and no token. The token and alert steps below apply when
13
+ you send to Railwatch Cloud, either as a cloud install or by exporting
14
+ from embedded.
12
15
 
13
16
  ## Decide whether Railwatch covers your workload
14
17
 
@@ -30,7 +33,7 @@ Railwatch does not claim those broader capabilities.
30
33
  | Ruby profiling | Conditional | Requires `vernier` or `stackprof`; there is no profiler bundled into the SDK. |
31
34
  | Browser monitoring | Partial | The optional Inertia client reports visits, Web Vitals, browser errors, and breadcrumbs. Session Replay, native/mobile SDKs, and Sentry's full browser/source-map workflow are outside the currently released Rails-server replacement. |
32
35
  | Runtime compatibility | Narrow today | The currently proved pair is Ruby 3.4 + Rails 8.1. A maintained compatibility matrix and any safe lowering of requirements are tracked by [#26](https://github.com/Rebulk/railwatch/issues/26). |
33
- | Managed integrations and operations | Partial | Railwatch Cloud supports its documented email, Slack, and webhook paths plus self-hosting. It does not promise Sentry's broader integration catalog. |
36
+ | Managed integrations and operations | Partial | Railwatch Cloud delivers alerts to email, Slack, webhooks and Linear, plus self-hosting; embedded mode records alerts without delivering them. It does not promise Sentry's broader integration catalog. |
34
37
  | SQL value privacy | Supported by default | Query records carry normalized SQL shapes without literal values, and Active Record binds are never sent. Raw SQL and query plans are separate opt-ins; either can contain values. |
35
38
 
36
39
  For a Rails 8.1 application whose work enters through Rack and Active Job,
@@ -70,9 +73,11 @@ deploy config once the app boots without it.
70
73
  `config/initializers/railwatch.rb` (written by the install generator) is
71
74
  where every option from `Sentry.init` lands. The mapping:
72
75
 
73
- - **`dsn:`** becomes `RAILWATCH_TOKEN`, one token per environment, created
74
- in Railwatch Cloud. Self-hosting adds `RAILWATCH_INGEST_URL`. The token is
75
- also the on/off switch: with it blank, Railwatch installs nothing.
76
+ - **`dsn:`** has no equivalent in embedded mode, which stores telemetry in
77
+ the app. For Railwatch Cloud it becomes `RAILWATCH_TOKEN`, one token per
78
+ environment, created in Railwatch Cloud. Self-hosting adds
79
+ `RAILWATCH_INGEST_URL`. On a cloud install the token is also the on/off
80
+ switch: with it blank, Railwatch installs nothing.
76
81
  - **`environment:`** becomes `c.environment`, which defaults to
77
82
  `Rails.env` — set it only to report under a different name.
78
83
  - **`release:`** becomes `c.deploy`, which auto-detects the release from
@@ -111,7 +116,9 @@ where every option from `Sentry.init` lands. The mapping:
111
116
  - **Rack `X-Request-Start` queue time** needs no setting: it is parsed
112
117
  into `queue_time` on every `request` record.
113
118
 
114
- A worked initializer, roughly what a `Sentry.init` block turns into:
119
+ A worked initializer for a cloud install, roughly what a `Sentry.init`
120
+ block turns into (an embedded install has `c.transport = :local` instead
121
+ of the token):
115
122
 
116
123
  ```ruby
117
124
  # config/initializers/railwatch.rb
@@ -415,9 +422,9 @@ startRailwatch({
415
422
 
416
423
  | Sentry | Railwatch |
417
424
  |---|---|
418
- | `Sentry.init({ dsn })` | `startRailwatch()`. There is no DSN: the beacon posts to the app's own origin and the *server* decides whether to record it (`c.beacon_enabled`, `RAILWATCH_TOKEN`). The gate you already have on whether `startRailwatch()` runs at all is the only gate. |
425
+ | `Sentry.init({ dsn })` | `startRailwatch()`. There is no DSN: the beacon posts to the app's own origin and the *server* decides whether to record it (`c.beacon_enabled`, and whether Railwatch is enabled). The gate you already have on whether `startRailwatch()` runs at all is the only gate. |
419
426
  | `release` | Automatic. The record is stamped with `c.deploy`, the same release the server records carry, so a browser issue and a server issue from one deploy line up without a matching pair of settings to get wrong. |
420
- | `environment` | Automatic — the ingest token identifies the environment. |
427
+ | `environment` | Automatic — the embedded databases belong to one environment, and on Railwatch Cloud the ingest token identifies it. |
421
428
  | `ignoreErrors` | `startRailwatch({ ignoreErrors })`. Strings match anywhere in the message; regexes are tested against it. Both `ResizeObserver` messages are ignored by default. |
422
429
  | `denyUrls` | `startRailwatch({ denyUrls })`, matched against the top stack frame's URL. `/extensions\//i`, `/^chrome:\/\//i`, and `/^moz-extension:\/\//i` are denied by default, **and** any frame from an origin that isn't the app's own is dropped — extensions, injected widgets, tag managers. |
423
430
  | `Sentry.setUser` | Server-side. The beacon is a same-origin POST carrying the session cookie, so the server resolves the user the same way it does for a request (`Railwatch.user`) when `Current.user` or Warden is set by middleware; an app that authenticates in a `before_action` gives Railwatch the same lookup with `c.beacon_user { \|request\| ... }`. Nothing the browser sends names the user, so it cannot be forged. |
@@ -561,8 +568,10 @@ hook matching `rails runner …` previews, or `sentry_runner_noise.rb` under
561
568
  | Inertia visit timing | Real browser page-visit duration, prop byte size, partial reloads, SSR time, and Core Web Vitals, from a client the generator installs. |
562
569
  | Spec matchers as a CI gate | `have_railwatch_queries`, `have_railwatch_n_plus_one`, `have_railwatch_outgoing_requests` fail the pull request that regresses a hot path. |
563
570
  | Zero app-DB writes | The gem holds records in memory and ships them from a background thread; a bench gate asserts no `INSERT`/`UPDATE`/`DELETE` ever originates in `lib/railwatch`. This is why it is safe on single-writer SQLite. |
564
- | One SQLite database per environment | The platform stores each monitored environment's telemetry in its own database file, which makes retention pruning, backup, and restore per-environment operations. |
565
- | An MCP server | AI assistants can ask what broke after the last deploy, list slow routes, read an execution's timeline, and search logs ([`ai-and-mcp.md`](ai-and-mcp.md)). |
571
+ | A dashboard inside the app | The default install serves the whole dashboard at `/railwatch` from two SQLite files the app owns, with no hosted service at all ([`embedded.md`](embedded.md)). |
572
+ | One SQLite database per environment | Railwatch Cloud stores each monitored environment's telemetry in its own database file, which makes retention pruning, backup, and restore per-environment operations. |
573
+ | LLM calls | RubyLLM calls are recorded with tokens, cost, finish reason, tool calls and workflows, in the request or job that made them ([`records.md`](records.md#llm_call)). |
574
+ | An MCP server | With Railwatch Cloud, AI assistants can ask what broke after the last deploy, list slow routes, read an execution's timeline, and search logs ([`ai-and-mcp.md`](ai-and-mcp.md)). |
566
575
 
567
576
  ## See also
568
577
 
data/docs/security.md CHANGED
@@ -3,8 +3,25 @@
3
3
  This review covers the open-source gem that runs in a customer's Rails
4
4
  application. Railwatch Cloud is a separate service and is outside this review.
5
5
 
6
+ ## Embedded dashboard
7
+
8
+ In embedded mode (the installer's default) the gem also serves a dashboard at
9
+ the engine's mount, showing every query, log line and exception the app
10
+ recorded. HTTP Basic authentication is on by default. With no credentials
11
+ configured, every dashboard request is 401 in every environment except
12
+ development, where it is open; the app logs a warning at boot outside
13
+ development, and `railwatch:doctor` reports the gate. Credentials, once set,
14
+ apply in development too. An app that turns Basic off gates pages with its own
15
+ `base_controller_class` or a routes constraint; live updates over Action Cable
16
+ are refused unless Basic, a `dashboard_user` resolver, or `dashboard_open`
17
+ authorizes them. See [Embedded mode](embedded.md#authentication).
18
+
19
+ The writer socket that web workers hand batches to is created with a 0700
20
+ directory and a 0600 socket, so only the app's own user can connect.
21
+
6
22
  ## Transport
7
23
 
24
+ This applies to a cloud install and to export from an embedded install.
8
25
  `Railwatch::Transport::Http` sends gzip NDJSON with `Net::HTTP`. HTTPS explicitly
9
26
  sets OpenSSL's `VERIFY_PEER`; a spec pins that setting. The transport does not
10
27
  implement redirect handling, so a redirect response is treated as a permanent
@@ -35,8 +52,9 @@ configuration and record specs:
35
52
  filtered. `Locals.inspect_value` rescues an `inspect` implementation that
36
53
  raises; this behavior is covered by an exception-record spec.
37
54
  - `capture_exception_source` is true. Source lines surrounding in-application
38
- backtrace frames are sent to Railwatch Cloud. Disable it if source context is
39
- outside the application's telemetry policy.
55
+ backtrace frames are recorded, and sent to Railwatch Cloud when the install
56
+ reports or exports there. Disable it if source context is outside the
57
+ application's telemetry policy.
40
58
 
41
59
  Log record messages are sent exactly as supplied to `Rails.logger`. Railwatch
42
60
  does not attempt to parse and partially filter `key=value` text because doing
@@ -46,7 +64,8 @@ and `Railwatch.reject_logs` or `RAILWATCH_IGNORE_LOGS=true` can omit log records
46
64
 
47
65
  ## Browser beacon
48
66
 
49
- `POST /railwatch/beacon` is the gem's only inbound unauthenticated endpoint. It
67
+ `POST /railwatch/beacon` is the gem's only inbound endpoint that is
68
+ unauthenticated by design. It
50
69
  is rate-limited per client IP through the Rails cache (120 requests per minute
51
70
  by default), limited to a 256 KiB body, and capped at 50 visits and 50 errors
52
71
  per request. Nested error stacks, messages, breadcrumbs, context, visit partial
@@ -88,6 +107,8 @@ application/deployment secret and must not be committed.
88
107
  application-specific credentials and personal data.
89
108
  - Keep secrets out of exception messages, log text, source files, tenant ids,
90
109
  user resolvers, and custom context.
110
+ - In embedded mode, set dashboard credentials (or your own gate) before
111
+ deploying; see [Embedded mode](embedded.md#authentication).
91
112
  - Use HTTPS in production and keep TLS verification enabled.
92
113
  - Put a request-body limit at the reverse proxy when the public beacon is
93
114
  enabled, and choose a shared cache if rate limits must span processes.
data/docs/self-hosting.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Self-hosting
2
2
 
3
+ This page is for sending telemetry to a Railwatch Cloud you run
4
+ yourself. An [embedded](embedded.md) install keeps everything inside
5
+ your app and needs none of it.
6
+
3
7
  Railwatch Cloud is a Rails app you can run yourself. The gem doesn't care
4
8
  which install it talks to — point it at yours and everything works the
5
9
  same.
@@ -17,12 +21,16 @@ end
17
21
  `ingest_url` defaults to `https://railwatch.rebulk.com`, so this is the one
18
22
  setting a self-hosted install always needs; everything the gem sends —
19
23
  records, ping, deploys — hangs off that host. Pass `--url=` to the
20
- installer to have it written for you:
24
+ installer to have it written for you (it implies `--cloud`):
21
25
 
22
26
  ```sh
23
27
  bin/rails generate railwatch:install --url=https://telemetry.example.com
24
28
  ```
25
29
 
30
+ To keep an embedded install and mirror it to your platform, set
31
+ `RAILWATCH_INGEST_URL` and `RAILWATCH_TOKEN` and turn on
32
+ [export](embedded.md#three-ways-to-run-it) instead.
33
+
26
34
  ## Getting a token
27
35
 
28
36
  On your install: sign up, create an application, then create an
data/docs/testing.md CHANGED
@@ -27,10 +27,20 @@ require "railwatch/minitest"
27
27
  ```
28
28
 
29
29
  Railwatch must be *enabled* in the test environment or every block would look
30
- empty. `config.enabled?` is true when `config.enabled` is set and a token is
31
- present, so set any non-blank `RAILWATCH_TOKEN` for the test env. Records go to
32
- an in-memory transport, never over the network. If Railwatch is disabled, the
33
- matchers raise `Railwatch::SpecHelper::Disabled` rather than quietly passing.
30
+ empty. An embedded install (`transport = :local`) is enabled with no token. A
31
+ cloud install needs a token present, so set any non-blank `RAILWATCH_TOKEN` for
32
+ the test env. Either way records go to an in-memory transport, never over the
33
+ network or into the telemetry database. If Railwatch is disabled, the matchers
34
+ raise `Railwatch::SpecHelper::Disabled` rather than quietly passing.
35
+
36
+ An embedded install adds its two databases to the `test` environment too, and
37
+ Rails' schema check refuses to run the suite while their migrations are
38
+ pending. `bin/rails db:test:prepare` does not create them, because they keep no
39
+ schema file; run this once locally and as a CI setup step:
40
+
41
+ ```sh
42
+ RAILS_ENV=test bin/rails db:prepare
43
+ ```
34
44
 
35
45
  Sampling is forced on for the block, so a fractional `c.sample` in the app's
36
46
  test config can't turn an assertion into one that never fires either.