koderift-rails 1.3.4 → 2.1.0
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 +67 -0
- data/README.md +183 -3
- data/lib/koderift/rails/breadcrumb.rb +70 -0
- data/lib/koderift/rails/capture.rb +514 -0
- data/lib/koderift/rails/configuration.rb +38 -0
- data/lib/koderift/rails/controller.rb +21 -0
- data/lib/koderift/rails/event.rb +27 -0
- data/lib/koderift/rails/instrumentation.rb +64 -19
- data/lib/koderift/rails/lograge_config.rb +7 -2
- data/lib/koderift/rails/railtie.rb +14 -0
- data/lib/koderift/rails/rake_integration.rb +43 -0
- data/lib/koderift/rails/rum_helper.rb +60 -4
- data/lib/koderift/rails/scope.rb +163 -0
- data/lib/koderift/rails/sidekiq_middleware.rb +24 -1
- data/lib/koderift/rails/tracing.rb +195 -0
- data/lib/koderift/rails/version.rb +1 -1
- data/lib/koderift/rails.rb +187 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b77962b847bf6127f3029ab4cab2eb3f71ae3cf0d0041c6d2df6ad1108449c7e
|
|
4
|
+
data.tar.gz: cc0a1ab9373ca3544bf3d9b48bf08ea1afa269236d4385e9873a139ea258fb0f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 274e3e08f2d325ef30063547658668b955eb32607c6705829c5de531b8ac40d63701d199a3fffb9d537f3298a29816dd491ba897a0ec93dc0237eebaa2964b24
|
|
7
|
+
data.tar.gz: d409cd77d3f77b28d86fb526ddff9b096314b8b12e4af3e1e687b281a98d811fabcb775b164eed5a785433ee6455697faabd1a25c3cb5953a8c8fadd3ccb1425
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,73 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
Releases before 1.3.4 were installed from git and were never published to
|
|
7
7
|
RubyGems; they are listed for continuity.
|
|
8
8
|
|
|
9
|
+
## [2.1.0] - 2026-11-06
|
|
10
|
+
|
|
11
|
+
Security fix: the Browser RUM meta tag no longer publishes your project token.
|
|
12
|
+
|
|
13
|
+
### Security
|
|
14
|
+
- `koderift_rum_meta_tag` used to render `config.project_token` into page HTML.
|
|
15
|
+
That token is the log agent's ingest secret, so anyone reading your page
|
|
16
|
+
source could use it to send log lines to your Koderift project, including
|
|
17
|
+
lines that count against its log quota. The tag now renders only the new
|
|
18
|
+
`config.rum_token`, a public token that can only send browser RUM data.
|
|
19
|
+
- If you rendered the RUM tag on 2.0 or older, treat your project token as
|
|
20
|
+
public. Once this version is deployed with `rum_token` set, rotate the ingest
|
|
21
|
+
token in Koderift (Log Analytics settings, "Rotate ingest token"), then update
|
|
22
|
+
`webhook_url` in the agent's `agent.yml` and `KODERIFT_PROJECT_TOKEN` in your
|
|
23
|
+
app, and restart the agent.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- `config.rum_token`: your project's public RUM token, from the Log Analytics
|
|
27
|
+
setup page in Koderift. It starts with `rum_`.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- **Action needed if you use Browser RUM:** set `config.rum_token`. Without it
|
|
31
|
+
the meta tag renders nothing, and the gem logs one warning per process the
|
|
32
|
+
first time a page renders it. There is deliberately no fallback to
|
|
33
|
+
`project_token`. The tag also refuses a `rum_token` equal to `project_token`.
|
|
34
|
+
|
|
35
|
+
### Notes
|
|
36
|
+
- Koderift keeps accepting RUM data sent with the old token until
|
|
37
|
+
1 February 2027, so pages not yet upgraded keep reporting until then.
|
|
38
|
+
- `project_token` is still required to turn the gem on. The gem never sends it
|
|
39
|
+
anywhere; the log agent uses its own copy in `agent.yml`.
|
|
40
|
+
|
|
41
|
+
## [2.0.0] - 2026-11-04
|
|
42
|
+
|
|
43
|
+
Manual error reporting, with a Sentry-compatible API.
|
|
44
|
+
|
|
45
|
+
Requires koderift-agent >= 1.4.0. Against an older agent, captures still reach
|
|
46
|
+
Raw Logs but not the Log Explorer or the Errors page.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
- `Koderift.capture_exception` and `Koderift.capture_message` for reporting
|
|
50
|
+
errors your code already handles. `capture_exception` returns an event with an
|
|
51
|
+
`event_id`, or nil when nothing was emitted.
|
|
52
|
+
- Scope (`configure_scope`, `with_scope`, `set_user`, `set_tags`, `set_tag`,
|
|
53
|
+
`set_extras`, `set_extra`, `set_context`), breadcrumbs
|
|
54
|
+
(`add_breadcrumb`, `Koderift::Breadcrumb`), and `exception_captured?`.
|
|
55
|
+
- Custom instrumentation: `with_child_span` records a span that renders in the
|
|
56
|
+
APM trace beside the SQL and HTTP spans, and `start_transaction` traces work
|
|
57
|
+
that is neither a request nor a Sidekiq job — a rake task or cron script.
|
|
58
|
+
- Uncaught rake task failures are now captured automatically. Previously a task
|
|
59
|
+
that raised wrote only to STDERR, which under cron is never in the Rails log,
|
|
60
|
+
so those failures were invisible.
|
|
61
|
+
- Configuration: `max_captures_per_minute` (default 100), `sample_rate`,
|
|
62
|
+
`before_send` and `excluded_exceptions`.
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
- Sidekiq jobs enqueued through ActiveJob are reported under their own class
|
|
66
|
+
(`job["wrapped"]`, as Sidekiq logs them) instead of the adapter's wrapper,
|
|
67
|
+
which filed every ActiveJob under one `Sidekiq::ActiveJob::Wrapper` row.
|
|
68
|
+
|
|
69
|
+
### Notes
|
|
70
|
+
- Captures appear under a new `manual` log source, and are excluded from the
|
|
71
|
+
APM request aggregates by it, so they cannot skew throughput, p95 or Apdex.
|
|
72
|
+
- The gem does not define `Sentry`. Migration is a namespace find-and-replace;
|
|
73
|
+
see the README for the full mapping and the short list of Sentry APIs with no
|
|
74
|
+
Koderift equivalent.
|
|
75
|
+
|
|
9
76
|
## [1.3.4] - 2026-08-27
|
|
10
77
|
|
|
11
78
|
First release published to RubyGems.
|
data/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Requires Ruby >= 2.7 and Rails 7.1-8.x.
|
|
|
12
12
|
Add to your `Gemfile`:
|
|
13
13
|
|
|
14
14
|
```ruby
|
|
15
|
-
gem 'koderift-rails', '~> 1
|
|
15
|
+
gem 'koderift-rails', '~> 2.1'
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Run `bundle install`. Lograge is a dependency of this gem and is installed
|
|
@@ -32,7 +32,37 @@ end
|
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
Set `KODERIFT_PROJECT_TOKEN` in your environment to the token from your
|
|
35
|
-
project's Log Analytics settings page in Koderift.
|
|
35
|
+
project's Log Analytics settings page in Koderift. It is the log agent's
|
|
36
|
+
ingest secret: keep it out of your repository and your page HTML.
|
|
37
|
+
|
|
38
|
+
## Browser RUM
|
|
39
|
+
|
|
40
|
+
To collect Core Web Vitals and JS error counts from real browsers, set your
|
|
41
|
+
project's public RUM token (from the Log Analytics setup page; it starts with
|
|
42
|
+
`rum_`) and add the meta tag and snippet inside your layout's `<head>`:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
Koderift::Rails.configure do |config|
|
|
46
|
+
config.project_token = ENV['KODERIFT_PROJECT_TOKEN']
|
|
47
|
+
config.rum_token = 'rum_...'
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```erb
|
|
52
|
+
<%= koderift_rum_meta_tag %>
|
|
53
|
+
<script src="https://<your Koderift host>/ingest/rum.js" async defer></script>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The meta tag publishes `rum_token` only. It can only send browser RUM data, so
|
|
57
|
+
it is safe in page HTML and safe to commit. The gem never renders
|
|
58
|
+
`project_token`, and it refuses a `rum_token` equal to it. Without a
|
|
59
|
+
`rum_token` the tag renders nothing and the gem logs one warning per process.
|
|
60
|
+
Set `config.rum_enabled = false` for API-only apps.
|
|
61
|
+
|
|
62
|
+
Upgrading from 2.0 or older with RUM in use? Those versions published
|
|
63
|
+
`project_token` in the meta tag. Set `rum_token`, deploy, then rotate the
|
|
64
|
+
ingest token in Koderift (Log Analytics settings) and update `webhook_url` in
|
|
65
|
+
the agent's `agent.yml`. See the [changelog](CHANGELOG.md).
|
|
36
66
|
|
|
37
67
|
## What it captures
|
|
38
68
|
|
|
@@ -47,8 +77,158 @@ project's Log Analytics settings page in Koderift.
|
|
|
47
77
|
* Exception class, message, and cause chain
|
|
48
78
|
* Stack traces (requires `keep_original_rails_log: true`)
|
|
49
79
|
|
|
80
|
+
## Reporting handled errors
|
|
81
|
+
|
|
82
|
+
Automatic instrumentation only sees what reaches Rails: a request, a Sidekiq
|
|
83
|
+
job, or something logged at ERROR. An error your code *handles* — rescued,
|
|
84
|
+
retried, degraded gracefully, returned as a 200 — is exactly the kind worth
|
|
85
|
+
knowing about, and nothing reports it for you.
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
begin
|
|
89
|
+
sync_contractors
|
|
90
|
+
rescue StandardError => e
|
|
91
|
+
Rails.logger.error "contractor sync failed: #{e.class} - #{e.message}"
|
|
92
|
+
Koderift.capture_exception(e)
|
|
93
|
+
end
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`capture_exception` returns an event, or `nil` when nothing was emitted:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
event_id = Koderift.capture_exception(error)&.event_id
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Paste that id into the Log Explorer search box to find the event.
|
|
103
|
+
|
|
104
|
+
### Errors vs. messages
|
|
105
|
+
|
|
106
|
+
`capture_exception` produces a tracked Error — grouped by exception class and
|
|
107
|
+
`component#action`, counted, and resolvable. `capture_message` produces a
|
|
108
|
+
searchable log line, and *also* a tracked Error at `:error` or `:fatal`:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
Koderift.capture_message("cache warm skipped", level: :info) # Log Explorer only
|
|
112
|
+
Koderift.capture_message("panel schema drifted", # a tracked Error
|
|
113
|
+
level: :error, name: "PanelSchemaDrift")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`name:` is what separates one message group from another. Without it, every
|
|
117
|
+
unnamed error-level message from the same place groups together — deliberately,
|
|
118
|
+
because grouping on the message text itself would either mint a new Error per
|
|
119
|
+
interpolated value or collapse unrelated messages into one bucket.
|
|
120
|
+
|
|
121
|
+
`:warn` and `:info` messages never become tracked Errors; they are searchable in
|
|
122
|
+
the Log Explorer.
|
|
123
|
+
|
|
124
|
+
### Context
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
Koderift.capture_exception(e,
|
|
128
|
+
tags: { region: "eu-west" },
|
|
129
|
+
extra: { order_id: order.id },
|
|
130
|
+
user: { id: current_user.id, email: current_user.email })
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Anything matching `config.filter_params` is stripped at every depth, as are the
|
|
134
|
+
keys `controller`, `method` and `job_class` (they collide with how the log agent
|
|
135
|
+
identifies request and job lines).
|
|
136
|
+
|
|
137
|
+
### Scope, breadcrumbs and custom spans
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
Koderift.set_user(id: current_user.id)
|
|
141
|
+
Koderift.set_tag(:tenant, current_tenant.slug)
|
|
142
|
+
Koderift.with_scope { |scope| scope.set_tag(:step, "import") }
|
|
143
|
+
|
|
144
|
+
Koderift.add_breadcrumb(Koderift::Breadcrumb.new(category: "payment",
|
|
145
|
+
message: "charging card"))
|
|
146
|
+
|
|
147
|
+
Koderift.with_child_span(op: "stripe", description: "charge") do |span|
|
|
148
|
+
span.set_data(:amount, 500)
|
|
149
|
+
gateway.charge!
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Scope is per-thread and reset at the end of every request and job. Breadcrumbs
|
|
154
|
+
interleave with the SQL and render crumbs the gem already collects. A child span
|
|
155
|
+
becomes a `custom` span in the APM trace, next to the SQL and HTTP spans.
|
|
156
|
+
|
|
157
|
+
### Rake tasks and cron
|
|
158
|
+
|
|
159
|
+
Uncaught rake failures are captured automatically — no task changes needed. This
|
|
160
|
+
matters because a task that raises writes to STDERR, which under cron goes to a
|
|
161
|
+
mail spool or `/dev/null`, so it was previously invisible.
|
|
162
|
+
|
|
163
|
+
To trace a task as a unit of work, wrap it:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
Koderift.start_transaction(name: "ringley_panel:sync_recent_contractors", op: "rake") do
|
|
167
|
+
sync_contractors
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Its queries, outbound HTTP calls and custom spans then render as one trace.
|
|
172
|
+
Prefer this block form: `start_transaction` without a block returns a
|
|
173
|
+
transaction you must `finish` yourself, and an abandoned one leaves notification
|
|
174
|
+
subscribers attached to the process.
|
|
175
|
+
|
|
176
|
+
### Configuration
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
Koderift::Rails.configure do |config|
|
|
180
|
+
config.max_captures_per_minute = 100 # nil to disable the cap
|
|
181
|
+
config.sample_rate = 1.0
|
|
182
|
+
config.excluded_exceptions = %w[ActiveRecord::RecordNotFound]
|
|
183
|
+
config.before_send = ->(payload, hint) { payload } # nil to drop
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
A capture never raises, and drops are never silent — the rate limiter says on
|
|
188
|
+
the log when it starts dropping and how many it dropped.
|
|
189
|
+
|
|
190
|
+
## Migrating from Sentry
|
|
191
|
+
|
|
192
|
+
Replace the `Sentry` namespace with `Koderift`; the call sites keep working.
|
|
193
|
+
|
|
194
|
+
| Sentry | Koderift |
|
|
195
|
+
| --- | --- |
|
|
196
|
+
| `Sentry.capture_exception(e, **opts, &blk)` | `Koderift.capture_exception` |
|
|
197
|
+
| `Sentry.capture_message(msg, **opts, &blk)` | `Koderift.capture_message` |
|
|
198
|
+
| `Sentry.capture_event(event)` | `Koderift.capture_event` |
|
|
199
|
+
| `Sentry.last_event_id` / `initialized?` | same |
|
|
200
|
+
| `Sentry.configure_scope` / `with_scope` | same |
|
|
201
|
+
| `Sentry.set_user` / `set_tags` / `set_tag` / `set_extras` / `set_extra` / `set_context` | same |
|
|
202
|
+
| `Sentry.get_current_scope` | same |
|
|
203
|
+
| `Sentry.add_breadcrumb` / `Sentry::Breadcrumb` | `Koderift.add_breadcrumb` / `Koderift::Breadcrumb` |
|
|
204
|
+
| `Sentry.start_transaction` / `with_child_span` | same |
|
|
205
|
+
| `Sentry.exception_captured?` | same |
|
|
206
|
+
| `Sentry.init { |c| ... }` | `Koderift::Rails.configure { |c| ... }` |
|
|
207
|
+
| `config.before_send` / `sample_rate` / `excluded_exceptions` | same |
|
|
208
|
+
|
|
209
|
+
Capture options are the same: `level:`, `tags:`, `extra:`, `user:`, `contexts:`,
|
|
210
|
+
`fingerprint:`, `backtrace:`, `hint:`, and `message:` on `capture_exception`.
|
|
211
|
+
Levels accept Sentry's names — `:warning` and `:debug` fold to `warn` and `info`.
|
|
212
|
+
|
|
213
|
+
`fingerprint:` is honoured by synthesising a grouping class, so
|
|
214
|
+
`fingerprint: %w[stripe timeout]` reports as `StripeTimeout: Net::ReadTimeout: ...`.
|
|
215
|
+
|
|
216
|
+
### Not provided
|
|
217
|
+
|
|
218
|
+
These have no stub: a method either does something real or is absent, so you
|
|
219
|
+
find out at migration time rather than from a silent no-op.
|
|
220
|
+
|
|
221
|
+
| Sentry | Why |
|
|
222
|
+
| --- | --- |
|
|
223
|
+
| `capture_check_in` | Cron *monitors* — alerting when a job never runs. Cron errors and slowness are covered (above); detecting an absence needs schedule definitions Koderift does not have yet. |
|
|
224
|
+
| `close`, `clone_hub_to_current_thread`, `with_session_tracking` | Nothing to do behind them: captures are written synchronously with no in-process buffer, and all state is already thread-local. |
|
|
225
|
+
| `get_traceparent`, `get_baggage` | Distributed tracing works, via the `X-Koderift-Trace-ID` header this gem already propagates. |
|
|
226
|
+
| Profiling, release health | Configured inside `Sentry.init` with no call sites, so nothing to replace. Release comparison is on the Koderift deploy-impact view, keyed by the agent's `app_version`. |
|
|
227
|
+
|
|
50
228
|
## Requirements
|
|
51
229
|
|
|
52
230
|
* Ruby >= 2.7
|
|
53
|
-
* Rails
|
|
231
|
+
* Rails 7.1-8.x
|
|
54
232
|
* lograge >= 0.11
|
|
233
|
+
* koderift-agent >= 1.4.0 for manual captures to reach the Log Explorer
|
|
234
|
+
(older agents ship them as raw log lines only)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
module Koderift
|
|
6
|
+
module Rails
|
|
7
|
+
# A manually recorded breadcrumb. Serialises to the same shape
|
|
8
|
+
# Instrumentation already emits for SQL and partial renders, so manual and
|
|
9
|
+
# automatic crumbs interleave in one ordered trail rather than living in
|
|
10
|
+
# two lists the UI would have to merge.
|
|
11
|
+
class Breadcrumb
|
|
12
|
+
attr_reader :category, :message, :level, :data, :timestamp
|
|
13
|
+
|
|
14
|
+
def initialize(category: nil, message: nil, level: nil, data: nil, timestamp: nil)
|
|
15
|
+
@category = category.to_s.empty? ? 'manual' : category.to_s
|
|
16
|
+
@message = message.to_s
|
|
17
|
+
@level = level&.to_s
|
|
18
|
+
@data = data
|
|
19
|
+
@timestamp = timestamp || Time.now.utc.iso8601
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_h
|
|
23
|
+
{
|
|
24
|
+
type: @category,
|
|
25
|
+
message: @message,
|
|
26
|
+
level: @level,
|
|
27
|
+
data: @data,
|
|
28
|
+
timestamp: @timestamp
|
|
29
|
+
}.compact
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The live breadcrumb trail for the current thread.
|
|
34
|
+
#
|
|
35
|
+
# Instrumentation.capture publishes ITS OWN array here for the duration of a
|
|
36
|
+
# request or job, so a manual crumb lands in the same list as the automatic
|
|
37
|
+
# SQL/render ones and ships with that unit's log line. Outside a captured
|
|
38
|
+
# unit the trail is a plain thread-local that captures still read, which is
|
|
39
|
+
# what makes breadcrumbs work in a rake task.
|
|
40
|
+
module Breadcrumbs
|
|
41
|
+
KEY = :koderift_breadcrumbs
|
|
42
|
+
|
|
43
|
+
class << self
|
|
44
|
+
def add(crumb)
|
|
45
|
+
crumb = Breadcrumb.new(message: crumb.to_s) unless crumb.is_a?(Breadcrumb)
|
|
46
|
+
buffer = (Thread.current[KEY] ||= [])
|
|
47
|
+
buffer << crumb.to_h
|
|
48
|
+
|
|
49
|
+
# Trim here rather than only at the end of Instrumentation.capture:
|
|
50
|
+
# outside a captured unit nothing else ever bounds this array, and a
|
|
51
|
+
# rake task adding crumbs in a loop would grow it without limit.
|
|
52
|
+
max = Koderift::Rails.configuration.max_breadcrumbs.to_i
|
|
53
|
+
buffer.shift(buffer.size - max) if max.positive? && buffer.size > max
|
|
54
|
+
|
|
55
|
+
crumb
|
|
56
|
+
rescue StandardError
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def current
|
|
61
|
+
(Thread.current[KEY] || []).dup
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def clear
|
|
65
|
+
Thread.current[KEY] = nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|