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
data/docs/configuration.md
CHANGED
|
@@ -2,18 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Everything below lives on `Railwatch::Configuration`, in
|
|
4
4
|
`lib/railwatch/configuration.rb`. Set it via
|
|
5
|
-
`Railwatch.configure { |c| ... }` in `config/initializers/railwatch.rb
|
|
6
|
-
|
|
7
|
-
`
|
|
8
|
-
|
|
9
|
-
initializer always win over the env var.
|
|
5
|
+
`Railwatch.configure { |c| ... }` in `config/initializers/railwatch.rb`,
|
|
6
|
+
which `bin/rails generate railwatch:install` creates. Most settings have a
|
|
7
|
+
`RAILWATCH_*` env var default; the tables below show which. Explicit
|
|
8
|
+
values set in the initializer always win over the env var.
|
|
10
9
|
|
|
11
10
|
## Core
|
|
12
11
|
|
|
13
12
|
| Attribute | Env var | Default | Meaning |
|
|
14
13
|
|---|---|---|---|
|
|
15
|
-
| `enabled` | `RAILWATCH_ENABLED` | `true` | Master switch. `Railwatch.enabled?` is also `false` whenever `token` is blank, so setting only `RAILWATCH_TOKEN` is enough to turn
|
|
16
|
-
| `token` | `RAILWATCH_TOKEN` | nil | Bearer token for `/ingest`. Required. |
|
|
14
|
+
| `enabled` | `RAILWATCH_ENABLED` | `true` | Master switch. With `transport = :http`, `Railwatch.enabled?` is also `false` whenever `token` is blank, so setting only `RAILWATCH_TOKEN` is enough to turn a cloud install on. An embedded install needs no token. |
|
|
15
|
+
| `token` | `RAILWATCH_TOKEN` | nil | Bearer token for `/ingest`. Required with `transport = :http` and for [export](#export-to-railwatch-cloud). |
|
|
17
16
|
| `ingest_url` | `RAILWATCH_INGEST_URL` | `https://railwatch.rebulk.com` | Platform base URL. Point at a self-hosted instance to override. |
|
|
18
17
|
| `allow_http` | `RAILWATCH_ALLOW_HTTP` | `false` | Permit a non-loopback plain HTTP ingest URL. HTTPS is required by default; `localhost`, `127.0.0.1`, and `::1` remain available for local self-hosted development. |
|
|
19
18
|
| `deploy` | `RAILWATCH_DEPLOY` | auto-detected (order below), then nil | Version tag stamped on every record and used by `railwatch:deploy`. Full 40-character SHAs are shortened to 12 characters. |
|
|
@@ -26,7 +25,8 @@ initializer always win over the env var.
|
|
|
26
25
|
| `beacon_rate_limit` | `RAILWATCH_BEACON_RATE_LIMIT` | `120` | Beacon POSTs accepted per client IP per minute before `POST /railwatch/beacon` answers 429. The beacon is unauthenticated and keeps every browser error it is sent, so this is what stops a script from spending the app's event quota. Counted in the app's cache store; `0` turns it off. |
|
|
27
26
|
|
|
28
27
|
`Railwatch.enabled?` delegates to `config.enabled?`, which is `@enabled &&
|
|
29
|
-
token.present
|
|
28
|
+
(local? || token.present?)`. There is no separate "is configured" check
|
|
29
|
+
elsewhere.
|
|
30
30
|
|
|
31
31
|
Deploy detection stops at the first value found: `RAILWATCH_DEPLOY`,
|
|
32
32
|
`KAMAL_VERSION`, `GIT_REV`, `GIT_SHA`, `SOURCE_VERSION`,
|
|
@@ -385,7 +385,7 @@ the app database.
|
|
|
385
385
|
|
|
386
386
|
| Attribute | Env var | Default | Meaning |
|
|
387
387
|
|---|---|---|---|
|
|
388
|
-
| `buffer_size` | `RAILWATCH_BUFFER_SIZE` | `10000` | Max buffered records (`Railwatch::Buffer`). Oldest is dropped (and counted) when full — never blocks the request thread.
|
|
388
|
+
| `buffer_size` | `RAILWATCH_BUFFER_SIZE` | `10000` | Max buffered records (`Railwatch::Buffer`). Oldest is dropped (and counted) when full — never blocks the request thread. In practice `buffer_bytes` fills first: on a realistic mix of records, 16 MiB holds about 5,000 of them, so this count is never reached and raising it changes nothing. Tune `buffer_bytes` instead. Do not lower this below `Execution::MAX_RECORDS` (10,000): a kept execution's whole tree is written here at once when it ends, and a queue smaller than the tree drops the tree's own oldest records first. |
|
|
389
389
|
| `buffer_bytes` | `RAILWATCH_BUFFER_BYTES` | `16777216` (16 MiB) | Estimated payload memory the reporter queue may hold. A record count alone does not bound memory: 10,000 records is a few megabytes of ordinary telemetry, or a gigabyte of captured attachments. Oldest records are dropped (and counted) under byte pressure, same as under count pressure. |
|
|
390
390
|
| `execution_buffer_bytes` | `RAILWATCH_EXECUTION_BUFFER_BYTES` | `8388608` (8 MiB) | The same ceiling for one execution's buffered tree, before it finishes. A normal execution keeps its earliest records; a failure-context ring keeps its latest. |
|
|
391
391
|
| `batch_bytes` | `RAILWATCH_BATCH_BYTES` | `8388608` (8 MiB) | Uncompressed NDJSON bytes in one ingest request. A queue holding more than this is delivered as several batches — the tail is kept for the next flush, not dropped. |
|
|
@@ -399,13 +399,13 @@ the app database.
|
|
|
399
399
|
|
|
400
400
|
Delivery is `Railwatch::Transport::Http`, in
|
|
401
401
|
`lib/railwatch/transport/http.rb`: a gzip NDJSON POST to
|
|
402
|
-
`{ingest_url}/ingest`,
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
the platform can return the first committed
|
|
408
|
-
payload twice. Records written while a request is in flight collect in a
|
|
402
|
+
`{ingest_url}/ingest`, one attempt per delivery. If it raises, or ingest
|
|
403
|
+
returns 402, 408, 429, or a 5xx, the immutable batch and its prior drop
|
|
404
|
+
count are retained for retry; the transport does not retry on its own,
|
|
405
|
+
so each rung of the reporter's ladder costs one timeout, not two. Every
|
|
406
|
+
newly formed batch gets an `X-Railwatch-Batch-Id` UUID. It is reused for
|
|
407
|
+
every reporter retry, so the platform can return the first committed
|
|
408
|
+
result without inserting the payload twice. Records written while a request is in flight collect in a
|
|
409
409
|
separate bounded buffer, so they never change the retained request's
|
|
410
410
|
identity. At most one retained batch plus one live buffer are held in
|
|
411
411
|
memory. The reporter retries with jittered exponential backoff, from one
|
|
@@ -569,7 +569,7 @@ Rake tasks and Solid Queue jobs are never interactive.
|
|
|
569
569
|
|
|
570
570
|
| Attribute | Env var | Default | Meaning |
|
|
571
571
|
|---|---|---|---|
|
|
572
|
-
| `capture_exception_source` | `RAILWATCH_CAPTURE_EXCEPTION_SOURCE_CODE` | `true` |
|
|
572
|
+
| `capture_exception_source` | `RAILWATCH_CAPTURE_EXCEPTION_SOURCE_CODE` | `true` | Record source snippet lines surrounding each in-application exception frame (sent to Railwatch Cloud when the install reports there or exports). This is on by default for crash context; disable it when source disclosure is outside the application's telemetry policy. |
|
|
573
573
|
| `capture_exception_locals` | `RAILWATCH_CAPTURE_EXCEPTION_LOCALS` | `false` | Snapshot the raising frame's local variables (up to 25, values truncated to 200 chars, run through the same filter as request params) onto each exception, like Sentry's locals panel. Installs a `TracePoint(:raise)`; opt in per environment. |
|
|
574
574
|
| `capture_request_payload` | `RAILWATCH_CAPTURE_REQUEST_PAYLOAD` | `false` | Capture (redacted) request params — only for a request that raised, never otherwise. |
|
|
575
575
|
| `capture_job_arguments` | `RAILWATCH_CAPTURE_JOB_ARGUMENTS` | `false` | Add the job's real arguments (`job.serialize["arguments"]`) to each `job_attempt`/`scheduled_task` record, capped at 8 KiB of JSON. Hash arguments run through the same filter as request params. Off by default because job arguments routinely carry PII; `arguments_preview` (argument *shapes* only) is always on regardless. |
|
|
@@ -827,9 +827,25 @@ Railwatch.on_unrecoverable { |error| Rails.error.report(error, handled: true) }
|
|
|
827
827
|
Called whenever Railwatch rescues one of its own internal errors, ingest
|
|
828
828
|
permanently rejects a batch, or shutdown expires with retained records
|
|
829
829
|
that could not be sent. Retryable delivery failures stay buffered and do
|
|
830
|
-
not fire the callback on every attempt. With no callback registered,
|
|
831
|
-
falls back to `Railwatch.debug
|
|
832
|
-
`RAILWATCH_DEBUG
|
|
830
|
+
not fire the callback on every attempt. With no callback registered, a
|
|
831
|
+
recovered internal error falls back to `Railwatch.debug`, gated on
|
|
832
|
+
`RAILWATCH_DEBUG` — the gem carried on and there is nothing to do about it.
|
|
833
|
+
|
|
834
|
+
**Lost records are different, and are reported by default.** A batch
|
|
835
|
+
dropped after its retry ladder, one the receiver permanently refused, or
|
|
836
|
+
records still unsent when the bounded shutdown ran out of time each print
|
|
837
|
+
one `[railwatch]` stderr line. Telemetry that vanishes silently looks
|
|
838
|
+
exactly like having nothing to report, and a short-lived process — a rake
|
|
839
|
+
task, a `rails runner`, a cron job — gets one bounded shutdown and no
|
|
840
|
+
second chance to mention it.
|
|
841
|
+
|
|
842
|
+
Two ways out, and the line names both. A registered `on_unrecoverable`
|
|
843
|
+
always wins, which is how an app routes the loss somewhere better
|
|
844
|
+
(`Rails.error.report`). Or set `warn_on_data_loss = false`
|
|
845
|
+
(`RAILWATCH_WARN_ON_DATA_LOSS=false`) and the gem goes back to saying
|
|
846
|
+
nothing.
|
|
847
|
+
|
|
848
|
+
Either way it is stderr, never `Rails.logger`, so gem-internal failures can
|
|
833
849
|
never themselves become `log` records.
|
|
834
850
|
|
|
835
851
|
## Faraday
|
|
@@ -868,46 +884,70 @@ ignore block's building blocks, and are nestable.
|
|
|
868
884
|
## Embedded mode
|
|
869
885
|
|
|
870
886
|
```ruby
|
|
871
|
-
c.transport = :local # RAILWATCH_TRANSPORT; default "http"
|
|
887
|
+
c.transport = :local # RAILWATCH_TRANSPORT; runtime default "http", but the installer writes :local
|
|
872
888
|
c.issue_prefix = "SHOP" # RAILWATCH_ISSUE_PREFIX; default from the app name
|
|
873
889
|
c.repository_url = "..." # RAILWATCH_REPOSITORY_URL
|
|
874
890
|
c.retention_days = 7 # RAILWATCH_RETENTION_DAYS
|
|
875
|
-
c.http_basic_auth_enabled = true # RAILWATCH_HTTP_BASIC_AUTH_ENABLED; on and closed until credentials exist
|
|
891
|
+
c.http_basic_auth_enabled = true # RAILWATCH_HTTP_BASIC_AUTH_ENABLED; on, and closed outside development until credentials exist
|
|
876
892
|
c.http_basic_auth_user = "ops" # RAILWATCH_HTTP_BASIC_AUTH_USER, or credentials railwatch.http_basic_auth_user
|
|
877
893
|
c.http_basic_auth_password = "..." # RAILWATCH_HTTP_BASIC_AUTH_PASSWORD, or credentials railwatch.http_basic_auth_password
|
|
878
894
|
c.base_controller_class = "AdminController" # RAILWATCH_BASE_CONTROLLER_CLASS; default ActionController::Base
|
|
879
|
-
c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN; public on purpose
|
|
895
|
+
c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN; default false, true makes it public on purpose
|
|
880
896
|
c.dashboard_user = ->(request) { { id:, name:, email: } or nil }
|
|
897
|
+
c.writer_socket = "tmp/sockets/railwatch-writer.sock" # RAILWATCH_WRITER_SOCKET
|
|
881
898
|
```
|
|
882
899
|
|
|
883
900
|
With `transport = :local` the reporter writes each batch into the app's
|
|
884
901
|
own `railwatch_telemetry` database instead of POSTing it, and the engine
|
|
885
902
|
serves the dashboard at its mount. `enabled?` no longer needs a token.
|
|
886
903
|
The others only matter in that mode. The dashboard is behind HTTP Basic
|
|
887
|
-
by default and answers 401 until `bin/rails
|
|
904
|
+
by default and, outside development, answers 401 until `bin/rails
|
|
888
905
|
railwatch:authentication:configure` has written credentials; a host with
|
|
889
906
|
its own admin auth turns Basic off and sets `base_controller_class` or a
|
|
890
907
|
routes constraint. Full walkthrough: [Embedded mode](embedded.md).
|
|
891
908
|
|
|
909
|
+
### Export to Railwatch Cloud
|
|
910
|
+
|
|
911
|
+
An embedded install can also mirror every record to Railwatch Cloud.
|
|
912
|
+
Off unless `export_enabled` is set; a configured token alone sends
|
|
913
|
+
nothing. Only meaningful with `transport = :local`.
|
|
914
|
+
|
|
915
|
+
| Attribute | Env var | Default | Meaning |
|
|
916
|
+
|---|---|---|---|
|
|
917
|
+
| `export_enabled` | `RAILWATCH_EXPORT_ENABLED` | `false` | Mirror every record to the cloud as well as storing it locally. |
|
|
918
|
+
| `export_token` | `RAILWATCH_EXPORT_TOKEN` | `token` | Cloud environment token to send with. |
|
|
919
|
+
| `export_url` | `RAILWATCH_EXPORT_URL` | `{ingest_url}/ingest` | Where to send. |
|
|
920
|
+
| `export_max_bytes` | `RAILWATCH_EXPORT_MAX_BYTES` | `268435456` (256 MiB) | Queue ceiling in bytes. At capacity new work is refused and counted, not swapped for old. |
|
|
921
|
+
| `export_max_deliveries` | `RAILWATCH_EXPORT_MAX_DELIVERIES` | `100000` | Queue ceiling in deliveries. |
|
|
922
|
+
| `export_max_age` | `RAILWATCH_EXPORT_MAX_AGE_SECONDS` | `86400` | How long a queued delivery is kept. At most seven days, past which the receiver no longer recognises it. |
|
|
923
|
+
|
|
924
|
+
`railwatch:doctor` fails when export is enabled and cannot work, and
|
|
925
|
+
`railwatch:export:status` shows the queue. See
|
|
926
|
+
[Embedded mode](embedded.md#three-ways-to-run-it).
|
|
927
|
+
|
|
892
928
|
## Rake tasks
|
|
893
929
|
|
|
894
930
|
Ship with the gem via Rails::Engine's default `lib/tasks` convention, in
|
|
895
931
|
`lib/tasks/railwatch_tasks.rake`:
|
|
896
932
|
|
|
897
|
-
- **`railwatch:status`**
|
|
898
|
-
|
|
899
|
-
fails.
|
|
900
|
-
- **`railwatch:doctor`** prints a ✓/✗ checklist of the whole install
|
|
901
|
-
|
|
933
|
+
- **`railwatch:status`** embedded: prints that telemetry is stored in the
|
|
934
|
+
app. Cloud: pings `{ingest_url}/ingest/ping` with the configured token,
|
|
935
|
+
and aborts if `RAILWATCH_TOKEN` is unset or the ping fails.
|
|
936
|
+
- **`railwatch:doctor`** prints a ✓/✗ checklist of the whole install.
|
|
937
|
+
Embedded, it checks both databases and their migrations, telemetry
|
|
938
|
+
disk mode, the maintenance clock, the writer process, dashboard access,
|
|
939
|
+
and export when enabled. Cloud, it checks the token, where the token is
|
|
940
|
+
stored, the ingest URL and its transport security, and
|
|
941
|
+
`GET /ingest/ping`. Then, either way: `Railwatch::Middleware::Request`
|
|
902
942
|
in the middleware stack, the mounted engine's beacon route,
|
|
903
|
-
`config.deploy` and its
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
server, timestamp, performer, destination, service, commits}` to
|
|
943
|
+
`config.deploy` and its source, sample rates, ignored record types, the
|
|
944
|
+
Kamal `post-deploy` hook, the browser client and whether an entrypoint
|
|
945
|
+
calls it, the profiler backend, and the test matchers. It exits
|
|
946
|
+
non-zero only on the lines marked fatal in
|
|
947
|
+
[Troubleshooting](troubleshooting.md).
|
|
948
|
+
- **`railwatch:deploy[ref,name,url]`** embedded: writes the deploy marker
|
|
949
|
+
to the app's `railwatch` database. Cloud: POSTs `{deploy, ref, name,
|
|
950
|
+
url, server, timestamp, performer, destination, service, commits}` to
|
|
911
951
|
`{ingest_url}/ingest/deploys`. `deploy` comes from `config.deploy`. It
|
|
912
952
|
aborts if that's unset. `ref` defaults to `git rev-parse HEAD` when not
|
|
913
953
|
passed. `performer`/`destination`/`service` come from `KAMAL_PERFORMER`,
|
|
@@ -915,13 +955,30 @@ Ship with the gem via Rails::Engine's default `lib/tasks` convention, in
|
|
|
915
955
|
`{sha, author, message, at}` objects, newest first, from `git log`. It
|
|
916
956
|
is empty inside an app container, which has no `.git`. That is why the
|
|
917
957
|
hook below posts from the deployer instead.
|
|
958
|
+
- **`railwatch:authentication:configure`** writes the embedded
|
|
959
|
+
dashboard's HTTP Basic credentials to the current environment's Rails
|
|
960
|
+
credentials.
|
|
961
|
+
- **`railwatch:export:status`**, **`railwatch:export:rebind`**,
|
|
962
|
+
**`railwatch:export:discard`** show the export queue, clear a
|
|
963
|
+
credential block (abandoning work queued under the old token), and
|
|
964
|
+
abandon everything queued.
|
|
965
|
+
- **`railwatch:vacuum:status`** and **`railwatch:vacuum`** report and
|
|
966
|
+
reclaim the telemetry database's disk
|
|
967
|
+
([Embedded mode](embedded.md#giving-the-disk-back)).
|
|
968
|
+
- **`railwatch:token`** and **`railwatch:mcp`** print where to create a
|
|
969
|
+
cloud ingest token and paste-ready MCP client configuration.
|
|
970
|
+
- **`railwatch:sourcemaps[directory,delete]`** uploads browser source
|
|
971
|
+
maps ([Source maps](source-maps.md)).
|
|
918
972
|
|
|
919
973
|
## Kamal integration
|
|
920
974
|
|
|
921
975
|
`bin/rails generate railwatch:install` writes `.kamal/hooks/post-deploy`,
|
|
922
|
-
but only if `config/deploy.yml` already exists.
|
|
923
|
-
`
|
|
924
|
-
|
|
976
|
+
but only if `config/deploy.yml` already exists. With
|
|
977
|
+
`RAILWATCH_TRANSPORT=local` in the deployer's environment it runs
|
|
978
|
+
`bin/rails railwatch:deploy[$KAMAL_VERSION]` in the primary container,
|
|
979
|
+
which records the marker in the embedded database, and stops. Otherwise
|
|
980
|
+
it no-ops when `RAILWATCH_TOKEN` isn't set. It never fails a deploy.
|
|
981
|
+
Every network call ends in `|| true`.
|
|
925
982
|
|
|
926
983
|
The hook runs on the **deployer machine**, not in a container, which is
|
|
927
984
|
the whole point. That's where the git history lives and where Kamal
|
data/docs/embedded.md
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
# Embedded mode: the dashboard inside your app
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
|
@@ -25,24 +24,40 @@ up:
|
|
|
25
24
|
| **Cloud** | Railwatch Cloud | the hosted one |
|
|
26
25
|
| **Both** | your app's files, *and* Railwatch Cloud | either |
|
|
27
26
|
|
|
28
|
-
Embedded is `c.transport = :local`, which is what
|
|
29
|
-
|
|
27
|
+
Embedded is `c.transport = :local`, which is what the installer writes
|
|
28
|
+
unless you ask it for the cloud (`--cloud`, or any token or URL option).
|
|
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:
|
|
30
41
|
|
|
31
42
|
```ruby
|
|
32
43
|
c.export_enabled = true # or RAILWATCH_EXPORT_ENABLED=true
|
|
33
44
|
```
|
|
34
45
|
|
|
35
|
-
It reuses
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
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.
|
|
40
52
|
|
|
41
53
|
It is off unless you set that flag. A token being present is not consent:
|
|
42
54
|
an embedded install that has one configured still sends nothing.
|
|
43
55
|
`railwatch:doctor` says nothing about export until you ask for it, and
|
|
44
|
-
fails
|
|
45
|
-
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).
|
|
46
61
|
|
|
47
62
|
## Railwatch Cloud runs the same models
|
|
48
63
|
|
|
@@ -59,13 +74,16 @@ documents: `id`, `slug`, `name`, `with_telemetry`, and the display attributes.
|
|
|
59
74
|
|
|
60
75
|
```sh
|
|
61
76
|
bundle add railwatch
|
|
62
|
-
bin/rails generate railwatch:install
|
|
77
|
+
bin/rails generate railwatch:install
|
|
63
78
|
```
|
|
64
79
|
|
|
65
|
-
Restart the app and open `/railwatch
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
Restart the app and open `/railwatch`; in development it is open with no
|
|
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.
|
|
69
87
|
|
|
70
88
|
The engine needs Active Job (its grouping and scan jobs are Active Job
|
|
71
89
|
classes even though embedded mode calls them directly) and loads it
|
|
@@ -79,20 +97,21 @@ databases it adds are SQLite files either way, so the generated entries
|
|
|
79
97
|
name `adapter: sqlite3` themselves rather than inheriting your default
|
|
80
98
|
block, and they need no `&default` anchor to exist.
|
|
81
99
|
|
|
82
|
-
On a PostgreSQL or MySQL app
|
|
83
|
-
|
|
100
|
+
On a PostgreSQL or MySQL app without the `sqlite3` gem, the install
|
|
101
|
+
takes two more commands:
|
|
84
102
|
|
|
85
103
|
```sh
|
|
86
|
-
bin/rails generate railwatch:install
|
|
104
|
+
bin/rails generate railwatch:install # adds gem "sqlite3", writes the config
|
|
87
105
|
bundle install
|
|
88
|
-
bin/rails db:prepare
|
|
106
|
+
bin/rails db:prepare # creates the two SQLite files
|
|
89
107
|
```
|
|
90
108
|
|
|
91
109
|
Verified end to end on both. On a PostgreSQL app and on a MySQL app, the
|
|
92
110
|
application's own four databases stay where they were, Railwatch's two are
|
|
93
111
|
files under `storage/`, and neither server gains a single Railwatch table.
|
|
94
112
|
|
|
95
|
-
What
|
|
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)):
|
|
96
115
|
|
|
97
116
|
- `config/initializers/railwatch.rb` with `c.transport = :local` and the
|
|
98
117
|
dashboard's own paths excluded from request capture.
|
|
@@ -104,6 +123,8 @@ What `--local` writes, on top of the usual install:
|
|
|
104
123
|
databases need that form. Each entry's `migrations_paths` points into
|
|
105
124
|
the gem, so `db:prepare` builds the tables from the gem's own
|
|
106
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).
|
|
107
128
|
- `mount Railwatch::Engine, at: "/railwatch"`, as always.
|
|
108
129
|
|
|
109
130
|
Nothing touches your primary database.
|
|
@@ -132,23 +153,32 @@ inside the gem, so the app needs no Node, no Vite and no asset pipeline
|
|
|
132
153
|
integration.
|
|
133
154
|
|
|
134
155
|
Not in embedded mode: accounts and members (the operator is whoever your
|
|
135
|
-
app lets through), integrations (alerts are recorded, not delivered
|
|
136
|
-
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.
|
|
137
161
|
|
|
138
162
|
## Authentication
|
|
139
163
|
|
|
140
164
|
The dashboard shows every query, log line and exception your app
|
|
141
165
|
produced, so it works the way Mission Control Jobs does: **HTTP Basic
|
|
142
|
-
authentication is on
|
|
143
|
-
configured every dashboard request is 401,
|
|
144
|
-
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
|
|
145
169
|
|
|
146
170
|
```sh
|
|
147
171
|
bin/rails railwatch:authentication:configure
|
|
148
172
|
RAILS_ENV=production bin/rails railwatch:authentication:configure
|
|
149
173
|
```
|
|
150
174
|
|
|
151
|
-
|
|
175
|
+
The one exception is development. There, with Basic on and no
|
|
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.
|
|
180
|
+
|
|
181
|
+
`railwatch:authentication:configure` writes them to that environment's Rails credentials:
|
|
152
182
|
|
|
153
183
|
```yml
|
|
154
184
|
railwatch:
|
|
@@ -256,18 +286,25 @@ no user table to check it against. Comments, saved views, issue activity
|
|
|
256
286
|
and assignment all key off whatever you return, so a person keeps their
|
|
257
287
|
own views and their name on their own comments however you identify them.
|
|
258
288
|
|
|
259
|
-
|
|
260
|
-
|
|
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.
|
|
261
293
|
|
|
262
294
|
## The writer process
|
|
263
295
|
|
|
264
296
|
Puma forks one Railwatch writer from its master when `config/puma.rb`
|
|
265
|
-
carries the plugin (
|
|
297
|
+
carries the plugin (the embedded install adds it):
|
|
266
298
|
|
|
267
299
|
```ruby
|
|
268
|
-
plugin :railwatch
|
|
300
|
+
plugin :railwatch
|
|
269
301
|
```
|
|
270
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
|
+
|
|
271
308
|
Every web worker keeps its reporter thread, but instead of writing
|
|
272
309
|
SQLite it hands each batch to the writer over a Unix socket
|
|
273
310
|
(`tmp/sockets/railwatch-writer.sock`, `RAILWATCH_WRITER_SOCKET`). The
|
|
@@ -319,6 +356,17 @@ through `Railwatch.on_unrecoverable`, so loss is never silent. A Puma
|
|
|
319
356
|
phased restart stops the writer and starts a fresh one once the new
|
|
320
357
|
workers are up.
|
|
321
358
|
|
|
359
|
+
Stopping the writer is bounded. Puma sends it TERM and waits up to
|
|
360
|
+
`c.shutdown_timeout` (2 seconds) for it to exit, the same allowance it
|
|
361
|
+
gives its own reporter, then kills it. A writer killed mid-batch loses
|
|
362
|
+
nothing: the transaction rolls back and the worker retries that batch by
|
|
363
|
+
id against the next writer, so waiting longer for its drain would buy no
|
|
364
|
+
data. The bound is what keeps Puma's exit short when the writer is wedged
|
|
365
|
+
in a SQLite write or on a full disk. It counts against the container's
|
|
366
|
+
stop grace (Docker's default is 10 seconds; Kamal's `stop_timeout` sets
|
|
367
|
+
it), and `RAILWATCH_SHUTDOWN_TIMEOUT` raises it for an app whose grace
|
|
368
|
+
allows more.
|
|
369
|
+
|
|
322
370
|
## Maintenance
|
|
323
371
|
|
|
324
372
|
Railwatch needs no job worker and nothing in `config/recurring.yml`.
|
|
@@ -371,7 +419,7 @@ Railwatch.configure do |c|
|
|
|
371
419
|
c.retention_days = 7 # RAILWATCH_RETENTION_DAYS
|
|
372
420
|
c.http_basic_auth_enabled = true # RAILWATCH_HTTP_BASIC_AUTH_ENABLED; credentials from Rails credentials or env
|
|
373
421
|
c.base_controller_class = "ActionController::Base" # RAILWATCH_BASE_CONTROLLER_CLASS
|
|
374
|
-
c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN;
|
|
422
|
+
c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN; true makes it public on purpose
|
|
375
423
|
c.dashboard_user = ->(request) { ... }
|
|
376
424
|
end
|
|
377
425
|
```
|
|
@@ -383,8 +431,11 @@ a busy app.
|
|
|
383
431
|
## Deploys
|
|
384
432
|
|
|
385
433
|
`bin/rails railwatch:deploy` records the marker in the app's own
|
|
386
|
-
database instead of posting it
|
|
387
|
-
|
|
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.
|
|
388
439
|
|
|
389
440
|
## Storage and overhead
|
|
390
441
|
|
|
@@ -445,6 +496,8 @@ never need to run it again.
|
|
|
445
496
|
|
|
446
497
|
## Switching to the cloud later
|
|
447
498
|
|
|
448
|
-
|
|
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
|
|
449
502
|
`RAILWATCH_TRANSPORT=http`). The local databases can stay; the dashboard
|
|
450
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
|
|
|
@@ -182,10 +194,10 @@ being silent.
|
|
|
182
194
|
A queue holding more than one batch is delivered as several batches: the
|
|
183
195
|
tail is put back for the next flush rather than dropped.
|
|
184
196
|
|
|
185
|
-
A background thread drains the buffer and POSTs
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
197
|
+
A background thread drains the buffer and POSTs, one attempt per delivery.
|
|
198
|
+
If it fails, the batch and its drop counter go back into the bounded
|
|
199
|
+
buffer; a raised network error, **402**, **408**, **429**, and all **5xx**
|
|
200
|
+
responses are retained the same way. So is a **2xx
|
|
189
201
|
that cannot acknowledge the batch** — a proxy's HTML sign-in page, malformed
|
|
190
202
|
JSON, or `accepted`/`rejected` counts that do not cover what was sent — which
|
|
191
203
|
would otherwise be a silent drop. The reporter
|
|
@@ -217,7 +229,8 @@ already appear on its own ingest batch. Those are visible under
|
|
|
217
229
|
On shutdown, `at_exit` gives the thread `c.shutdown_timeout` (2 seconds) to
|
|
218
230
|
attempt retained records immediately and retry within the remaining time.
|
|
219
231
|
If the deadline expires, the records stay retained and their count is sent
|
|
220
|
-
to `Railwatch.on_unrecoverable` (or
|
|
232
|
+
to `Railwatch.on_unrecoverable` (or, with no callback, to `Railwatch.debug`, and
|
|
233
|
+
to stderr by default, which `warn_on_data_loss = false` turns off). This is an
|
|
221
234
|
in-memory buffer, not an on-disk spool: a hard kill, or exiting after that
|
|
222
235
|
deadline, cannot carry those records into the next process. Railwatch never
|
|
223
236
|
uses `Rails.logger` for its own failures, which would turn them into `log`
|