lapsoss 0.2.0 → 0.3.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +153 -733
  3. data/lib/lapsoss/adapters/appsignal_adapter.rb +22 -22
  4. data/lib/lapsoss/adapters/base.rb +0 -3
  5. data/lib/lapsoss/adapters/insight_hub_adapter.rb +108 -104
  6. data/lib/lapsoss/adapters/logger_adapter.rb +1 -1
  7. data/lib/lapsoss/adapters/rollbar_adapter.rb +108 -68
  8. data/lib/lapsoss/adapters/sentry_adapter.rb +24 -24
  9. data/lib/lapsoss/backtrace_frame.rb +37 -206
  10. data/lib/lapsoss/backtrace_frame_factory.rb +228 -0
  11. data/lib/lapsoss/backtrace_processor.rb +26 -23
  12. data/lib/lapsoss/client.rb +2 -4
  13. data/lib/lapsoss/configuration.rb +28 -32
  14. data/lib/lapsoss/current.rb +10 -2
  15. data/lib/lapsoss/event.rb +28 -5
  16. data/lib/lapsoss/exception_backtrace_frame.rb +39 -0
  17. data/lib/lapsoss/exclusion_configuration.rb +30 -0
  18. data/lib/lapsoss/exclusion_filter.rb +0 -273
  19. data/lib/lapsoss/exclusion_presets.rb +249 -0
  20. data/lib/lapsoss/fingerprinter.rb +28 -28
  21. data/lib/lapsoss/http_client.rb +8 -8
  22. data/lib/lapsoss/merged_scope.rb +63 -0
  23. data/lib/lapsoss/middleware/base.rb +15 -0
  24. data/lib/lapsoss/middleware/conditional_filter.rb +18 -0
  25. data/lib/lapsoss/middleware/event_enricher.rb +19 -0
  26. data/lib/lapsoss/middleware/event_transformer.rb +19 -0
  27. data/lib/lapsoss/middleware/exception_filter.rb +43 -0
  28. data/lib/lapsoss/middleware/metrics_collector.rb +44 -0
  29. data/lib/lapsoss/middleware/rate_limiter.rb +31 -0
  30. data/lib/lapsoss/middleware/release_tracker.rb +117 -0
  31. data/lib/lapsoss/middleware/sample_filter.rb +23 -0
  32. data/lib/lapsoss/middleware/sampling_middleware.rb +18 -0
  33. data/lib/lapsoss/middleware/user_context_enhancer.rb +46 -0
  34. data/lib/lapsoss/middleware.rb +0 -339
  35. data/lib/lapsoss/pipeline.rb +0 -68
  36. data/lib/lapsoss/pipeline_builder.rb +69 -0
  37. data/lib/lapsoss/rails_error_subscriber.rb +42 -0
  38. data/lib/lapsoss/rails_middleware.rb +78 -0
  39. data/lib/lapsoss/railtie.rb +22 -50
  40. data/lib/lapsoss/registry.rb +18 -5
  41. data/lib/lapsoss/release_providers.rb +110 -0
  42. data/lib/lapsoss/release_tracker.rb +159 -232
  43. data/lib/lapsoss/sampling/adaptive_sampler.rb +46 -0
  44. data/lib/lapsoss/sampling/base.rb +11 -0
  45. data/lib/lapsoss/sampling/composite_sampler.rb +26 -0
  46. data/lib/lapsoss/sampling/consistent_hash_sampler.rb +30 -0
  47. data/lib/lapsoss/sampling/exception_type_sampler.rb +44 -0
  48. data/lib/lapsoss/sampling/health_based_sampler.rb +19 -0
  49. data/lib/lapsoss/sampling/rate_limiter.rb +32 -0
  50. data/lib/lapsoss/sampling/sampling_factory.rb +69 -0
  51. data/lib/lapsoss/sampling/time_based_sampler.rb +44 -0
  52. data/lib/lapsoss/sampling/uniform_sampler.rb +15 -0
  53. data/lib/lapsoss/sampling/user_based_sampler.rb +42 -0
  54. data/lib/lapsoss/sampling.rb +0 -322
  55. data/lib/lapsoss/scope.rb +12 -48
  56. data/lib/lapsoss/scrubber.rb +7 -7
  57. data/lib/lapsoss/user_context.rb +30 -203
  58. data/lib/lapsoss/user_context_integrations.rb +39 -0
  59. data/lib/lapsoss/user_context_middleware.rb +50 -0
  60. data/lib/lapsoss/user_context_provider.rb +93 -0
  61. data/lib/lapsoss/utils.rb +13 -0
  62. data/lib/lapsoss/validators.rb +15 -15
  63. data/lib/lapsoss/version.rb +1 -1
  64. data/lib/lapsoss.rb +3 -3
  65. metadata +54 -5
