railwatch 0.6.0 → 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 +42 -26
- data/CHANGELOG.md +28 -0
- data/README.md +40 -29
- data/app/jobs/railwatch/rollup_job.rb +3 -1
- data/app/models/railwatch/ingest/rollup_absorber.rb +1 -1
- data/docs/ai-and-mcp.md +9 -3
- data/docs/configuration.md +68 -27
- data/docs/embedded.md +76 -43
- data/docs/faq.md +22 -10
- data/docs/getting-started.md +116 -41
- 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 +62 -23
- data/lib/generators/railwatch/install/install_generator.rb +5 -4
- data/lib/generators/railwatch/install/templates/initializer.rb.tt +2 -2
- data/lib/railwatch/version.rb +1 -1
- data/llms.txt +21 -18
- data/public/railwatch/assets/{app-layout-2xAKndVD.js → app-layout-Zc0v-hYh.js} +1 -1
- data/public/railwatch/assets/{app-wordmark-Bt6XqURo.js → app-wordmark-BA_60AVb.js} +1 -1
- data/public/railwatch/assets/{appearance-DyCfeh9o.js → appearance-CcfP9tZ7.js} +1 -1
- data/public/railwatch/assets/application-BrN3Sz94.css +1 -0
- data/public/railwatch/assets/{arrow-up-EmePsTKP.js → arrow-up-CLQ-7heQ.js} +1 -1
- data/public/railwatch/assets/{auth-layout-BuwogWMZ.js → auth-layout-C05gEBIQ.js} +1 -1
- data/public/railwatch/assets/{badge-Dv7I-ebP.js → badge-DRae8XwK.js} +1 -1
- data/public/railwatch/assets/{braces-iZ7MEfYP.js → braces-rSYydpLY.js} +1 -1
- data/public/railwatch/assets/{card-DqdEEKcJ.js → card-DPjFKfen.js} +1 -1
- data/public/railwatch/assets/{chart-C4k7Ub9W.js → chart-DWh7l8yM.js} +1 -1
- data/public/railwatch/assets/{chart-hover-2yZG3w2u.js → chart-hover-CfoZUY4J.js} +1 -1
- data/public/railwatch/assets/{chart-panel-CONkZcn9.js → chart-panel-CC49WTQL.js} +1 -1
- data/public/railwatch/assets/{checkbox-DKbThbxr.js → checkbox-DPkLUiwM.js} +1 -1
- data/public/railwatch/assets/{code-CYahYQHZ.js → code-CLmYS6FU.js} +1 -1
- data/public/railwatch/assets/{copy-block-CbJum9s8.js → copy-block-CGcXxp8J.js} +1 -1
- data/public/railwatch/assets/{copy-id-JmvLfkmx.js → copy-id-vBYHQwxg.js} +1 -1
- data/public/railwatch/assets/{cursor-load-more-C1ylbqqL.js → cursor-load-more-Ddwxe5dZ.js} +1 -1
- data/public/railwatch/assets/{data-table-CVKR2wkq.js → data-table-CzKTEE-O.js} +1 -1
- data/public/railwatch/assets/{edit-BwSEVrTG.js → edit-B1kmWkzd.js} +1 -1
- data/public/railwatch/assets/{edit-BXF5oNYA.js → edit-D9cx4pbG.js} +1 -1
- data/public/railwatch/assets/{edit-D5BRDwLT.js → edit-yv6j9p-V.js} +1 -1
- data/public/railwatch/assets/{empty-state-Bn48Gplm.js → empty-state-CW4wclK_.js} +1 -1
- data/public/railwatch/assets/{env-layout-CKLL8Ds7.js → env-layout-Kz7wks1x.js} +1 -1
- data/public/railwatch/assets/{execution-path-DWovhgLi.js → execution-path-FAuIzOXB.js} +1 -1
- data/public/railwatch/assets/{filter-bar-xcbJC93z.js → filter-bar-CJDjWFib.js} +1 -1
- data/public/railwatch/assets/{flamegraph-BNbNIuKT.js → flamegraph-EaGkP2NT.js} +1 -1
- data/public/railwatch/assets/{frames-Bk39xfJG.js → frames-zZuIaNH9.js} +1 -1
- data/public/railwatch/assets/{google-sign-in-button-B5as2ps1.js → google-sign-in-button-2_zgbgVy.js} +1 -1
- data/public/railwatch/assets/{index-XjXcV-EO.js → index-B49SWz7K.js} +1 -1
- data/public/railwatch/assets/{index-B0xgzHuN.js → index-BHlY4wKe.js} +1 -1
- data/public/railwatch/assets/{index-CiaeS-n7.js → index-BZpPtyFY.js} +1 -1
- data/public/railwatch/assets/{index-BfitxVBv.js → index-BeVK6jCL.js} +1 -1
- data/public/railwatch/assets/{index-C-EwH4lO.js → index-BfbSo01U.js} +1 -1
- data/public/railwatch/assets/{index-CrA7jBTK.js → index-BfgncAv6.js} +1 -1
- data/public/railwatch/assets/{index-D0mKj3Hg.js → index-BhNszK1k.js} +1 -1
- data/public/railwatch/assets/{index-CDXlITyG.js → index-Bu01uWvw.js} +1 -1
- data/public/railwatch/assets/{index-w_skgMmK.js → index-C1s_hK3p.js} +1 -1
- data/public/railwatch/assets/{index-Zkt4W_CM.js → index-C8Cggnbw.js} +1 -1
- data/public/railwatch/assets/{index-BDVShWMs.js → index-CBip6V4z.js} +1 -1
- data/public/railwatch/assets/{index-DeLtMWou.js → index-CMJGss5R.js} +1 -1
- data/public/railwatch/assets/{index-LKgpCzoR.js → index-CRo3yK20.js} +1 -1
- data/public/railwatch/assets/{index-gCvuyG1l.js → index-CWB_p2J8.js} +1 -1
- data/public/railwatch/assets/{index-C8JIdb3_.js → index-CWHpndGc.js} +1 -1
- data/public/railwatch/assets/{index-CBFCtJAR.js → index-Ca_S4Sc3.js} +1 -1
- data/public/railwatch/assets/{index-o1xqGvLo.js → index-CanPDDOa.js} +1 -1
- data/public/railwatch/assets/{index-Bd4YpOIO.js → index-Cie90Yat.js} +1 -1
- data/public/railwatch/assets/{index-C_hQmVTP.js → index-CiepQ_pR.js} +1 -1
- data/public/railwatch/assets/{index-BRvRkfob.js → index-Cm1uCIGN.js} +1 -1
- data/public/railwatch/assets/{index-dpG-19ZN.js → index-CzitnnSC.js} +1 -1
- data/public/railwatch/assets/{index-Vj93BTR5.js → index-DEUfClv3.js} +1 -1
- data/public/railwatch/assets/index-DJKwo-mI.js +1 -0
- data/public/railwatch/assets/{index-cTzBycCg.js → index-DK6y0YHp.js} +1 -1
- data/public/railwatch/assets/{index-B-FXAo1w.js → index-DMNPpLH9.js} +1 -1
- data/public/railwatch/assets/{index-BeQw69GM.js → index-DZO1mSfX.js} +1 -1
- data/public/railwatch/assets/{index-BVWapDeB.js → index-Db5wj3M5.js} +1 -1
- data/public/railwatch/assets/{index-DV5MdmpY.js → index-Dcy5WktB.js} +1 -1
- data/public/railwatch/assets/{index-aPCZX-pW.js → index-DvG0-7Lx.js} +1 -1
- data/public/railwatch/assets/{index-Atp1FTgX.js → index-Dvj1wuka.js} +1 -1
- data/public/railwatch/assets/{index-COd9lT84.js → index-KyZX46qX.js} +1 -1
- data/public/railwatch/assets/{index-S0NVaLj9.js → index-Ze-KP-sl.js} +1 -1
- data/public/railwatch/assets/{index-DaE-1xhx.js → index-mhRbWLBM.js} +1 -1
- data/public/railwatch/assets/{index-DdVg9LKX.js → index-x099JL5f.js} +1 -1
- data/public/railwatch/assets/{index-CcennT28.js → index-x28zb_nf.js} +1 -1
- data/public/railwatch/assets/{inertia-CqnzqPVD.js → inertia-Cuyz2ZHO.js} +2 -2
- data/public/railwatch/assets/{input-error-ByT28UdP.js → input-error-hog6gGxg.js} +1 -1
- data/public/railwatch/assets/{json-viewer-Bnu9RsJL.js → json-viewer-DH2W9HXf.js} +1 -1
- data/public/railwatch/assets/{klass-nLiBV-Az.js → klass-DHbDelLk.js} +1 -1
- data/public/railwatch/assets/{label-1nSzpSB6.js → label-DdCBgiUn.js} +1 -1
- data/public/railwatch/assets/{layout-D7lE1Doo.js → layout-Cueyl7c5.js} +1 -1
- data/public/railwatch/assets/{live-dot-UdzOfzJb.js → live-dot-BZgYTYdt.js} +1 -1
- data/public/railwatch/assets/{nav-BRCgp2w3.js → nav-BSSGObDZ.js} +1 -1
- data/public/railwatch/assets/{new-COyDKOOb.js → new-B8FSb8Bl.js} +1 -1
- data/public/railwatch/assets/{new-Cd9zPNJ8.js → new-C39_v2Ll.js} +1 -1
- data/public/railwatch/assets/{new-vjRzERL3.js → new-DVOPwaC5.js} +1 -1
- data/public/railwatch/assets/{new-C60G72DR.js → new-Dd40nvJR.js} +1 -1
- data/public/railwatch/assets/{new-DMJGKjmH.js → new-SxnYe1SE.js} +1 -1
- data/public/railwatch/assets/{new-Omvf-c9s.js → new-eP5vKD3Y.js} +1 -1
- data/public/railwatch/assets/{onboarding-gUjJQc-6.js → onboarding-CUpZl5KB.js} +1 -1
- data/public/railwatch/assets/{origin-identity-M1YUPYS0.js → origin-identity-BYt2uiuo.js} +1 -1
- data/public/railwatch/assets/{percentile-picker-BxIVblBw.js → percentile-picker-CeQgllxD.js} +1 -1
- data/public/railwatch/assets/{relative-time-DQ4kjowZ.js → relative-time-D5UbF4oO.js} +1 -1
- data/public/railwatch/assets/{release-health-Cb1tMOOO.js → release-health-uP-GF3_V.js} +1 -1
- data/public/railwatch/assets/{route-BJkoEm7r.js → route-DQAY8JEr.js} +1 -1
- data/public/railwatch/assets/{segmented-C4oWE73o.js → segmented-BeIe4uqk.js} +1 -1
- data/public/railwatch/assets/{select-ocdQJdQo.js → select-2R16457Z.js} +1 -1
- data/public/railwatch/assets/{separator-Ct2UcZ2x.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-BLJOyLb_.js → show-B0X1hRQH.js} +1 -1
- data/public/railwatch/assets/{show-C8TkpEgm.js → show-BKUV5l5q.js} +1 -1
- data/public/railwatch/assets/{show-BAjfddEn.js → show-BQJD_CsF.js} +1 -1
- data/public/railwatch/assets/{show-C6OiJ6Hr.js → show-BhmD6Sxp.js} +1 -1
- data/public/railwatch/assets/{show-CEhPVMMT.js → show-C3KcnVo2.js} +1 -1
- data/public/railwatch/assets/{show-CX_pab9S.js → show-C95cHd06.js} +1 -1
- data/public/railwatch/assets/{show-Bsu1rvnK.js → show-CdB4uVQS.js} +1 -1
- data/public/railwatch/assets/{show-yEOrk6Pt.js → show-CoCqcVIp.js} +1 -1
- data/public/railwatch/assets/{show-DDVsv-uS.js → show-D2LqjEsU.js} +1 -1
- data/public/railwatch/assets/{show-D6lgzL6n.js → show-DNpnKyTh.js} +1 -1
- data/public/railwatch/assets/{show-eKazflRI.js → show-DOlTxbig.js} +1 -1
- data/public/railwatch/assets/{show-Dwiq-8hA.js → show-DQCkttL8.js} +1 -1
- data/public/railwatch/assets/{show-CBFBiBY2.js → show-DcxesipC.js} +1 -1
- data/public/railwatch/assets/{show-BoVyMQS6.js → show-IGJoc_X0.js} +1 -1
- data/public/railwatch/assets/{show-gtwKsTsl.js → show-YsNpqbcI.js} +1 -1
- data/public/railwatch/assets/{show-B4bwI9x2.js → show-qNV6H8SH.js} +1 -1
- data/public/railwatch/assets/{sort-header-CPM6fhXv.js → sort-header-mEJUM3Av.js} +1 -1
- data/public/railwatch/assets/{sparkline-cell-CXCxXPWt.js → sparkline-cell-Dwg7awwh.js} +1 -1
- data/public/railwatch/assets/{stat-BNEfE8X1.js → stat-ZtxGU8lE.js} +1 -1
- data/public/railwatch/assets/{status-badge-D0aylrM5.js → status-badge-CH5P-Xkj.js} +1 -1
- data/public/railwatch/assets/{tenant-path-C4aM_cO0.js → tenant-path-CQoP-BeF.js} +1 -1
- data/public/railwatch/assets/{text-link-CP4lqUi6.js → text-link-DHJ8BXx5.js} +1 -1
- data/public/railwatch/assets/{textarea-DzJo2ds4.js → textarea-BeQtQyl5.js} +1 -1
- data/public/railwatch/assets/{timeline-BNcJ9PMc.js → timeline-CaKQVu48.js} +1 -1
- data/public/railwatch/assets/{transition-BX2M1iaq.js → transition-ksDpqhKJ.js} +1 -1
- data/public/railwatch/assets/{use-clipboard-C1ApSwzA.js → use-clipboard-DNefo-ky.js} +1 -1
- data/public/railwatch/assets/{use-live-DWwLg1Nj.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-DiQ4mqT_.js +0 -1
- data/public/railwatch/assets/series-chart-D6upsrSn.js +0 -1
- data/public/railwatch/assets/show-CV-lG2b6.js +0 -2
data/docs/replacing-sentry.md
CHANGED
|
@@ -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
|
|
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:`**
|
|
74
|
-
|
|
75
|
-
|
|
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`
|
|
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`,
|
|
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
|
|
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
|
-
|
|
|
565
|
-
|
|
|
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
|
|
39
|
-
|
|
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
|
|
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.
|
|
31
|
-
present, so set any non-blank `RAILWATCH_TOKEN` for
|
|
32
|
-
an in-memory transport, never over the
|
|
33
|
-
|
|
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.
|
data/docs/troubleshooting.md
CHANGED
|
@@ -8,20 +8,48 @@ below are keyed to those lines.
|
|
|
8
8
|
bin/rails railwatch:doctor
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
The
|
|
12
|
-
|
|
11
|
+
The first lines depend on the transport. An embedded install checks its
|
|
12
|
+
databases, writer and dashboard; a cloud install checks its token and
|
|
13
|
+
ingest host. The task exits non-zero only on the lines marked fatal
|
|
14
|
+
below. Everything else is informational: a `✗` there means a feature
|
|
13
15
|
isn't wired, not that the install is broken.
|
|
14
16
|
|
|
17
|
+
Embedded (`transport = :local`):
|
|
18
|
+
|
|
19
|
+
| Doctor line | What a `✗` means |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `export` | Only shown with `export_enabled` on. Export is on and cannot work: no token, no URL, a plain-HTTP URL, or an unsupported policy. Fatal. |
|
|
22
|
+
| `export destination` | The export queue is blocked or deferred; the line gives the reason. `bin/rails railwatch:export:rebind` clears a credential block. |
|
|
23
|
+
| `railwatch database`, `railwatch_telemetry database` | The database is missing from `config/database.yml` for this environment. Fatal. Re-run the install generator. |
|
|
24
|
+
| `railwatch migrations`, `railwatch_telemetry migrations` | Migrations are pending. Fatal. Run `bin/rails db:prepare`. |
|
|
25
|
+
| `telemetry disk` | The telemetry database is not in incremental auto-vacuum, so pruning never shrinks the file. See [Embedded mode](embedded.md#giving-the-disk-back). |
|
|
26
|
+
| `maintenance` | No maintenance tick in the last ten minutes. Expected when the app is stopped: the clock runs in the app's processes, not in rake. |
|
|
27
|
+
| `writer process` | The writer socket is not answering, `plugin :railwatch` is missing from `config/puma.rb`, or the socket path is over Linux's 108-byte limit. Expected when the app is stopped. |
|
|
28
|
+
| `last write` | Shown when the writer answers: nothing written in five minutes. With the app serving traffic, the writer is stuck or workers are not reaching it. |
|
|
29
|
+
| `dashboard access` | HTTP Basic is on with no credentials outside development, so every page is 401; or Basic is off and nothing else is declared. Run `bin/rails railwatch:authentication:configure`, or see [Embedded mode](embedded.md#authentication). |
|
|
30
|
+
| `json compatibility` | The installed `json` gem cannot decode on this Rails; see [below](#binjobs-dies-in-a-loop-with-wrong-number-of-arguments-given-2-expected-1). |
|
|
31
|
+
| `recurring.yml` | `config/recurring.yml` still lists `Railwatch::*` jobs from a pre-release. Remove them. |
|
|
32
|
+
|
|
33
|
+
Cloud (`transport = :http`):
|
|
34
|
+
|
|
15
35
|
| Doctor line | What a `✗` means |
|
|
16
36
|
|---|---|
|
|
17
37
|
| `token` | `RAILWATCH_TOKEN` is unset or empty. Fatal: nothing is recorded at all. |
|
|
38
|
+
| `token storage` | A plaintext token is in a file Git tracks. Fatal. Move it to credentials or a secret manager. |
|
|
18
39
|
| `ingest url` | `ingest_url` isn't a parseable HTTP(S) URL. |
|
|
40
|
+
| `ingest transport security` | The ingest URL is plain HTTP on a non-loopback host without `RAILWATCH_ALLOW_HTTP=true`. |
|
|
19
41
|
| `ingest reachable` | `GET {ingest_url}/ingest/ping` didn't return success. Fatal. The ping carries the token, so a missing or wrong token fails this line too; fix `token` first. |
|
|
42
|
+
|
|
43
|
+
Both:
|
|
44
|
+
|
|
45
|
+
| Doctor line | What a `✗` means |
|
|
46
|
+
|---|---|
|
|
20
47
|
| `request middleware` | `Railwatch::Middleware::Request` isn't in the stack, so requests aren't executions. |
|
|
21
48
|
| `engine mounted` | `mount Railwatch::Engine, at: "/railwatch"` is missing from `config/routes.rb`; the browser beacon has nowhere to post. |
|
|
22
49
|
| `deploy` | `config.deploy` is unset — records ship, charts get no deploy markers. |
|
|
23
50
|
| `sample rates` | Never fails; it prints the effective rate per execution kind. |
|
|
24
51
|
| `ignored record types` | Never fails; it prints what `c.ignore` is dropping. |
|
|
52
|
+
| `interactive sessions` | Never fails; it prints whether consoles are captured and the runner scratch paths. |
|
|
25
53
|
| `kamal post-deploy hook` | `.kamal/hooks/post-deploy` is missing or doesn't mention Railwatch. Only matters if you deploy with Kamal. |
|
|
26
54
|
| `browser client` | `app/frontend/lib/railwatch.ts` isn't there. Only matters for Inertia visit timing. |
|
|
27
55
|
| `browser client imported` | The client exists but nothing calls `startRailwatch()` — no `startRailwatch` found in `app/frontend/entrypoints`. Visits won't report. |
|
|
@@ -33,30 +61,35 @@ isn't wired, not that the install is broken.
|
|
|
33
61
|
**Symptom.** The environment's pages stay empty however much traffic the
|
|
34
62
|
app takes.
|
|
35
63
|
|
|
36
|
-
Work down this list.
|
|
64
|
+
Work down this list. Most of it is the same root cause seen from
|
|
37
65
|
different angles: Railwatch decided not to record.
|
|
38
66
|
|
|
39
|
-
**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
67
|
+
**Embedded: the writer is not writing.** Run `railwatch:doctor` with the
|
|
68
|
+
app serving traffic. `writer process` and `last write` say whether the
|
|
69
|
+
writer is up and when it last wrote; the migrations lines catch a
|
|
70
|
+
database that was never prepared.
|
|
71
|
+
|
|
72
|
+
**Cloud: the token is missing or blank.** With `transport = :http`,
|
|
73
|
+
`Railwatch.enabled?` is `config.enabled && token.present?`. With no token
|
|
74
|
+
the engine's `railwatch.subscribe` initializer returns early, so no
|
|
75
|
+
subscribers and no patches are installed at all. Fix: set
|
|
76
|
+
`RAILWATCH_TOKEN`, restart, and re-run `railwatch:doctor`. The `token`
|
|
77
|
+
line prints the first 6 characters and the length, which is enough to
|
|
78
|
+
spot a truncated or quoted value.
|
|
46
79
|
|
|
47
|
-
**
|
|
80
|
+
**Cloud: the token is wrong.** A 401 from the ingest marks the transport
|
|
48
81
|
permanently unauthorized: no further flush is attempted for the lifetime
|
|
49
82
|
of that process. Fixing the env var isn't enough. Restart the process.
|
|
50
83
|
`railwatch:doctor`'s `ingest reachable` line catches this before you
|
|
51
84
|
deploy.
|
|
52
85
|
|
|
53
|
-
|
|
54
|
-
them. `railwatch:status` prints the URL it is actually using.
|
|
55
|
-
against the platform you're looking at. Self-hosting: see
|
|
86
|
+
**Cloud: `RAILWATCH_INGEST_URL` points somewhere else.** Records go where
|
|
87
|
+
you sent them. `railwatch:status` prints the URL it is actually using.
|
|
88
|
+
Compare it against the platform you're looking at. Self-hosting: see
|
|
56
89
|
[`self-hosting.md`](self-hosting.md).
|
|
57
90
|
|
|
58
91
|
**`config.enabled` is false.** `RAILWATCH_ENABLED=0` (or `false`/`no`/`off`)
|
|
59
|
-
turns everything off
|
|
92
|
+
turns everything off, embedded or not.
|
|
60
93
|
|
|
61
94
|
**Sample rates are at zero.** `c.sample = { requests: 0.0 }` means no
|
|
62
95
|
request records. So does the per-route `railwatch_never_sample` macro on
|
|
@@ -70,11 +103,11 @@ rather than a broken install.
|
|
|
70
103
|
built. The `ignored record types` doctor line prints the list. Ignoring
|
|
71
104
|
`:queries` also drops `n_plus_one`, since both key off `:queries`.
|
|
72
105
|
|
|
73
|
-
**You're looking at the test environment.**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
106
|
+
**You're looking at the test environment.** The spec helpers swap the
|
|
107
|
+
reporter's transport for an in-memory one, so a suite records normally
|
|
108
|
+
but never sends or stores anything. Independently: the health sampler,
|
|
109
|
+
the session flusher, and the profiler all refuse to start when
|
|
110
|
+
`Rails.env.test?`.
|
|
78
111
|
|
|
79
112
|
Still nothing? Set `RAILWATCH_DEBUG=1` and restart. Internal diagnostics go
|
|
80
113
|
to stderr prefixed `[railwatch]`. They never go to `Rails.logger`, so they
|
|
@@ -296,7 +329,13 @@ hook below.
|
|
|
296
329
|
|
|
297
330
|
**Cause and fix**, in the order the hook itself checks:
|
|
298
331
|
|
|
299
|
-
-
|
|
332
|
+
- **Embedded: `RAILWATCH_TRANSPORT=local` isn't exported to the hook.**
|
|
333
|
+
The hook reads the deployer's environment, not your initializer. With
|
|
334
|
+
that variable set it runs `bin/rails railwatch:deploy[$KAMAL_VERSION]`
|
|
335
|
+
in the primary container, which writes the marker to the embedded
|
|
336
|
+
database. Without it, the hook treats the install as a cloud one and
|
|
337
|
+
exits at the next check, since an embedded install has no token.
|
|
338
|
+
- **Cloud: `RAILWATCH_TOKEN` isn't exported to the hook.** The next thing
|
|
300
339
|
`.kamal/hooks/post-deploy` does is `[ -z "$RAILWATCH_TOKEN" ] && exit 0`.
|
|
301
340
|
The hook runs on the deployer machine, in your shell, not in a
|
|
302
341
|
container. So a token that only exists in `.kamal/secrets` for the
|
|
@@ -317,8 +356,8 @@ it exits 0 regardless.
|
|
|
317
356
|
|
|
318
357
|
## Log search finds less than it should
|
|
319
358
|
|
|
320
|
-
**Symptom.** On a
|
|
321
|
-
fewer lines and highlights nothing.
|
|
359
|
+
**Symptom.** On a self-hosted platform backed by Postgres, log search
|
|
360
|
+
matches fewer lines and highlights nothing.
|
|
322
361
|
|
|
323
362
|
**Cause.** Full-text search uses SQLite's FTS5 (`logs_fts`). The
|
|
324
363
|
platform checks for both a SQLite adapter *and* the `logs_fts` table.
|
|
@@ -258,10 +258,11 @@ module Railwatch
|
|
|
258
258
|
config/puma.rb Puma forks one Railwatch writer process that
|
|
259
259
|
writes every batch and runs the maintenance clock, so no web
|
|
260
260
|
process ever holds the telemetry database. No job worker.
|
|
261
|
-
4. Optional: mirror to Railwatch Cloud for alerts
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
c.export_enabled = true
|
|
261
|
+
4. Optional: mirror to Railwatch Cloud for alerts delivered even
|
|
262
|
+
when this app is down, MCP for your AI assistant, and every
|
|
263
|
+
app in one place. Get a token (bin/rails railwatch:token), set
|
|
264
|
+
RAILWATCH_TOKEN, then c.export_enabled = true in the
|
|
265
|
+
initializer (docs/embedded.md).
|
|
265
266
|
STEPS
|
|
266
267
|
return
|
|
267
268
|
end
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
Railwatch.configure do |c|
|
|
6
6
|
<% if local? -%>
|
|
7
7
|
# Telemetry stays in this app's own railwatch_telemetry database and the
|
|
8
|
-
# dashboard is served at /railwatch. No token, no cloud.
|
|
9
|
-
#
|
|
8
|
+
# dashboard is served at /railwatch. No token, no cloud. Who can open the
|
|
9
|
+
# dashboard is the Access block below.
|
|
10
10
|
c.transport = :local # RAILWATCH_TRANSPORT
|
|
11
11
|
c.ignored_request_paths += ["/railwatch", %r{\A/railwatch/}]
|
|
12
12
|
# c.issue_prefix = "APP" # RAILWATCH_ISSUE_PREFIX; issue keys like APP-12
|
data/lib/railwatch/version.rb
CHANGED
data/llms.txt
CHANGED
|
@@ -3,39 +3,42 @@
|
|
|
3
3
|
> Railwatch is a Ruby gem that instruments a Rails application end to end —
|
|
4
4
|
> requests, jobs, scheduled tasks, rake/runner commands, database queries and
|
|
5
5
|
> N+1s, exceptions, cache, mail, notifications, broadcasts, outgoing HTTP,
|
|
6
|
-
> Active Storage, view renders, and logs — links every one of
|
|
7
|
-
> single execution tree by `execution_id`/`trace_id`, and writes
|
|
8
|
-
> background thread into the app
|
|
9
|
-
> to Railwatch Cloud. It replaces a separate APM and a
|
|
6
|
+
> Active Storage, view renders, RubyLLM calls, and logs — links every one of
|
|
7
|
+
> them into a single execution tree by `execution_id`/`trace_id`, and writes
|
|
8
|
+
> them from a background thread into two SQLite files the app owns (embedded,
|
|
9
|
+
> the default) or to Railwatch Cloud. It replaces a separate APM and a
|
|
10
10
|
> separate error tracker with one gem and one configuration block, costs
|
|
11
11
|
> under a millisecond of CPU per request, and never writes to the
|
|
12
|
-
> application's
|
|
13
|
-
> `bin/rails generate railwatch:install`, which by default keeps
|
|
14
|
-
> the app (two SQLite databases,
|
|
15
|
-
>
|
|
16
|
-
>
|
|
17
|
-
>
|
|
18
|
-
>
|
|
19
|
-
>
|
|
12
|
+
> application's primary database. Install is `bundle add railwatch` followed
|
|
13
|
+
> by `bin/rails generate railwatch:install`, which by default keeps
|
|
14
|
+
> everything in the app (two SQLite databases, a Puma writer process, the
|
|
15
|
+
> dashboard at `/railwatch`, open in development and password-protected
|
|
16
|
+
> elsewhere); with `--cloud` or a token option it sends to Railwatch Cloud
|
|
17
|
+
> instead, and an embedded install can export to the cloud as well. It
|
|
18
|
+
> writes the initializer, mounts the engine, adds a Kamal post-deploy hook
|
|
19
|
+
> and the Inertia browser client where the app has them, and wires the test
|
|
20
|
+
> matchers. `bin/rails railwatch:doctor` prints a ✓/✗ line for every piece.
|
|
20
21
|
|
|
21
22
|
## Docs
|
|
22
23
|
|
|
23
|
-
- [README](README.md): what Railwatch is, the install,
|
|
24
|
-
- [Getting started](docs/getting-started.md):
|
|
24
|
+
- [README](README.md): what Railwatch is, the install, embedded mode, and where each document fits.
|
|
25
|
+
- [Getting started](docs/getting-started.md): the two-command embedded install, the Railwatch Cloud install and where its token comes from, the three optional lines, and deploying under Kamal, Docker/Heroku/Render, or no deploy tool at all.
|
|
26
|
+
- [Embedded mode](docs/embedded.md): the default install — the dashboard inside the app, its two SQLite databases, dashboard authentication, the Puma writer process, maintenance without a job worker, disk reclaim, and optional export to Railwatch Cloud.
|
|
25
27
|
- [Configuration](docs/configuration.md): every configuration option and its `RAILWATCH_*` environment variable, plus the full public facade, redaction, rejection, transport and buffering behaviour, and the rake tasks.
|
|
26
|
-
- [Record types](docs/records.md): every record type the gem ships and every attribute on it, sourced from the code that builds it.
|
|
28
|
+
- [Record types](docs/records.md): every record type the gem ships and every attribute on it, including RubyLLM calls (tokens, cost, finish reason, tool calls, workflows), sourced from the code that builds it.
|
|
27
29
|
- [Testing](docs/testing.md): the RSpec and Minitest matchers (`have_railwatch_queries`, `have_railwatch_n_plus_one`, `record_railwatch_span`, `record_railwatch_exception`, `have_railwatch_outgoing_requests`) and a CI performance-gate recipe.
|
|
28
30
|
- [Production source maps](docs/source-maps.md): hidden Vite maps, private upload before publishing assets, safe opt-in deletion, resolved browser stacks and default issue grouping.
|
|
29
|
-
- [AI assistants and MCP](docs/ai-and-mcp.md):
|
|
31
|
+
- [AI assistants and MCP](docs/ai-and-mcp.md): Railwatch Cloud's MCP endpoint, how to get a token, paste-ready client configuration for Claude Code, Claude Desktop, Cursor, VS Code, and Zed, and every tool, prompt, and resource the server exposes.
|
|
30
32
|
- [Replacing Sentry](docs/replacing-sentry.md): a step-by-step migration — removing the gems, porting each option, rewriting each call site, breadcrumbs, spans, profiling, attachments, `before_send`, fingerprints, and release health.
|
|
31
33
|
- [Coming from Laravel Nightwatch](docs/replacing-nightwatch.md): the record-type mapping, what "execution" means in Railwatch, sampling parity, and the facade method names in Ruby.
|
|
32
34
|
- [Self-hosting](docs/self-hosting.md): pointing the gem at a self-hosted Railwatch Cloud, creating a token there, and checking the connection.
|
|
33
|
-
- [Troubleshooting](docs/troubleshooting.md): every
|
|
35
|
+
- [Troubleshooting](docs/troubleshooting.md): every `railwatch:doctor` line, embedded and cloud, and the failure modes behind them — no records, doubled scheduled tasks, WebMock in specs, Puma fork, tail-sampling memory, missing profiler, missing deploy marker, Kamal hook, log search, empty tenants.
|
|
36
|
+
- [Security](docs/security.md): transport, capture defaults, the browser beacon, token handling, and application responsibilities.
|
|
34
37
|
- [FAQ](docs/faq.md): overhead numbers and how they are measured, retention, what is redacted by default versus opt-in, SQLite, and what happens when the platform is unreachable.
|
|
35
38
|
|
|
36
39
|
## For coding agents
|
|
37
40
|
|
|
38
|
-
- [AGENTS.md](AGENTS.md): how to install and use Railwatch from inside a Rails app — the facade methods, the spec matchers, `railwatch:doctor`, and the MCP hookup.
|
|
41
|
+
- [AGENTS.md](AGENTS.md): how to install and use Railwatch from inside a Rails app — the facade methods, the spec matchers, `railwatch:doctor`, and the Railwatch Cloud MCP hookup.
|
|
39
42
|
|
|
40
43
|
## Optional
|
|
41
44
|
|