koderift-rails 1.3.4 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e49d9d7f56f87dde732a6465a3ced955d8960b692408f6818cf686e30965c8c
4
- data.tar.gz: 299cb7491b8211baa559aebbca51b7759929891caefe5048d562791431759202
3
+ metadata.gz: a9c83f5ae85e74957769db72695555fbdd39aebef940ecb97b7cf1dda72d75c0
4
+ data.tar.gz: d27d1449f36d00606d187eed46783a6cbcd6df03ad1f2ee8cf515f834d294735
5
5
  SHA512:
6
- metadata.gz: e01be29d2c18193b54c04d93ae80ce6bca85d58b8c3c57e92227d5e706c4e033d8d13cb103c285a61d90a19e14431f80eec95e2ace8ed38fe2bbb8f92cd9fabf
7
- data.tar.gz: b620b59cbe817e063ef7f862be7ed1fc0a36390b6465b4f52b20d59afe51f34de53a47dc72371f7ba14a0079c579a3946c16dbfa1bb3d7cd316c7ad381afe53f
6
+ metadata.gz: 43e553f25047fc9501be705793df7e7d9b82ec5ecd17850ca8b45b07baacb4e52dd954611a11b580284aedfb6abc070a9ad139482aba6210b63432ceee3c6c11
7
+ data.tar.gz: e55b1666a9e531b7a4ca9e1f02acaeb4086ef17b65cd0aee6750fd3d641106b248e5027fa083d0f11f95797409688fff9618ed09d8a886e20b6e12ac71e98009
data/CHANGELOG.md CHANGED
@@ -6,6 +6,41 @@ 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.0.0] - 2026-11-04
10
+
11
+ Manual error reporting, with a Sentry-compatible API.
12
+
13
+ Requires koderift-agent >= 1.4.0. Against an older agent, captures still reach
14
+ Raw Logs but not the Log Explorer or the Errors page.
15
+
16
+ ### Added
17
+ - `Koderift.capture_exception` and `Koderift.capture_message` for reporting
18
+ errors your code already handles. `capture_exception` returns an event with an
19
+ `event_id`, or nil when nothing was emitted.
20
+ - Scope (`configure_scope`, `with_scope`, `set_user`, `set_tags`, `set_tag`,
21
+ `set_extras`, `set_extra`, `set_context`), breadcrumbs
22
+ (`add_breadcrumb`, `Koderift::Breadcrumb`), and `exception_captured?`.
23
+ - Custom instrumentation: `with_child_span` records a span that renders in the
24
+ APM trace beside the SQL and HTTP spans, and `start_transaction` traces work
25
+ that is neither a request nor a Sidekiq job — a rake task or cron script.
26
+ - Uncaught rake task failures are now captured automatically. Previously a task
27
+ that raised wrote only to STDERR, which under cron is never in the Rails log,
28
+ so those failures were invisible.
29
+ - Configuration: `max_captures_per_minute` (default 100), `sample_rate`,
30
+ `before_send` and `excluded_exceptions`.
31
+
32
+ ### Fixed
33
+ - Sidekiq jobs enqueued through ActiveJob are reported under their own class
34
+ (`job["wrapped"]`, as Sidekiq logs them) instead of the adapter's wrapper,
35
+ which filed every ActiveJob under one `Sidekiq::ActiveJob::Wrapper` row.
36
+
37
+ ### Notes
38
+ - Captures appear under a new `manual` log source, and are excluded from the
39
+ APM request aggregates by it, so they cannot skew throughput, p95 or Apdex.
40
+ - The gem does not define `Sentry`. Migration is a namespace find-and-replace;
41
+ see the README for the full mapping and the short list of Sentry APIs with no
42
+ Koderift equivalent.
43
+
9
44
  ## [1.3.4] - 2026-08-27
10
45
 
11
46
  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.3'