@@ -2,344 +2,5 @@
2
2
 
3
3
  module Lapsoss
4
4
  module Middleware
5
- class Base
6
- def initialize(app)
7
- @app = app
8
- end
9
-
10
- def call(event, hint = {})
11
- @app.call(event, hint)
12
- end
13
- end
14
-
15
- # Built-in middleware for common functionality
16
- class SampleFilter < Base
17
- def initialize(app, sample_rate: 1.0, sample_callback: nil)
18
- super(app)
19
- @sample_rate = sample_rate
20
- @sample_callback = sample_callback
21
- end
22
-
23
- def call(event, hint = {})
24
- # Apply custom sampling logic first
25
- return nil if @sample_callback && !@sample_callback.call(event, hint)
26
-
27
- # Apply rate-based sampling
28
- return nil if (@sample_rate < 1.0) && (rand > @sample_rate)
29
-
30
- @app.call(event, hint)
31
- end
32
- end
33
-
34
- class ExceptionFilter < Base
35
- def initialize(app, excluded_exceptions: [], excluded_patterns: [])
36
- super(app)
37
- @excluded_exceptions = Array(excluded_exceptions)
38
- @excluded_patterns = Array(excluded_patterns)
39
- end
40
-
41
- def call(event, hint = {})
42
- return nil if should_exclude?(event)
43
-
44
- @app.call(event, hint)
45
- end
46
-
47
- private
48
-
49
- def should_exclude?(event)
50
- return false unless event.exception
51
-
52
- exception_class = event.exception.class
53
- exception_message = event.exception.message
54
-
55
- # Check exact class matches
56
- return true if @excluded_exceptions.any? { |klass| exception_class <= klass }
57
-
58
- # Check pattern matches
59
- @excluded_patterns.any? do |pattern|
60
- case pattern
61
- when Regexp
62
- exception_message&.match?(pattern) || exception_class.name.match?(pattern)
63
- when String
64
- exception_message&.include?(pattern) || exception_class.name.include?(pattern)
65
- else
66
- false
67
- end
68
- end
69
- end
70
- end
71
-
72
- class UserContextEnhancer < Base
73
- def initialize(app, user_provider: nil, privacy_mode: false)
74
- super(app)
75
- @user_provider = user_provider
76
- @privacy_mode = privacy_mode
77
- end
78
-
79
- def call(event, hint = {})
80
- enhance_user_context(event, hint)
81
- @app.call(event, hint)
82
- end
83
-
84
- private
85
-
86
- def enhance_user_context(event, hint)
87
- # Get user from provider if available
88
- user_data = @user_provider&.call(event, hint) || {}
89
-
90
- # Merge with existing user context
91
- existing_user = event.context[:user] || {}
92
- enhanced_user = existing_user.merge(user_data)
93
-
94
- # Apply privacy filtering if enabled
95
- enhanced_user = apply_privacy_filtering(enhanced_user) if @privacy_mode
96
-
97
- event.context[:user] = enhanced_user unless enhanced_user.empty?
98
- end
99
-
100
- def apply_privacy_filtering(user_data)
101
- # Remove sensitive fields in privacy mode
102
- sensitive_fields = %i[email phone address ssn credit_card]
103
- filtered = user_data.dup
104
-
105
- sensitive_fields.each do |field|
106
- filtered[field] = '[FILTERED]' if filtered.key?(field)
107
- end
108
-
109
- filtered
110
- end
111
- end
112
-
113
- class ReleaseTracker < Base
114
- def initialize(app, release_provider: nil)
115
- super(app)
116
- @release_provider = release_provider
117
- end
118
-
119
- def call(event, hint = {})
120
- add_release_info(event, hint)
121
- @app.call(event, hint)
122
- end
123
-
124
- private
125
-
126
- def add_release_info(event, hint)
127
- release_info = @release_provider&.call(event, hint) || auto_detect_release
128
-
129
- event.context[:release] = release_info if release_info
130
- end
131
-
132
- def auto_detect_release
133
- release_info = {}
134
-
135
- # Try to detect Git information
136
- if git_info = detect_git_info
137
- release_info.merge!(git_info)
138
- end
139
-
140
- # Try to detect deployment info
141
- if deployment_info = detect_deployment_info
142
- release_info.merge!(deployment_info)
143
- end
144
-
145
- release_info.empty? ? nil : release_info
146
- end
147
-
148
- def detect_git_info
149
- return nil unless File.exist?('.git')
150
-
151
- begin
152
- # Get current commit SHA
153
- commit_sha = `git rev-parse HEAD`.strip
154
- return nil if commit_sha.empty?
155
-
156
- # Get branch name
157
- branch = `git rev-parse --abbrev-ref HEAD`.strip
158
- branch = nil if branch.empty? || branch == 'HEAD'
159
-
160
- # Get commit timestamp
161
- commit_time = `git log -1 --format=%ct`.strip
162
- commit_timestamp = commit_time.empty? ? nil : Time.zone.at(commit_time.to_i)
163
-
164
- # Get tag if on a tag
165
- tag = `git describe --exact-match --tags HEAD 2>/dev/null`.strip
166
- tag = nil if tag.empty?
167
-
168
- {
169
- commit_sha: commit_sha,
170
- branch: branch,
171
- tag: tag,
172
- commit_timestamp: commit_timestamp
173
- }.compact
174
- rescue StandardError
175
- nil
176
- end
177
- end
178
-
179
- def detect_deployment_info
180
- info = {}
181
-
182
- # Check common deployment environment variables
183
- info[:deployment_id] = ENV['DEPLOYMENT_ID'] if ENV['DEPLOYMENT_ID']
184
- info[:build_number] = ENV['BUILD_NUMBER'] if ENV['BUILD_NUMBER']
185
- info[:deployment_time] = parse_deployment_time(ENV['DEPLOYMENT_TIME']) if ENV['DEPLOYMENT_TIME']
186
-
187
- # Check Heroku
188
- if ENV['HEROKU_APP_NAME']
189
- info[:platform] = 'heroku'
190
- info[:app_name] = ENV['HEROKU_APP_NAME']
191
- info[:dyno] = ENV.fetch('DYNO', nil)
192
- info[:slug_commit] = ENV.fetch('HEROKU_SLUG_COMMIT', nil)
193
- end
194
-
195
- # Check AWS
196
- if ENV['AWS_EXECUTION_ENV']
197
- info[:platform] = 'aws'
198
- info[:execution_env] = ENV['AWS_EXECUTION_ENV']
199
- info[:region] = ENV.fetch('AWS_REGION', nil)
200
- end
201
-
202
- # Check Docker
203
- if ENV['DOCKER_CONTAINER_ID'] || File.exist?('/.dockerenv')
204
- info[:platform] = 'docker'
205
- info[:container_id] = ENV['DOCKER_CONTAINER_ID']
206
- end
207
-
208
- # Check Kubernetes
209
- if ENV['KUBERNETES_SERVICE_HOST']
210
- info[:platform] = 'kubernetes'
211
- info[:namespace] = ENV.fetch('KUBERNETES_NAMESPACE', nil)
212
- info[:pod_name] = ENV.fetch('HOSTNAME', nil)
213
- end
214
-
215
- info.empty? ? nil : info
216
- end
217
-
218
- def parse_deployment_time(time_str)
219
- Time.zone.parse(time_str)
220
- rescue StandardError
221
- nil
222
- end
223
- end
224
-
225
- class EventEnricher < Base
226
- def initialize(app, enrichers: [])
227
- super(app)
228
- @enrichers = enrichers
229
- end
230
-
231
- def call(event, hint = {})
232
- @enrichers.each do |enricher|
233
- enricher.call(event, hint)
234
- end
235
- @app.call(event, hint)
236
- end
237
- end
238
-
239
- class ConditionalFilter < Base
240
- def initialize(app, condition)
241
- super(app)
242
- @condition = condition
243
- end
244
-
245
- def call(event, hint = {})
246
- return nil unless @condition.call(event, hint)
247
-
248
- @app.call(event, hint)
249
- end
250
- end
251
-
252
- class EventTransformer < Base
253
- def initialize(app, transformer)
254
- super(app)
255
- @transformer = transformer
256
- end
257
-
258
- def call(event, hint = {})
259
- transformed_event = @transformer.call(event, hint)
260
- return nil unless transformed_event
261
-
262
- @app.call(transformed_event, hint)
263
- end
264
- end
265
-
266
- class RateLimiter < Base
267
- def initialize(app, max_events: 100, time_window: 60)
268
- super(app)
269
- @max_events = max_events
270
- @time_window = time_window
271
- @events = []
272
- @mutex = Mutex.new
273
- end
274
-
275
- def call(event, hint = {})
276
- @mutex.synchronize do
277
- now = Time.zone.now
278
- # Remove old events outside time window
279
- @events.reject! { |timestamp| now - timestamp > @time_window }
280
-
281
- # Check if we're over the limit
282
- return nil if @events.length >= @max_events
283
-
284
- # Add current event
285
- @events << now
286
- end
287
-
288
- @app.call(event, hint)
289
- end
290
- end
291
-
292
- class MetricsCollector < Base
293
- def initialize(app, collector: nil)
294
- super(app)
295
- @collector = collector
296
- @metrics = {
297
- events_processed: 0,
298
- events_dropped: 0,
299
- events_by_type: Hash.new(0),
300
- events_by_level: Hash.new(0)
301
- }
302
- @mutex = Mutex.new
303
- end
304
-
305
- def call(event, hint = {})
306
- @mutex.synchronize do
307
- @metrics[:events_processed] += 1
308
- @metrics[:events_by_type][event.type] += 1
309
- @metrics[:events_by_level][event.level] += 1
310
- end
311
-
312
- result = @app.call(event, hint)
313
-
314
- if result.nil?
315
- @mutex.synchronize do
316
- @metrics[:events_dropped] += 1
317
- end
318
- end
319
-
320
- # Send to external collector if provided
321
- @collector&.call(@metrics.dup, event, hint)
322
-
323
- result
324
- end
325
-
326
- def metrics
327
- @mutex.synchronize { @metrics.dup }
328
- end
329
- end
330
-
331
- # Middleware wrapper for sampling strategies
332
- class SamplingMiddleware < Base
333
- def initialize(app, sampler)
334
- super(app)
335
- @sampler = sampler
336
- end
337
-
338
- def call(event, hint = {})
339
- return nil unless @sampler.sample?(event, hint)
340
-
341
- @app.call(event, hint)
342
- end
343
- end
344
5
  end
