wide_events 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -0
- data/README.md +149 -8
- data/lib/generators/wide_events/install/install_generator.rb +16 -2
- data/lib/generators/wide_events/skill_installer.rb +24 -0
- data/lib/generators/wide_events/skills/skills_generator.rb +9 -7
- data/lib/generators/wide_events/store/store_generator.rb +145 -0
- data/lib/generators/wide_events/store/templates/accessory.yml.erb +33 -0
- data/lib/wide_event/configuration.rb +74 -4
- data/lib/wide_event/job_instrumentation.rb +2 -0
- data/lib/wide_event/kamal/deploy_editor.rb +499 -0
- data/lib/wide_event/kamal/secrets_editor.rb +103 -0
- data/lib/wide_event/middleware.rb +2 -0
- data/lib/wide_event/railtie.rb +10 -2
- data/lib/wide_event/registry/defaults.yml +5 -0
- data/lib/wide_event/registry.rb +10 -1
- data/lib/wide_event/setup/checker.rb +402 -0
- data/lib/wide_event/setup/command_runner.rb +25 -0
- data/lib/wide_event/sinks/store.rb +33 -0
- data/lib/wide_event/store/client.rb +258 -0
- data/lib/wide_event/store/envelope.rb +60 -0
- data/lib/wide_event/store/formatter.rb +60 -0
- data/lib/wide_event/store/query_result.rb +19 -0
- data/lib/wide_event/store/sender.rb +448 -0
- data/lib/wide_event/tasks/registry.rake +1 -1
- data/lib/wide_event/tasks/setup.rake +55 -0
- data/lib/wide_event/tasks/store.rake +122 -0
- data/lib/wide_event/version.rb +1 -1
- data/lib/wide_event.rb +21 -0
- data/skills/debugging-with-wide-events/SKILL.md +92 -7
- data/skills/setting-up-wide-events-store/SKILL.md +120 -0
- metadata +36 -6
|
@@ -13,6 +13,17 @@ never needs child spans: filter `main = true` and aggregate.
|
|
|
13
13
|
|
|
14
14
|
## Where the rows live
|
|
15
15
|
|
|
16
|
+
- **Wide Events store (bundled DuckDB accessory):** the `wide_events` table.
|
|
17
|
+
`route`, `job_class`, `status`, `error`, `request_id`, `duration_ms`,
|
|
18
|
+
`kind`, `deployment`, and `source` are promoted columns; everything else
|
|
19
|
+
the app set is in the `attributes` VARIANT column, read with
|
|
20
|
+
`attributes['key.name']`. A VARIANT cannot be returned as-is, so cast it
|
|
21
|
+
to the type you want: `attributes['exception.type']::VARCHAR`,
|
|
22
|
+
`attributes['account.id']::BIGINT`,
|
|
23
|
+
`attributes['feature_flag.new_ui']::BOOLEAN`. `IS NULL` works without a
|
|
24
|
+
cast. Query it with `kamal telemetry` (an interactive prompt, or pipe SQL
|
|
25
|
+
to it non-interactively) - never SSH in and open the database file
|
|
26
|
+
directly.
|
|
16
27
|
- **ClickHouse (HyperDX/ClickStack default schema):** the `otel_traces`
|
|
17
28
|
table; wide-event attributes are in the `SpanAttributes` map, resource
|
|
18
29
|
attributes (build, host, environment) in `ResourceAttributes`. Adapt table
|
|
@@ -20,12 +31,84 @@ never needs child spans: filter `main = true` and aggregate.
|
|
|
20
31
|
- **Log sink deployments:** one JSON object per line; query with your log
|
|
21
32
|
store's JSON operators.
|
|
22
33
|
|
|
23
|
-
Filter by environment when staging and production share storage
|
|
34
|
+
Filter by environment when staging and production share storage (the
|
|
35
|
+
DuckDB store is single-tenant per deployment, so this mainly applies to the
|
|
36
|
+
ClickHouse/log backends).
|
|
24
37
|
|
|
25
|
-
##
|
|
38
|
+
## Operational health (DuckDB store)
|
|
39
|
+
|
|
40
|
+
`SELECT * FROM wide_events_store_status;` is the one-row operational view:
|
|
41
|
+
row counts for the last hour/day, oldest/newest received event, database and
|
|
42
|
+
free-disk bytes, write-readiness and its reason, last checkpoint/retention/
|
|
43
|
+
backup times and errors, and the store/protocol/DuckDB versions. Check this
|
|
44
|
+
first when `bin/rails wide_events:setup:check` reports `degraded`, or before
|
|
45
|
+
trusting a "no events" result - it tells you whether anything has arrived at
|
|
46
|
+
all.
|
|
47
|
+
|
|
48
|
+
## Standing queries (DuckDB store)
|
|
49
|
+
|
|
50
|
+
"The reports route is slow":
|
|
51
|
+
|
|
52
|
+
```sql
|
|
53
|
+
SELECT route,
|
|
54
|
+
count(*) AS requests,
|
|
55
|
+
quantile_cont(duration_ms, 0.5) AS p50_ms,
|
|
56
|
+
quantile_cont(duration_ms, 0.95) AS p95_ms
|
|
57
|
+
FROM wide_events
|
|
58
|
+
WHERE route = 'ReportsController#index'
|
|
59
|
+
AND occurred_at > current_timestamp - INTERVAL 1 DAY
|
|
60
|
+
GROUP BY route
|
|
61
|
+
ORDER BY p95_ms DESC
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
"Which jobs are failing":
|
|
65
|
+
|
|
66
|
+
```sql
|
|
67
|
+
SELECT job_class, count(*) AS failures
|
|
68
|
+
FROM wide_events
|
|
69
|
+
WHERE kind = 'job' AND error
|
|
70
|
+
AND occurred_at > current_timestamp - INTERVAL 1 DAY
|
|
71
|
+
GROUP BY job_class
|
|
72
|
+
ORDER BY failures DESC
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
"Unhandled exceptions" - `error` true with no `exception.slug` set is the
|
|
76
|
+
standing "instrument this rescue" query (see instrumenting-wide-events):
|
|
77
|
+
|
|
78
|
+
```sql
|
|
79
|
+
SELECT route, job_class, attributes['exception.type']::VARCHAR AS exception_type, count(*) AS occurrences
|
|
80
|
+
FROM wide_events
|
|
81
|
+
WHERE error
|
|
82
|
+
AND coalesce(attributes['exception.slug']::VARCHAR, '') = ''
|
|
83
|
+
AND occurred_at > current_timestamp - INTERVAL 1 DAY
|
|
84
|
+
GROUP BY route, job_class, exception_type
|
|
85
|
+
ORDER BY occurrences DESC
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
"Did the deploy change anything" - group by `deployment` (the Kamal version
|
|
89
|
+
string) and compare:
|
|
90
|
+
|
|
91
|
+
```sql
|
|
92
|
+
SELECT deployment,
|
|
93
|
+
count(*) AS requests,
|
|
94
|
+
avg(duration_ms) AS avg_ms,
|
|
95
|
+
quantile_cont(duration_ms, 0.95) AS p95_ms,
|
|
96
|
+
sum(error::INT) AS errors
|
|
97
|
+
FROM wide_events
|
|
98
|
+
WHERE route = 'ReportsController#index'
|
|
99
|
+
AND occurred_at > current_timestamp - INTERVAL 1 DAY
|
|
100
|
+
GROUP BY deployment
|
|
101
|
+
ORDER BY deployment DESC
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
"Why did this specific request fail" - filter by `request_id` and read
|
|
105
|
+
every promoted column and attribute of the one row before reaching for logs.
|
|
106
|
+
|
|
107
|
+
## Symptom to query (ClickHouse)
|
|
26
108
|
|
|
27
109
|
Start from the unit of work, not from logs. One GROUP BY usually replaces a
|
|
28
|
-
cross-tool investigation.
|
|
110
|
+
cross-tool investigation. (On the DuckDB store, use the equivalent queries
|
|
111
|
+
above instead - same symptoms, promoted columns instead of `SpanAttributes`.)
|
|
29
112
|
|
|
30
113
|
"The reports route is slow":
|
|
31
114
|
|
|
@@ -52,7 +135,7 @@ LIMIT 20
|
|
|
52
135
|
`SpanAttributes['http.request.id']` and read every attribute of the one row
|
|
53
136
|
before reaching for logs or the trace waterfall.
|
|
54
137
|
|
|
55
|
-
## Standing queries
|
|
138
|
+
## Standing queries (ClickHouse)
|
|
56
139
|
|
|
57
140
|
- **Unnamed exception paths**: `SpanAttributes['error'] = 'true' AND
|
|
58
141
|
SpanAttributes['exception.slug'] = '' AND SpanAttributes['exception.type'] != ''`:
|
|
@@ -69,6 +152,8 @@ before reaching for logs or the trace waterfall.
|
|
|
69
152
|
## Verifying your own fix
|
|
70
153
|
|
|
71
154
|
After a deploy, don't ask a human to check a dashboard. Query the affected
|
|
72
|
-
route/job filtered to the new `
|
|
73
|
-
|
|
74
|
-
|
|
155
|
+
route/job filtered to the new build (`deployment` on the DuckDB store,
|
|
156
|
+
`ResourceAttributes['service.version']` on ClickHouse) and compare against
|
|
157
|
+
the prior build: error rate, p50/p95, query count, and the specific
|
|
158
|
+
attribute your change was supposed to move. State the numbers in your
|
|
159
|
+
summary.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: setting-up-wide-events-store
|
|
3
|
+
description: Use when a human asks to set up, deploy, or finish deploying the bundled Wide Events DuckDB store (the Kamal accessory that receives production telemetry). Covers the store generator, DNS, `kamal setup`, and post-deploy verification end to end.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Setting up the Wide Events store
|
|
7
|
+
|
|
8
|
+
This is the guided path from "wide_events is installed" to "production
|
|
9
|
+
requests are queryable." It wraps deterministic generator/checker code -
|
|
10
|
+
never hand-edit `config/deploy.yml` or `.kamal/secrets` yourself; every
|
|
11
|
+
mutation here is idempotent, so re-running any step (including this whole
|
|
12
|
+
skill) is always safe.
|
|
13
|
+
|
|
14
|
+
## What to ask for
|
|
15
|
+
|
|
16
|
+
Exactly three things, nothing else:
|
|
17
|
+
|
|
18
|
+
1. the SSH address (IP or DNS name) of the VM that will run the store;
|
|
19
|
+
2. the public DNS hostname end users/agents will query through (no scheme,
|
|
20
|
+
no path); and
|
|
21
|
+
3. retention in days (default 30 if the human has no preference).
|
|
22
|
+
|
|
23
|
+
## Steps
|
|
24
|
+
|
|
25
|
+
1. **Generate the accessory.**
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bin/rails generate wide_events:store --host=<ssh-address> --hostname=<telemetry-hostname> --retention-days=<n>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This edits `config/deploy.yml` (accessory, app environment, `telemetry`
|
|
32
|
+
and `wide-events-backup` aliases) and writes durable, gitignored tokens to
|
|
33
|
+
`.kamal/wide-events-ingest-token` and `.kamal/wide-events-query-token`. If
|
|
34
|
+
it refuses instead of editing (ambiguous YAML, a conflicting existing
|
|
35
|
+
key), it prints a complete snippet - merge that by hand, then continue.
|
|
36
|
+
|
|
37
|
+
2. **Show the exact DNS record.** Tell the human to point the telemetry
|
|
38
|
+
hostname at the store host now, before deploying, so propagation happens
|
|
39
|
+
in parallel with the next steps instead of after them.
|
|
40
|
+
|
|
41
|
+
3. **Run the checker.**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
bin/rails wide_events:setup:check
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This is the same idempotent command at every stage - before the first
|
|
48
|
+
deploy, while waiting on DNS, and after `kamal setup` - so there is never
|
|
49
|
+
a different command to remember. It reports one of five states:
|
|
50
|
+
|
|
51
|
+
- `failed` - a Kamal version/config problem, or a host-directory
|
|
52
|
+
permission problem. Fix what the message says (it includes the exact
|
|
53
|
+
one-time administrator command if root is needed to create the data
|
|
54
|
+
directory) and re-run.
|
|
55
|
+
- `needs_dns` - DNS has not resolved yet. **This is expected and
|
|
56
|
+
resumable, not an error.** Nothing generated is reverted or lost;
|
|
57
|
+
re-running the same command later picks up exactly where it left off.
|
|
58
|
+
Wait for propagation and run it again.
|
|
59
|
+
- `ready_to_deploy` - DNS is good and the store isn't answering yet.
|
|
60
|
+
Proceed to step 4.
|
|
61
|
+
- `degraded` - the store is routable but not accepting writes (usually
|
|
62
|
+
low disk space). Do not proceed; investigate on the host.
|
|
63
|
+
- `healthy` - already fully deployed and working (useful if you're
|
|
64
|
+
re-running this skill against an existing setup).
|
|
65
|
+
|
|
66
|
+
4. **Deploy, once the checker says `ready_to_deploy`.**
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
kamal setup
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
5. **Re-run the checker.** `bin/rails wide_events:setup:check` again. On a
|
|
73
|
+
healthy deploy it sends one synthetic event, queries it back by ID, and
|
|
74
|
+
prints that row plus a route-count/p95-latency query - proof the whole
|
|
75
|
+
path (ingest token, query token, TLS, DuckDB) works before any real
|
|
76
|
+
traffic exists.
|
|
77
|
+
|
|
78
|
+
6. **Ask the human to exercise one real route** of the app now that the
|
|
79
|
+
store is live (any request or job is fine).
|
|
80
|
+
|
|
81
|
+
7. **After they confirm, query it back:**
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
kamal telemetry
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
opens an interactive SQL prompt against the store (or pipe SQL to it
|
|
88
|
+
non-interactively). Run a query for the most recent real request, e.g.:
|
|
89
|
+
|
|
90
|
+
```sql
|
|
91
|
+
SELECT id, occurred_at, route, duration_ms, status
|
|
92
|
+
FROM wide_events
|
|
93
|
+
ORDER BY occurred_at DESC
|
|
94
|
+
LIMIT 1;
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Display that captured row, then the route count and p95 latency for the
|
|
98
|
+
exercised route, and state both numbers back to the human.
|
|
99
|
+
|
|
100
|
+
## Things to tell the human, unprompted
|
|
101
|
+
|
|
102
|
+
- **DNS timeouts are resumable.** If `wide_events:setup:check` reports
|
|
103
|
+
`needs_dns`, that is normal while waiting on propagation, not a failure -
|
|
104
|
+
running the exact same command again later continues correctly.
|
|
105
|
+
- **Backups are local-only.** The store itself takes a verified local backup
|
|
106
|
+
once a day in-process (three most recent kept, in `backups/` next to the
|
|
107
|
+
database); `kamal wide-events-backup` is the on-demand version of the same
|
|
108
|
+
thing and prints the path it wrote. Either way this protects against
|
|
109
|
+
logical mistakes and bad upgrades, not loss of the VM or volume itself.
|
|
110
|
+
There is no off-host backup in v1.
|
|
111
|
+
- **Query access uses a separate token from ingestion.** `.kamal/wide-events-
|
|
112
|
+
query-token` is read-only and distinct from `.kamal/wide-events-ingest-
|
|
113
|
+
token`; sharing query access (e.g. with a teammate or another agent) never
|
|
114
|
+
requires sharing the ability to write events.
|
|
115
|
+
|
|
116
|
+
## Where full querying guidance lives
|
|
117
|
+
|
|
118
|
+
Once the store is live, use the `debugging-with-wide-events` skill for the
|
|
119
|
+
standing queries (slow routes, job failures, unhandled exceptions, deploy
|
|
120
|
+
comparisons) and the `wide_events_store_status` operational view.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wide_events
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Adam Miribyan
|
|
@@ -37,11 +37,26 @@ dependencies:
|
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '2.2'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: csv
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
40
54
|
description: 'Collects everything a Rails app knows about each request or job execution
|
|
41
55
|
into one flat, high-cardinality event: route, user, build, query counts, cache hits,
|
|
42
|
-
feature flags, phase timings, errors.
|
|
43
|
-
a
|
|
44
|
-
|
|
56
|
+
feature flags, phase timings, errors. Ships a bundled DuckDB store you deploy as
|
|
57
|
+
a Kamal accessory (one generator, then SQL against production), or flushes onto
|
|
58
|
+
the OpenTelemetry root span or a JSON log line instead. Includes a checkable attribute
|
|
59
|
+
registry, test helpers, and agent skills for instrumenting, querying, and deploying.'
|
|
45
60
|
email:
|
|
46
61
|
- adam@adam.am
|
|
47
62
|
executables: []
|
|
@@ -55,25 +70,41 @@ files:
|
|
|
55
70
|
- lib/generators/wide_events/install/templates/agents_md_section.md
|
|
56
71
|
- lib/generators/wide_events/install/templates/initializer.rb
|
|
57
72
|
- lib/generators/wide_events/install/templates/registry.yml
|
|
73
|
+
- lib/generators/wide_events/skill_installer.rb
|
|
58
74
|
- lib/generators/wide_events/skills/skills_generator.rb
|
|
75
|
+
- lib/generators/wide_events/store/store_generator.rb
|
|
76
|
+
- lib/generators/wide_events/store/templates/accessory.yml.erb
|
|
59
77
|
- lib/wide_event.rb
|
|
60
78
|
- lib/wide_event/configuration.rb
|
|
61
79
|
- lib/wide_event/job_instrumentation.rb
|
|
80
|
+
- lib/wide_event/kamal/deploy_editor.rb
|
|
81
|
+
- lib/wide_event/kamal/secrets_editor.rb
|
|
62
82
|
- lib/wide_event/middleware.rb
|
|
63
83
|
- lib/wide_event/railtie.rb
|
|
64
84
|
- lib/wide_event/registry.rb
|
|
65
85
|
- lib/wide_event/registry/defaults.yml
|
|
86
|
+
- lib/wide_event/setup/checker.rb
|
|
87
|
+
- lib/wide_event/setup/command_runner.rb
|
|
66
88
|
- lib/wide_event/sinks/log_line.rb
|
|
67
89
|
- lib/wide_event/sinks/memory.rb
|
|
68
90
|
- lib/wide_event/sinks/otel_span.rb
|
|
91
|
+
- lib/wide_event/sinks/store.rb
|
|
69
92
|
- lib/wide_event/span_counter_processor.rb
|
|
93
|
+
- lib/wide_event/store/client.rb
|
|
94
|
+
- lib/wide_event/store/envelope.rb
|
|
95
|
+
- lib/wide_event/store/formatter.rb
|
|
96
|
+
- lib/wide_event/store/query_result.rb
|
|
97
|
+
- lib/wide_event/store/sender.rb
|
|
70
98
|
- lib/wide_event/subscribers.rb
|
|
71
99
|
- lib/wide_event/tasks/registry.rake
|
|
100
|
+
- lib/wide_event/tasks/setup.rake
|
|
101
|
+
- lib/wide_event/tasks/store.rake
|
|
72
102
|
- lib/wide_event/test_helper.rb
|
|
73
103
|
- lib/wide_event/version.rb
|
|
74
104
|
- lib/wide_events.rb
|
|
75
105
|
- skills/debugging-with-wide-events/SKILL.md
|
|
76
106
|
- skills/instrumenting-wide-events/SKILL.md
|
|
107
|
+
- skills/setting-up-wide-events-store/SKILL.md
|
|
77
108
|
homepage: https://github.com/adammiribyan/wide_events
|
|
78
109
|
licenses:
|
|
79
110
|
- MIT
|
|
@@ -100,6 +131,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
100
131
|
requirements: []
|
|
101
132
|
rubygems_version: 3.6.9
|
|
102
133
|
specification_version: 4
|
|
103
|
-
summary: One wide telemetry event per request or job,
|
|
104
|
-
export.
|
|
134
|
+
summary: One wide telemetry event per Rails request or job, in a database you own.
|
|
105
135
|
test_files: []
|