15
+ gem 'koderift-rails', '~> 2.0'
16
16
  ```
17
17
 
18
18
  Run `bundle install`. Lograge is a dependency of this gem and is installed
@@ -47,8 +47,158 @@ project's Log Analytics settings page in Koderift.
47
47
  * Exception class, message, and cause chain
48
48
  * Stack traces (requires `keep_original_rails_log: true`)
49
49
 
50
+ ## Reporting handled errors
51
+
52
+ Automatic instrumentation only sees what reaches Rails: a request, a Sidekiq
53
+ job, or something logged at ERROR. An error your code *handles* — rescued,
54
+ retried, degraded gracefully, returned as a 200 — is exactly the kind worth
55
+ knowing about, and nothing reports it for you.
56
+
57
+ ```ruby
58
+ begin
59
+ sync_contractors
60
+ rescue StandardError => e
61
+ Rails.logger.error "contractor sync failed: #{e.class} - #{e.message}"
62
+ Koderift.capture_exception(e)
63
+ end
64
+ ```
65
+
66
+ `capture_exception` returns an event, or `nil` when nothing was emitted:
67
+
68
+ ```ruby
69
+ event_id = Koderift.capture_exception(error)&.event_id
70
+ ```
71
+
72
+ Paste that id into the Log Explorer search box to find the event.
73
+
74
+ ### Errors vs. messages
75
+
76
+ `capture_exception` produces a tracked Error — grouped by exception class and
77
+ `component#action`, counted, and resolvable. `capture_message` produces a
78
+ searchable log line, and *also* a tracked Error at `:error` or `:fatal`:
79
+
80
+ ```ruby
81
+ Koderift.capture_message("cache warm skipped", level: :info) # Log Explorer only
82
+ Koderift.capture_message("panel schema drifted", # a tracked Error
83
+ level: :error, name: "PanelSchemaDrift")
84
+ ```
85
+
86
+ `name:` is what separates one message group from another. Without it, every
87
+ unnamed error-level message from the same place groups together — deliberately,
88
+ because grouping on the message text itself would either mint a new Error per
89
+ interpolated value or collapse unrelated messages into one bucket.
90
+
91
+ `:warn` and `:info` messages never become tracked Errors; they are searchable in
92
+ the Log Explorer.
93
+
94
+ ### Context
95
+
96
+ ```ruby
97
+ Koderift.capture_exception(e,
98
+ tags: { region: "eu-west" },
99
+ extra: { order_id: order.id },
100
+ user: { id: current_user.id, email: current_user.email })
101
+ ```
102
+
103
+ Anything matching `config.filter_params` is stripped at every depth, as are the
104
+ keys `controller`, `method` and `job_class` (they collide with how the log agent
105
+ identifies request and job lines).
106
+
107
+ ### Scope, breadcrumbs and custom spans
108
+
109
+ ```ruby
110
+ Koderift.set_user(id: current_user.id)
111
+ Koderift.set_tag(:tenant, current_tenant.slug)
112
+ Koderift.with_scope { |scope| scope.set_tag(:step, "import") }
113
+
114
+ Koderift.add_breadcrumb(Koderift::Breadcrumb.new(category: "payment",
115
+ message: "charging card"))
116
+
117
+ Koderift.with_child_span(op: "stripe", description: "charge") do |span|
118
+ span.set_data(:amount, 500)
119
+ gateway.charge!
120
+ end
121
+ ```
122
+
123
+ Scope is per-thread and reset at the end of every request and job. Breadcrumbs
124
+ interleave with the SQL and render crumbs the gem already collects. A child span
125
+ becomes a `custom` span in the APM trace, next to the SQL and HTTP spans.
126
+
127
+ ### Rake tasks and cron
128
+
129
+ Uncaught rake failures are captured automatically — no task changes needed. This
130
+ matters because a task that raises writes to STDERR, which under cron goes to a
131
+ mail spool or `/dev/null`, so it was previously invisible.
132
+
133
+ To trace a task as a unit of work, wrap it:
134
+
135
+ ```ruby
136
+ Koderift.start_transaction(name: "ringley_panel:sync_recent_contractors", op: "rake") do
137
+ sync_contractors
138
+ end
139
+ ```
140
+
141
+ Its queries, outbound HTTP calls and custom spans then render as one trace.
142
+ Prefer this block form: `start_transaction` without a block returns a
143
+ transaction you must `finish` yourself, and an abandoned one leaves notification
144
+ subscribers attached to the process.
145
+
146
+ ### Configuration
147
+
148
+ ```ruby
149
+ Koderift::Rails.configure do |config|
150
+ config.max_captures_per_minute = 100 # nil to disable the cap
151
+ config.sample_rate = 1.0
152
+ config.excluded_exceptions = %w[ActiveRecord::RecordNotFound]
153
+ config.before_send = ->(payload, hint) { payload } # nil to drop
154
+ end
155
+ ```
156
+
157
+ A capture never raises, and drops are never silent — the rate limiter says on
158
+ the log when it starts dropping and how many it dropped.
159
+
160
+ ## Migrating from Sentry
161
+
162
+ Replace the `Sentry` namespace with `Koderift`; the call sites keep working.
163
+
164
+ | Sentry | Koderift |
165
+ | --- | --- |
166
+ | `Sentry.capture_exception(e, **opts, &blk)` | `Koderift.capture_exception` |
167
+ | `Sentry.capture_message(msg, **opts, &blk)` | `Koderift.capture_message` |
168
+ | `Sentry.capture_event(event)` | `Koderift.capture_event` |
169
+ | `Sentry.last_event_id` / `initialized?` | same |
170
+ | `Sentry.configure_scope` / `with_scope` | same |
171
+ | `Sentry.set_user` / `set_tags` / `set_tag` / `set_extras` / `set_extra` / `set_context` | same |
172
+ | `Sentry.get_current_scope` | same |
173
+ | `Sentry.add_breadcrumb` / `Sentry::Breadcrumb` | `Koderift.add_breadcrumb` / `Koderift::Breadcrumb` |
174
+ | `Sentry.start_transaction` / `with_child_span` | same |
175
+ | `Sentry.exception_captured?` | same |
176
+ | `Sentry.init { |c| ... }` | `Koderift::Rails.configure { |c| ... }` |
177
+ | `config.before_send` / `sample_rate` / `excluded_exceptions` | same |
178
+
179
+ Capture options are the same: `level:`, `tags:`, `extra:`, `user:`, `contexts:`,
180
+ `fingerprint:`, `backtrace:`, `hint:`, and `message:` on `capture_exception`.
181
+ Levels accept Sentry's names — `:warning` and `:debug` fold to `warn` and `info`.
182
+
183
+ `fingerprint:` is honoured by synthesising a grouping class, so
184
+ `fingerprint: %w[stripe timeout]` reports as `StripeTimeout: Net::ReadTimeout: ...`.
185
+
186
+ ### Not provided
187
+
188
+ These have no stub: a method either does something real or is absent, so you
189
+ find out at migration time rather than from a silent no-op.
190
+
191
+ | Sentry | Why |
192
+ | --- | --- |
193
+ | `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. |
194
+ | `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. |
195
+ | `get_traceparent`, `get_baggage` | Distributed tracing works, via the `X-Koderift-Trace-ID` header this gem already propagates. |
196
+ | 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`. |
197
+
50
198
  ## Requirements
51
199
 
52
200
  * Ruby >= 2.7
53
- * Rails >= 6.1
201
+ * Rails 7.1-8.x
54
202
  * lograge >= 0.11
203
+ * koderift-agent >= 1.4.0 for manual captures to reach the Log Explorer
204
+ (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