railwatch 0.1.0 → 0.1.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 +1 -1
- data/CHANGELOG.md +9 -0
- data/README.md +44 -166
- data/docs/ai-and-mcp.md +9 -9
- data/docs/configuration.md +419 -392
- data/docs/getting-started.md +10 -8
- data/docs/records.md +304 -280
- data/docs/source-maps.md +1 -1
- data/docs/testing.md +17 -17
- data/docs/troubleshooting.md +90 -87
- data/lib/railwatch/secret_safety.rb +3 -1
- data/lib/railwatch/version.rb +1 -1
- data/lib/tasks/railwatch_tasks.rake +4 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 27a48b972fe7616fbf46c861f8b0f1a9ffb4d99146ec2121cb05274ea3dee031
|
|
4
|
+
data.tar.gz: 45c879afc12f731e446be6aa43f8a78b61d078a8f5a7cdc6294bd94c4dfe864e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 29512ddbb519a85a1334531289be33d59d4ceb3cf0a8c7ef55d3db04ab41ae884adf65b64ddecd40d1b70254ca76ed0ce0bbb38ec03eee0125dbae5a8b7730de
|
|
7
|
+
data.tar.gz: 67d7a5573b95aa032a227a8dc10e440bfaee6681628de90d2948f074ceb15ffb9ea839968984beaaec545d0db161a266ba94e99cd2b3a0908c14c9e98969c2c7
|
data/AGENTS.md
CHANGED
|
@@ -103,7 +103,7 @@ token at Settings → Profile → "API & MCP token", then:
|
|
|
103
103
|
|
|
104
104
|
```sh
|
|
105
105
|
claude mcp add railwatch --transport http https://railwatch.rebulk.com/mcp \
|
|
106
|
-
--header "Authorization: Bearer
|
|
106
|
+
--header "Authorization: Bearer rwp_your_token_here"
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
`bin/rails railwatch:mcp` prints this and the Claude Desktop, Cursor, VS Code, and
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1 (2026-09-14)
|
|
4
|
+
|
|
5
|
+
- Token prefixes are `rw_` for an environment's ingest token and `rwp_`
|
|
6
|
+
for a personal API and MCP token. They were `lt_` and `lnt_`, Lantern's
|
|
7
|
+
initials, which no longer name anything a user can see. The platform
|
|
8
|
+
authenticates by digest, so tokens minted before this keep working; the
|
|
9
|
+
doctor's plaintext-token scan matches both the old and the new ingest
|
|
10
|
+
prefix.
|
|
11
|
+
|
|
3
12
|
## 0.1.0 (2026-09-14)
|
|
4
13
|
|
|
5
14
|
First public release.
|
data/README.md
CHANGED
|
@@ -15,29 +15,49 @@ bin/rails generate railwatch:install --prompt-token # 2. hidden token input plu
|
|
|
15
15
|
bin/rails railwatch:doctor # 3. check every piece is wired up after restart
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
Getting the token, the generator's flags, and deploying with Kamal,
|
|
19
|
+
Docker, Heroku, or Render are covered in
|
|
20
|
+
[Getting started](docs/getting-started.md). For a self-hosted deployment
|
|
21
|
+
or an unreleased revision, use the Git source instead:
|
|
21
22
|
|
|
22
23
|
```ruby
|
|
23
24
|
gem "railwatch", github: "Rebulk/railwatch"
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
27
|
+
## What you get
|
|
28
|
+
|
|
29
|
+
- Requests, jobs, scheduled tasks, commands, queries, exceptions, and
|
|
30
|
+
logs, linked into one trace per execution
|
|
31
|
+
([Record types](docs/records.md)).
|
|
32
|
+
- Sampling decided once per execution, plus tail sampling that keeps a
|
|
33
|
+
sampled-out request that turns out slow or raises
|
|
34
|
+
([Configuration](docs/configuration.md)).
|
|
35
|
+
- An optional stack profiler through `vernier` or `stackprof`, off by
|
|
36
|
+
default ([Configuration](docs/configuration.md)).
|
|
37
|
+
- A browser client for Inertia apps: page-visit timing, Core Web Vitals,
|
|
38
|
+
and browser errors ([Getting started](docs/getting-started.md)).
|
|
39
|
+
- RSpec and Minitest matchers that turn a query budget into a CI gate
|
|
40
|
+
([Testing](docs/testing.md)).
|
|
41
|
+
- An MCP server so Claude Code, Cursor, VS Code, or Zed can read your
|
|
42
|
+
production data ([AI assistants and MCP](docs/ai-and-mcp.md)).
|
|
43
|
+
|
|
44
|
+
Configuration lives in `config/initializers/railwatch.rb`; most options
|
|
45
|
+
also have a `RAILWATCH_*` environment variable.
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
Railwatch.configure do |c|
|
|
49
|
+
c.sample = { requests: 0.1, jobs: 1.0 }
|
|
50
|
+
c.user { |u| { id: u.id, name: u.name, email: u.email } }
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The same instrumentation runs in your test suite, so a spec can hold a
|
|
55
|
+
hot path to a query budget:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
expect { get "/widgets" }.to have_railwatch_queries(at_most: 6)
|
|
59
|
+
expect { get "/widgets" }.not_to have_railwatch_n_plus_one
|
|
60
|
+
```
|
|
41
61
|
|
|
42
62
|
## Documentation
|
|
43
63
|
|
|
@@ -67,156 +87,14 @@ prints ready-to-paste MCP client configuration.
|
|
|
67
87
|
AI coding agents working on an app that uses Railwatch: [`llms.txt`](llms.txt)
|
|
68
88
|
and [`AGENTS.md`](AGENTS.md).
|
|
69
89
|
|
|
70
|
-
Configuration lives in `config/initializers/railwatch.rb`; every option has
|
|
71
|
-
a `RAILWATCH_*` environment variable. Sampling is decided once per execution:
|
|
72
|
-
a sampled-in request ships its whole tree, a sampled-out one ships nothing
|
|
73
|
-
except unhandled exceptions.
|
|
74
|
-
|
|
75
|
-
```ruby
|
|
76
|
-
Railwatch.configure do |c|
|
|
77
|
-
c.sample = { requests: 0.1, jobs: 1.0 }
|
|
78
|
-
c.user { |u| { id: u.id, name: u.name, email: u.email } }
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
class ReportsController < ApplicationController
|
|
82
|
-
railwatch_sample 0.01, only: :index
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
Railwatch.ignore { ExpensiveSync.run }
|
|
86
|
-
Railwatch.context(tenant: org.slug, plan: org.plan)
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Time any block of your own code as a `span` on the surrounding request,
|
|
90
|
-
job, or command — the block's value is returned untouched:
|
|
91
|
-
|
|
92
|
-
```ruby
|
|
93
|
-
Railwatch.span("pdf.render", template: "invoice", pages: 12) { renderer.call }
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
When a span isn't enough to say where the time went, Railwatch can attach a
|
|
97
|
-
real stack profile to an execution. Add `gem "vernier"` (Ruby ≥ 3.2) or
|
|
98
|
-
`gem "stackprof"` to the Gemfile and set `c.profile_sample = 0.01` to
|
|
99
|
-
profile 1% of executions, or `c.profile_slow_ms = 500` alongside
|
|
100
|
-
`c.tail_sample_slow_ms` to profile the slow ones. The collapsed stacks
|
|
101
|
-
ship as their own `profile` record, gzipped, and the request or job it
|
|
102
|
-
belongs to is marked `profiled`. Off by default, and one Float comparison
|
|
103
|
-
per execution while it stays off.
|
|
104
|
-
|
|
105
|
-
Sampling can also be decided at the *end* of an execution instead of the
|
|
106
|
-
start: set `c.tail_sample_slow_ms = 500` (or call `Railwatch.keep!`) and a
|
|
107
|
-
head-sampled-out request that turns out to be slow, or to have raised,
|
|
108
|
-
ships its whole tree anyway. Outgoing HTTP carries a W3C `traceparent`,
|
|
109
|
-
and an inbound one is adopted, so a trace spans services.
|
|
110
|
-
|
|
111
|
-
Inertia apps get real page-visit timing by calling `startRailwatch()` from the
|
|
112
|
-
generated `app/frontend/lib/railwatch.ts`. Server-side rendering is timed
|
|
113
|
-
automatically wherever `inertia_rails` SSR is already enabled — no extra
|
|
114
|
-
configuration needed.
|
|
115
|
-
|
|
116
|
-
Outgoing HTTP made through Faraday is instrumented by adding
|
|
117
|
-
`Railwatch::Faraday` to the connection's middleware stack (`Net::HTTP` is
|
|
118
|
-
already covered globally, with no setup); any other client can be wrapped
|
|
119
|
-
with `Railwatch.instrument_outgoing`:
|
|
120
|
-
|
|
121
|
-
```ruby
|
|
122
|
-
Faraday.new(url) { |f| f.use Railwatch::Faraday }
|
|
123
|
-
Railwatch.instrument_outgoing(:get, url) { http_client.get(url) }
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
## Testing
|
|
127
|
-
|
|
128
|
-
Production browser errors can be resolved to original source files using
|
|
129
|
-
[private source-map uploads](docs/source-maps.md).
|
|
130
|
-
|
|
131
|
-
The same instrumentation runs in your test suite, so a spec can hold a hot
|
|
132
|
-
path to a query budget and CI can fail the pull request that regresses it:
|
|
133
|
-
|
|
134
|
-
```ruby
|
|
135
|
-
expect { get "/widgets" }.to have_railwatch_queries(at_most: 6)
|
|
136
|
-
expect { get "/widgets" }.not_to have_railwatch_n_plus_one
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
Failures list the offending SQL. Set-up, every matcher (RSpec and Minitest),
|
|
140
|
-
and a CI performance-gate recipe are in [`docs/testing.md`](docs/testing.md).
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
Every attribute, the full public facade, sampling, redaction/rejection,
|
|
145
|
-
transport/buffering behavior, the overhead gate, and the Kamal deploy hook
|
|
146
|
-
are documented field-by-field in [`docs/configuration.md`](docs/configuration.md).
|
|
147
|
-
Every record type Railwatch ships — `request`, `job_attempt`, `query`,
|
|
148
|
-
`exception`, and the rest — is documented field-by-field, sourced directly
|
|
149
|
-
from the code that builds it, in [`docs/records.md`](docs/records.md).
|
|
150
|
-
|
|
151
90
|
## Replacing Sentry
|
|
152
91
|
|
|
153
|
-
Railwatch subscribes to `Rails.error` on install
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
thread. Delivery is still asynchronous and memory-only, so a hard kill,
|
|
160
|
-
OOM, or process exit after the shutdown deadline can lose it.
|
|
161
|
-
|
|
162
|
-
What differs from a dedicated error tracker: exceptions aren't reported in
|
|
163
|
-
isolation — each one is linked (`execution_id`/`trace_id`) to the request,
|
|
164
|
-
job, or command it happened inside, alongside every query, cache read,
|
|
165
|
-
outgoing request, and log line from that same execution. There's no
|
|
166
|
-
separate error-tracking SDK/config to maintain — `severity`, `handled`,
|
|
167
|
-
and `context` all come from the same `Railwatch.configure` block and
|
|
168
|
-
`Railwatch.context` calls used for everything else the gem instruments.
|
|
169
|
-
|
|
170
|
-
### Coming from Sentry
|
|
171
|
-
|
|
172
|
-
| Sentry | Railwatch |
|
|
173
|
-
|---|---|
|
|
174
|
-
| `dsn:` | `RAILWATCH_TOKEN` (+ `RAILWATCH_INGEST_URL` for a self-hosted platform). |
|
|
175
|
-
| `environment:` | `config.environment` — defaults to `Rails.env`, set it only to report under a different name. |
|
|
176
|
-
| `release:` | `config.deploy` — auto-detected from the deploy platform or Git checkout. Stamped on every record. |
|
|
177
|
-
| `traces_sample_rate:` / `profiles_sample_rate:` | `config.sample`, a rate per execution kind (`requests`, `jobs`, `commands`, `scheduled_tasks`, `exceptions`), decided once per execution rather than per event. Per-route: `railwatch_sample 0.01, only: :index`. |
|
|
178
|
-
| `excluded_exceptions:` | `config.ignored_exceptions` — same default list, plus every named ancestor is matched, not just the exact class. |
|
|
179
|
-
| `before_send:` / `before_send_transaction:` | `Railwatch.before_ingest { \|batch\| ... }` for the whole outgoing batch; `Railwatch.redact_queries`/`redact_logs`/... to scrub one record type in place; `Railwatch.reject_queries`/`reject_logs`/... to drop records by predicate. |
|
|
180
|
-
| `fingerprint` / grouping rules | `Railwatch.fingerprint { \|error, default\| ... }` globally, `def railwatch_fingerprint` on your own error class, or `Railwatch.report(error, fingerprint: [...])` per call. The literal `:default` splices in the parts Railwatch would have hashed, like Sentry's `{{ default }}`. |
|
|
181
|
-
| `include_local_variables:` | `config.capture_exception_locals`. |
|
|
182
|
-
| `config.rails.active_job_report_on_retry_error` | `config.capture_job_retry_errors` (off by default). |
|
|
183
|
-
| `send_default_pii:` | Deliberately split: `config.capture_request_payload` for params, `config.capture_job_arguments` for job arguments, `config.capture_response_body_on_error` for what a failing upstream sent back, `config.redact_headers`/`redact_params` for what's scrubbed, and the `Railwatch.user { ... }` block for who. There is no single "send everything" switch. |
|
|
184
|
-
| Breadcrumbs | Not a separate concept — every query, cache read, outgoing request, log line, and view render is already a first-class record linked to its execution by `execution_id`/`trace_id`. The execution *is* the breadcrumb trail, and it's queryable. |
|
|
185
|
-
| `Sentry.capture_message` | `Railwatch.report(error, ...)` for an exception; plain `Rails.logger` for a message — log lines at or above `config.log_level` become `log` records automatically. |
|
|
186
|
-
| `Sentry.set_user` | `Railwatch.user { ... }` (a resolver block, evaluated per execution). |
|
|
187
|
-
| `Sentry.set_tags` / `set_context` / `set_extras` | `Railwatch.context(key: value)` — serialized onto the parent record and every exception. |
|
|
188
|
-
| `Sentry.with_child_span` | `Railwatch.span("name") { ... }`. |
|
|
189
|
-
| `Sentry.add_attachment` | `Railwatch.attach("payload.json", data)` — a String, `Pathname`, or IO, gzipped on the wire and capped at `config.max_attachment_bytes`. `exception:` files it against that error's issue, and `Railwatch.report(error, attachments: { "payload.json" => data })` captures and attaches in one call. |
|
|
190
|
-
| `Sentry.capture_check_in` (cron monitoring) | Automatic: Solid Queue recurring tasks become `scheduled_task` records with `task_key`, `schedule`, and `drift`. Nothing to instrument. |
|
|
191
|
-
| `config.rails.report_rescued_exceptions` | `config.capture_rescued_exceptions` (on by default). |
|
|
192
|
-
| Rack `X-Request-Start` queue time | Automatic: `queue_time` on every `request` record. |
|
|
193
|
-
| `auto_session_tracking:` (release health) | Automatic: `session` records from the browser client and the request middleware, keyed on `config.deploy` as the release. `config.track_sessions` turns both off. |
|
|
194
|
-
|
|
195
|
-
These mappings cover the Rails-server migration path. Browser Replay,
|
|
196
|
-
native/mobile SDKs, some direct worker and scheduler entry points, and
|
|
197
|
-
Sentry's broader managed integration catalog are not equivalent today.
|
|
198
|
-
Use the supported-workload matrix in
|
|
199
|
-
[`docs/replacing-sentry.md`](docs/replacing-sentry.md) before removing
|
|
200
|
-
Sentry from an application that depends on those capabilities.
|
|
201
|
-
|
|
202
|
-
`config.deploy` checks `RAILWATCH_DEPLOY`, `KAMAL_VERSION`, common Git and
|
|
203
|
-
platform environment variables, `REVISION`, then `.git/HEAD`. Full 40-character
|
|
204
|
-
SHAs are consistently shortened to 12 characters. Set
|
|
205
|
-
`RAILWATCH_DETECT_DEPLOY=false` (or `c.detect_deploy = false`) to keep only the
|
|
206
|
-
explicit `RAILWATCH_DEPLOY` and Kamal defaults.
|
|
207
|
-
|
|
208
|
-
To report an exception manually (the `Rails.error.report`-equivalent):
|
|
209
|
-
|
|
210
|
-
```ruby
|
|
211
|
-
Railwatch.report(error, handled: true, context: { order_id: order.id })
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
`severity` defaults to `:warning` when `handled: true`, `:error`
|
|
215
|
-
otherwise. See the `exception` section of
|
|
216
|
-
[`docs/records.md`](docs/records.md) for the full field list, and
|
|
217
|
-
[`docs/configuration.md`](docs/configuration.md) for redaction
|
|
218
|
-
(`Railwatch.redact_exceptions`), `capture_exception_source`, and
|
|
219
|
-
`Railwatch.on_unrecoverable` (Railwatch watching its own internal failures).
|
|
92
|
+
Railwatch subscribes to `Rails.error` on install, so existing
|
|
93
|
+
`Rails.error.report` and `Rails.error.handle` calls are captured with no
|
|
94
|
+
code changes. Each exception is linked to the request, job, or command
|
|
95
|
+
it happened inside. The option-by-option mapping and the
|
|
96
|
+
supported-workload matrix live in
|
|
97
|
+
[Replacing Sentry](docs/replacing-sentry.md).
|
|
220
98
|
|
|
221
99
|
## Development
|
|
222
100
|
|
data/docs/ai-and-mcp.md
CHANGED
|
@@ -23,10 +23,10 @@ prints every block below with your platform's host already filled in.
|
|
|
23
23
|
|
|
24
24
|
MCP tokens are **per person**, not per application: sign in to the platform,
|
|
25
25
|
go to **Settings → Profile → "API & MCP token"**, and press *Generate token*.
|
|
26
|
-
The token starts with `
|
|
26
|
+
The token starts with `rwp_` and is shown once. It can reach every account
|
|
27
27
|
your user belongs to, and nothing else.
|
|
28
28
|
|
|
29
|
-
This is a different token from the `
|
|
29
|
+
This is a different token from the `rw_...` ingest token the gem uses. The
|
|
30
30
|
ingest token writes telemetry for one environment; the MCP token reads it
|
|
31
31
|
back as you.
|
|
32
32
|
|
|
@@ -36,13 +36,13 @@ each with a copy button — so the fastest path is: generate, copy, paste.
|
|
|
36
36
|
## 2. Connect a client
|
|
37
37
|
|
|
38
38
|
Everything below uses `https://railwatch.rebulk.com/mcp`; substitute your own
|
|
39
|
-
host if you self-host, and `
|
|
39
|
+
host if you self-host, and `rwp_your_token_here` for the token.
|
|
40
40
|
|
|
41
41
|
### Claude Code
|
|
42
42
|
|
|
43
43
|
```sh
|
|
44
44
|
claude mcp add railwatch --transport http https://railwatch.rebulk.com/mcp \
|
|
45
|
-
--header "Authorization: Bearer
|
|
45
|
+
--header "Authorization: Bearer rwp_your_token_here"
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
### Claude Desktop
|
|
@@ -60,7 +60,7 @@ Claude Desktop speaks stdio, so it needs the `mcp-remote` bridge. In
|
|
|
60
60
|
"mcp-remote",
|
|
61
61
|
"https://railwatch.rebulk.com/mcp",
|
|
62
62
|
"--header",
|
|
63
|
-
"Authorization: Bearer
|
|
63
|
+
"Authorization: Bearer rwp_your_token_here"
|
|
64
64
|
]
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -76,7 +76,7 @@ Claude Desktop speaks stdio, so it needs the `mcp-remote` bridge. In
|
|
|
76
76
|
"mcpServers": {
|
|
77
77
|
"railwatch": {
|
|
78
78
|
"url": "https://railwatch.rebulk.com/mcp",
|
|
79
|
-
"headers": { "Authorization": "Bearer
|
|
79
|
+
"headers": { "Authorization": "Bearer rwp_your_token_here" }
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
}
|
|
@@ -92,7 +92,7 @@ Claude Desktop speaks stdio, so it needs the `mcp-remote` bridge. In
|
|
|
92
92
|
"railwatch": {
|
|
93
93
|
"type": "http",
|
|
94
94
|
"url": "https://railwatch.rebulk.com/mcp",
|
|
95
|
-
"headers": { "Authorization": "Bearer
|
|
95
|
+
"headers": { "Authorization": "Bearer rwp_your_token_here" }
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -113,7 +113,7 @@ Claude Desktop speaks stdio, so it needs the `mcp-remote` bridge. In
|
|
|
113
113
|
"mcp-remote",
|
|
114
114
|
"https://railwatch.rebulk.com/mcp",
|
|
115
115
|
"--header",
|
|
116
|
-
"Authorization: Bearer
|
|
116
|
+
"Authorization: Bearer rwp_your_token_here"
|
|
117
117
|
]
|
|
118
118
|
}
|
|
119
119
|
}
|
|
@@ -124,7 +124,7 @@ Claude Desktop speaks stdio, so it needs the `mcp-remote` bridge. In
|
|
|
124
124
|
|
|
125
125
|
```sh
|
|
126
126
|
curl -sS https://railwatch.rebulk.com/mcp \
|
|
127
|
-
-H "Authorization: Bearer
|
|
127
|
+
-H "Authorization: Bearer rwp_your_token_here" \
|
|
128
128
|
-H "Content-Type: application/json" \
|
|
129
129
|
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
|
|
130
130
|
```
|