errorgap 0.7.0 → 0.9.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.
data/lib/errorgap.rb CHANGED
@@ -3,24 +3,34 @@
3
3
  require "securerandom"
4
4
 
5
5
  require_relative "errorgap/configuration"
6
+ require_relative "errorgap/sanitizer"
7
+ require_relative "errorgap/release_detector"
8
+ require_relative "errorgap/delivery"
6
9
  require_relative "errorgap/breadcrumbs"
7
10
  require_relative "errorgap/notifier"
8
11
  require_relative "errorgap/notice"
9
12
  require_relative "errorgap/log_delivery"
13
+ require_relative "errorgap/sign_ins"
14
+ require_relative "errorgap/warden_hooks"
10
15
  require_relative "errorgap/transaction"
11
16
  require_relative "errorgap/span_collector"
12
17
  require_relative "errorgap/span_recorder"
13
18
  require_relative "errorgap/transacter"
14
19
  require_relative "errorgap/rack_middleware"
20
+ require_relative "errorgap/integrations/sidekiq"
21
+ require_relative "errorgap/integrations/active_job"
22
+ require_relative "errorgap/integrations/net_http"
15
23
  require_relative "errorgap/version"
16
24
 
17
25
  module Errorgap
18
26
  class << self
19
27
  def configure
20
28
  yield(configuration)
29
+ delivery.configure(configuration)
21
30
  notifier.configure(configuration)
22
31
  transacter.configure(configuration)
23
32
  log_delivery.configure(configuration)
33
+ sign_ins.configure(configuration)
24
34
  @breadcrumbs = Breadcrumbs.new(configuration.max_breadcrumbs)
25
35
  end
26
36
 
@@ -32,6 +42,11 @@ module Errorgap
32
42
  @notifier ||= Notifier.new(configuration)
33
43
  end
34
44
 
45
+ # The shared sender behind notices, transactions, logs and sign-ins.
46
+ def delivery
47
+ @delivery ||= Delivery.new(configuration)
48
+ end
49
+
35
50
  def transacter
36
51
  @transacter ||= Transacter.new(configuration)
37
52
  end
@@ -40,6 +55,19 @@ module Errorgap
40
55
  @log_delivery ||= LogDelivery.new(configuration)
41
56
  end
42
57
 
58
+ def sign_ins
59
+ @sign_ins ||= SignIns.new(configuration)
60
+ end
61
+
62
+ # Report a sign-in to this app: "success", "failure", "password_reset",
63
+ # "mfa_failure" or "locked". Pass the request for its IP, user agent and
64
+ # path. Needs `config.auth_events = true`; Devise apps get this
65
+ # automatically from the Warden hooks.
66
+ def sign_in(outcome, user: nil, request: nil, ip: nil, user_agent: nil, path: nil, method: nil, sync: false)
67
+ sign_ins.record(outcome, user: user, request: request, ip: ip, user_agent: user_agent,
68
+ path: path, method: method, sync: sync)
69
+ end
70
+
43
71
  def breadcrumbs
44
72
  @breadcrumbs ||= Breadcrumbs.new(configuration.max_breadcrumbs)
45
73
  end
@@ -49,6 +77,7 @@ module Errorgap
49
77
  if (transaction_id = current_transaction_id) && !context.key?(:transaction_id) && !context.key?("transaction_id")
50
78
  context = context.merge(transaction_id: transaction_id)
51
79
  end
80
+ session = user_session.merge(session || {}) if current_user
52
81
  notifier.notify(
53
82
  error,
54
83
  context: context,
@@ -60,6 +89,82 @@ module Errorgap
60
89
  )
61
90
  end
62
91
 
