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.
data/docs/records.md CHANGED
@@ -1,13 +1,14 @@
1
1
  # Record types
2
2
 
3
- Every record Railwatch ships is a flat hash (`lib/railwatch/record.rb`). This
4
- lists all 26, field by field, sourced from the subscriber or patch that
5
- builds each one. Field names below are the hash keys as sent over the
6
- wire (symbols in Ruby, strings in the gzip NDJSON payload).
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 (`Record.build`, `lib/railwatch/record.rb`):
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; for example, `4.0` means each configured execution sample
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 (everything except the five parent
31
- types, plus `user`/`process`/`visit`, which stand alone) also merge in the
32
- execution's envelope (`Execution#envelope`, `lib/railwatch/execution.rb`):
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`, and `command` are the four
48
- parent types (`Railwatch::PARENT_TYPES`). Each opens an `Execution` and, in
49
- addition to its own fields below, always carries (`Railwatch.build_parent`,
50
- `lib/railwatch.rb`):
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 (`Railwatch.finish_execution`) sampling controls
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 (`lib/railwatch/middleware/request.rb`),
70
- which also owns the `middleware_before`/`action`/`render`/`middleware_after`
71
- stage boundaries (the `action`/`render` boundaries come from
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` (`perform.active_job`,
106
- `lib/railwatch/subscribers/jobs.rb`), for jobs Solid Queue's own recurring
107
- scheduler didn't originate (see `scheduled_task` below for the ones it did).
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 (and therefore on every child record
130
- under it) come from the execution that enqueued the job, not from the
131
- worker process, which usually has no signed-in user to resolve.
132
- `JobTracing#serialize` puts the enqueuing execution's resolved user id and
133
- tenant into the Active Job payload as `railwatch_user`/`railwatch_tenant`,
134
- alongside `railwatch_trace_id`/`railwatch_parent_id`; `perform_start` restores
135
- them onto the job's execution before its first record is built. Details
136
- worth knowing:
137
-
138
- - **Identifiers only.** Two strings — the same tenant-prefixed id the
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 and the worker
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 a job scheduled for next week is attributed to
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 (normally nil).
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, and a payload without them (one enqueued by
154
- an older version of the gem, still sitting in a queue through a deploy)
155
- deserializes to nil and falls back to `Users.resolve_from_current`,
156
- exactly as before. Inline `perform_now` never serializes, so it resolves
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 — the
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
- (`fail_many_claimed.solid_queue`) never reach `perform.active_job` because
168
- their worker was killed or reaped. Each gets its own throwaway execution
169
- and reports `job_attempt` with `job_id: nil`, `name: "(pruned)"`,
170
- `status: "failed"`, `duration: 0`, and `exception_preview` set to the
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 (`recurring_task_key`,
178
- `lib/railwatch/subscribers/jobs.rb`). Carries every `job_attempt` field
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
- (prerequisites nest inside the same command instead of opening their own
192
- see `lib/railwatch/patches/rake_task.rb`'s comment on `Rake::Task#invoke`
193
- vs `#execute`). `db:migrate` and other tasks in
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 after the
210
- action returns or raises, so the SQL, logs, broadcasts, transmits, and
211
- exceptions inside share one trace an Action Cable action has no HTTP request
212
- and no Rack middleware around it, so without this they had no parent at all.
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 (`lib/railwatch/subscribers/queries.rb`).
232
- The hottest record type in the gem — built as one hash literal rather than
233
- going through `Railwatch.record`.
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 (`payload[:cached]`) only increments the execution's
253
- `cached_queries` counter it never becomes a `query` record.
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` (`lib/railwatch/subscribers/queries.rb`)
259
- not on every repeat, just the crossing.
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` (`lib/railwatch/subscribers/queries.rb`).
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` (handled or not), plus anything
283
- the request middleware or command patches catch directly, plus anything a
284
- controller swallows with `rescue_from`
285
- (`lib/railwatch/subscribers/exceptions.rb`). Standalone-capable reports
286
- even with no execution open (console, boot). Deduplicated per error
287
- object, execution, and handled/unhandled disposition, so Rails.error plus
288
- outer middleware report a re-raised error only once without suppressing the
289
- same object when it is reused in another execution. A capture discarded by
290
- sampling or `Railwatch.pause` does not mark the object as seen. Unhandled
291
- exceptions bypass the execution buffer: `Railwatch.record_now` enqueues the
292
- record and wakes the in-memory reporter immediately, without network I/O on
293
- the application thread. This improves the chance of delivery before a normal
294
- exit but is not a durable crash spool; a hard kill, OOM, or exit after the
295
- shutdown deadline can lose the record.
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
- (matching everything else); an *unhandled* one still ships, governed by
317
- its own `exceptions` sample rate rolled once per execution
318
- (`exception_sampled?`).
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: URLs, email addresses, UUIDs, ISO
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 `?`, whitespace collapses, and the
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 up to the first `:` for
326
- `ActiveRecord::RecordNotFound`, `ActiveRecord::RecordInvalid`, `KeyError`,
327
- `ArgumentError`, and `TypeError`, up to the first `for ` for
328
- `NoMethodError` and `NameError` so `key not found: :order_id` and `key
329
- not found: :user_id` are one issue rather than two. Override any of it
330
- with `Railwatch.fingerprint`, `#railwatch_fingerprint`, or
331
- `Railwatch.report(error, fingerprint: [...])`; see
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 or any named ancestor of it appears in
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"`, from Rails'
339
- `rescue_from_callback.action_controller` notification; set
340
- `config.capture_rescued_exceptions = false` to turn that off. Active Job's
341
- equivalents (`retry_on` exhausted, `discard_on`) are already covered by
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; set
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 is
347
- off by default because retries are usually expected and can flood the issues
348
- list. See
349
- [`docs/configuration.md`](configuration.md) for both settings.
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 `window.onerror`,
354
- unhandled promise rejections, Inertia's failed-request events (`exception`
355
- and `invalid` on Inertia 2, `networkError` and `httpException` on 3; a
356
- dropped connection is reported only while the user is waiting on a visit
357
- that shows the progress bar or loads deferred props, not for a background
358
- poll, `router.reload`, or prefetch),
359
- and anything the app reports itself with `reportError` arrives on the
360
- same beacon as visits (`POST /railwatch/beacon`, 50 errors per beacon at
361
- most) and is recorded as an ordinary `exception`: `source: "browser"`,
362
- `handled: false`, `severity: "error"`, `class` set to the JavaScript
363
- error's `name`, `message` truncated to 1024 chars. It carries the same
364
- envelope every other record does, including `deploy`, so a browser issue
365
- regresses with a release exactly like a Ruby one.
366
-
367
- The browser's stack (8192 chars at most) is parsed into the same frame
368
- shape a Ruby backtrace produces V8's `at fn (url:line:col)` and
369
- SpiderMonkey/JavaScriptCore's `fn@url:line:col` are both understood, and a
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 class, top in-app frame, and
381
- the normalized message so browser errors group, split, merge, resolve,
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 (if any), the tab's
386
- `session` id, the `user_agent`, and up to 20 `breadcrumbs`
387
- (`{at, kind, text}`, `kind` being `console`, `click`, or `navigate`) — the
388
- trail the client recorded before the crash. Anything the app passed as
389
- `reportError(error, context)` is merged in alongside it, flattened to
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` (`lib/railwatch/subscribers/cache.rb`). Vendor cache key prefixes
396
- (rack-attack, flipper, solid_cable, by default) are skipped unless
397
- `config.capture_default_vendor_cache_keys` is on; keys matching
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` (`lib/railwatch/subscribers/mail.rb`).
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 (see
428
- below) via `process.action_mailer`, `kind: "mailer"`.
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, which also covers Turbo Streams
433
- and `inertia_cable` since both go through `broadcast.action_cable`
434
- (`lib/railwatch/subscribers/broadcasts.rb`). Three sub-shapes share the type:
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 (`lib/railwatch/subscribers/notifications.rb`)
452
- tagged by hooking the same `perform.active_job` event the `job_attempt`
453
- subscriber uses, filtered to jobs whose class starts with `Noticed::`.
454
- No-ops entirely if the `noticed` gem isn't loaded.
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 (covers Faraday's default adapter, HTTParty,
468
- RestClient, most of the HTTP ecosystem `lib/railwatch/patches/net_http.rb`),
469
- plus Faraday connections that explicitly add `Railwatch::Faraday` middleware
470
- (`lib/railwatch/faraday.rb`, for apps using a non-default Faraday adapter).
471
- Requests to Railwatch's own ingest URL are always skipped so shipping
472
- telemetry never generates telemetry about itself. A Faraday connection
473
- using the default (Net::HTTP) adapter defers to the Net::HTTP patch via a
474
- thread-local reentry flag, so it's never double-recorded.
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
- (`lib/railwatch/subscribers/storage.rb`): upload, download, streaming
494
- download, delete, delete_prefixed, exist, url, update_metadata, analyze,
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
- (`lib/railwatch/subscribers/views.rb`), plus mailer template renders
510
- (`process.action_mailer`, `lib/railwatch/subscribers/mail.rb`, `kind:
511
- "mailer"`). Only the first `config.max_view_renders_per_execution` per
512
- execution are stored as records all are still counted toward the
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
- (`Railwatch.span(name, **attributes) { ... }`, `lib/railwatch.rb`). Returns
529
- the block's value untouched and is a no-op wrapper it still yields —
530
- when Railwatch is disabled, nothing is executing, or the execution isn't
531
- recording. Every span also increments the parent's `spans` counter.
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 exception
548
- (`Railwatch.attach(name, data, content_type:, exception:)`,
549
- `lib/railwatch/attachments.rb`) the payload that failed to parse, a
550
- rendered PDF, the webhook body a customer swears they sent. Sentry's
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 (the bytes themselves), a `Pathname` (the file is
561
- read), or any IO. This is one of the standalone types
562
- (`Railwatch::STANDALONE_TYPES`): inside a recording execution it ships as a
563
- child of it, and with nothing executing — a boot hook, a console, a rescue
564
- outside any request it ships on its own. Returns nil and records nothing
565
- when Railwatch is disabled or the payload is empty.
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 (`lib/railwatch/subscribers/logs.rb`):
580
- `Rails.logger` lines, captured by broadcasting to a `Logger` subclass
581
- that intercepts every `add` call, and Rails 8.1's structured
582
- `Rails.event` framework events. Lines matching Rails' own per-request/job
583
- noise (`"Started GET"`, `"Processing by"`, `"Rendered"`, etc. already
584
- covered by the `request`/`job_attempt` records) are dropped, as are lines
585
- below `config.log_level` and Railwatch's own `[railwatch]`-prefixed debug
586
- output. Framework structured events (`action_controller.*`,
587
- `active_record.*`, etc.) are dropped unless `config.capture_framework_events`
588
- is on, for the same reason.
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
- (`lib/railwatch/subscribers/jobs.rb`) one record per job enqueued, distinct
602
- from `job_attempt`/`scheduled_task` which record the later `perform`.
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 emitted once per distinct user id per process-hour
619
- (`lib/railwatch/subscribers/users.rb`), not per request, so the platform
620
- can show names/emails without every other record carrying them. Resolved
621
- via `config.user` block if set, else `Current.user` (authentication-zero
622
- / Rails 8 auth generator), else Warden (Devise).
623
- The process-hour cache entry is written only once the execution carrying
624
- the entity has been handed to the reporter, so a sighting that was sampled
625
- out or paused does not suppress the next sighting that would ship. Forked
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` (`lib/railwatch/subscribers/deprecations.rb`).
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 Inertia page-visit timing reported by the browser client
650
- (`app/frontend/lib/railwatch.ts`, generated by `railwatch:install`), POSTed
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` (`app/controllers/railwatch/beacon_controller.rb`).
653
- Batched client-side (flushed every 5s, on `pagehide`, or once 20 visits
654
- queue up) and capped at 50 visits per beacon request, and at
655
- `config.beacon_rate_limit` requests per client IP per minute (default 120;
656
- `0` turns the limit off). No-ops entirely if `config.beacon_enabled` is 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: `method` `"GET"`, `status` `"success"`, `component` read
679
- from the Inertia root's `#app[data-page]` JSON, and `duration` taken from
680
- navigation timing (`loadEventEnd` or `responseEnd`, minus `startTime`).
681
- It is the only visit that carries the four Core Web Vitals, and it is held
682
- back until the page is first hidden (`visibilitychange`/`pagehide`) so
683
- those numbers are final when it ships. Every vital is nil on a browser
684
- that doesn't support the `PerformanceObserver` entry type behind it.
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 one session of the monitored app, for release health. The
689
- `deploy` on the envelope *is* the release; the platform counts sessions per
690
- deploy and reports crash-free rates from them. Two sources produce the same
691
- record:
692
-
693
- - **Browser** (`source: "browser"`). The client (`app/frontend/lib/railwatch.ts`)
694
- mints a 16-hex id per tab in `sessionStorage` (key `railwatch.session`, so it
695
- dies with the tab), mirrors it into a `railwatch_session` cookie, and sends it
696
- with every beacon flush. `Railwatch::BeaconController` writes at most one
697
- `session` record per flush: the first (no `duration_ms` yet) opens the
698
- session, later ones beat it along, and the `pagehide`/`visibilitychange`
699
- flush closes it with `ended`.
700
- - **Server** (`source: "server"`). `lib/railwatch/sessions.rb` aggregates, per
701
- process, every request that resolves a user or carries that cookie (or an
702
- `X-Railwatch-Session` header), and a background thread ships one record per
703
- session every `config.session_flush_interval` (default 60s). A session idle
704
- for `config.session_timeout` (default 30 minutes) ships with `ended` and is
705
- dropped. At most 10,000 keys are tracked per process; past that the oldest
706
- is dropped and counted in `Railwatch::Sessions.dropped`.
707
-
708
- Both are off when `config.track_sessions` is false, and both key on the same
709
- id when the browser cookie is present, so the platform dedupes the two halves
710
- of one session rather than counting it twice.
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 one per process boot (`lib/railwatch/subscribers/process_info.rb`),
729
- fired unconditionally during subscriber installation, not gated on
730
- sampling. Gives the platform a server/deploy inventory for free.
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 one every `config.health_interval` seconds (default 15) from
747
- a single background thread per process (`lib/railwatch/health.rb`), started
748
- by the engine's `railwatch.health` initializer only when Railwatch is enabled,
749
- the process `role` is `"web"` or `"worker"`, and the Rails env isn't
750
- `test`. This is the gem's only *sampled gauge*: everything else is an
751
- event, this is a periodic snapshot of how loaded the process is.
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, so a
754
- missing constant, an unmigrated queue database, or a checkout timeout
755
- degrades each field to nil instead of raising on a thread nobody watches —
756
- the record still ships with whatever it did manage to read.
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` (Rails' `ActiveSupport::ForkTracker`
777
- callback), so
778
- clustered Puma workers and forked Solid Queue workers each report without
779
- any `on_worker_boot` configuration.
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, and `stop!` (registered by the
782
- engine's `at_exit`, ahead of the reporter's final flush) wakes the thread
783
- off its `ConditionVariable` immediately rather than waiting out the
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 (`lib/railwatch/profiler.rb`,
789
- `Railwatch.start_profile`/`ship_profile` in `lib/railwatch.rb`). Off by
790
- default; see `docs/configuration.md`'s **Profiling** section for how an
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, and the execution's parent record then carries
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: one line per unique stack, outermost frame
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, an installed gem's path becomes `<gem>/relative/path` (the
817
- version is dropped; the deploy already records it), Ruby's own library
818
- becomes `ruby/...`, and a C function which has no Ruby file of its own —
819
- reads `<cfunc>:0`.
820
- Lines are ordered by sample count descending, ties broken by the stack
821
- text, so the same profile always serialises to the same bytes.
822
-
823
- The text is capped at **4 MiB uncompressed**
824
- (`Railwatch::Profiler::MAX_COLLAPSED_BYTES`); past that the least frequent
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
- which is why `samples` is reported separately from the counts in `stacks`.
828
-
829
- Both backends are process-global there is one profiler per process, not
830
- one per thread so an execution that starts while another is being
831
- profiled simply isn't profiled (counted in `Railwatch::Profiler.skipped`).
832
- Vernier samples every thread in the process, so only the thread that
833
- started the profile is folded in; StackProf samples wherever its `SIGPROF`
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.