345
6
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'middleware'
4
-
5
3
  module Lapsoss
6
4
  class Pipeline
7
5
  def initialize
@@ -58,70 +56,4 @@ module Lapsoss
58
56
  end
59
57
  end
60
58
  end
61
-
62
- class PipelineBuilder
63
- def initialize
64
- @pipeline = Pipeline.new
65
- end
66
-
67
- def sample(rate: 1.0, &block)
68
- @pipeline.use(Middleware::SampleFilter, sample_rate: rate, sample_callback: block)
69
- self
70
- end
71
-
72
- def exclude_exceptions(*exception_classes, patterns: [])
73
- @pipeline.use(Middleware::ExceptionFilter,
74
- excluded_exceptions: exception_classes,
75
- excluded_patterns: patterns)
76
- self
77
- end
78
-
79
- def enhance_user_context(provider: nil, privacy_mode: false)
80
- @pipeline.use(Middleware::UserContextEnhancer,
81
- user_provider: provider,
82
- privacy_mode: privacy_mode)
83
- self
84
- end
85
-
86
- def track_releases(provider: nil)
87
- @pipeline.use(Middleware::ReleaseTracker, release_provider: provider)
88
- self
89
- end
90
-
91
- def rate_limit(max_events: 100, time_window: 60)
92
- @pipeline.use(Middleware::RateLimiter,
93
- max_events: max_events,
94
- time_window: time_window)
95
- self
96
- end
97
-
98
- def collect_metrics(collector: nil)
99
- @pipeline.use(Middleware::MetricsCollector, collector: collector)
100
- self
101
- end
102
-
103
- def enrich_events(*enrichers)
104
- @pipeline.use(Middleware::EventEnricher, enrichers: enrichers)
105
- self
106
- end
107
-
108
- def filter_if(&condition)
109
- @pipeline.use(Middleware::ConditionalFilter, condition)
110
- self
111
- end
112
-
113
- def transform_events(&transformer)
114
- @pipeline.use(Middleware::EventTransformer, transformer)
115
- self
116
- end
117
-
118
- def use_middleware(middleware_class, *, **)
119
- @pipeline.use(middleware_class, *, **)
120
- self
121
- end
122
-
123
- delegate :build, to: :@pipeline
124
-
125
- attr_reader :pipeline
126
- end
127
59
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lapsoss
4
+ class PipelineBuilder
5
+ def initialize
6
+ @pipeline = Pipeline.new
7
+ end
8
+
9
+ def sample(rate: 1.0, &block)
10
+ @pipeline.use(Middleware::SampleFilter, sample_rate: rate, sample_callback: block)
11
+ self
12
+ end
13
+
14
+ def exclude_exceptions(*exception_classes, patterns: [])
15
+ @pipeline.use(Middleware::ExceptionFilter,
16
+ excluded_exceptions: exception_classes,
17
+ excluded_patterns: patterns)
18
+ self
19
+ end
20
+
21
+ def enhance_user_context(provider: nil, privacy_mode: false)
22
+ @pipeline.use(Middleware::UserContextEnhancer,
23
+ user_provider: provider,
24
+ privacy_mode: privacy_mode)
25
+ self
26
+ end
27
+
28
+ def track_releases(provider: nil)
29
+ @pipeline.use(Middleware::ReleaseTracker, release_provider: provider)
30
+ self
31
+ end
32
+
33
+ def rate_limit(max_events: 100, time_window: 60)
34
+ @pipeline.use(Middleware::RateLimiter,
35
+ max_events: max_events,
36
+ time_window: time_window)
37
+ self
38
+ end
39
+
40
+ def collect_metrics(collector: nil)
41
+ @pipeline.use(Middleware::MetricsCollector, collector: collector)
42
+ self
43
+ end
44
+
45
+ def enrich_events(*enrichers)
46
+ @pipeline.use(Middleware::EventEnricher, enrichers: enrichers)
47
+ self
48
+ end
49
+
50
+ def filter_if(&condition)
51
+ @pipeline.use(Middleware::ConditionalFilter, condition)
52
+ self
53
+ end
54
+
55
+ def transform_events(&transformer)
56
+ @pipeline.use(Middleware::EventTransformer, transformer)
57
+ self
58
+ end
59
+
60
+ def use_middleware(middleware_class, *, **)
61
+ @pipeline.use(middleware_class, *, **)
62
+ self
63
+ end
64
+
65
+ delegate :build, to: :@pipeline
66
+
67
+ attr_reader :pipeline
68
+ end
69
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lapsoss
4
+ class RailsErrorSubscriber
5
+ def report(error, handled:, severity:, context:, source: nil)
6
+ # Skip certain framework errors
7
+ return if skip_error?(error, source)
8
+
9
+ level = map_severity(severity)
10
+
11
+ Lapsoss.capture_exception(
12
+ error,
13
+ level: level,
14
+ tags: {
15
+ handled: handled,
16
+ source: source || "rails"
17
+ },
18
+ context: context
19
+ )
20
+ end
21
+
22
+ private
23
+
24
+ def skip_error?(error, source)
25
+ # Skip cache-related Redis errors if configured to do so
26
+ if Lapsoss.configuration.skip_rails_cache_errors && source&.include?("cache") && error.is_a?(Redis::CannotConnectError)
27
+ return true
28
+ end
29
+
30
+ false
31
+ end
32
+
33
+ def map_severity(severity)
34
+ case severity
35
+ when :error then :error
36
+ when :warning then :warning
37
+ when :info then :info
38
+ else :error
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lapsoss
4
+ class RailsMiddleware
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ Lapsoss::Current.with_clean_scope do
11
+ # Add request context to current scope
12
+ if Lapsoss.configuration.capture_request_context
13
+ Rails.logger.debug "[Lapsoss] Adding request context" if Rails.env.test?
14
+ add_request_context(env)
15
+ end
16
+
17
+ begin
18
+ @app.call(env)
19
+ rescue Exception => e
20
+ Rails.logger.debug { "[Lapsoss] Capturing exception: #{e.class} - #{e.message}" } if Rails.env.test?
21
+ # Capture the exception
22
+ Lapsoss.capture_exception(e)
23
+ # Re-raise the exception to maintain Rails error handling
24
+ raise
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def add_request_context(env)
32
+ request = Rack::Request.new(env)
33
+
34
+ return unless Lapsoss::Current.scope
35
+
36
+ Lapsoss::Current.scope.set_context("request", {
37
+ method: request.request_method,
38
+ url: request.url,
39
+ path: request.path,
40
+ query_string: request.query_string,
41
+ headers: extract_headers(env),
42
+ ip: request.ip,
43
+ user_agent: request.user_agent,
44
+ referer: request.referer,
45
+ request_id: env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"]
46
+ })
47
+
48
+ # Add user context if available
49
+ return unless env["warden"]&.user
50
+
51
+ user = env["warden"].user
52
+ Lapsoss::Current.scope.set_user(
53
+ id: user.id,
54
+ email: user.respond_to?(:email) ? user.email : nil
55
+ )
56
+ end
57
+
58
+ def extract_headers(env)
59
+ headers = {}
60
+
61
+ env.each do |key, value|
62
+ if key.start_with?("HTTP_") && FILTERED_HEADERS.exclude?(key)
63
+ header_name = key.sub(/^HTTP_/, "").split("_").map(&:capitalize).join("-")
64
+ headers[header_name] = value
65
+ end
66
+ end
67
+
68
+ headers
69
+ end
70
+
71
+ FILTERED_HEADERS = %w[
72
+ HTTP_AUTHORIZATION
73
+ HTTP_COOKIE
74
+ HTTP_X_API_KEY
75
+ HTTP_X_AUTH_TOKEN
76
+ ].freeze
77
+ end
78
+ end
@@ -2,71 +2,43 @@
2
2
 