92
+ REPORTED_IVAR = :@__errorgap_reported
93
+ USER_KEY = :errorgap_user
94
+
95
+ # `notify`, unless this exception object was already reported: lets the
96
+ # Rack middleware, a job integration and the app's own rescue all see the
97
+ # same error without sending it more than once.
98
+ def notify_once(error, **options)
99
+ return nil if reported?(error)
100
+
101
+ mark_reported(error)
102
+ notify(error, **options)
103
+ end
104
+
105
+ def reported?(error)
106
+ error.instance_variable_defined?(REPORTED_IVAR)
107
+ rescue StandardError
108
+ false
109
+ end
110
+
111
+ def mark_reported(error)
112
+ error.instance_variable_set(REPORTED_IVAR, true) unless error.frozen?
113
+ rescue StandardError
114
+ nil
115
+ end
116
+
117
+ # Name the user the current request or job is acting for. Errors reported
118
+ # on this thread until `clear_user` carry it as `session.user_id` /
119
+ # `session.user_email`, which errorgap's user filters read.
120
+ def set_user(id: nil, email: nil)
121
+ user = {}
122
+ user[:id] = id.to_s unless id.nil? || id.to_s.empty?
123
+ user[:email] = email.to_s unless email.nil? || email.to_s.empty?
124
+ Thread.current[USER_KEY] = user.empty? ? nil : user
125
+ end
126
+
127
+ def clear_user
128
+ Thread.current[USER_KEY] = nil
129
+ end
130
+
131
+ def current_user
132
+ Thread.current[USER_KEY]
133
+ end
134
+
135
+ # Record a release with errorgap (Deploys): errors are then attributed to
136
+ # the deploy that shipped them. Defaults come from the configuration.
137
+ def track_release(version: nil, sha: nil, branch: nil, environment: nil, sync: true)
138
+ configuration.validate!
139
+ return if configuration.ignored_environment?
140
+
141
+ payload = {
142
+ version: version || configuration.release,
143
+ sha: sha || configuration.release_sha,
144
+ branch: branch || configuration.release_branch,
145
+ environment: environment || configuration.environment
146
+ }.compact
147
+ return unless payload[:version] || payload[:sha]
148
+
149
+ if sync
150
+ delivery.post("releases", payload)
151
+ else
152
+ delivery.enqueue("releases", payload)
153
+ end
154
+ rescue StandardError => exception
155
+ configuration.logger&.warn("[errorgap] release not recorded: #{exception.class}: #{exception.message}")
156
+ nil
157
+ end
158
+
159
+ # `config.track_release`: the first notice of a process records the
160
+ # release, so an app that never errors never calls errorgap.
161
+ def record_release_once
162
+ return if @release_recorded
163
+
164
+ @release_recorded = true
165
+ track_release(sync: false)
166
+ end
167
+
63
168
  # Record a diagnostic breadcrumb attached to subsequent notices.
64
169
  def add_breadcrumb(message, category: nil, metadata: nil)
65
170
  breadcrumbs.add(message, category: category, metadata: metadata)
@@ -110,7 +215,7 @@ module Errorgap
110
215
  if sync || !configuration.async
111
216
  transacter.deliver(transaction)
112
217
  else
113
- register_thread(Thread.new { transacter.deliver(transaction) })
218
+ delivery.enqueue("transactions", transaction.to_h)
114
219
  end
115
220
  end
116
221
 
@@ -160,25 +265,38 @@ module Errorgap
160
265
  end
161
266
  end
162
267
 
163
- # Join any in-flight async delivery threads. Call before process exit to
164
- # avoid dropping queued notices/transactions/logs.
165
- def flush
268
+ # Wait for queued notices, transactions and logs to be sent. Call before
269
+ # process exit; queued payloads are also flushed from an `at_exit` hook.
270
+ def flush(timeout = Delivery::FLUSH_TIMEOUT)
271
+ delivery.flush(timeout)
166
272
  threads = thread_mutex.synchronize do
167
273
  pending = delivery_threads.dup
168
274
  delivery_threads.clear
169
275
  pending
170
276
  end
171
- threads.each { |thread| thread.join(5) }
277
+ threads.each { |thread| thread.join(timeout) }
172
278
  nil
173
279
  end
174
280
 
281
+ # Kept for callers that run their own delivery threads.
175
282
  def register_thread(thread)
176
- thread_mutex.synchronize { delivery_threads << thread }
283
+ thread_mutex.synchronize do
284
+ delivery_threads.select!(&:alive?)
285
+ delivery_threads << thread
286
+ end
177
287
  thread
178
288
  end
179
289
 
180
290
  private
181
291
 
292
+ def user_session
293
+ user = current_user || {}
294
+ session = {}
295
+ session["user_id"] = user[:id] if user[:id]
296
+ session["user_email"] = user[:email] if user[:email]
297
+ session
298
+ end
299
+
182
300
  def sample_apm?
183
301
  rate = configuration.apm_sample_rate.to_f
184
302
  return true if rate >= 1.0
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: errorgap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Errorgap
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-04 00:00:00.000000000 Z
11
+ date: 2026-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -67,16 +67,24 @@ files:
67
67
  - lib/errorgap.rb
68
68
  - lib/errorgap/breadcrumbs.rb
69
69
  - lib/errorgap/configuration.rb
70
+ - lib/errorgap/delivery.rb
71
+ - lib/errorgap/integrations/active_job.rb
72
+ - lib/errorgap/integrations/net_http.rb
73
+ - lib/errorgap/integrations/sidekiq.rb
70
74
  - lib/errorgap/log_delivery.rb
71
75
  - lib/errorgap/notice.rb
72
76
  - lib/errorgap/notifier.rb
73
77
  - lib/errorgap/rack_middleware.rb
74
78
  - lib/errorgap/rails/railtie.rb
79
+ - lib/errorgap/release_detector.rb
80
+ - lib/errorgap/sanitizer.rb
81
+ - lib/errorgap/sign_ins.rb
75
82
  - lib/errorgap/span_collector.rb
76
83
  - lib/errorgap/span_recorder.rb
77
84
  - lib/errorgap/transacter.rb
78
85
  - lib/errorgap/transaction.rb
79
86
  - lib/errorgap/version.rb
87
+ - lib/errorgap/warden_hooks.rb
80
88
  - lib/generators/errorgap/install_generator.rb
81
89
  homepage: https://github.com/errorgaphq/errorgap-ruby
82
90
  licenses: