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/embedded.md
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
# Embedded mode: the dashboard inside your app
|
|
2
2
|
|
|
3
|
-
This is the default. Railwatch keeps every
|
|
4
|
-
and serves the full dashboard at
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
there.
|
|
3
|
+
This is what the installer sets up by default. Railwatch keeps every
|
|
4
|
+
record in your own application and serves the full dashboard at
|
|
5
|
+
`/railwatch`, with no token and no cloud. The gem's reporter, buffer and
|
|
6
|
+
sampling are the same as with the cloud; the only difference is where a
|
|
7
|
+
batch ends up. In embedded mode it is written into a SQLite database
|
|
8
|
+
your app owns, and the dashboard reads it back from there.
|
|
9
9
|
|
|
10
10
|
Use it when one server runs the app. Telemetry lands in a file next to
|
|
11
11
|
your other SQLite databases, so several servers would each see only
|
|
12
|
-
their own slice. For more than one server, or
|
|
13
|
-
|
|
14
|
-
([Getting started](getting-started.md))
|
|
15
|
-
one setting.
|
|
12
|
+
their own slice. For more than one server, or one place for many apps,
|
|
13
|
+
add Railwatch Cloud: export alongside embedded, or the cloud on its own
|
|
14
|
+
([Getting started](getting-started.md#railwatch-cloud-instead)).
|
|
16
15
|
|
|
17
16
|
## Three ways to run it
|
|
18
17
|
|
|
@@ -27,23 +26,38 @@ up:
|
|
|
27
26
|
|
|
28
27
|
Embedded is `c.transport = :local`, which is what the installer writes
|
|
29
28
|
unless you ask it for the cloud (`--cloud`, or any token or URL option).
|
|
30
|
-
|
|
29
|
+
With no initializer setting it, the runtime default is `:http`, the
|
|
30
|
+
cloud. "Both" is embedded plus export, which needs a Railwatch Cloud
|
|
31
|
+
environment token. The default embedded install has none, so set one
|
|
32
|
+
first (`bin/rails railwatch:token` prints where to create it), plus the
|
|
33
|
+
ingest URL if you self-host:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
RAILWATCH_TOKEN=rw_...
|
|
37
|
+
RAILWATCH_INGEST_URL=https://telemetry.example.com # only when self-hosting
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
With a token in place, export is one more line:
|
|
31
41
|
|
|
32
42
|
```ruby
|
|
33
43
|
c.export_enabled = true # or RAILWATCH_EXPORT_ENABLED=true
|
|
34
44
|
```
|
|
35
45
|
|
|
36
|
-
It reuses
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
dashboard is as complete as
|
|
46
|
+
It reuses `RAILWATCH_TOKEN` and `RAILWATCH_INGEST_URL`
|
|
47
|
+
(`RAILWATCH_EXPORT_TOKEN` and `RAILWATCH_EXPORT_URL` override them), so
|
|
48
|
+
an install that was pointed at the cloud and moved to embedded needs
|
|
49
|
+
nothing else. Every record captured locally is mirrored, so the hosted
|
|
50
|
+
dashboard is as complete as a cloud-only install's, and `/railwatch`
|
|
51
|
+
keeps working.
|
|
41
52
|
|
|
42
53
|
It is off unless you set that flag. A token being present is not consent:
|
|
43
54
|
an embedded install that has one configured still sends nothing.
|
|
44
55
|
`railwatch:doctor` says nothing about export until you ask for it, and
|
|
45
|
-
fails
|
|
46
|
-
shows what is queued.
|
|
56
|
+
fails if you ask for it and it cannot work. `railwatch:export:status`
|
|
57
|
+
shows what is queued. The queue is durable, in the telemetry database,
|
|
58
|
+
and bounded: 256 MiB (`RAILWATCH_EXPORT_MAX_BYTES`), 100,000 deliveries
|
|
59
|
+
(`RAILWATCH_EXPORT_MAX_DELIVERIES`), and a day's age
|
|
60
|
+
(`RAILWATCH_EXPORT_MAX_AGE_SECONDS`, at most seven days).
|
|
47
61
|
|
|
48
62
|
## Railwatch Cloud runs the same models
|
|
49
63
|
|
|
@@ -64,10 +78,12 @@ bin/rails generate railwatch:install
|
|
|
64
78
|
```
|
|
65
79
|
|
|
66
80
|
Restart the app and open `/railwatch`; in development it is open with no
|
|
67
|
-
password (see [Authentication](#authentication) for production).
|
|
68
|
-
checks the wiring.
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
password (see [Authentication](#authentication) for production).
|
|
82
|
+
`bin/rails railwatch:doctor` checks the wiring. When the `sqlite3` gem
|
|
83
|
+
is already in the bundle, the generator creates and migrates both
|
|
84
|
+
databases itself; when it is not, see the next section. After that,
|
|
85
|
+
`bin/rails db:prepare`, which a deploy already runs, migrates them after
|
|
86
|
+
every gem update.
|
|
71
87
|
|
|
72
88
|
The engine needs Active Job (its grouping and scan jobs are Active Job
|
|
73
89
|
classes even though embedded mode calls them directly) and loads it
|
|
@@ -81,20 +97,21 @@ databases it adds are SQLite files either way, so the generated entries
|
|
|
81
97
|
name `adapter: sqlite3` themselves rather than inheriting your default
|
|
82
98
|
block, and they need no `&default` anchor to exist.
|
|
83
99
|
|
|
84
|
-
On a PostgreSQL or MySQL app
|
|
85
|
-
|
|
100
|
+
On a PostgreSQL or MySQL app without the `sqlite3` gem, the install
|
|
101
|
+
takes two more commands:
|
|
86
102
|
|
|
87
103
|
```sh
|
|
88
104
|
bin/rails generate railwatch:install # adds gem "sqlite3", writes the config
|
|
89
105
|
bundle install
|
|
90
|
-
bin/rails db:prepare
|
|
106
|
+
bin/rails db:prepare # creates the two SQLite files
|
|
91
107
|
```
|
|
92
108
|
|
|
93
109
|
Verified end to end on both. On a PostgreSQL app and on a MySQL app, the
|
|
94
110
|
application's own four databases stay where they were, Railwatch's two are
|
|
95
111
|
files under `storage/`, and neither server gains a single Railwatch table.
|
|
96
112
|
|
|
97
|
-
What the embedded install writes, on top of what every install writes
|
|
113
|
+
What the embedded install writes, on top of what every install writes
|
|
114
|
+
(listed in [Getting started](getting-started.md#what-the-generator-writes)):
|
|
98
115
|
|
|
99
116
|
- `config/initializers/railwatch.rb` with `c.transport = :local` and the
|
|
100
117
|
dashboard's own paths excluded from request capture.
|
|
@@ -106,6 +123,8 @@ What the embedded install writes, on top of what every install writes:
|
|
|
106
123
|
databases need that form. Each entry's `migrations_paths` points into
|
|
107
124
|
the gem, so `db:prepare` builds the tables from the gem's own
|
|
108
125
|
migrations and nothing is copied into `db/`.
|
|
126
|
+
- `plugin :railwatch` at the end of `config/puma.rb`, which forks the
|
|
127
|
+
[writer process](#the-writer-process).
|
|
109
128
|
- `mount Railwatch::Engine, at: "/railwatch"`, as always.
|
|
110
129
|
|
|
111
130
|
Nothing touches your primary database.
|
|
@@ -134,16 +153,19 @@ inside the gem, so the app needs no Node, no Vite and no asset pipeline
|
|
|
134
153
|
integration.
|
|
135
154
|
|
|
136
155
|
Not in embedded mode: accounts and members (the operator is whoever your
|
|
137
|
-
app lets through), integrations (alerts are recorded, not delivered
|
|
138
|
-
and the
|
|
156
|
+
app lets through), integrations (alerts are recorded, not delivered to
|
|
157
|
+
Slack, email, webhooks or Linear), and the
|
|
158
|
+
[MCP server](ai-and-mcp.md). Those are Railwatch Cloud's, and
|
|
159
|
+
[export](#three-ways-to-run-it) gets them without giving up the local
|
|
160
|
+
dashboard.
|
|
139
161
|
|
|
140
162
|
## Authentication
|
|
141
163
|
|
|
142
164
|
The dashboard shows every query, log line and exception your app
|
|
143
165
|
produced, so it works the way Mission Control Jobs does: **HTTP Basic
|
|
144
|
-
authentication is on
|
|
145
|
-
configured every dashboard request is 401, the app logs a
|
|
146
|
-
boot, and `railwatch:doctor` says so. Set them with
|
|
166
|
+
authentication is on by default, and closed until credentials exist**.
|
|
167
|
+
With none configured every dashboard request is 401, the app logs a
|
|
168
|
+
warning at boot, and `railwatch:doctor` says so. Set them with
|
|
147
169
|
|
|
148
170
|
```sh
|
|
149
171
|
bin/rails railwatch:authentication:configure
|
|
@@ -151,11 +173,10 @@ RAILS_ENV=production bin/rails railwatch:authentication:configure
|
|
|
151
173
|
```
|
|
152
174
|
|
|
153
175
|
The one exception is development. There, with Basic on and no
|
|
154
|
-
credentials set, the dashboard is open, so a first run
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
production are closed until you do.
|
|
176
|
+
credentials set, the dashboard is open, so a first run needs no password
|
|
177
|
+
step; Rails shows full error pages in development for the same reason.
|
|
178
|
+
Set credentials there too and development asks for them like everywhere
|
|
179
|
+
else. Test, staging and production are closed until you do.
|
|
159
180
|
|
|
160
181
|
`railwatch:authentication:configure` writes them to that environment's Rails credentials:
|
|
161
182
|
|
|
@@ -265,8 +286,10 @@ no user table to check it against. Comments, saved views, issue activity
|
|
|
265
286
|
and assignment all key off whatever you return, so a person keeps their
|
|
266
287
|
own views and their name on their own comments however you identify them.
|
|
267
288
|
|
|
268
|
-
|
|
269
|
-
|
|
289
|
+
On a page, returning `nil` does not refuse the request: the page renders
|
|
290
|
+
with the default "Operator". With HTTP Basic off, `nil` refuses the
|
|
291
|
+
live-update subscription. Pages are gated by HTTP Basic, your
|
|
292
|
+
`base_controller_class`, or a mount constraint, never by this resolver.
|
|
270
293
|
|
|
271
294
|
## The writer process
|
|
272
295
|
|
|
@@ -274,9 +297,14 @@ Puma forks one Railwatch writer from its master when `config/puma.rb`
|
|
|
274
297
|
carries the plugin (the embedded install adds it):
|
|
275
298
|
|
|
276
299
|
```ruby
|
|
277
|
-
plugin :railwatch
|
|
300
|
+
plugin :railwatch
|
|
278
301
|
```
|
|
279
302
|
|
|
303
|
+
Leave it unconditional: `bundle exec puma` reads this file before it
|
|
304
|
+
loads the app, so `if defined?(Railwatch)` would be false there and no
|
|
305
|
+
writer would start. The plugin does nothing when Railwatch is off or not
|
|
306
|
+
in embedded mode.
|
|
307
|
+
|
|
280
308
|
Every web worker keeps its reporter thread, but instead of writing
|
|
281
309
|
SQLite it hands each batch to the writer over a Unix socket
|
|
282
310
|
(`tmp/sockets/railwatch-writer.sock`, `RAILWATCH_WRITER_SOCKET`). The
|
|
@@ -391,7 +419,7 @@ Railwatch.configure do |c|
|
|
|
391
419
|
c.retention_days = 7 # RAILWATCH_RETENTION_DAYS
|
|
392
420
|
c.http_basic_auth_enabled = true # RAILWATCH_HTTP_BASIC_AUTH_ENABLED; credentials from Rails credentials or env
|
|
393
421
|
c.base_controller_class = "ActionController::Base" # RAILWATCH_BASE_CONTROLLER_CLASS
|
|
394
|
-
c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN;
|
|
422
|
+
c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN; true makes it public on purpose
|
|
395
423
|
c.dashboard_user = ->(request) { ... }
|
|
396
424
|
end
|
|
397
425
|
```
|
|
@@ -403,8 +431,11 @@ a busy app.
|
|
|
403
431
|
## Deploys
|
|
404
432
|
|
|
405
433
|
`bin/rails railwatch:deploy` records the marker in the app's own
|
|
406
|
-
database instead of posting it
|
|
407
|
-
|
|
434
|
+
database instead of posting it. The Kamal post-deploy hook runs on the
|
|
435
|
+
deployer, which does not read your initializer, so it only knows the
|
|
436
|
+
install is embedded when `RAILWATCH_TRANSPORT=local` is set in the
|
|
437
|
+
deployer's environment. Then it runs `railwatch:deploy` inside the
|
|
438
|
+
primary container.
|
|
408
439
|
|
|
409
440
|
## Storage and overhead
|
|
410
441
|
|
|
@@ -465,6 +496,8 @@ never need to run it again.
|
|
|
465
496
|
|
|
466
497
|
## Switching to the cloud later
|
|
467
498
|
|
|
468
|
-
|
|
499
|
+
To keep the local dashboard and add the cloud, turn on
|
|
500
|
+
[export](#three-ways-to-run-it). To move to the cloud entirely, set a
|
|
501
|
+
token and drop `c.transport = :local` (or set
|
|
469
502
|
`RAILWATCH_TRANSPORT=http`). The local databases can stay; the dashboard
|
|
470
503
|
at `/railwatch` keeps reading what is there until it is pruned.
|
data/docs/faq.md
CHANGED
|
@@ -51,20 +51,26 @@ A second gate, `bench/no_db_writes.rb`, drives 200 requests and a job with
|
|
|
51
51
|
a `sql.active_record` subscriber watching for any `INSERT`/`UPDATE`/
|
|
52
52
|
`DELETE` issued from a frame inside `lib/railwatch`, and fails if it finds
|
|
53
53
|
one. **Railwatch never writes to your application's database.** Records
|
|
54
|
-
live in memory and are
|
|
55
|
-
|
|
54
|
+
live in memory and are delivered by a background thread, to the cloud or
|
|
55
|
+
to the embedded writer process, which owns its own two SQLite files. That
|
|
56
|
+
is not a nicety: instrumentation that takes a write lock is what turns a
|
|
56
57
|
single-writer SQLite app into a "database is locked" incident.
|
|
57
58
|
|
|
58
59
|
## Where does the data go, and how long is it kept?
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
Embedded (the default): into the app's own `railwatch_telemetry` SQLite
|
|
62
|
+
database under `storage/`, written by one writer process Puma forks.
|
|
63
|
+
Raw rows are pruned nightly to `retention_days` (7 by default), and
|
|
64
|
+
hourly rollups back the charts. Nothing leaves the machine unless you
|
|
65
|
+
turn on export. See [Embedded mode](embedded.md).
|
|
66
|
+
|
|
67
|
+
Railwatch Cloud: one gzip-NDJSON POST to `{ingest_url}/ingest` per
|
|
61
68
|
batch. The platform stores each monitored environment's telemetry in its
|
|
62
69
|
own database, prunes raw rows on a retention window, and keeps hourly
|
|
63
|
-
rollups for the charts.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
backups, and pruning are the platform operator's responsibility.
|
|
70
|
+
rollups for the charts. Retention there is set by the account's plan
|
|
71
|
+
tier, not by the gem — 7, 30, or 90 days depending on the plan. For a
|
|
72
|
+
self-hosted platform, retention, backups, and pruning are the operator's
|
|
73
|
+
responsibility.
|
|
68
74
|
|
|
69
75
|
## What about PII?
|
|
70
76
|
|
|
@@ -119,8 +125,9 @@ Yes, on both sides, and it's the first-class target.
|
|
|
119
125
|
|
|
120
126
|
**In your app:** the gem does no I/O on the request path and never writes
|
|
121
127
|
to the app database, so there is no contention with SQLite's single
|
|
122
|
-
writer.
|
|
123
|
-
|
|
128
|
+
writer. Embedded mode's own two databases are separate SQLite files,
|
|
129
|
+
whatever the app runs on. SQL normalization is per adapter, so SQLite,
|
|
130
|
+
Postgres, MySQL, and Trilogy all group correctly.
|
|
124
131
|
|
|
125
132
|
**On the platform:** telemetry is stored one SQLite database per
|
|
126
133
|
monitored environment. That is what makes retention pruning, backup, and
|
|
@@ -163,6 +170,11 @@ oldest-job age on `health` records.
|
|
|
163
170
|
|
|
164
171
|
## What happens when the platform is unreachable?
|
|
165
172
|
|
|
173
|
+
This section is about the cloud transport. In embedded mode the
|
|
174
|
+
equivalent is the writer process being down, covered in
|
|
175
|
+
[Embedded mode](embedded.md#the-writer-process), and export keeps its
|
|
176
|
+
own durable queue.
|
|
177
|
+
|
|
166
178
|
Nothing, from your app's point of view. This is the property everything
|
|
167
179
|
else is built around: **delivery never raises into application code.**
|
|
168
180
|
|
data/docs/getting-started.md
CHANGED
|
@@ -1,26 +1,99 @@
|
|
|
1
1
|
# Getting started
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
`/railwatch`, no token and no cloud; see [Embedded mode](embedded.md).
|
|
9
|
-
This page is the cloud install, which is the same generator with
|
|
10
|
-
`--cloud` or any of the token options below.
|
|
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
|
-
|
|
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:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
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`:
|
|
22
94
|
|
|
23
95
|
```sh
|
|
96
|
+
bundle add railwatch
|
|
24
97
|
bin/rails generate railwatch:install --cloud
|
|
25
98
|
```
|
|
26
99
|
|
|
@@ -51,24 +124,12 @@ bin/rails generate railwatch:install \
|
|
|
51
124
|
`config/deploy.yml`, which is the pair of edits Kamal needs to pass a
|
|
52
125
|
secret through to the containers.
|
|
53
126
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
its default.
|
|
58
|
-
- `mount Railwatch::Engine, at: "/railwatch"` in `config/routes.rb` (the
|
|
59
|
-
beacon endpoint the browser client posts to).
|
|
60
|
-
- `.kamal/hooks/post-deploy` — only if `config/deploy.yml` already
|
|
61
|
-
exists.
|
|
62
|
-
- `app/frontend/lib/railwatch.ts` plus the `startRailwatch()` call in your
|
|
63
|
-
Inertia entrypoint — only if `app/frontend/` exists. If it can't find
|
|
64
|
-
an entrypoint it prints the two lines to add.
|
|
65
|
-
- `require "railwatch/rspec"` in `spec/rails_helper.rb`, or
|
|
66
|
-
`require "railwatch/minitest"` in `test/test_helper.rb`.
|
|
67
|
-
|
|
68
|
-
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
|
|
69
130
|
ends in a clean checklist or tells you what is still missing.
|
|
70
131
|
|
|
71
|
-
|
|
132
|
+
### Where the token comes from
|
|
72
133
|
|
|
73
134
|
In Railwatch Cloud, create an application, then an environment inside it
|
|
74
135
|
(`production`, `staging`, one token each). The token is shown once, on
|
|
@@ -80,12 +141,12 @@ update `RAILWATCH_TOKEN`.
|
|
|
80
141
|
bin/rails railwatch:token # prints the URL to create/copy a token
|
|
81
142
|
```
|
|
82
143
|
|
|
83
|
-
A token looks like `rw_` followed by 40 characters.
|
|
84
|
-
|
|
85
|
-
token.present?`, so an app with no token installs no
|
|
86
|
-
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.
|
|
87
148
|
|
|
88
|
-
|
|
149
|
+
### Check the cloud wiring
|
|
89
150
|
|
|
90
151
|
```sh
|
|
91
152
|
bin/rails railwatch:doctor
|
|
@@ -93,12 +154,14 @@ bin/rails railwatch:doctor
|
|
|
93
154
|
|
|
94
155
|
```
|
|
95
156
|
✓ token: rw_9Qv... (43 chars)
|
|
157
|
+
✓ token storage: no tracked plaintext Railwatch token found
|
|
96
158
|
✓ ingest url: https://railwatch.rebulk.com
|
|
159
|
+
✓ ingest transport security: HTTPS with certificate verification
|
|
97
160
|
✓ ingest reachable: GET https://railwatch.rebulk.com/ingest/ping
|
|
98
161
|
✓ request middleware: Railwatch::Middleware::Request at position 0
|
|
99
162
|
✓ engine mounted: POST /railwatch/beacon -> railwatch/beacon#create
|
|
100
163
|
✓ deploy: 8f31c0a42e91 (from GIT_REV)
|
|
101
|
-
✓ 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
|
|
102
165
|
✓ ignored record types: none
|
|
103
166
|
```
|
|
104
167
|
|
|
@@ -106,13 +169,12 @@ It exits non-zero on three lines: `token` (missing), `token storage`
|
|
|
106
169
|
(a plaintext token in a tracked file), and `ingest reachable`. The rest
|
|
107
170
|
of the checklist is informational. Run it after the token is in place:
|
|
108
171
|
`ingest reachable` sends the token, so a missing or wrong token makes
|
|
109
|
-
the platform answer 401 and that line is `✗` as well.
|
|
110
|
-
what to do about a `✗` is in [`troubleshooting.md`](troubleshooting.md).
|
|
172
|
+
the platform answer 401 and that line is `✗` as well.
|
|
111
173
|
|
|
112
174
|
`bin/rails railwatch:status` is the one-line version: it pings
|
|
113
175
|
`{ingest_url}/ingest/ping` and prints the ingest URL, deploy, and server.
|
|
114
176
|
|
|
115
|
-
##
|
|
177
|
+
## Make one request
|
|
116
178
|
|
|
117
179
|
```sh
|
|
118
180
|
bin/rails server
|
|
@@ -121,9 +183,10 @@ curl http://localhost:3000/
|
|
|
121
183
|
|
|
122
184
|
Records are batched in-process and flushed every `flush_interval`
|
|
123
185
|
(2 seconds by default) or every 500 records, whichever comes first, so
|
|
124
|
-
the request shows up on the
|
|
125
|
-
|
|
126
|
-
and log lines already
|
|
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.
|
|
127
190
|
|
|
128
191
|
## Three optional lines
|
|
129
192
|
|
|
@@ -191,8 +254,8 @@ newest issues, deploy markers), Requests (routes table and per-request
|
|
|
191
254
|
waterfall of every child record), Jobs, Scheduled tasks, Commands,
|
|
192
255
|
Exceptions, Queries (slow list and N+1 list with the app line that
|
|
193
256
|
issued them), Spans, Profiles, Transactions, View renders, Cache, Mail,
|
|
194
|
-
Notifications, Broadcasts, Outgoing requests,
|
|
195
|
-
Deprecations.
|
|
257
|
+
Notifications, Broadcasts, Outgoing requests, LLM (RubyLLM calls with
|
|
258
|
+
tokens, cost, cut-offs and tool calls), Storage, Logs, Deprecations.
|
|
196
259
|
|
|
197
260
|
**Monitoring** — Visits (Inertia page-visit timing and web vitals),
|
|
198
261
|
Users, Tenants, Deploys, Releases (crash-free session and user rates),
|
|
@@ -203,6 +266,15 @@ up, on the account.
|
|
|
203
266
|
|
|
204
267
|
## Deploying
|
|
205
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
|
+
|
|
206
278
|
### Kamal
|
|
207
279
|
|
|
208
280
|
Two edits, both of which `--kamal-secrets` makes for you:
|
|
@@ -280,9 +352,12 @@ one — the deploy is still recorded.
|
|
|
280
352
|
`sentry-rails`.
|
|
281
353
|
- [`replacing-nightwatch.md`](replacing-nightwatch.md) — for people
|
|
282
354
|
coming from Laravel.
|
|
355
|
+
- [`embedded.md`](embedded.md) — authentication, the writer process,
|
|
356
|
+
maintenance, disk, and export to the cloud.
|
|
283
357
|
- [`self-hosting.md`](self-hosting.md) — pointing the gem at your own
|
|
284
358
|
platform install.
|
|
285
359
|
- [`troubleshooting.md`](troubleshooting.md) — every `railwatch:doctor`
|
|
286
360
|
line and what a failure means.
|
|
287
361
|
- [`faq.md`](faq.md) — overhead, retention, PII, unreachable platform.
|
|
288
|
-
- [`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
|
|
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
|
-
**
|
|
530
|
-
`tool_concurrency
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
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
|
|
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
|
|
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) —
|
|
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
|
|
218
|
+
- [`records.md`](records.md) — all 28 record types, field by field.
|