3
3
  module Lapsoss
4
4
  class Railtie < Rails::Railtie
5
+ Rails.logger.debug "[Lapsoss] Railtie loaded" if ENV["DEBUG_LAPSOSS"]
5
6
  config.lapsoss = ActiveSupport::OrderedOptions.new
6
7
 
7
- initializer 'lapsoss.configure' do |_app|
8
+ initializer "lapsoss.configure" do |_app|
8
9
  Lapsoss.configure do |config|
9
- config.environment ||= Rails.env
10
+ # Use rails_app_version gem if available, otherwise fallback to Rails defaults
11
+ config.environment ||= if Rails.application.respond_to?(:env)
12
+ Rails.application.env
13
+ else
14
+ Rails.env
15
+ end
16
+
10
17
  config.logger ||= Rails.logger
11
- config.release ||= Rails.application.config.try(:release)
18
+
19
+ config.release ||= if Rails.application.respond_to?(:version)
20
+ Rails.application.version.to_s
21
+ else
22
+ Rails.application.config.try(:release)
23
+ end
12
24
 
13
25
  # Set default tags
14
26
  config.default_tags = {
15
- rails_env: Rails.env,
27
+ rails_env: config.environment,
16
28
  rails_version: Rails.version
17
29
  }
18
30
  end
