railwatch 0.1.0 → 0.1.2
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 +29 -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 +306 -280
- data/docs/replacing-sentry.md +6 -6
- data/docs/source-maps.md +1 -1
- data/docs/testing.md +18 -21
- data/docs/troubleshooting.md +126 -87
- data/lib/railwatch/minitest.rb +4 -3
- data/lib/railwatch/secret_safety.rb +3 -1
- data/lib/railwatch/subscribers/exceptions.rb +20 -0
- data/lib/railwatch/subscribers/process_info.rb +5 -2
- data/lib/railwatch/version.rb +1 -1
- data/lib/tasks/railwatch_tasks.rake +4 -4
- metadata +2 -2
data/docs/records.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# Record types
|
|
2
2
|
|
|
3
|
-
Every record Railwatch ships is a flat hash
|
|
4
|
-
lists all
|
|
5
|
-
builds each one. Field names below are the hash keys as sent over
|
|
6
|
-
wire
|
|
3
|
+
Every record Railwatch ships is a flat hash. See `lib/railwatch/record.rb`.
|
|
4
|
+
This lists all 27, field by field, sourced from the subscriber or patch
|
|
5
|
+
that builds each one. Field names below are the hash keys as sent over
|
|
6
|
+
the wire: symbols in Ruby, strings in the gzip NDJSON payload.
|
|
7
7
|
|
|
8
8
|
## Shared envelope
|
|
9
9
|
|
|
10
|
-
Every record carries these
|
|
10
|
+
Every record carries these fields, from `Record.build` in
|
|
11
|
+
`lib/railwatch/record.rb`:
|
|
11
12
|
|
|
12
13
|
| Field | Meaning |
|
|
13
14
|
|---|---|
|
|
@@ -21,15 +22,16 @@ Every record carries these (`Record.build`, `lib/railwatch/record.rb`):
|
|
|
21
22
|
Each gzip NDJSON batch also has a small HTTP-header envelope. Drop accounting
|
|
22
23
|
rides as `X-Railwatch-Dropped` and `X-Railwatch-Dropped-Bytes` when non-zero.
|
|
23
24
|
`X-Railwatch-Backpressure-Factor` is present when adaptive backpressure has
|
|
24
|
-
reduced sampling
|
|
25
|
+
reduced sampling. For example, `4.0` means each configured execution sample
|
|
25
26
|
rate was divided by four when the batch was sent. The reporter doubles the
|
|
26
27
|
factor on each pressured tick up to 8, then halves it toward 1 as pressure
|
|
27
28
|
clears. This makes buffer loss visible alongside the sampling response that
|
|
28
29
|
was active at delivery time.
|
|
29
30
|
|
|
30
|
-
Records created inside an execution
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
Records created inside an execution also merge in the execution's
|
|
32
|
+
envelope. That is everything except the five parent types, plus
|
|
33
|
+
`user`/`process`/`visit`, which stand alone. The envelope comes from
|
|
34
|
+
`Execution#envelope` in `lib/railwatch/execution.rb`:
|
|
33
35
|
|
|
34
36
|
| Field | Meaning |
|
|
35
37
|
|---|---|
|
|
@@ -44,10 +46,11 @@ execution's envelope (`Execution#envelope`, `lib/railwatch/execution.rb`):
|
|
|
44
46
|
|
|
45
47
|
## Parent records
|
|
46
48
|
|
|
47
|
-
`request`, `job_attempt`, `scheduled_task`,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
`
|
|
49
|
+
`request`, `job_attempt`, `scheduled_task`, `command`, and
|
|
50
|
+
`channel_action` are the five parent types, listed in
|
|
51
|
+
`Railwatch::PARENT_TYPES`. Each opens an
|
|
52
|
+
`Execution`. In addition to its own fields below, each always carries
|
|
53
|
+
these fields, from `Railwatch.build_parent` in `lib/railwatch.rb`:
|
|
51
54
|
|
|
52
55
|
| Field | Meaning |
|
|
53
56
|
|---|---|
|
|
@@ -61,16 +64,17 @@ addition to its own fields below, always carries (`Railwatch.build_parent`,
|
|
|
61
64
|
| `context` | Serialized `Railwatch.context(...)` key/values active for this execution, parameter-filtered like request params. `"_railwatch_truncated": true` when it did not fit in 64KB. |
|
|
62
65
|
|
|
63
66
|
A sampled-out execution still ships its parent record if it raised an
|
|
64
|
-
unhandled exception
|
|
67
|
+
unhandled exception. See `Railwatch.finish_execution`. Sampling controls
|
|
65
68
|
whether child records ship, not whether an error is visible.
|
|
66
69
|
|
|
67
70
|
### `request`
|
|
68
71
|
|
|
69
|
-
Built by the outermost Rack middleware
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
Built by the outermost Rack middleware in
|
|
73
|
+
`lib/railwatch/middleware/request.rb`. That middleware also owns the
|
|
74
|
+
`middleware_before`/`action`/`render`/`middleware_after` stage
|
|
75
|
+
boundaries. The `action`/`render` boundaries come from
|
|
72
76
|
`start_processing.action_controller` and `render_template.action_view` in
|
|
73
|
-
`lib/railwatch/subscribers/requests.rb
|
|
77
|
+
`lib/railwatch/subscribers/requests.rb`.
|
|
74
78
|
|
|
75
79
|
| Field | Meaning |
|
|
76
80
|
|---|---|
|
|
@@ -102,9 +106,10 @@ stage boundaries (the `action`/`render` boundaries come from
|
|
|
102
106
|
|
|
103
107
|
### `job_attempt`
|
|
104
108
|
|
|
105
|
-
One per Active Job `perform
|
|
106
|
-
`lib/railwatch/subscribers/jobs.rb
|
|
107
|
-
scheduler didn't originate
|
|
109
|
+
One per Active Job `perform`, from the `perform.active_job` event in
|
|
110
|
+
`lib/railwatch/subscribers/jobs.rb`. Covers jobs Solid Queue's own
|
|
111
|
+
recurring scheduler didn't originate. See `scheduled_task` below for the
|
|
112
|
+
ones it did.
|
|
108
113
|
|
|
109
114
|
| Field | Meaning |
|
|
110
115
|
|---|---|
|
|
@@ -126,57 +131,58 @@ scheduler didn't originate (see `scheduled_task` below for the ones it did).
|
|
|
126
131
|
| `arguments_truncated` | `true` when trailing arguments had to be dropped to fit `arguments` into 8 KiB of JSON. Absent otherwise, and absent entirely when `capture_job_arguments` is off. |
|
|
127
132
|
| `profiled` | `true` when a `profile` record shipped for this attempt; absent otherwise. |
|
|
128
133
|
|
|
129
|
-
`user` and `tenant` on a job attempt
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
`JobTracing#serialize` puts the enqueuing execution's
|
|
133
|
-
tenant into the Active Job payload as
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
worth
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
`user` and `tenant` on a job attempt come from the execution that
|
|
135
|
+
enqueued the job, not from the worker process. The worker usually has no
|
|
136
|
+
signed-in user to resolve. The same applies to every child record under
|
|
137
|
+
the attempt. `JobTracing#serialize` puts the enqueuing execution's
|
|
138
|
+
resolved user id and tenant into the Active Job payload as
|
|
139
|
+
`railwatch_user`/`railwatch_tenant`, alongside
|
|
140
|
+
`railwatch_trace_id`/`railwatch_parent_id`. `perform_start` restores them
|
|
141
|
+
onto the job's execution before its first record is built. Details worth
|
|
142
|
+
knowing:
|
|
143
|
+
|
|
144
|
+
- **Identifiers only.** Two strings: the same tenant-prefixed id the
|
|
139
145
|
`user` record carries, and the tenant name. No user or tenant model is
|
|
140
146
|
serialized, hydrated, or looked up, on either side. When the enqueuing
|
|
141
|
-
request had not bound its tenant yet, the raw id travels
|
|
142
|
-
qualifies it with the propagated tenant on restore.
|
|
147
|
+
request had not bound its tenant yet, the raw id travels. The worker
|
|
148
|
+
then qualifies it with the propagated tenant on restore.
|
|
143
149
|
- **Jobs enqueuing jobs.** A job serializes the values it was given, so a
|
|
144
150
|
chain of jobs keeps the identity of the request that started it.
|
|
145
151
|
- **Retries and scheduled jobs.** A retry re-enqueues the same job object,
|
|
146
152
|
and Active Job re-serializes it, so every attempt keeps the original
|
|
147
153
|
identity. `perform_later(wait:)`/`set(wait_until:)` serialize at enqueue
|
|
148
|
-
time like any other job
|
|
154
|
+
time like any other job. A job scheduled for next week is attributed to
|
|
149
155
|
whoever scheduled it. Solid Queue's recurring scheduler enqueues nothing
|
|
150
156
|
on anyone's behalf, so a `scheduled_task` has no propagated user and
|
|
151
|
-
falls back to local resolution
|
|
157
|
+
falls back to local resolution, normally nil.
|
|
152
158
|
- **Nothing to propagate.** The keys are omitted from the payload when
|
|
153
|
-
there is no user or tenant
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
locally too.
|
|
159
|
+
there is no user or tenant. A payload without them deserializes to nil
|
|
160
|
+
and falls back to `Users.resolve_from_current`, exactly as before. That
|
|
161
|
+
covers a job enqueued by an older version of the gem, still sitting in
|
|
162
|
+
a queue through a deploy. Inline `perform_now` never serializes, so it
|
|
163
|
+
resolves locally too.
|
|
158
164
|
- **A propagated user does not emit a `user` record.** The worker skips
|
|
159
165
|
local resolution, and it is resolution that emits the name/email record.
|
|
160
166
|
The enqueuing process already emitted it for that id.
|
|
161
167
|
- **Cardinality.** The user id is one more high-cardinality dimension on
|
|
162
168
|
every job record. Apps that do not want a user attached to jobs at all
|
|
163
|
-
can return nil from `config.user` for the cases they care about
|
|
169
|
+
can return nil from `config.user` for the cases they care about. The
|
|
164
170
|
propagation only ever carries what that resolver already produced.
|
|
165
171
|
|
|
166
|
-
Also has a special case with no `Execution`: **Solid Queue pruned jobs
|
|
167
|
-
|
|
168
|
-
their worker was killed or reaped. Each gets
|
|
169
|
-
and reports `job_attempt` with `job_id: nil`,
|
|
170
|
-
`status: "failed"`, `duration: 0`, and
|
|
171
|
-
pruning error, truncated to 255 chars.
|
|
172
|
+
Also has a special case with no `Execution`: **Solid Queue pruned jobs**,
|
|
173
|
+
from `fail_many_claimed.solid_queue`. These never reach
|
|
174
|
+
`perform.active_job` because their worker was killed or reaped. Each gets
|
|
175
|
+
its own throwaway execution and reports `job_attempt` with `job_id: nil`,
|
|
176
|
+
`name: "(pruned)"`, `status: "failed"`, `duration: 0`, and
|
|
177
|
+
`exception_preview` set to the pruning error, truncated to 255 chars.
|
|
172
178
|
|
|
173
179
|
### `scheduled_task`
|
|
174
180
|
|
|
175
181
|
Same `perform.active_job` subscriber as `job_attempt`, but for a job
|
|
176
182
|
Solid Queue's `RecurringExecution` table shows was triggered by
|
|
177
|
-
`config/recurring.yml` rather than an ad hoc enqueue
|
|
178
|
-
`lib/railwatch/subscribers/jobs.rb
|
|
179
|
-
above, plus:
|
|
183
|
+
`config/recurring.yml` rather than an ad hoc enqueue. See
|
|
184
|
+
`recurring_task_key` in `lib/railwatch/subscribers/jobs.rb`. Carries
|
|
185
|
+
every `job_attempt` field above, plus:
|
|
180
186
|
|
|
181
187
|
| Field | Meaning |
|
|
182
188
|
|---|---|
|
|
@@ -187,10 +193,10 @@ above, plus:
|
|
|
187
193
|
|
|
188
194
|
### `command`
|
|
189
195
|
|
|
190
|
-
One per top-level `bin/rails runner` invocation or Rake task invocation
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
vs `#execute
|
|
196
|
+
One per top-level `bin/rails runner` invocation or Rake task invocation.
|
|
197
|
+
Prerequisites nest inside the same command instead of opening their own.
|
|
198
|
+
See `lib/railwatch/patches/rake_task.rb`'s comment on `Rake::Task#invoke`
|
|
199
|
+
vs `#execute`. `db:migrate` and other tasks in
|
|
194
200
|
`Configuration::DEFAULT_VENDOR_COMMANDS` are skipped unless
|
|
195
201
|
`config.capture_default_vendor_commands` is on.
|
|
196
202
|
|
|
@@ -206,10 +212,11 @@ vs `#execute`). `db:migrate` and other tasks in
|
|
|
206
212
|
### `channel_action`
|
|
207
213
|
|
|
208
214
|
One parent per Action Cable channel action. Railwatch opens it before
|
|
209
|
-
`perform_action.action_cable` invokes application code and closes it
|
|
210
|
-
action returns or raises
|
|
211
|
-
exceptions inside share one trace
|
|
212
|
-
and no Rack middleware around it, so without this
|
|
215
|
+
`perform_action.action_cable` invokes application code and closes it
|
|
216
|
+
after the action returns or raises. The SQL, logs, broadcasts, transmits,
|
|
217
|
+
and exceptions inside therefore share one trace. An Action Cable action
|
|
218
|
+
has no HTTP request and no Rack middleware around it, so without this
|
|
219
|
+
they had no parent at all.
|
|
213
220
|
|
|
214
221
|
| Field | Meaning |
|
|
215
222
|
|---|---|
|
|
@@ -228,9 +235,10 @@ with this parent even when the channel sample rate is zero.
|
|
|
228
235
|
### `query`
|
|
229
236
|
|
|
230
237
|
Every non-cached `sql.active_record` notification except `SCHEMA`,
|
|
231
|
-
`TRANSACTION`, and `EXPLAIN` statements
|
|
232
|
-
The hottest record type in the
|
|
233
|
-
going through
|
|
238
|
+
`TRANSACTION`, and `EXPLAIN` statements. See
|
|
239
|
+
`lib/railwatch/subscribers/queries.rb`. The hottest record type in the
|
|
240
|
+
gem. It is built as one hash literal rather than going through
|
|
241
|
+
`Railwatch.record`.
|
|
234
242
|
|
|
235
243
|
| Field | Meaning |
|
|
236
244
|
|---|---|
|
|
@@ -249,14 +257,15 @@ going through `Railwatch.record`.
|
|
|
249
257
|
| `allocations` | Ruby object allocations for this query (`event.allocations`). |
|
|
250
258
|
| `explain` | The adapter's own query plan (Postgres `EXPLAIN`, SQLite `EXPLAIN QUERY PLAN`, ...), truncated to 4000 chars, or nil. Only when `config.capture_query_explain` is on (its own privacy decision — the plan is produced from the raw statement and can echo literal predicates even though `sql` above is normalized), the statement is a `SELECT`, and it took at least `config.explain_threshold_ms`; then at most once per query shape per process per 10 minutes. The EXPLAIN runs on the same connection the query used, with Railwatch paused, so it never becomes a `query` record of its own. |
|
|
251
259
|
|
|
252
|
-
A cached query
|
|
253
|
-
`cached_queries` counter
|
|
260
|
+
A cached query, where `payload[:cached]` is set, only increments the
|
|
261
|
+
execution's `cached_queries` counter. It never becomes a `query` record.
|
|
254
262
|
|
|
255
263
|
### `n_plus_one`
|
|
256
264
|
|
|
257
265
|
Fired once per query group when its count within the current execution
|
|
258
|
-
crosses `config.n_plus_one_threshold
|
|
259
|
-
|
|
266
|
+
crosses `config.n_plus_one_threshold`. See
|
|
267
|
+
`lib/railwatch/subscribers/queries.rb`. Not on every repeat, just the
|
|
268
|
+
crossing.
|
|
260
269
|
|
|
261
270
|
| Field | Meaning |
|
|
262
271
|
|---|---|
|
|
@@ -267,7 +276,8 @@ crosses `config.n_plus_one_threshold` (`lib/railwatch/subscribers/queries.rb`)
|
|
|
267
276
|
|
|
268
277
|
### `transaction`
|
|
269
278
|
|
|
270
|
-
One per `transaction.active_record
|
|
279
|
+
One per `transaction.active_record`. See
|
|
280
|
+
`lib/railwatch/subscribers/queries.rb`.
|
|
271
281
|
|
|
272
282
|
| Field | Meaning |
|
|
273
283
|
|---|---|
|
|
@@ -279,20 +289,21 @@ One per `transaction.active_record` (`lib/railwatch/subscribers/queries.rb`).
|
|
|
279
289
|
|
|
280
290
|
### `exception`
|
|
281
291
|
|
|
282
|
-
Every error that reaches `Rails.error
|
|
283
|
-
the request middleware or command patches catch directly,
|
|
284
|
-
controller swallows with `rescue_from
|
|
285
|
-
|
|
286
|
-
even with no execution open
|
|
287
|
-
object, execution, and handled/unhandled disposition
|
|
288
|
-
outer middleware report a re-raised error only once
|
|
289
|
-
same object
|
|
290
|
-
sampling or `Railwatch.pause` does not mark the object as
|
|
291
|
-
exceptions bypass the execution buffer
|
|
292
|
-
record and wakes the in-memory
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
292
|
+
Every error that reaches `Rails.error`, handled or not. Also anything
|
|
293
|
+
the request middleware or command patches catch directly, and anything a
|
|
294
|
+
controller swallows with `rescue_from`. See
|
|
295
|
+
`lib/railwatch/subscribers/exceptions.rb`. Standalone-capable: reports
|
|
296
|
+
even with no execution open, such as a console or boot. Deduplicated per
|
|
297
|
+
error object, execution, and handled/unhandled disposition. Rails.error
|
|
298
|
+
plus outer middleware therefore report a re-raised error only once, while
|
|
299
|
+
the same object reused in another execution is not suppressed. A capture
|
|
300
|
+
discarded by sampling or `Railwatch.pause` does not mark the object as
|
|
301
|
+
seen. Unhandled exceptions bypass the execution buffer.
|
|
302
|
+
`Railwatch.record_now` enqueues the record and wakes the in-memory
|
|
303
|
+
reporter immediately, without network I/O on the application thread. This
|
|
304
|
+
improves the chance of delivery before a normal exit but is not a durable
|
|
305
|
+
crash spool. A hard kill, OOM, or exit after the shutdown deadline can
|
|
306
|
+
lose the record.
|
|
296
307
|
|
|
297
308
|
| Field | Meaning |
|
|
298
309
|
|---|---|
|
|
@@ -312,61 +323,62 @@ shutdown deadline can lose the record.
|
|
|
312
323
|
| `sql_state` | Postgres SQLSTATE, for `ActiveRecord::StatementInvalid` wrapping a driver error that exposes one (not populated for SQLite). |
|
|
313
324
|
| `ruby_version` / `rails_version` | Process versions. |
|
|
314
325
|
|
|
315
|
-
A handled exception on a sampled-out execution is dropped entirely
|
|
316
|
-
|
|
317
|
-
its own `exceptions` sample rate rolled once per execution
|
|
318
|
-
|
|
326
|
+
A handled exception on a sampled-out execution is dropped entirely,
|
|
327
|
+
matching everything else. An *unhandled* one still ships, governed by
|
|
328
|
+
its own `exceptions` sample rate rolled once per execution. See
|
|
329
|
+
`exception_sampled?`.
|
|
319
330
|
|
|
320
331
|
The default fingerprint normalizes the message before hashing it, so one
|
|
321
|
-
issue doesn't shatter into thousands
|
|
332
|
+
issue doesn't shatter into thousands. URLs, email addresses, UUIDs, ISO
|
|
322
333
|
timestamps, IPv4 addresses, quoted strings, hex runs of six characters or
|
|
323
|
-
more, and plain integers all become
|
|
334
|
+
more, and plain integers all become `?`. Whitespace collapses, and the
|
|
324
335
|
result is cut at 200 chars. For classes whose message is mostly the data
|
|
325
|
-
that varied, only the message *prefix* is kept
|
|
326
|
-
`ActiveRecord::RecordNotFound`, `ActiveRecord::RecordInvalid`,
|
|
327
|
-
`ArgumentError`, and `TypeError`, up to the first `for `
|
|
328
|
-
`NoMethodError` and `NameError
|
|
329
|
-
not found: :user_id` are one issue rather than two. Override any of
|
|
330
|
-
with `Railwatch.fingerprint`, `#railwatch_fingerprint`, or
|
|
331
|
-
`Railwatch.report(error, fingerprint: [...])
|
|
336
|
+
that varied, only the message *prefix* is kept. The prefix runs up to the
|
|
337
|
+
first `:` for `ActiveRecord::RecordNotFound`, `ActiveRecord::RecordInvalid`,
|
|
338
|
+
`KeyError`, `ArgumentError`, and `TypeError`, and up to the first `for `
|
|
339
|
+
for `NoMethodError` and `NameError`. So `key not found: :order_id` and
|
|
340
|
+
`key not found: :user_id` are one issue rather than two. Override any of
|
|
341
|
+
it with `Railwatch.fingerprint`, `#railwatch_fingerprint`, or
|
|
342
|
+
`Railwatch.report(error, fingerprint: [...])`. See
|
|
332
343
|
[`docs/configuration.md`](configuration.md).
|
|
333
344
|
|
|
334
|
-
An error whose class
|
|
345
|
+
An error whose class, or any named ancestor of it, appears in
|
|
335
346
|
`config.ignored_exceptions` is never captured at all, handled or not.
|
|
336
347
|
An error a controller rescues with `rescue_from` is captured as
|
|
337
348
|
`handled: true`, `severity: "warning"`, `source:
|
|
338
|
-
"action_controller.rescue_from"
|
|
339
|
-
`rescue_from_callback.action_controller` notification
|
|
340
|
-
`config.capture_rescued_exceptions = false` to turn
|
|
341
|
-
equivalents
|
|
342
|
-
the `retry_stopped`/`discard` subscriptions in
|
|
349
|
+
"action_controller.rescue_from"`. That comes from Rails'
|
|
350
|
+
`rescue_from_callback.action_controller` notification. Set
|
|
351
|
+
`config.capture_rescued_exceptions = false` to turn it off. Active Job's
|
|
352
|
+
equivalents, `retry_on` exhausted and `discard_on`, are already covered
|
|
353
|
+
by the `retry_stopped`/`discard` subscriptions in
|
|
343
354
|
`lib/railwatch/subscribers/jobs.rb`. A retry that has not exhausted its
|
|
344
|
-
attempts is logged but is not an exception by default
|
|
355
|
+
attempts is logged but is not an exception by default. Set
|
|
345
356
|
`config.capture_job_retry_errors = true` to capture it as handled with
|
|
346
|
-
severity `warning` and source `application.active_job.enqueue_retry`. This
|
|
347
|
-
off by default because retries are usually expected and can flood the
|
|
348
|
-
list. See
|
|
349
|
-
|
|
357
|
+
severity `warning` and source `application.active_job.enqueue_retry`. This
|
|
358
|
+
is off by default because retries are usually expected and can flood the
|
|
359
|
+
issues list. See [`docs/configuration.md`](configuration.md) for both
|
|
360
|
+
settings.
|
|
350
361
|
|
|
351
362
|
#### Browser errors (`source: "browser"`)
|
|
352
363
|
|
|
353
|
-
Every JavaScript error the browser client catches
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
`handled: false`, `severity: "error"`,
|
|
363
|
-
error's `name`, `message` truncated to 1024
|
|
364
|
-
envelope every other record does, including
|
|
365
|
-
regresses with a release exactly like a Ruby
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
364
|
+
Every JavaScript error the browser client catches arrives on the same
|
|
365
|
+
beacon as visits, `POST /railwatch/beacon`, 50 errors per beacon at most.
|
|
366
|
+
That covers `window.onerror`, unhandled promise rejections, Inertia's
|
|
367
|
+
failed-request events, and anything the app reports itself with
|
|
368
|
+
`reportError`. The Inertia events are `exception` and `invalid` on
|
|
369
|
+
Inertia 2, `networkError` and `httpException` on 3. A dropped connection
|
|
370
|
+
is reported only while the user is waiting on a visit that shows the
|
|
371
|
+
progress bar or loads deferred props, not for a background poll,
|
|
372
|
+
`router.reload`, or prefetch. Each error is recorded as an ordinary
|
|
373
|
+
`exception`: `source: "browser"`, `handled: false`, `severity: "error"`,
|
|
374
|
+
`class` set to the JavaScript error's `name`, `message` truncated to 1024
|
|
375
|
+
chars. It carries the same envelope every other record does, including
|
|
376
|
+
`deploy`, so a browser issue regresses with a release exactly like a Ruby
|
|
377
|
+
one.
|
|
378
|
+
|
|
379
|
+
The browser's stack, 8192 chars at most, is parsed into the same frame
|
|
380
|
+
shape a Ruby backtrace produces. V8's `at fn (url:line:col)` and
|
|
381
|
+
SpiderMonkey/JavaScriptCore's `fn@url:line:col` are both understood. A
|
|
370
382
|
line with no location on it is dropped:
|
|
371
383
|
|
|
372
384
|
| Frame key | Meaning |
|
|
@@ -377,25 +389,25 @@ line with no location on it is dropped:
|
|
|
377
389
|
| `in_app` | True when the script came from the app's own origin and is not under `node_modules/` or `vendor/`. |
|
|
378
390
|
|
|
379
391
|
No source snippets: the file is on the client, not on the server. Frames
|
|
380
|
-
are fingerprinted exactly like Ruby ones
|
|
381
|
-
the normalized message
|
|
392
|
+
are fingerprinted exactly like Ruby ones: class, top in-app frame, and
|
|
393
|
+
the normalized message. So browser errors group, split, merge, resolve,
|
|
382
394
|
and regress through the same Issue machinery.
|
|
383
395
|
|
|
384
396
|
`context` carries a `browser` key with the page `url`, the Inertia
|
|
385
|
-
`component`, the `visit` the error happened in
|
|
386
|
-
`session` id, the `user_agent`, and up to 20 `breadcrumbs
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
`reportError(error, context)` is
|
|
390
|
-
strings, 20 keys at most.
|
|
397
|
+
`component`, the `visit` the error happened in if any, the tab's
|
|
398
|
+
`session` id, the `user_agent`, and up to 20 `breadcrumbs`. Each
|
|
399
|
+
breadcrumb is `{at, kind, text}`, with `kind` being `console`, `click`,
|
|
400
|
+
or `navigate`. The breadcrumbs are the trail the client recorded before
|
|
401
|
+
the crash. Anything the app passed as `reportError(error, context)` is
|
|
402
|
+
merged in alongside it, flattened to strings, 20 keys at most.
|
|
391
403
|
|
|
392
404
|
### `cache_event`
|
|
393
405
|
|
|
394
406
|
Every `cache_*.active_support` notification except the inner read inside
|
|
395
|
-
a `fetch
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
`config.ignored_cache_key_prefixes` are always skipped.
|
|
407
|
+
a `fetch`. See `lib/railwatch/subscribers/cache.rb`. Vendor cache key
|
|
408
|
+
prefixes are skipped unless `config.capture_default_vendor_cache_keys` is
|
|
409
|
+
on. By default those are rack-attack, flipper, and solid_cable. Keys
|
|
410
|
+
matching `config.ignored_cache_key_prefixes` are always skipped.
|
|
399
411
|
|
|
400
412
|
| Field | Meaning |
|
|
401
413
|
|---|---|
|
|
@@ -409,7 +421,7 @@ a `fetch` (`lib/railwatch/subscribers/cache.rb`). Vendor cache key prefixes
|
|
|
409
421
|
|
|
410
422
|
### `mail`
|
|
411
423
|
|
|
412
|
-
`deliver.action_mailer
|
|
424
|
+
`deliver.action_mailer`. See `lib/railwatch/subscribers/mail.rb`.
|
|
413
425
|
|
|
414
426
|
| Field | Meaning |
|
|
415
427
|
|---|---|
|
|
@@ -424,14 +436,15 @@ a `fetch` (`lib/railwatch/subscribers/cache.rb`). Vendor cache key prefixes
|
|
|
424
436
|
| `failed` | Whether an exception occurred during delivery. |
|
|
425
437
|
| `message_id` | Truncated to 255 chars. |
|
|
426
438
|
|
|
427
|
-
A mailer's own template render is a separate `view_render` record
|
|
428
|
-
|
|
439
|
+
A mailer's own template render is a separate `view_render` record via
|
|
440
|
+
`process.action_mailer`, `kind: "mailer"`. See below.
|
|
429
441
|
|
|
430
442
|
### `broadcast`
|
|
431
443
|
|
|
432
|
-
Action Cable broadcast/transmit/perform
|
|
433
|
-
and `inertia_cable
|
|
434
|
-
|
|
444
|
+
Action Cable broadcast/transmit/perform. This also covers Turbo Streams
|
|
445
|
+
and `inertia_cable`, since both go through `broadcast.action_cable`. See
|
|
446
|
+
`lib/railwatch/subscribers/broadcasts.rb`. Three sub-shapes share the
|
|
447
|
+
type:
|
|
435
448
|
|
|
436
449
|
| Field | Present for | Meaning |
|
|
437
450
|
|---|---|---|
|
|
@@ -448,10 +461,11 @@ and `inertia_cable` since both go through `broadcast.action_cable`
|
|
|
448
461
|
|
|
449
462
|
### `notification`
|
|
450
463
|
|
|
451
|
-
Noticed gem deliveries only
|
|
452
|
-
|
|
453
|
-
subscriber uses, filtered to
|
|
454
|
-
No-ops entirely if the
|
|
464
|
+
Noticed gem deliveries only. See
|
|
465
|
+
`lib/railwatch/subscribers/notifications.rb`. Tagged by hooking the same
|
|
466
|
+
`perform.active_job` event the `job_attempt` subscriber uses, filtered to
|
|
467
|
+
jobs whose class starts with `Noticed::`. No-ops entirely if the
|
|
468
|
+
`noticed` gem isn't loaded.
|
|
455
469
|
|
|
456
470
|
| Field | Meaning |
|
|
457
471
|
|---|---|
|
|
@@ -464,14 +478,15 @@ No-ops entirely if the `noticed` gem isn't loaded.
|
|
|
464
478
|
|
|
465
479
|
### `outgoing_request`
|
|
466
480
|
|
|
467
|
-
Any `Net::HTTP#request` call
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
Requests to Railwatch's own ingest
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
thread-local reentry flag, so it's
|
|
481
|
+
Any `Net::HTTP#request` call, via `lib/railwatch/patches/net_http.rb`.
|
|
482
|
+
That covers Faraday's default adapter, HTTParty, RestClient, and most of
|
|
483
|
+
the HTTP ecosystem. Also Faraday connections that explicitly add
|
|
484
|
+
`Railwatch::Faraday` middleware from `lib/railwatch/faraday.rb`, for apps
|
|
485
|
+
using a non-default Faraday adapter. Requests to Railwatch's own ingest
|
|
486
|
+
URL are always skipped so shipping telemetry never generates telemetry
|
|
487
|
+
about itself. A Faraday connection using the default Net::HTTP adapter
|
|
488
|
+
defers to the Net::HTTP patch via a thread-local reentry flag, so it's
|
|
489
|
+
never double-recorded.
|
|
475
490
|
|
|
476
491
|
| Field | Meaning |
|
|
477
492
|
|---|---|
|
|
@@ -489,10 +504,10 @@ thread-local reentry flag, so it's never double-recorded.
|
|
|
489
504
|
|
|
490
505
|
### `storage_op`
|
|
491
506
|
|
|
492
|
-
Every Active Storage service operation
|
|
493
|
-
|
|
494
|
-
download, delete, delete_prefixed, exist, url,
|
|
495
|
-
transform, preview.
|
|
507
|
+
Every Active Storage service operation. See
|
|
508
|
+
`lib/railwatch/subscribers/storage.rb`. The operations are upload,
|
|
509
|
+
download, streaming download, delete, delete_prefixed, exist, url,
|
|
510
|
+
update_metadata, analyze, transform, preview.
|
|
496
511
|
|
|
497
512
|
| Field | Meaning |
|
|
498
513
|
|---|---|
|
|
@@ -505,11 +520,11 @@ transform, preview.
|
|
|
505
520
|
|
|
506
521
|
### `view_render`
|
|
507
522
|
|
|
508
|
-
Template, partial, layout, and collection renders
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
"mailer"
|
|
512
|
-
execution are stored as records
|
|
523
|
+
Template, partial, layout, and collection renders, from
|
|
524
|
+
`lib/railwatch/subscribers/views.rb`. Plus mailer template renders, from
|
|
525
|
+
`process.action_mailer` in `lib/railwatch/subscribers/mail.rb`, with
|
|
526
|
+
`kind: "mailer"`. Only the first `config.max_view_renders_per_execution`
|
|
527
|
+
per execution are stored as records. All are still counted toward the
|
|
513
528
|
parent's `view_renders` counter regardless of the cap.
|
|
514
529
|
|
|
515
530
|
| Field | Meaning |
|
|
@@ -524,11 +539,12 @@ parent's `view_renders` counter regardless of the cap.
|
|
|
524
539
|
|
|
525
540
|
### `span`
|
|
526
541
|
|
|
527
|
-
Custom timing around any block of app code
|
|
528
|
-
|
|
529
|
-
the block's value untouched
|
|
530
|
-
|
|
531
|
-
|
|
542
|
+
Custom timing around any block of app code:
|
|
543
|
+
`Railwatch.span(name, **attributes) { ... }`, in `lib/railwatch.rb`.
|
|
544
|
+
Returns the block's value untouched. It is a no-op wrapper when Railwatch
|
|
545
|
+
is disabled, nothing is executing, or the execution isn't recording. It
|
|
546
|
+
still yields in that case. Every span also increments the parent's
|
|
547
|
+
`spans` counter.
|
|
532
548
|
|
|
533
549
|
```ruby
|
|
534
550
|
Railwatch.span("pdf.render", template: "invoice", pages: 12) { renderer.call }
|
|
@@ -544,11 +560,11 @@ Railwatch.span("pdf.render", template: "invoice", pages: 12) { renderer.call }
|
|
|
544
560
|
|
|
545
561
|
### `attachment`
|
|
546
562
|
|
|
547
|
-
An arbitrary blob filed against an execution and, optionally, an
|
|
548
|
-
|
|
549
|
-
`lib/railwatch/attachments.rb
|
|
550
|
-
rendered PDF, the webhook body a customer swears they sent.
|
|
551
|
-
`Sentry.add_attachment` equivalent.
|
|
563
|
+
An arbitrary blob filed against an execution and, optionally, an
|
|
564
|
+
exception: `Railwatch.attach(name, data, content_type:, exception:)`, in
|
|
565
|
+
`lib/railwatch/attachments.rb`. For example the payload that failed to
|
|
566
|
+
parse, a rendered PDF, or the webhook body a customer swears they sent.
|
|
567
|
+
Sentry's `Sentry.add_attachment` equivalent.
|
|
552
568
|
|
|
553
569
|
```ruby
|
|
554
570
|
Railwatch.attach("payload.json", request.raw_post)
|
|
@@ -557,12 +573,13 @@ Railwatch.attach("payload.json", body, exception: error)
|
|
|
557
573
|
Railwatch.report(error, attachments: { "payload.json" => body })
|
|
558
574
|
```
|
|
559
575
|
|
|
560
|
-
`data` may be a String
|
|
561
|
-
read
|
|
562
|
-
|
|
563
|
-
child of it
|
|
564
|
-
outside any request
|
|
565
|
-
when Railwatch is disabled or the
|
|
576
|
+
`data` may be a String, a `Pathname`, or any IO. A String is the bytes
|
|
577
|
+
themselves; a `Pathname` is read as a file. This is one of the standalone
|
|
578
|
+
types, listed in `Railwatch::STANDALONE_TYPES`. Inside a recording
|
|
579
|
+
execution it ships as a child of it. With nothing executing, such as a
|
|
580
|
+
boot hook, a console, or a rescue outside any request, it ships on its
|
|
581
|
+
own. Returns nil and records nothing when Railwatch is disabled or the
|
|
582
|
+
payload is empty.
|
|
566
583
|
|
|
567
584
|
| Field | Meaning |
|
|
568
585
|
|---|---|
|
|
@@ -576,16 +593,17 @@ when Railwatch is disabled or the payload is empty.
|
|
|
576
593
|
|
|
577
594
|
### `log`
|
|
578
595
|
|
|
579
|
-
Two independent sources feed this type
|
|
580
|
-
`
|
|
581
|
-
|
|
582
|
-
`
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
596
|
+
Two independent sources feed this type. See
|
|
597
|
+
`lib/railwatch/subscribers/logs.rb`. The first is `Rails.logger` lines,
|
|
598
|
+
captured by broadcasting to a `Logger` subclass that intercepts every
|
|
599
|
+
`add` call. The second is Rails 8.1's structured `Rails.event` framework
|
|
600
|
+
events. Lines matching Rails' own per-request/job noise are dropped:
|
|
601
|
+
`"Started GET"`, `"Processing by"`, `"Rendered"`, etc. Those are already
|
|
602
|
+
covered by the `request`/`job_attempt` records. Lines below
|
|
603
|
+
`config.log_level` and Railwatch's own `[railwatch]`-prefixed debug
|
|
604
|
+
output are dropped too. Framework structured events such as
|
|
605
|
+
`action_controller.*` and `active_record.*` are dropped unless
|
|
606
|
+
`config.capture_framework_events` is on, for the same reason.
|
|
589
607
|
|
|
590
608
|
| Field | Meaning |
|
|
591
609
|
|---|---|
|
|
@@ -597,9 +615,9 @@ is on, for the same reason.
|
|
|
597
615
|
|
|
598
616
|
### `enqueued_job`
|
|
599
617
|
|
|
600
|
-
`enqueue`/`enqueue_at`/`enqueue_all.active_job
|
|
601
|
-
|
|
602
|
-
from `job_attempt`/`scheduled_task
|
|
618
|
+
`enqueue`/`enqueue_at`/`enqueue_all.active_job`, in
|
|
619
|
+
`lib/railwatch/subscribers/jobs.rb`. One record per job enqueued. Distinct
|
|
620
|
+
from `job_attempt`/`scheduled_task`, which record the later `perform`.
|
|
603
621
|
|
|
604
622
|
| Field | Meaning |
|
|
605
623
|
|---|---|
|
|
@@ -615,15 +633,15 @@ from `job_attempt`/`scheduled_task` which record the later `perform`.
|
|
|
615
633
|
|
|
616
634
|
### `user`
|
|
617
635
|
|
|
618
|
-
Standalone
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
via `config.user` block if set, else `Current.user`
|
|
622
|
-
|
|
623
|
-
The process-hour cache entry is written only once the execution
|
|
624
|
-
the entity has been handed to the reporter
|
|
625
|
-
out or paused does not suppress the next sighting that would
|
|
626
|
-
workers start with an empty cache.
|
|
636
|
+
Standalone. Emitted once per distinct user id per process-hour, not per
|
|
637
|
+
request, so the platform can show names/emails without every other
|
|
638
|
+
record carrying them. See `lib/railwatch/subscribers/users.rb`. Resolved
|
|
639
|
+
via `config.user` block if set, else `Current.user` from
|
|
640
|
+
authentication-zero or the Rails 8 auth generator, else Warden from
|
|
641
|
+
Devise. The process-hour cache entry is written only once the execution
|
|
642
|
+
carrying the entity has been handed to the reporter. So a sighting that
|
|
643
|
+
was sampled out or paused does not suppress the next sighting that would
|
|
644
|
+
ship. Forked workers start with an empty cache.
|
|
627
645
|
|
|
628
646
|
| Field | Meaning |
|
|
629
647
|
|---|---|
|
|
@@ -634,7 +652,9 @@ workers start with an empty cache.
|
|
|
634
652
|
|
|
635
653
|
### `deprecation`
|
|
636
654
|
|
|
637
|
-
`deprecation.rails
|
|
655
|
+
`deprecation.rails`. See `lib/railwatch/subscribers/deprecations.rb`. Rails
|
|
656
|
+
only emits that notification when `config.active_support.deprecation`
|
|
657
|
+
includes `:notify`; see [Troubleshooting](troubleshooting.md#deprecations-are-counted-but-never-listed).
|
|
638
658
|
|
|
639
659
|
| Field | Meaning |
|
|
640
660
|
|---|---|
|
|
@@ -646,14 +666,15 @@ workers start with an empty cache.
|
|
|
646
666
|
|
|
647
667
|
### `visit`
|
|
648
668
|
|
|
649
|
-
Standalone
|
|
650
|
-
|
|
669
|
+
Standalone. Inertia page-visit timing reported by the browser client,
|
|
670
|
+
`app/frontend/lib/railwatch.ts`, generated by `railwatch:install`. POSTed
|
|
651
671
|
to `POST /railwatch/beacon` and recorded server-side by
|
|
652
|
-
`Railwatch::BeaconController`
|
|
653
|
-
Batched client-side
|
|
654
|
-
|
|
655
|
-
`config.beacon_rate_limit` requests per
|
|
656
|
-
`0` turns the limit off
|
|
672
|
+
`Railwatch::BeaconController` in
|
|
673
|
+
`app/controllers/railwatch/beacon_controller.rb`. Batched client-side:
|
|
674
|
+
flushed every 5s, on `pagehide`, or once 20 visits queue up. Capped at 50
|
|
675
|
+
visits per beacon request, and at `config.beacon_rate_limit` requests per
|
|
676
|
+
client IP per minute. The default is 120; `0` turns the limit off. No-ops
|
|
677
|
+
entirely if `config.beacon_enabled` is off.
|
|
657
678
|
|
|
658
679
|
| Field | Meaning |
|
|
659
680
|
|---|---|
|
|
@@ -675,39 +696,42 @@ queue up) and capped at 50 visits per beacon request, and at
|
|
|
675
696
|
| `user_agent` | Truncated to 256 chars. |
|
|
676
697
|
|
|
677
698
|
The **initial page load** is reported as a visit too, even though Inertia
|
|
678
|
-
never routed it
|
|
679
|
-
from the Inertia root's `#app[data-page]` JSON
|
|
680
|
-
navigation timing
|
|
681
|
-
It is the only visit that carries the
|
|
682
|
-
back until the page is first hidden
|
|
683
|
-
those numbers are final when it ships.
|
|
684
|
-
that doesn't support the
|
|
699
|
+
never routed it. It has `method` `"GET"`, `status` `"success"`, and
|
|
700
|
+
`component` read from the Inertia root's `#app[data-page]` JSON. Its
|
|
701
|
+
`duration` is taken from navigation timing: `loadEventEnd` or
|
|
702
|
+
`responseEnd`, minus `startTime`. It is the only visit that carries the
|
|
703
|
+
four Core Web Vitals. It is held back until the page is first hidden, on
|
|
704
|
+
`visibilitychange`/`pagehide`, so those numbers are final when it ships.
|
|
705
|
+
Every vital is nil on a browser that doesn't support the
|
|
706
|
+
`PerformanceObserver` entry type behind it.
|
|
685
707
|
|
|
686
708
|
### `session`
|
|
687
709
|
|
|
688
|
-
Standalone
|
|
689
|
-
`deploy` on the envelope *is* the release
|
|
690
|
-
deploy and reports crash-free rates from them. Two sources produce
|
|
691
|
-
record:
|
|
692
|
-
|
|
693
|
-
- **Browser
|
|
694
|
-
mints a 16-hex id per tab in
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
session
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
is dropped
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
710
|
+
Standalone. One session of the monitored app, for release health. The
|
|
711
|
+
`deploy` on the envelope *is* the release. The platform counts sessions
|
|
712
|
+
per deploy and reports crash-free rates from them. Two sources produce
|
|
713
|
+
the same record:
|
|
714
|
+
|
|
715
|
+
- **Browser**, `source: "browser"`. The client,
|
|
716
|
+
`app/frontend/lib/railwatch.ts`, mints a 16-hex id per tab in
|
|
717
|
+
`sessionStorage` under the key `railwatch.session`, so it dies with the
|
|
718
|
+
tab. It mirrors the id into a `railwatch_session` cookie and sends it
|
|
719
|
+
with every beacon flush. `Railwatch::BeaconController` writes at most
|
|
720
|
+
one `session` record per flush. The first, with no `duration_ms` yet,
|
|
721
|
+
opens the session. Later ones beat it along, and the
|
|
722
|
+
`pagehide`/`visibilitychange` flush closes it with `ended`.
|
|
723
|
+
- **Server**, `source: "server"`. `lib/railwatch/sessions.rb` aggregates,
|
|
724
|
+
per process, every request that resolves a user or carries that cookie
|
|
725
|
+
or an `X-Railwatch-Session` header. A background thread ships one
|
|
726
|
+
record per session every `config.session_flush_interval`, default 60s.
|
|
727
|
+
A session idle for `config.session_timeout`, default 30 minutes, ships
|
|
728
|
+
with `ended` and is dropped. At most 10,000 keys are tracked per
|
|
729
|
+
process. Past that the oldest is dropped and counted in
|
|
730
|
+
`Railwatch::Sessions.dropped`.
|
|
731
|
+
|
|
732
|
+
Both are off when `config.track_sessions` is false. Both key on the same
|
|
733
|
+
id when the browser cookie is present, so the platform dedupes the two
|
|
734
|
+
halves of one session rather than counting it twice.
|
|
711
735
|
|
|
712
736
|
| Field | Meaning |
|
|
713
737
|
|---|---|
|
|
@@ -725,9 +749,10 @@ of one session rather than counting it twice.
|
|
|
725
749
|
|
|
726
750
|
### `process`
|
|
727
751
|
|
|
728
|
-
Standalone
|
|
729
|
-
|
|
730
|
-
sampling. Gives the platform a
|
|
752
|
+
Standalone. One per process boot, from
|
|
753
|
+
`lib/railwatch/subscribers/process_info.rb`. Fired unconditionally during
|
|
754
|
+
subscriber installation, not gated on sampling. Gives the platform a
|
|
755
|
+
server/deploy inventory for free.
|
|
731
756
|
|
|
732
757
|
| Field | Meaning |
|
|
733
758
|
|---|---|
|
|
@@ -743,17 +768,18 @@ sampling. Gives the platform a server/deploy inventory for free.
|
|
|
743
768
|
|
|
744
769
|
### `health`
|
|
745
770
|
|
|
746
|
-
Standalone
|
|
747
|
-
a single background thread per process
|
|
748
|
-
by the engine's `railwatch.health` initializer
|
|
749
|
-
the process `role` is `"web"` or
|
|
750
|
-
`test`. This is the gem's only
|
|
751
|
-
event
|
|
771
|
+
Standalone. One every `config.health_interval` seconds, default 15, from
|
|
772
|
+
a single background thread per process. See `lib/railwatch/health.rb`.
|
|
773
|
+
The thread is started by the engine's `railwatch.health` initializer
|
|
774
|
+
only when Railwatch is enabled, the process `role` is `"web"` or
|
|
775
|
+
`"worker"`, and the Rails env isn't `test`. This is the gem's only
|
|
776
|
+
*sampled gauge*. Everything else is an event; this is a periodic snapshot
|
|
777
|
+
of how loaded the process is.
|
|
752
778
|
|
|
753
|
-
The whole sample runs inside `Railwatch.ignore` and rescues everything
|
|
754
|
-
missing constant, an unmigrated queue database, or a checkout
|
|
755
|
-
degrades each field to nil instead of raising on a thread nobody
|
|
756
|
-
|
|
779
|
+
The whole sample runs inside `Railwatch.ignore` and rescues everything.
|
|
780
|
+
So a missing constant, an unmigrated queue database, or a checkout
|
|
781
|
+
timeout degrades each field to nil instead of raising on a thread nobody
|
|
782
|
+
watches. The record still ships with whatever it did manage to read.
|
|
757
783
|
|
|
758
784
|
| Field | Meaning |
|
|
759
785
|
|---|---|
|
|
@@ -773,24 +799,24 @@ the record still ships with whatever it did manage to read.
|
|
|
773
799
|
Every Puma field is nil when no `Puma::Server` exists in the process, and
|
|
774
800
|
every Solid Queue field is nil when `SolidQueue` isn't loaded.
|
|
775
801
|
|
|
776
|
-
The sampler re-arms itself after `fork
|
|
777
|
-
callback
|
|
778
|
-
|
|
779
|
-
|
|
802
|
+
The sampler re-arms itself after `fork`, via Rails'
|
|
803
|
+
`ActiveSupport::ForkTracker` callback. So clustered Puma workers and
|
|
804
|
+
forked Solid Queue workers each report without any `on_worker_boot`
|
|
805
|
+
configuration.
|
|
780
806
|
|
|
781
|
-
`Railwatch::Health.start!` is idempotent
|
|
782
|
-
engine's `at_exit`, ahead of the reporter's final flush
|
|
783
|
-
off its `ConditionVariable` immediately rather than waiting out
|
|
784
|
-
interval.
|
|
807
|
+
`Railwatch::Health.start!` is idempotent. `stop!` is registered by the
|
|
808
|
+
engine's `at_exit`, ahead of the reporter's final flush. It wakes the
|
|
809
|
+
thread off its `ConditionVariable` immediately rather than waiting out
|
|
810
|
+
the interval.
|
|
785
811
|
|
|
786
812
|
### `profile`
|
|
787
813
|
|
|
788
|
-
A sampling profile of one execution
|
|
789
|
-
`Railwatch.start_profile`/`ship_profile` in `lib/railwatch.rb
|
|
790
|
-
default
|
|
814
|
+
A sampling profile of one execution. See `lib/railwatch/profiler.rb` and
|
|
815
|
+
`Railwatch.start_profile`/`ship_profile` in `lib/railwatch.rb`. Off by
|
|
816
|
+
default. See `docs/configuration.md`'s **Profiling** section for how an
|
|
791
817
|
execution is picked and which backend gem the app has to install. Exactly
|
|
792
818
|
one `profile` per execution, buffered as a child of that execution and
|
|
793
|
-
shipped with it
|
|
819
|
+
shipped with it. The execution's parent record then carries
|
|
794
820
|
`profiled: true`.
|
|
795
821
|
|
|
796
822
|
| Field | Meaning |
|
|
@@ -804,7 +830,7 @@ shipped with it, and the execution's parent record then carries
|
|
|
804
830
|
| `stacks_bytes` | Uncompressed size of that text, in bytes. |
|
|
805
831
|
|
|
806
832
|
`stacks` decodes to *folded stacks*, the same shape Brendan Gregg's
|
|
807
|
-
`stackcollapse` produces
|
|
833
|
+
`stackcollapse` produces. Each line is one unique stack: outermost frame
|
|
808
834
|
first, semicolon-separated, then a space and the number of samples that
|
|
809
835
|
landed on it.
|
|
810
836
|
|
|
@@ -813,22 +839,22 @@ landed on it.
|
|
|
813
839
|
```
|
|
814
840
|
|
|
815
841
|
Each frame is `Class#method (path:line)`. The Rails root is stripped from
|
|
816
|
-
app paths
|
|
817
|
-
version is dropped
|
|
818
|
-
becomes `ruby
|
|
819
|
-
reads `<cfunc>:0`.
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
The text is capped at **4 MiB uncompressed
|
|
824
|
-
|
|
842
|
+
app paths. An installed gem's path becomes `<gem>/relative/path`; the
|
|
843
|
+
version is dropped, since the deploy already records it. Ruby's own
|
|
844
|
+
library becomes `ruby/...`. A C function, which has no Ruby file of its
|
|
845
|
+
own, reads `<cfunc>:0`. Lines are ordered by sample count descending,
|
|
846
|
+
ties broken by the stack text, so the same profile always serialises to
|
|
847
|
+
the same bytes.
|
|
848
|
+
|
|
849
|
+
The text is capped at **4 MiB uncompressed**, by
|
|
850
|
+
`Railwatch::Profiler::MAX_COLLAPSED_BYTES`. Past that the least frequent
|
|
825
851
|
stacks are dropped, since the shape of a profile lives in its frequent
|
|
826
|
-
ones. Rails stacks are deep enough that a busy request can reach the cap
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
Both backends are process-global
|
|
830
|
-
one per thread
|
|
831
|
-
profiled simply isn't profiled
|
|
832
|
-
Vernier samples every thread in the
|
|
833
|
-
started the profile is folded in
|
|
834
|
-
lands.
|
|
852
|
+
ones. Rails stacks are deep enough that a busy request can reach the cap.
|
|
853
|
+
That is why `samples` is reported separately from the counts in `stacks`.
|
|
854
|
+
|
|
855
|
+
Both backends are process-global: there is one profiler per process, not
|
|
856
|
+
one per thread. So an execution that starts while another is being
|
|
857
|
+
profiled simply isn't profiled. Such skips are counted in
|
|
858
|
+
`Railwatch::Profiler.skipped`. Vernier samples every thread in the
|
|
859
|
+
process, so only the thread that started the profile is folded in.
|
|
860
|
+
StackProf samples wherever its `SIGPROF` lands.
|