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.
- checksums.yaml +4 -4
- data/AGENTS.md +45 -25
- data/CHANGELOG.md +147 -0
- data/README.md +50 -31
- data/app/controllers/railwatch/dashboard_controller.rb +1 -0
- data/app/jobs/railwatch/rollup_job.rb +3 -1
- data/app/models/railwatch/application_record.rb +2 -2
- data/app/models/railwatch/ingest/rollup_absorber.rb +1 -1
- data/app/models/railwatch/telemetry_record.rb +2 -2
- data/docs/ai-and-mcp.md +9 -3
- data/docs/configuration.md +97 -40
- data/docs/embedded.md +96 -43
- data/docs/faq.md +28 -15
- data/docs/getting-started.md +121 -42
- data/docs/records.md +13 -10
- data/docs/replacing-nightwatch.md +16 -14
- data/docs/replacing-sentry.md +19 -10
- data/docs/security.md +24 -3
- data/docs/self-hosting.md +9 -1
- data/docs/testing.md +14 -4
- data/docs/troubleshooting.md +72 -29
- data/lib/generators/railwatch/install/install_generator.rb +32 -16
- data/lib/generators/railwatch/install/templates/initializer.rb.tt +6 -6
- data/lib/puma/plugin/railwatch.rb +48 -3
- data/lib/railwatch/configuration.rb +16 -1
- data/lib/railwatch/engine.rb +14 -3
- data/lib/railwatch/reporter.rb +52 -16
- data/lib/railwatch/transport/http.rb +4 -9
- data/lib/railwatch/version.rb +1 -1
- data/lib/railwatch.rb +23 -1
- data/lib/tasks/railwatch_tasks.rake +11 -4
- data/llms.txt +21 -14
- data/public/railwatch/assets/{app-layout-DDyQa72H.js → app-layout-Zc0v-hYh.js} +1 -1
- data/public/railwatch/assets/{app-wordmark-o9CODKP0.js → app-wordmark-BA_60AVb.js} +1 -1
- data/public/railwatch/assets/{appearance-BwuCXabr.js → appearance-CcfP9tZ7.js} +1 -1
- data/public/railwatch/assets/application-BrN3Sz94.css +1 -0
- data/public/railwatch/assets/{arrow-up-C6PxDiY3.js → arrow-up-CLQ-7heQ.js} +1 -1
- data/public/railwatch/assets/{auth-layout-BRt8MGFD.js → auth-layout-C05gEBIQ.js} +1 -1
- data/public/railwatch/assets/{badge-CAxXV8za.js → badge-DRae8XwK.js} +1 -1
- data/public/railwatch/assets/{braces-DgomTCNf.js → braces-rSYydpLY.js} +1 -1
- data/public/railwatch/assets/{card-cAtqCxWl.js → card-DPjFKfen.js} +1 -1
- data/public/railwatch/assets/{chart-BBeBkkNa.js → chart-DWh7l8yM.js} +1 -1
- data/public/railwatch/assets/{chart-hover-B1M9jc0y.js → chart-hover-CfoZUY4J.js} +1 -1
- data/public/railwatch/assets/{chart-panel-DUQTz_C8.js → chart-panel-CC49WTQL.js} +1 -1
- data/public/railwatch/assets/{checkbox-CmhMHWZO.js → checkbox-DPkLUiwM.js} +1 -1
- data/public/railwatch/assets/{code-DESvxyTj.js → code-CLmYS6FU.js} +1 -1
- data/public/railwatch/assets/{copy-block-BkSU5832.js → copy-block-CGcXxp8J.js} +1 -1
- data/public/railwatch/assets/{copy-id-D03GhN9F.js → copy-id-vBYHQwxg.js} +1 -1
- data/public/railwatch/assets/{cursor-load-more-CRyuMeQb.js → cursor-load-more-Ddwxe5dZ.js} +1 -1
- data/public/railwatch/assets/{data-table-BIlt7Rtm.js → data-table-CzKTEE-O.js} +1 -1
- data/public/railwatch/assets/{edit-O0NSBWxo.js → edit-B1kmWkzd.js} +1 -1
- data/public/railwatch/assets/{edit-DJ0D0wHN.js → edit-D9cx4pbG.js} +1 -1
- data/public/railwatch/assets/{edit-Bb6MKoe4.js → edit-yv6j9p-V.js} +1 -1
- data/public/railwatch/assets/{empty-state-C38il627.js → empty-state-CW4wclK_.js} +1 -1
- data/public/railwatch/assets/{env-layout-REF7OM4q.js → env-layout-Kz7wks1x.js} +1 -1
- data/public/railwatch/assets/{execution-path-FYLq1TwC.js → execution-path-FAuIzOXB.js} +1 -1
- data/public/railwatch/assets/{filter-bar-CYog9Alp.js → filter-bar-CJDjWFib.js} +1 -1
- data/public/railwatch/assets/{flamegraph-DSs69foN.js → flamegraph-EaGkP2NT.js} +1 -1
- data/public/railwatch/assets/{frames-BUi2J5Mk.js → frames-zZuIaNH9.js} +1 -1
- data/public/railwatch/assets/{google-sign-in-button-BQiIKFdd.js → google-sign-in-button-2_zgbgVy.js} +1 -1
- data/public/railwatch/assets/{index-ZOGOB8SA.js → index-B49SWz7K.js} +1 -1
- data/public/railwatch/assets/{index-DqTFTP8p.js → index-BHlY4wKe.js} +1 -1
- data/public/railwatch/assets/{index-DrcKVG2f.js → index-BZpPtyFY.js} +1 -1
- data/public/railwatch/assets/{index-Dh4IRLFI.js → index-BeVK6jCL.js} +1 -1
- data/public/railwatch/assets/{index-umIAl-pL.js → index-BfbSo01U.js} +1 -1
- data/public/railwatch/assets/{index-BoUBioBP.js → index-BfgncAv6.js} +1 -1
- data/public/railwatch/assets/{index-C3A_9imx.js → index-BhNszK1k.js} +1 -1
- data/public/railwatch/assets/{index-tpz-OGUP.js → index-Bu01uWvw.js} +1 -1
- data/public/railwatch/assets/{index-CGs4m_fa.js → index-C1s_hK3p.js} +1 -1
- data/public/railwatch/assets/{index-ZSZg9rtq.js → index-C8Cggnbw.js} +1 -1
- data/public/railwatch/assets/{index-so4lRrRq.js → index-CBip6V4z.js} +1 -1
- data/public/railwatch/assets/{index-DvjY3dPD.js → index-CMJGss5R.js} +1 -1
- data/public/railwatch/assets/{index-DtHmuB9Q.js → index-CRo3yK20.js} +1 -1
- data/public/railwatch/assets/{index-r0tSIplE.js → index-CWB_p2J8.js} +1 -1
- data/public/railwatch/assets/{index-CICUIFHL.js → index-CWHpndGc.js} +1 -1
- data/public/railwatch/assets/{index-CFFpnzIS.js → index-Ca_S4Sc3.js} +1 -1
- data/public/railwatch/assets/{index-DSvlZVWG.js → index-CanPDDOa.js} +1 -1
- data/public/railwatch/assets/{index-CrZ3vHDL.js → index-Cie90Yat.js} +1 -1
- data/public/railwatch/assets/{index-FhUaPPab.js → index-CiepQ_pR.js} +1 -1
- data/public/railwatch/assets/{index-C_upSl_k.js → index-Cm1uCIGN.js} +1 -1
- data/public/railwatch/assets/{index-sTYvcbkh.js → index-CzitnnSC.js} +1 -1
- data/public/railwatch/assets/{index-C7OtLq_3.js → index-DEUfClv3.js} +1 -1
- data/public/railwatch/assets/index-DJKwo-mI.js +1 -0
- data/public/railwatch/assets/{index-DDI_Zx5V.js → index-DK6y0YHp.js} +1 -1
- data/public/railwatch/assets/{index-C-PmdhXA.js → index-DMNPpLH9.js} +1 -1
- data/public/railwatch/assets/{index-BaR1U9An.js → index-DZO1mSfX.js} +1 -1
- data/public/railwatch/assets/{index-CsoN51vW.js → index-Db5wj3M5.js} +1 -1
- data/public/railwatch/assets/{index-BiiyMcA0.js → index-Dcy5WktB.js} +1 -1
- data/public/railwatch/assets/{index-8-hnAhOD.js → index-DvG0-7Lx.js} +1 -1
- data/public/railwatch/assets/{index-QpTtwFwu.js → index-Dvj1wuka.js} +1 -1
- data/public/railwatch/assets/{index-DW2CBbxU.js → index-KyZX46qX.js} +1 -1
- data/public/railwatch/assets/{index-BeOh2t_S.js → index-Ze-KP-sl.js} +1 -1
- data/public/railwatch/assets/{index-CiPo4Gob.js → index-mhRbWLBM.js} +1 -1
- data/public/railwatch/assets/{index-CpkI015n.js → index-x099JL5f.js} +1 -1
- data/public/railwatch/assets/{index-JdCVBrw8.js → index-x28zb_nf.js} +1 -1
- data/public/railwatch/assets/{inertia-DLew8ZNx.js → inertia-Cuyz2ZHO.js} +2 -2
- data/public/railwatch/assets/{input-error-cvM6_Jht.js → input-error-hog6gGxg.js} +1 -1
- data/public/railwatch/assets/{json-viewer-D922McGi.js → json-viewer-DH2W9HXf.js} +1 -1
- data/public/railwatch/assets/{klass-CrwICqN8.js → klass-DHbDelLk.js} +1 -1
- data/public/railwatch/assets/{label-GWl7I6sf.js → label-DdCBgiUn.js} +1 -1
- data/public/railwatch/assets/{layout-0ZAnD3zl.js → layout-Cueyl7c5.js} +1 -1
- data/public/railwatch/assets/{live-dot-D1n_BreY.js → live-dot-BZgYTYdt.js} +1 -1
- data/public/railwatch/assets/{nav-DPxr1NNC.js → nav-BSSGObDZ.js} +1 -1
- data/public/railwatch/assets/{new-D-ZzUK9a.js → new-B8FSb8Bl.js} +1 -1
- data/public/railwatch/assets/{new-DEVkYv-z.js → new-C39_v2Ll.js} +1 -1
- data/public/railwatch/assets/{new-Cdl6pqST.js → new-DVOPwaC5.js} +1 -1
- data/public/railwatch/assets/{new-DHAHDrN7.js → new-Dd40nvJR.js} +1 -1
- data/public/railwatch/assets/{new-Dz4lZf1L.js → new-SxnYe1SE.js} +1 -1
- data/public/railwatch/assets/{new-GMrRFurX.js → new-eP5vKD3Y.js} +1 -1
- data/public/railwatch/assets/onboarding-CUpZl5KB.js +1 -0
- data/public/railwatch/assets/{origin-identity-Bk9yHWZ1.js → origin-identity-BYt2uiuo.js} +1 -1
- data/public/railwatch/assets/{percentile-picker-DfSx9yJO.js → percentile-picker-CeQgllxD.js} +1 -1
- data/public/railwatch/assets/{relative-time-CjIjb8Lg.js → relative-time-D5UbF4oO.js} +1 -1
- data/public/railwatch/assets/{release-health-4b3tivEf.js → release-health-uP-GF3_V.js} +1 -1
- data/public/railwatch/assets/{route-C_5BUtHK.js → route-DQAY8JEr.js} +1 -1
- data/public/railwatch/assets/{segmented-BgbT3wZa.js → segmented-BeIe4uqk.js} +1 -1
- data/public/railwatch/assets/{select-DmunxCKE.js → select-2R16457Z.js} +1 -1
- data/public/railwatch/assets/{separator-BXzEdZ_8.js → separator-D5I0UCB5.js} +1 -1
- data/public/railwatch/assets/series-chart-gnrzhmM6.js +1 -0
- data/public/railwatch/assets/show-2BkeNRUC.js +2 -0
- data/public/railwatch/assets/{show-mU38uGTg.js → show-B0X1hRQH.js} +1 -1
- data/public/railwatch/assets/{show-CAl7xcex.js → show-BKUV5l5q.js} +1 -1
- data/public/railwatch/assets/{show-DnR1Dnjd.js → show-BQJD_CsF.js} +1 -1
- data/public/railwatch/assets/{show-Dn-GwFZL.js → show-BhmD6Sxp.js} +1 -1
- data/public/railwatch/assets/{show-Y74rM0VT.js → show-C3KcnVo2.js} +1 -1
- data/public/railwatch/assets/{show-Dily73Xk.js → show-C95cHd06.js} +1 -1
- data/public/railwatch/assets/{show-DXs4deaC.js → show-CdB4uVQS.js} +1 -1
- data/public/railwatch/assets/{show-DI8IhNUH.js → show-CoCqcVIp.js} +1 -1
- data/public/railwatch/assets/{show-vQ4bndYD.js → show-D2LqjEsU.js} +1 -1
- data/public/railwatch/assets/{show-DcpTFiLi.js → show-DNpnKyTh.js} +1 -1
- data/public/railwatch/assets/{show-B2zLAW83.js → show-DOlTxbig.js} +1 -1
- data/public/railwatch/assets/{show-SvLOcPrx.js → show-DQCkttL8.js} +1 -1
- data/public/railwatch/assets/{show-BLpWUHWD.js → show-DcxesipC.js} +1 -1
- data/public/railwatch/assets/{show-DYskfl3-.js → show-IGJoc_X0.js} +1 -1
- data/public/railwatch/assets/{show-DSP9Cq_C.js → show-YsNpqbcI.js} +1 -1
- data/public/railwatch/assets/{show-DlRVS18-.js → show-qNV6H8SH.js} +1 -1
- data/public/railwatch/assets/{sort-header-Dcq9bzmo.js → sort-header-mEJUM3Av.js} +1 -1
- data/public/railwatch/assets/{sparkline-cell-BON3qQUB.js → sparkline-cell-Dwg7awwh.js} +1 -1
- data/public/railwatch/assets/{stat-DFEyFxkO.js → stat-ZtxGU8lE.js} +1 -1
- data/public/railwatch/assets/{status-badge-BaUKP7Yo.js → status-badge-CH5P-Xkj.js} +1 -1
- data/public/railwatch/assets/{tenant-path-DPZPc985.js → tenant-path-CQoP-BeF.js} +1 -1
- data/public/railwatch/assets/{text-link-BO77t9Xk.js → text-link-DHJ8BXx5.js} +1 -1
- data/public/railwatch/assets/{textarea-DTqrCiV0.js → textarea-BeQtQyl5.js} +1 -1
- data/public/railwatch/assets/{timeline-D5rJ0es2.js → timeline-CaKQVu48.js} +1 -1
- data/public/railwatch/assets/{transition-DMIrZVth.js → transition-ksDpqhKJ.js} +1 -1
- data/public/railwatch/assets/{use-clipboard-ByoUGQqA.js → use-clipboard-DNefo-ky.js} +1 -1
- data/public/railwatch/assets/{use-live-D7xKz2ma.js → use-live-DMTuhKfB.js} +1 -1
- data/public/railwatch/manifest.json +1286 -1286
- metadata +116 -116
- data/public/railwatch/assets/application-B7h1MIhi.css +0 -1
- data/public/railwatch/assets/index-CFRLPs4J.js +0 -1
- data/public/railwatch/assets/onboarding-D1vwaHYT.js +0 -1
- data/public/railwatch/assets/series-chart-Xf49v9cv.js +0 -1
- data/public/railwatch/assets/show-SHwZjXb7.js +0 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 293f64188856fdbadb54fb91bc640441a0996ba3d914d320b778c72536cbb3a8
|
|
4
|
+
data.tar.gz: b10e36a4cbb054e5170d1984003be84d2ea62acee2c1a3bda1c3b560a5f54bab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e45fc461237f0d9aceccbd99f359b8a81a404d6620600361d3d4fd973f986baf32779ea3921f581a81527c4764687aa8771897c466498860cd8f30c53cd5e4b
|
|
7
|
+
data.tar.gz: 5152d7d1b250d98299e2f675a951ec6f544e0a91405365877b434e8fa6fb1cbfddf061f84fe06aca00cb9202de0092f0016c024779707e69f9d400d33117a007
|
data/AGENTS.md
CHANGED
|
@@ -4,35 +4,51 @@ About *using* the `railwatch` gem from a Rails application; copy
|
|
|
4
4
|
this into that application's repository. Index of everything else:
|
|
5
5
|
[`llms.txt`](llms.txt).
|
|
6
6
|
|
|
7
|
-
Railwatch instruments a Rails app end to end and
|
|
7
|
+
Railwatch instruments a Rails app end to end and stores linked telemetry in
|
|
8
|
+
two SQLite databases of the app's own (embedded, the default) or sends it to
|
|
8
9
|
Railwatch Cloud. Every request, job attempt, scheduled task run, and command is
|
|
9
|
-
an **execution**; every query, cache read, log line, outgoing HTTP call,
|
|
10
|
-
render, exception, and span is a child of one, linked by
|
|
11
|
-
`execution_id`/`trace_id`. It never writes to the app's database.
|
|
10
|
+
an **execution**; every query, cache read, log line, outgoing HTTP call, LLM
|
|
11
|
+
call, view render, exception, and span is a child of one, linked by
|
|
12
|
+
`execution_id`/`trace_id`. It never writes to the app's primary database.
|
|
12
13
|
|
|
13
14
|
## Install
|
|
14
15
|
|
|
15
16
|
```sh
|
|
16
17
|
bundle add railwatch
|
|
17
|
-
bin/rails generate railwatch:install
|
|
18
|
+
bin/rails generate railwatch:install # embedded: dashboard at /railwatch
|
|
19
|
+
bin/rails generate railwatch:install --prompt-token --kamal-secrets # or: Railwatch Cloud
|
|
18
20
|
```
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
With no flags the install is embedded (see [Embedded mode](docs/embedded.md)):
|
|
23
|
+
telemetry stays in two SQLite databases the app owns (`railwatch`,
|
|
24
|
+
`railwatch_telemetry`, added to `config/database.yml`), Puma forks one writer
|
|
25
|
+
for them (`plugin :railwatch` in `config/puma.rb`), and the dashboard is served
|
|
26
|
+
at `/railwatch`. It is open in development and answers 401 elsewhere until
|
|
27
|
+
`RAILS_ENV=production bin/rails railwatch:authentication:configure` sets a
|
|
28
|
+
password. An exported `RAILWATCH_TOKEN` alone does not change the mode.
|
|
29
|
+
`--cloud`, or any of `--prompt-token`, `--token-stdin`, `--url`,
|
|
30
|
+
`--kamal-secrets`, sends telemetry to Railwatch Cloud instead.
|
|
31
|
+
|
|
32
|
+
Both modes write `config/initializers/railwatch.rb`, mount `Railwatch::Engine`
|
|
33
|
+
at `/railwatch`, add the Kamal `post-deploy` hook and the Inertia browser client
|
|
34
|
+
where the app has them, and require `railwatch/rspec` (or `railwatch/minitest`)
|
|
35
|
+
in the test helper; the cloud install then runs `railwatch:doctor`. A
|
|
36
|
+
prompted/stdin/environment token goes into `.env` only when Git confirms that
|
|
37
|
+
file is ignored; token values are never printed. Configuration lives only in
|
|
38
|
+
that initializer; every option also has a `RAILWATCH_*` environment variable.
|
|
39
|
+
An embedded install mirrors to Railwatch Cloud as well with a token and
|
|
40
|
+
`c.export_enabled = true`.
|
|
27
41
|
|
|
28
42
|
## Rake tasks
|
|
29
43
|
|
|
30
44
|
| Task | Does |
|
|
31
45
|
|---|---|
|
|
32
|
-
| `bin/rails railwatch:doctor` | ✓/✗ per check: token, ingest URL, reachability
|
|
33
|
-
| `bin/rails railwatch:
|
|
34
|
-
| `bin/rails railwatch:
|
|
35
|
-
| `bin/rails railwatch:
|
|
46
|
+
| `bin/rails railwatch:doctor` | ✓/✗ per check. Embedded: databases, migrations, writer process, last write, maintenance, dashboard access, export. Cloud: token, token storage, ingest URL, reachability. Both: middleware, engine mount, deploy, sample rates, ignored types, Kamal hook, browser client and whether an entrypoint calls it, profiler backend, test matchers. Exits non-zero on a missing database, pending migrations, broken export, a missing or tracked token, or an unreachable host. **Run this first when telemetry is missing.** |
|
|
47
|
+
| `bin/rails railwatch:authentication:configure` | Sets the embedded dashboard's HTTP Basic credentials for the current `RAILS_ENV`. |
|
|
48
|
+
| `bin/rails railwatch:export:status` | What the export queue holds and whether it can send. |
|
|
49
|
+
| `bin/rails railwatch:token` | Where to create a Railwatch Cloud ingest token for this app. |
|
|
50
|
+
| `bin/rails railwatch:mcp` | Paste-ready MCP client configuration for this app's Railwatch Cloud. |
|
|
51
|
+
| `bin/rails railwatch:deploy[ref,name,url]` | Records a deploy marker (in the embedded database, or on the cloud). Use as a release step when not deploying with Kamal. |
|
|
36
52
|
| `bin/rails 'railwatch:sourcemaps[public,true]'` | Uploads Vite source maps for the configured deploy, then deletes acknowledged files. Run after building and before publishing assets. Omit `true` to retain files. See [Source maps](docs/source-maps.md). |
|
|
37
53
|
|
|
38
54
|
## Facade
|
|
@@ -73,9 +89,11 @@ Per action, in a controller class body: `railwatch_sample 0.01, only: :index`,
|
|
|
73
89
|
## Specs
|
|
74
90
|
|
|
75
91
|
`require "railwatch/rspec"` in `spec/rails_helper.rb` (or `"railwatch/minitest"` in
|
|
76
|
-
`test/test_helper.rb`). Railwatch must be enabled in the test env
|
|
77
|
-
|
|
78
|
-
|
|
92
|
+
`test/test_helper.rb`). Railwatch must be enabled in the test env: an embedded
|
|
93
|
+
install is, and needs `RAILS_ENV=test bin/rails db:prepare` once so its test
|
|
94
|
+
databases exist; a cloud install needs any non-blank `RAILWATCH_TOKEN`. Records
|
|
95
|
+
go to an in-memory transport, never over the wire. All matchers are block
|
|
96
|
+
matchers.
|
|
79
97
|
|
|
80
98
|
```ruby
|
|
81
99
|
expect { get "/widgets" }.to have_railwatch_queries(at_most: 6) # or exactly:/at_least:
|
|
@@ -98,8 +116,8 @@ melt in production.
|
|
|
98
116
|
|
|
99
117
|
## MCP
|
|
100
118
|
|
|
101
|
-
|
|
102
|
-
token at Settings → Profile → "API & MCP token", then:
|
|
119
|
+
Railwatch Cloud (not embedded mode) is an MCP server at `<ingest host>/mcp`.
|
|
120
|
+
Generate a personal token at Settings → Profile → "API & MCP token", then:
|
|
103
121
|
|
|
104
122
|
```sh
|
|
105
123
|
claude mcp add railwatch --transport http https://railwatch.rebulk.com/mcp \
|
|
@@ -108,10 +126,12 @@ claude mcp add railwatch --transport http https://railwatch.rebulk.com/mcp \
|
|
|
108
126
|
|
|
109
127
|
`bin/rails railwatch:mcp` prints this and the Claude Desktop, Cursor, VS Code, and
|
|
110
128
|
Zed equivalents for whichever platform the app points at. Call
|
|
111
|
-
`list_applications` first; then `list_issues`, `get_issue
|
|
112
|
-
|
|
113
|
-
`
|
|
114
|
-
`
|
|
129
|
+
`list_applications` first; then `list_issues`, `get_issue` (stack frames,
|
|
130
|
+
cause, locals, breadcrumbs), `get_route`, `search_requests`, `get_execution`,
|
|
131
|
+
`explain_query`, `get_profile`, `search_logs`, `search_telemetry`,
|
|
132
|
+
`release_health`, `recent_deploys`, `list_alerts`, and the `triage_issue` /
|
|
133
|
+
`slow_route` / `daily_summary` prompts. `update_issue` and `add_comment` write,
|
|
134
|
+
attributed to the token's user and your agent name. All durations are
|
|
115
135
|
milliseconds; these docs are served at `railwatch://docs/<name>`.
|
|
116
136
|
|
|
117
137
|
## Gotchas
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,152 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
<!-- Pull requests add their entry here. The version number and the date are
|
|
6
|
+
filled in by the release commit, which is also the only commit that
|
|
7
|
+
touches lib/railwatch/version.rb and Gemfile.lock. See CONTRIBUTING.md. -->
|
|
8
|
+
|
|
9
|
+
## 0.6.1 (2026-09-23)
|
|
10
|
+
|
|
11
|
+
- The docs describe embedded as the default install throughout: two
|
|
12
|
+
commands, then optional Railwatch Cloud. They correct what 0.6.0 left
|
|
13
|
+
stale: an embedded install needs no token; the PostgreSQL and MySQL
|
|
14
|
+
steps; export needs a token first; a `dashboard_user` resolver names
|
|
15
|
+
the operator but does not gate pages; the Puma plugin line must be
|
|
16
|
+
unconditional; an embedded app's test databases need one
|
|
17
|
+
`RAILS_ENV=test bin/rails db:prepare`. MCP is marked as a Railwatch
|
|
18
|
+
Cloud feature. LLM calls are listed.
|
|
19
|
+
- The installer's embedded next steps say to get a token before turning
|
|
20
|
+
on export.
|
|
21
|
+
- Draw the Trend sparklines. Recharts pads every side of a chart by 5px,
|
|
22
|
+
which left the 32x10 table-cell sparkline a plot area 0px tall, so every
|
|
23
|
+
Trend column on every page was blank.
|
|
24
|
+
- LLM calls: a failed call no longer counts as "unpriced". It used no
|
|
25
|
+
tokens and has nothing to price, yet any failure made its model read
|
|
26
|
+
"+N unpriced" beside a complete spend total; the Recent table shows "–"
|
|
27
|
+
for it instead. The model and tool call charts label their bars ok /
|
|
28
|
+
failed instead of HTTP status classes, and the Recent table's Call
|
|
29
|
+
column truncates instead of pushing Detail off the right edge.
|
|
30
|
+
- Issues: the daily chart marks each deploy day with a line and names the
|
|
31
|
+
deploys in the tooltip, instead of printing refs that overprint when
|
|
32
|
+
several ship in a week.
|
|
33
|
+
- Jobs: the recent runs card keeps the job name readable. The exception
|
|
34
|
+
truncates, and the origin user and tenant columns appear from 2xl up
|
|
35
|
+
(the job class page shows them at every width).
|
|
36
|
+
|
|
37
|
+
## 0.6.0 (2026-09-22)
|
|
38
|
+
|
|
39
|
+
- Embedded is now the installer's default. `bin/rails generate
|
|
40
|
+
railwatch:install` with no flags writes what `--local` used to: the
|
|
41
|
+
`railwatch` and `railwatch_telemetry` SQLite databases, `plugin
|
|
42
|
+
:railwatch` in `config/puma.rb`, and `c.transport = :local`, so two
|
|
43
|
+
commands give a working dashboard at `/railwatch` with no account and no
|
|
44
|
+
token. The cloud install is `--cloud`, and any option that only means
|
|
45
|
+
something there (`--prompt-token`, `--token-stdin`, `--url`,
|
|
46
|
+
`--kamal-secrets`) implies it, so existing cloud instructions keep
|
|
47
|
+
working. An exported `RAILWATCH_TOKEN` on its own does not pick the
|
|
48
|
+
cloud: a token in the shell is not a decision about where data goes.
|
|
49
|
+
`--local` is gone (it is the default); the gem's runtime default when no
|
|
50
|
+
initializer sets a transport is unchanged. The embedded next steps now
|
|
51
|
+
end with how to mirror to Railwatch Cloud (`c.export_enabled`).
|
|
52
|
+
|
|
53
|
+
- The embedded dashboard is open in development when HTTP Basic has no
|
|
54
|
+
credentials, so the first run needs no password step. Every other
|
|
55
|
+
environment is unchanged: closed, 401, until credentials exist. That
|
|
56
|
+
case now also logs a boot warning outside development and test (it was
|
|
57
|
+
only in the doctor), since a 401 on a deployed dashboard otherwise looks
|
|
58
|
+
like a broken install. Configured credentials apply in development too.
|
|
59
|
+
|
|
60
|
+
- Bound how long Puma waits for the embedded writer to stop. The plugin sent
|
|
61
|
+
the writer TERM and then called `Process.wait` on it, which has no timeout:
|
|
62
|
+
a writer that did not exit -- stuck in a SQLite write, on a full disk --
|
|
63
|
+
held Puma's shutdown open for as long as it stayed stuck. Measured against
|
|
64
|
+
a child that ignores TERM, the stop never returned (the harness gave up at
|
|
65
|
+
10s with the child still alive). Puma now waits `shutdown_timeout` (2s),
|
|
66
|
+
the same allowance it gives its own reporter, then KILLs the writer and
|
|
67
|
+
reaps it, so a wedged writer costs a bounded 2s and never leaves a zombie.
|
|
68
|
+
A writer killed mid-batch loses nothing -- the batch is retried by id
|
|
69
|
+
against the next writer -- which is why the bound is not the writer's own
|
|
70
|
+
worst-case drain (13s at the defaults): waiting for it would buy no data,
|
|
71
|
+
only exit time, and that time counts against the container's stop grace.
|
|
72
|
+
A writer that exits on TERM is let go the moment it does (measured
|
|
73
|
+
~50ms), and a pid the cluster has already reaped is still treated as gone.
|
|
74
|
+
|
|
75
|
+
- Make one HTTP attempt per delivery. `Transport::Http#deliver` retried a
|
|
76
|
+
raised error or a 5xx once on its own, inside a reporter that already owns
|
|
77
|
+
a retry ladder of eight attempts, so each rung cost two socket timeouts
|
|
78
|
+
and the effective attempt count was about sixteen. Against a receiver that
|
|
79
|
+
accepts connections and never answers, one delivery cost 6.0s; it now
|
|
80
|
+
costs 3.0s (one `read_timeout`), and one attempt, like `deliver_encoded`.
|
|
81
|
+
Response classification and `Retry-After` are unchanged: a 5xx or a raised
|
|
82
|
+
error still comes back retryable and the reporter still schedules it.
|
|
83
|
+
|
|
84
|
+
- Correct `docs/configuration.md` and `docs/troubleshooting.md`, which told
|
|
85
|
+
users to raise `buffer_size` under pressure. At the defaults the byte
|
|
86
|
+
ceiling (`buffer_bytes`, 16 MiB) fills at roughly 5,000 records on a
|
|
87
|
+
realistic mix, so the 10,000-record count is never reached and raising it
|
|
88
|
+
changes nothing. The setting stays; the advice now points at `buffer_bytes`.
|
|
89
|
+
|
|
90
|
+
- Say so when records are lost. `Railwatch.on_unrecoverable` fell back to
|
|
91
|
+
the debug log, so with no callback registered and `RAILWATCH_DEBUG` unset
|
|
92
|
+
a batch dropped after its retry ladder, one the receiver permanently
|
|
93
|
+
refused, or the records still unsent when `at_exit`'s bounded shutdown
|
|
94
|
+
ran out of time all vanished without a word. Since 0.3.7 that shutdown is
|
|
95
|
+
the only delivery a rake task or `rails runner` gets, so a cron job whose
|
|
96
|
+
exception never reached the platform looked exactly like one that had
|
|
97
|
+
nothing to report. Confirmed against a receiver that accepts and never
|
|
98
|
+
answers: the process left inside `shutdown_timeout` carrying three unsent
|
|
99
|
+
records and printed nothing.
|
|
100
|
+
|
|
101
|
+
A `Reporter::DeliveryError` -- raised only once the records are already
|
|
102
|
+
gone -- now prints one `[railwatch]` stderr line, and `warn_on_data_loss`
|
|
103
|
+
(`RAILWATCH_WARN_ON_DATA_LOSS`) defaults to **on**. Silence was the wrong
|
|
104
|
+
default: telemetry that disappears without a word looks exactly like
|
|
105
|
+
having nothing to report, which is the one failure an operator cannot
|
|
106
|
+
diagnose from the platform side, because the evidence is what went
|
|
107
|
+
missing. One line a deploy is the whole cost, and it only ever appears
|
|
108
|
+
when something was actually lost.
|
|
109
|
+
|
|
110
|
+
Both ways out are named in the line itself, so nobody has to find this
|
|
111
|
+
entry to stop it: a registered `on_unrecoverable` always wins, which is
|
|
112
|
+
how an app routes the loss somewhere better (`Rails.error.report`), and
|
|
113
|
+
`warn_on_data_loss = false` restores silence. Recovered internal errors (a
|
|
114
|
+
subscriber that raised, a flush that will be retried) stay debug-only
|
|
115
|
+
either way: the gem carried on and there is nothing for an operator to do.
|
|
116
|
+
|
|
117
|
+
- Report a lost batch outside the flush lock too. The fix above moved the
|
|
118
|
+
callback out of `@mutex`, the inner lock -- but `Reporter#flush` holds
|
|
119
|
+
`@flush_mutex` around the whole of `deliver_buffer`, and the give-up path,
|
|
120
|
+
the permanent-rejection path and the rescue all report from inside it. A
|
|
121
|
+
callback that asks this same reporter to flush (`Railwatch.flush` is public
|
|
122
|
+
and documented) hit the same non-reentrant `Mutex` one level out: the same
|
|
123
|
+
`ThreadError: deadlock; recursive locking`, rescued and hidden by
|
|
124
|
+
`notify_unrecoverable`, so the callback ran halfway and reported nothing.
|
|
125
|
+
The locked path now collects what it needs to report and `flush` hands it
|
|
126
|
+
over once the lock is released. Found by CodeRabbit on this pull request.
|
|
127
|
+
|
|
128
|
+
- Report a given-up batch after releasing the reporter lock, not under it.
|
|
129
|
+
`Reporter#retain` called `on_unrecoverable` inside `@mutex.synchronize`.
|
|
130
|
+
The documented callback is `Rails.error.report`, whose subscriber records
|
|
131
|
+
the error as an exception -- a write back into the same reporter, which
|
|
132
|
+
takes `@mutex` to arm its thread or request a flush. That was
|
|
133
|
+
`ThreadError: deadlock; recursive locking`, rescued and hidden by
|
|
134
|
+
`notify_unrecoverable`, so the callback died halfway and the loss it was
|
|
135
|
+
reporting was never seen. A callback that merely blocked held every
|
|
136
|
+
request thread's `write_now` and `shutdown` itself behind it for the
|
|
137
|
+
duration. `notify_unsent` and `delivery_rejected` already called out
|
|
138
|
+
unlocked; this was the one that did not.
|
|
139
|
+
|
|
140
|
+
- Investigated and left alone: `Reporter#shutdown` after `thread.join`.
|
|
141
|
+
The bookkeeping that follows the join (`pending_delivery`, the
|
|
142
|
+
once-only notify latch) takes `@mutex` for microseconds and never does
|
|
143
|
+
I/O -- measured 0.2ms over `shutdown_timeout` against a transport wedged
|
|
144
|
+
forever. The only thing that can extend it is the operator's own
|
|
145
|
+
callback, which runs once and is theirs to bound, as any `at_exit`
|
|
146
|
+
handler is. Wrapping it in `Timeout` would trade a visible cost for a
|
|
147
|
+
killed thread.
|
|
148
|
+
|
|
149
|
+
|
|
3
150
|
## 0.5.1 (2026-09-21)
|
|
4
151
|
|
|
5
152
|
Three small seams for a host that runs these models on its own routes and
|
data/README.md
CHANGED
|
@@ -6,31 +6,41 @@ outgoing HTTP, storage, views, and logs, and links them into one trace
|
|
|
6
6
|
per execution, for about half a millisecond per request plus tens of
|
|
7
7
|
microseconds per query, with zero writes to your database.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
[embedded mode](docs/embedded.md) serves the
|
|
9
|
+
By default all of it stays inside the app:
|
|
10
|
+
[embedded mode](docs/embedded.md) serves the full dashboard at
|
|
11
11
|
`/railwatch` out of two SQLite files your app owns, with no token, no
|
|
12
|
-
Node, no Redis and no job worker.
|
|
12
|
+
Node, no Redis and no job worker. Railwatch Cloud is optional: it
|
|
13
|
+
delivers alerts to Slack, email, webhooks or Linear (including when the
|
|
14
|
+
app is down), serves MCP to your AI assistant, and puts many apps and
|
|
15
|
+
servers in one place.
|
|
13
16
|
|
|
14
17
|
## Install
|
|
15
18
|
|
|
16
19
|
```sh
|
|
17
|
-
bundle add railwatch
|
|
18
|
-
bin/rails generate railwatch:install
|
|
19
|
-
bin/rails railwatch:doctor # 3. check every piece is wired up after restart
|
|
20
|
+
bundle add railwatch # 1. add the public gem
|
|
21
|
+
bin/rails generate railwatch:install # 2. embedded: databases, Puma writer, dashboard at /railwatch
|
|
20
22
|
```
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
Restart and open `/railwatch`. It is open in development; before you
|
|
25
|
+
deploy, give it a password with
|
|
26
|
+
`RAILS_ENV=production bin/rails railwatch:authentication:configure`
|
|
27
|
+
(it answers 401 in production until you do). On a PostgreSQL or MySQL
|
|
28
|
+
app without the `sqlite3` gem, the generator adds it to the Gemfile;
|
|
29
|
+
run `bundle install` and `bin/rails db:prepare` to finish.
|
|
30
|
+
|
|
31
|
+
To send everything to Railwatch Cloud instead, pass `--cloud` or a token
|
|
32
|
+
option:
|
|
24
33
|
|
|
25
34
|
```sh
|
|
26
35
|
bundle add railwatch
|
|
27
|
-
bin/rails generate railwatch:install --
|
|
36
|
+
bin/rails generate railwatch:install --prompt-token # hidden token input plus app wiring
|
|
37
|
+
bin/rails railwatch:doctor # check every piece is wired up after restart
|
|
28
38
|
```
|
|
29
39
|
|
|
30
|
-
|
|
40
|
+
The generator's flags, getting a token, and deploying with Kamal,
|
|
31
41
|
Docker, Heroku, or Render are covered in
|
|
32
|
-
[Getting started](docs/getting-started.md). For
|
|
33
|
-
|
|
42
|
+
[Getting started](docs/getting-started.md). For an unreleased revision,
|
|
43
|
+
use the Git source instead:
|
|
34
44
|
|
|
35
45
|
```ruby
|
|
36
46
|
gem "railwatch", github: "Rebulk/railwatch"
|
|
@@ -46,12 +56,16 @@ gem "railwatch", github: "Rebulk/railwatch"
|
|
|
46
56
|
([Configuration](docs/configuration.md)).
|
|
47
57
|
- An optional stack profiler through `vernier` or `stackprof`, off by
|
|
48
58
|
default ([Configuration](docs/configuration.md)).
|
|
59
|
+
- LLM calls made through RubyLLM: tokens, cost, cut-off answers, tool
|
|
60
|
+
calls and workflows, each tied to the request or job that made it
|
|
61
|
+
([Record types](docs/records.md#llm_call)).
|
|
49
62
|
- A browser client for Inertia apps: page-visit timing, Core Web Vitals,
|
|
50
63
|
and browser errors ([Getting started](docs/getting-started.md)).
|
|
51
64
|
- RSpec and Minitest matchers that turn a query budget into a CI gate
|
|
52
65
|
([Testing](docs/testing.md)).
|
|
53
|
-
-
|
|
54
|
-
|
|
66
|
+
- With Railwatch Cloud, an MCP server so Claude Code, Cursor, VS Code, or
|
|
67
|
+
Zed can read your production data
|
|
68
|
+
([AI assistants and MCP](docs/ai-and-mcp.md)).
|
|
55
69
|
|
|
56
70
|
Configuration lives in `config/initializers/railwatch.rb`; most options
|
|
57
71
|
also have a `RAILWATCH_*` environment variable.
|
|
@@ -73,11 +87,11 @@ expect { get "/widgets" }.not_to have_railwatch_n_plus_one
|
|
|
73
87
|
|
|
74
88
|
## Embedded mode
|
|
75
89
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
Telemetry goes to two SQLite files the app owns -- `railwatch` for
|
|
91
|
+
issues, comments and saved views, `railwatch_telemetry` for what the app
|
|
92
|
+
reports -- and the dashboard is served at `/railwatch` from a bundle
|
|
93
|
+
shipped inside the gem. Nothing leaves the machine unless you turn on
|
|
94
|
+
export to Railwatch Cloud, and there is nothing else to run.
|
|
81
95
|
|
|
82
96
|
Puma forks a single writer process (`plugin :railwatch`, which the
|
|
83
97
|
installer adds) that owns both files. The web workers hand it batches
|
|
@@ -86,18 +100,21 @@ runs the maintenance clock too, so exception grouping, rollups, retention
|
|
|
86
100
|
and threshold scans happen without a queue.
|
|
87
101
|
|
|
88
102
|
That dashboard reads every query, log line and exception the app
|
|
89
|
-
produced, so it is
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
103
|
+
produced, so it is gated the way Mission Control Jobs is: HTTP Basic is
|
|
104
|
+
on, and with no credentials every request is 401 until you set them
|
|
105
|
+
with `bin/rails railwatch:authentication:configure`. Development is the
|
|
106
|
+
exception: with no credentials set there, it is open. Apps that would
|
|
107
|
+
rather use their own authentication turn Basic off and gate the pages
|
|
108
|
+
with `base_controller_class` or a routes constraint around the mount; a
|
|
109
|
+
`dashboard_user` resolver then names the operator and authorizes live
|
|
110
|
+
updates, but does not gate pages. [Embedded mode](docs/embedded.md)
|
|
111
|
+
covers all of it, including upgrades and what it costs to store.
|
|
95
112
|
|
|
96
113
|
## Documentation
|
|
97
114
|
|
|
98
|
-
- [Getting started](docs/getting-started.md) —
|
|
99
|
-
|
|
100
|
-
Heroku, Render, or none of them.
|
|
115
|
+
- [Getting started](docs/getting-started.md) — the embedded install, the
|
|
116
|
+
Railwatch Cloud install and its token, the three optional lines, and
|
|
117
|
+
deploying with Kamal, Docker, Heroku, Render, or none of them.
|
|
101
118
|
- [Configuration](docs/configuration.md) — every option and `RAILWATCH_*`
|
|
102
119
|
variable, field by field.
|
|
103
120
|
- [Record types](docs/records.md) — every record Railwatch ships and every
|
|
@@ -105,13 +122,15 @@ upgrades and what it costs to store.
|
|
|
105
122
|
- [Testing](docs/testing.md) — the RSpec and Minitest matchers, and a CI
|
|
106
123
|
performance gate.
|
|
107
124
|
- [AI assistants and MCP](docs/ai-and-mcp.md) — connecting Claude Code,
|
|
108
|
-
Cursor, VS Code, or Zed to your production data
|
|
125
|
+
Cursor, VS Code, or Zed to your production data through Railwatch
|
|
126
|
+
Cloud.
|
|
109
127
|
- [Replacing Sentry](docs/replacing-sentry.md) — a step-by-step migration,
|
|
110
128
|
option by option and call site by call site.
|
|
111
129
|
- [Coming from Laravel Nightwatch](docs/replacing-nightwatch.md) — the
|
|
112
130
|
record-type mapping and the sampling model, for Laravel people.
|
|
113
|
-
- [Embedded mode](docs/embedded.md) — the whole dashboard
|
|
114
|
-
app, telemetry in your own SQLite files,
|
|
131
|
+
- [Embedded mode](docs/embedded.md) — the default: the whole dashboard
|
|
132
|
+
inside your app, telemetry in your own SQLite files, and optional
|
|
133
|
+
export to Railwatch Cloud.
|
|
115
134
|
- [Self-hosting](docs/self-hosting.md) — pointing the gem at your own
|
|
116
135
|
Railwatch Cloud.
|
|
117
136
|
- [Troubleshooting](docs/troubleshooting.md) — every failure mode, paired
|
|
@@ -47,6 +47,7 @@ module Railwatch
|
|
|
47
47
|
def authenticate_by_http_basic
|
|
48
48
|
config = Railwatch.config
|
|
49
49
|
return unless config.http_basic_auth_enabled
|
|
50
|
+
return if config.http_basic_auth_waived?
|
|
50
51
|
|
|
51
52
|
if config.http_basic_auth_configured?
|
|
52
53
|
http_basic_authenticate_or_request_with(name: config.http_basic_auth_user, password: config.http_basic_auth_password,
|
|
@@ -118,7 +118,9 @@ module Railwatch
|
|
|
118
118
|
cache_read_tokens: group.sum { |r| r.cache_read_tokens.to_i }, cache_write_tokens: group.sum { |r| r.cache_write_tokens.to_i },
|
|
119
119
|
cost_nanos: group.sum { |r| r.cost_nanos.to_i },
|
|
120
120
|
priced: group.count { |r| r.cost_nanos },
|
|
121
|
-
|
|
121
|
+
# A call that failed before the provider answered used no tokens,
|
|
122
|
+
# so there is nothing to price; only an answered call can be unpriced.
|
|
123
|
+
unpriced: group.count { |r| r.cost_nanos.nil? && r.status != "failed" },
|
|
122
124
|
# A cut-off answer is not an error and will never show in the error
|
|
123
125
|
# rate, so it needs counting on its own or it stays invisible.
|
|
124
126
|
truncated: group.count { |r| r.finish_reason == "max_tokens" },
|
|
@@ -15,7 +15,7 @@ module Railwatch
|
|
|
15
15
|
# No `railwatch` entry in this environment's database.yml, or an entry
|
|
16
16
|
# whose adapter gem is not in the bundle yet (LoadError). A cloud-transport
|
|
17
17
|
# app has none and still eager-loads this class in production, and so
|
|
18
|
-
# does the
|
|
18
|
+
# does the installer's own boot, before it has written the
|
|
19
19
|
# entry -- so loading must not raise. Using it must, though: without
|
|
20
20
|
# connects_to this class would inherit ActiveRecord::Base's PRIMARY
|
|
21
21
|
# connection, and its tables are unprefixed, so a query would read and
|
|
@@ -25,7 +25,7 @@ module Railwatch
|
|
|
25
25
|
def self.connection_pool
|
|
26
26
|
raise Railwatch::DatabaseNotConfigured,
|
|
27
27
|
"the `railwatch` database (issues, comments, saved views and deploys) is not configured for the " \
|
|
28
|
-
"#{Rails.env} environment; run `bin/rails generate railwatch:install
|
|
28
|
+
"#{Rails.env} environment; run `bin/rails generate railwatch:install` " \
|
|
29
29
|
"or add it to config/database.yml (docs/embedded.md)"
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -160,7 +160,7 @@ module Railwatch
|
|
|
160
160
|
extra["cache_write_tokens"] += row[:cache_write_tokens].to_i
|
|
161
161
|
extra["cost_nanos"] += row[:cost_nanos].to_i
|
|
162
162
|
extra["priced"] += 1 if row[:cost_nanos]
|
|
163
|
-
extra["unpriced"] += 1 if row[:cost_nanos].nil?
|
|
163
|
+
extra["unpriced"] += 1 if row[:cost_nanos].nil? && row[:status] != "failed"
|
|
164
164
|
extra["truncated"] += 1 if row[:finish_reason] == "max_tokens"
|
|
165
165
|
extra["with_attachments"] += 1 if row[:attachments].to_i.positive?
|
|
166
166
|
end
|
|
@@ -18,7 +18,7 @@ module Railwatch
|
|
|
18
18
|
# No `railwatch_telemetry` entry in this environment's database.yml, or an entry
|
|
19
19
|
# whose adapter gem is not in the bundle yet (LoadError). A cloud-transport
|
|
20
20
|
# app has none and still eager-loads this class in production, and so
|
|
21
|
-
# does the
|
|
21
|
+
# does the installer's own boot, before it has written the
|
|
22
22
|
# entry -- so loading must not raise. Using it must, though: without
|
|
23
23
|
# connects_to this class would inherit ActiveRecord::Base's PRIMARY
|
|
24
24
|
# connection, and its tables are unprefixed, so a query would read and
|
|
@@ -28,7 +28,7 @@ module Railwatch
|
|
|
28
28
|
def self.connection_pool
|
|
29
29
|
raise Railwatch::DatabaseNotConfigured,
|
|
30
30
|
"the `railwatch_telemetry` database (everything the app reports) is not configured for the " \
|
|
31
|
-
"#{Rails.env} environment; run `bin/rails generate railwatch:install
|
|
31
|
+
"#{Rails.env} environment; run `bin/rails generate railwatch:install` " \
|
|
32
32
|
"or add it to config/database.yml (docs/embedded.md)"
|
|
33
33
|
end
|
|
34
34
|
end
|
data/docs/ai-and-mcp.md
CHANGED
|
@@ -9,6 +9,10 @@ crash-free rates per release. It can also write — resolve an issue, set a
|
|
|
9
9
|
priority, leave a comment — and every write is signed with your user and the
|
|
10
10
|
agent's name in the issue's activity feed.
|
|
11
11
|
|
|
12
|
+
MCP is a Railwatch Cloud feature. The embedded dashboard at `/railwatch`
|
|
13
|
+
does not serve it; an embedded install gets it by
|
|
14
|
+
[exporting to the cloud](embedded.md#three-ways-to-run-it).
|
|
15
|
+
|
|
12
16
|
The endpoint is `<your ingest host>/mcp`. For the hosted platform that's
|
|
13
17
|
`https://railwatch.rebulk.com/mcp`; if you self-host, it is your own host (see
|
|
14
18
|
[`self-hosting.md`](self-hosting.md)). The gem knows which one you're on:
|
|
@@ -145,9 +149,9 @@ durations are milliseconds.** Every `window` argument takes `1h`, `6h`,
|
|
|
145
149
|
|
|
146
150
|
| Tool | Arguments | Returns |
|
|
147
151
|
|---|---|---|
|
|
148
|
-
| `list_applications` | — | applications, their account, and their environments. Start here — everything else takes the `application_slug` and `environment` this returns. |
|
|
152
|
+
| `list_applications` | — | applications, their account, and their environments. Start here — everything else takes the `application_slug` and `environment` this returns (plus `account_slug`/`application_id` when two accounts share a slug). |
|
|
149
153
|
| `list_issues` | `application_slug?`, `environment?`, `status?` (`open`/`resolved`/`ignored`, default `open`), `limit?` | issues with key, title, kind, status, priority, occurrence and affected-user counts, culprit, first/last seen. |
|
|
150
|
-
| `get_issue` | `key` | one issue
|
|
154
|
+
| `get_issue` | `key` or `issue_id` | one issue. For an exception, the sample and latest occurrences with stored stack frames, cause, locals, parsed context, browser breadcrumbs, and the execution it happened inside. For a performance or anomaly issue, the metric, limit or baseline, representative records, trend, and deploy comparison. |
|
|
151
155
|
| `update_issue` | `key`, `status?`, `priority?`, `assignee_email?`, `agent?` | the updated issue. Writes an activity entry attributed to your user and `agent`. |
|
|
152
156
|
| `add_comment` | `key`, `body`, `agent?` | the created comment, attributed the same way. |
|
|
153
157
|
| `list_slow_routes` | `application_slug`, `environment`, `window?` | the 20 slowest routes by p95, each with `group_hash`, count, errors, `p95_ms`. |
|
|
@@ -157,6 +161,7 @@ durations are milliseconds.** Every `window` argument takes `1h`, `6h`,
|
|
|
157
161
|
| `explain_query` | `application_slug`, `environment`, `group_hash`, `window?` | the stored query plan for a query group, with the SQL and the sample's duration. `explain` is null unless the app sets `RAILWATCH_CAPTURE_QUERY_EXPLAIN`. `sql` is the normalized shape unless the app also sets `RAILWATCH_CAPTURE_SQL_VALUES`. |
|
|
158
162
|
| `get_profile` | `application_slug`, `environment`, `profile_id?`, `execution_id?`, `limit?` | the hottest frames of a stack profile — self and total samples, each with a percentage. |
|
|
159
163
|
| `search_logs` | `application_slug`, `environment`, `q`, `level?`, `limit?` | matching log lines, each with the `execution_id` to expand with `get_execution`. |
|
|
164
|
+
| `search_telemetry` | `application_slug`, `environment`, `resource` (`jobs`/`exceptions`/`logs`/`queries`), `q?`, `window?`, `cursor?`, `limit?` | raw rows of that resource with cursor pagination (`meta.next_cursor`, `meta.has_more`). `q` takes `after:`, `before:`, `user:`, `tenant:`, `deploy:`, plus per-resource keys such as `class:`, `queue:`, `level:`. |
|
|
160
165
|
| `list_tenants` | `application_slug`, `environment`, `window?`, `q?` | your app's own tenants (whatever it passes to `Railwatch.context(tenant:)`) with request, error, job, exception, and user counts. |
|
|
161
166
|
| `recent_deploys` | `application_slug`, `environment` | the 20 most recent deploys with ref, name, time, and link. |
|
|
162
167
|
| `release_health` | `application_slug`, `environment`, `window?` | crash-free session rate, crash-free user rate, and adoption per release. |
|
|
@@ -220,7 +225,8 @@ Copy either into your own app's repo to give its agent the same context.
|
|
|
220
225
|
|
|
221
226
|
## See also
|
|
222
227
|
|
|
223
|
-
- [`getting-started.md`](getting-started.md) — install, token,
|
|
228
|
+
- [`getting-started.md`](getting-started.md) — install, the cloud token,
|
|
229
|
+
first request.
|
|
224
230
|
- [`self-hosting.md`](self-hosting.md) — pointing the gem, and this endpoint,
|
|
225
231
|
at your own platform.
|
|
226
232
|
- [`troubleshooting.md`](troubleshooting.md) — when something isn't
|