19
31
  end
20
32
 
21
- initializer 'lapsoss.add_middleware', after: 'lapsoss.configure' do |app|
22
- app.middleware.insert_before ActionDispatch::ShowExceptions, Lapsoss::Middleware
23
- end
24
-
25
- initializer 'lapsoss.rails_error_subscriber', after: 'lapsoss.add_middleware' do |app|
26
- app.executor.error_reporter.subscribe(Lapsoss::RailsErrorSubscriber.new)
27
- end
28
-
29
- rake_tasks do
30
- # Add any Lapsoss-specific rake tasks here
31
- end
32
- end
33
-
34
- class RailsErrorSubscriber
35
- def report(error, handled:, severity:, context:, source: nil)
36
- # Skip certain framework errors
37
- return if skip_error?(error, source)
38
-
39
- level = map_severity(severity)
33
+ initializer "lapsoss.add_middleware" do |app|
34
+ require "lapsoss/rails_middleware"
40
35
 
41
- Lapsoss.capture_exception(
42
- error,
43
- level: level,
44
- tags: {
45
- handled: handled,
46
- source: source || 'rails'
47
- },
48
- context: context
49
- )
36
+ # Use config.middleware to ensure it's added during initialization
37
+ app.config.middleware.use Lapsoss::RailsMiddleware
50
38
  end
51
39
 
52
- private
53
-
54
- def skip_error?(error, source)
55
- # Skip cache-related Redis errors if configured to do so
56
- if Lapsoss.configuration.skip_rails_cache_errors && source&.include?('cache') && error.is_a?(Redis::CannotConnectError)
57
- return true
58
- end
59
-
60
- false
61
- end
62
-
63
- def map_severity(severity)
64
- case severity
65
- when :error then :error
66
- when :warning then :warning
67
- when :info then :info
68
- else :error
69
- end
40
+ initializer "lapsoss.rails_error_subscriber", after: "lapsoss.add_middleware" do |app|
41
+ app.executor.error_reporter.subscribe(Lapsoss::RailsErrorSubscriber.new)
70
42
  end
71
43
  end
72
44
  end