lapsoss 0.1.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +153 -733
  3. data/lib/lapsoss/adapters/appsignal_adapter.rb +7 -8
  4. data/lib/lapsoss/adapters/base.rb +0 -3
  5. data/lib/lapsoss/adapters/bugsnag_adapter.rb +12 -0
  6. data/lib/lapsoss/adapters/insight_hub_adapter.rb +102 -101
  7. data/lib/lapsoss/adapters/logger_adapter.rb +7 -7
  8. data/lib/lapsoss/adapters/rollbar_adapter.rb +93 -54
  9. data/lib/lapsoss/adapters/sentry_adapter.rb +11 -17
  10. data/lib/lapsoss/backtrace_frame.rb +35 -214
  11. data/lib/lapsoss/backtrace_frame_factory.rb +228 -0
  12. data/lib/lapsoss/backtrace_processor.rb +37 -37
  13. data/lib/lapsoss/client.rb +2 -6
  14. data/lib/lapsoss/configuration.rb +25 -22
  15. data/lib/lapsoss/current.rb +9 -1
  16. data/lib/lapsoss/event.rb +30 -6
  17. data/lib/lapsoss/exception_backtrace_frame.rb +39 -0
  18. data/lib/lapsoss/exclusion_configuration.rb +30 -0
  19. data/lib/lapsoss/exclusion_filter.rb +156 -0
  20. data/lib/lapsoss/{exclusions.rb → exclusion_presets.rb} +1 -181
  21. data/lib/lapsoss/fingerprinter.rb +9 -13
  22. data/lib/lapsoss/http_client.rb +42 -8
  23. data/lib/lapsoss/merged_scope.rb +63 -0
  24. data/lib/lapsoss/middleware/base.rb +15 -0
  25. data/lib/lapsoss/middleware/conditional_filter.rb +18 -0
  26. data/lib/lapsoss/middleware/event_enricher.rb +19 -0
  27. data/lib/lapsoss/middleware/event_transformer.rb +19 -0
  28. data/lib/lapsoss/middleware/exception_filter.rb +43 -0
  29. data/lib/lapsoss/middleware/metrics_collector.rb +44 -0
  30. data/lib/lapsoss/middleware/rate_limiter.rb +31 -0
  31. data/lib/lapsoss/middleware/release_tracker.rb +117 -0
  32. data/lib/lapsoss/middleware/sample_filter.rb +23 -0
  33. data/lib/lapsoss/middleware/sampling_middleware.rb +18 -0
  34. data/lib/lapsoss/middleware/user_context_enhancer.rb +46 -0
  35. data/lib/lapsoss/middleware.rb +0 -347
  36. data/lib/lapsoss/pipeline.rb +1 -73
  37. data/lib/lapsoss/pipeline_builder.rb +69 -0
  38. data/lib/lapsoss/rails_error_subscriber.rb +42 -0
  39. data/lib/lapsoss/rails_middleware.rb +78 -0
  40. data/lib/lapsoss/railtie.rb +22 -50
  41. data/lib/lapsoss/registry.rb +34 -20
  42. data/lib/lapsoss/release_providers.rb +110 -0
  43. data/lib/lapsoss/release_tracker.rb +112 -207
  44. data/lib/lapsoss/router.rb +3 -5
  45. data/lib/lapsoss/sampling/adaptive_sampler.rb +46 -0
  46. data/lib/lapsoss/sampling/base.rb +11 -0
  47. data/lib/lapsoss/sampling/composite_sampler.rb +26 -0
  48. data/lib/lapsoss/sampling/consistent_hash_sampler.rb +30 -0
  49. data/lib/lapsoss/sampling/exception_type_sampler.rb +44 -0
  50. data/lib/lapsoss/sampling/health_based_sampler.rb +19 -0
  51. data/lib/lapsoss/sampling/rate_limiter.rb +32 -0
  52. data/lib/lapsoss/sampling/sampling_factory.rb +69 -0
  53. data/lib/lapsoss/sampling/time_based_sampler.rb +44 -0
  54. data/lib/lapsoss/sampling/uniform_sampler.rb +15 -0
  55. data/lib/lapsoss/sampling/user_based_sampler.rb +42 -0
  56. data/lib/lapsoss/sampling.rb +0 -326
  57. data/lib/lapsoss/scope.rb +17 -57
  58. data/lib/lapsoss/scrubber.rb +16 -18
  59. data/lib/lapsoss/user_context.rb +18 -198
  60. data/lib/lapsoss/user_context_integrations.rb +39 -0
  61. data/lib/lapsoss/user_context_middleware.rb +50 -0
  62. data/lib/lapsoss/user_context_provider.rb +93 -0
  63. data/lib/lapsoss/utils.rb +13 -0
  64. data/lib/lapsoss/validators.rb +14 -27
  65. data/lib/lapsoss/version.rb +1 -1
  66. data/lib/lapsoss.rb +12 -25
  67. metadata +106 -21
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "concurrent"
4
- require_relative "scope"
5
- require_relative "current"
6
4
 
7
5
  module Lapsoss
8
6
  class Client
@@ -47,7 +45,7 @@ module Lapsoss
47
45
  original_scope = current_scope
48
46
 
49
47
  # Create a merged scope with the new context
50
- merged_scope = MergedScope.new([context], original_scope)
48
+ merged_scope = MergedScope.new([ context ], original_scope)
51
49
  Current.scope = merged_scope
52
50
 
53
51
  yield(merged_scope)
@@ -70,8 +68,6 @@ module Lapsoss
70
68
 
71
69
  private
72
70
 
73
-
74
-
75
71
  def capture_event(event)
76
72
  # Apply pipeline processing if enabled
77
73
  if @configuration.enable_pipeline && @configuration.pipeline
@@ -87,7 +83,7 @@ module Lapsoss
87
83
  else
88
84
  Router.process_event(event)
89
85
  end
90
- rescue => e
86
+ rescue StandardError => e
91
87
  handle_capture_error(e)
92
88
  end
93
89
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "validators"
4
3
  require "active_support/configurable"
5
4
 
6
5
  module Lapsoss
@@ -8,19 +7,16 @@ module Lapsoss
8
7
  include Validators
9
8
  include ActiveSupport::Configurable
10
9
 
11
- attr_accessor :async, :logger, :enabled, :release,
10
+ attr_accessor :async, :logger, :enabled, :release, :debug,
12
11
  :scrub_fields, :scrub_all, :whitelist_fields, :randomize_scrub_length,
13
12
  :transport_jitter, :fingerprint_patterns,
14
13
  :normalize_fingerprint_paths, :normalize_fingerprint_ids, :fingerprint_include_environment,
15
14
  :backtrace_context_lines, :backtrace_in_app_patterns, :backtrace_exclude_patterns,
16
15
  :backtrace_strip_load_path, :backtrace_max_frames, :backtrace_enable_code_context,
17
16
  :enable_pipeline, :pipeline_builder, :sampling_strategy,
18
- :skip_rails_cache_errors
19
- attr_reader :fingerprint_callback
20
- attr_reader :environment, :before_send, :sample_rate, :error_handler,
21
- :transport_timeout, :transport_max_retries, :transport_initial_backoff,
22
- :transport_max_backoff, :transport_backoff_multiplier, :transport_ssl_verify
23
- attr_reader :default_context, :adapter_configs
17
+ :skip_rails_cache_errors, :force_sync_http, :capture_request_context
18
+ attr_reader :fingerprint_callback, :environment, :before_send, :sample_rate, :error_handler, :transport_timeout,
19
+ :transport_max_retries, :transport_initial_backoff, :transport_max_backoff, :transport_backoff_multiplier, :transport_ssl_verify, :default_context, :adapter_configs
24
20
 
25
21
  def initialize
26
22
  @adapter_configs = {}
@@ -29,11 +25,12 @@ module Lapsoss
29
25
  @environment = nil
30
26
  @enabled = true
31
27
  @release = nil
28
+ @debug = false
32
29
  @before_send = nil
33
30
  @sample_rate = 1.0
34
31
  @default_context = {}
35
32
  @error_handler = nil
36
- @scrub_fields = nil # Will use defaults from Scrubber
33
+ @scrub_fields = nil # Will use defaults from Scrubber
37
34
  @scrub_all = false
38
35
  @whitelist_fields = []
39
36
  @randomize_scrub_length = false
@@ -47,7 +44,7 @@ module Lapsoss
47
44
  @transport_ssl_verify = true
48
45
  # Fingerprinting settings
49
46
  @fingerprint_callback = nil
50
- @fingerprint_patterns = nil # Will use defaults from Fingerprinter
47
+ @fingerprint_patterns = nil # Will use defaults from Fingerprinter
51
48
  @normalize_fingerprint_paths = true
52
49
  @normalize_fingerprint_ids = true
53
50
  @fingerprint_include_environment = false
@@ -64,9 +61,12 @@ module Lapsoss
64
61
  @sampling_strategy = nil
65
62
  # Rails error filtering
66
63
  @skip_rails_cache_errors = true
64
+ # HTTP client settings
65
+ @force_sync_http = false
66
+ # Capture request context in middleware
67
+ @capture_request_context = true
67
68
  end
68
69
 
69
-
70
70
  # Register a named adapter configuration
71
71
  #
72
72
  # @param name [Symbol] Unique name for this adapter instance
@@ -99,7 +99,6 @@ module Lapsoss
99
99
  register_adapter(name, :bugsnag, **settings)
100
100
  end
101
101
 
102
-
103
102
  # Convenience method for Rollbar
104
103
  def use_rollbar(name: :rollbar, **settings)
105
104
  register_adapter(name, :rollbar, **settings)
@@ -164,9 +163,16 @@ module Lapsoss
164
163
  initialize
165
164
  end
166
165
 
166
+ def debug?
167
+ @debug
168
+ end
169
+
170
+ def async?
171
+ @async
172
+ end
173
+
167
174
  # Pipeline configuration
168
- def configure_pipeline(&block)
169
- require_relative "pipeline"
175
+ def configure_pipeline
170
176
  @pipeline_builder = PipelineBuilder.new
171
177
  yield(@pipeline_builder) if block_given?
172
178
  @pipeline_builder
@@ -178,8 +184,6 @@ module Lapsoss
178
184
 
179
185
  # Sampling configuration
180
186
  def configure_sampling(strategy = nil, &block)
181
- require_relative "sampling"
182
-
183
187
  if strategy
184
188
  @sampling_strategy = strategy
185
189
  elsif block_given?
@@ -188,8 +192,6 @@ module Lapsoss
188
192
  end
189
193
 
190
194
  def create_sampling_strategy
191
- require_relative "sampling"
192
-
193
195
  case @sampling_strategy
194
196
  when Symbol
195
197
  case @sampling_strategy
@@ -254,7 +256,7 @@ module Lapsoss
254
256
 
255
257
  def transport_backoff_multiplier=(value)
256
258
  if value
257
- validate_type!(value, [Numeric], "transport_backoff_multiplier")
259
+ validate_type!(value, [ Numeric ], "transport_backoff_multiplier")
258
260
  validate_numeric_range!(value, 1.0..10.0, "transport_backoff_multiplier")
259
261
  end
260
262
  @transport_backoff_multiplier = value
@@ -285,13 +287,14 @@ module Lapsoss
285
287
  validate_timeout!(@transport_max_backoff, "transport_max_backoff")
286
288
 
287
289
  if @transport_backoff_multiplier
288
- validate_type!(@transport_backoff_multiplier, [Numeric], "transport_backoff_multiplier")
290
+ validate_type!(@transport_backoff_multiplier, [ Numeric ], "transport_backoff_multiplier")
289
291
  validate_numeric_range!(@transport_backoff_multiplier, 1.0..10.0, "transport_backoff_multiplier")
290
292
  end
291
293
 
292
294
  # Validate that initial backoff is less than max backoff
293
295
  if @transport_initial_backoff && @transport_max_backoff && @transport_initial_backoff > @transport_max_backoff
294
- raise ValidationError, "transport_initial_backoff (#{@transport_initial_backoff}) must be less than transport_max_backoff (#{@transport_max_backoff})"
296
+ raise ValidationError,
297
+ "transport_initial_backoff (#{@transport_initial_backoff}) must be less than transport_max_backoff (#{@transport_max_backoff})"
295
298
  end
296
299
 
297
300
  # Validate adapter configurations
@@ -304,7 +307,7 @@ module Lapsoss
304
307
 
305
308
  def validate_adapter_config!(name, config)
306
309
  validate_presence!(config[:type], "adapter type for '#{name}'")
307
- validate_type!(config[:settings], [Hash], "adapter settings for '#{name}'")
310
+ validate_type!(config[:settings], [ Hash ], "adapter settings for '#{name}'")
308
311
  end
309
312
  end
310
313
  end
@@ -4,6 +4,14 @@ require "active_support/all"
4
4
 
5
5
  module Lapsoss
6
6
  class Current < ActiveSupport::CurrentAttributes
7
- attribute :scope
7
+ attribute :scope, default: -> { Scope.new }
8
+
9
+ def self.with_clean_scope
10
+ previous_scope = scope
11
+ self.scope = Scope.new
12
+ yield
13
+ ensure
14
+ self.scope = previous_scope
15
+ end
8
16
  end
9
17
  end
data/lib/lapsoss/event.rb CHANGED
@@ -1,17 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "scrubber"
4
- require_relative "fingerprinter"
5
- require_relative "backtrace_processor"
6
-
7
3
  module Lapsoss
8
4
  class Event
9
- attr_accessor :type, :timestamp, :level, :message, :exception, :context, :environment, :fingerprint, :backtrace_frames
5
+ attr_accessor :type, :timestamp, :level, :message, :exception, :context, :environment, :fingerprint,
6
+ :backtrace_frames
10
7
 
11
8
  def initialize(type:, level: :info, **attributes)
12
9
  @type = type
13
10
  @level = level
14
- @timestamp = Time.now
11
+ @timestamp = Utils.current_time
15
12
  @context = {}
16
13
  @environment = Lapsoss.configuration.environment
17
14
 
@@ -22,6 +19,9 @@ module Lapsoss
22
19
  # Process backtrace if we have an exception
23
20
  @backtrace_frames = process_backtrace if @exception
24
21
 
22
+ # Set message from exception if not provided
23
+ @message ||= @exception.message if @exception
24
+
25
25
  # Generate fingerprint after all attributes are set (unless explicitly set to nil or a value)
26
26
  @fingerprint = generate_fingerprint if @fingerprint.nil? && !attributes.key?(:fingerprint)
27
27
  end
@@ -103,5 +103,29 @@ module Lapsoss
103
103
 
104
104
  @backtrace_frames.map(&:to_h)
105
105
  end
106
+
107
+ public
108
+
109
+ def exception_type
110
+ exception&.class&.name
111
+ end
112
+
113
+ def backtrace
114
+ if @backtrace_frames
115
+ @backtrace_frames.map(&:raw_line)
116
+ elsif exception
117
+ exception.backtrace
118
+ else
119
+ []
120
+ end
121
+ end
122
+
123
+ def request_context
124
+ # Request context is stored in extra by the middleware
125
+ # Check both string and symbol keys for compatibility
126
+ return nil unless context[:extra]
127
+
128
+ context[:extra]["request"] || context[:extra][:request]
129
+ end
106
130
  end
107
131
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lapsoss
4
+ # Wrapper for BacktraceFrame that adds exception-specific metadata
5
+ class ExceptionBacktraceFrame
6
+ attr_reader :frame, :exception_class, :is_crash_frame
7
+
8
+ def initialize(frame, exception_class: nil, is_crash_frame: false)
9
+ @frame = frame
10
+ @exception_class = exception_class
11
+ @is_crash_frame = is_crash_frame
12
+ end
13
+
14
+ def crash_frame?
15
+ @is_crash_frame
16
+ end
17
+
18
+ # Delegate all other methods to the wrapped frame
19
+ def method_missing(method, *, &)
20
+ if @frame.respond_to?(method)
21
+ @frame.send(method, *, &)
22
+ else
23
+ super
24
+ end
25
+ end
26
+
27
+ def respond_to_missing?(method, include_private = false)
28
+ @frame.respond_to?(method, include_private) || super
29
+ end
30
+
31
+ # Override to_h to include exception metadata
32
+ def to_h
33
+ @frame.to_h.merge(
34
+ exception_class: @exception_class,
35
+ crash_frame: @is_crash_frame
36
+ ).compact
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lapsoss
4
+ # Configuration helper for exclusions
5
+ module ExclusionConfiguration
6
+ def self.configure_exclusions(config, preset: nil, **custom_config)
7
+ exclusion_config = if preset
8
+ case preset
9
+ when Array
10
+ ExclusionPresets.combined(preset)
11
+ else
12
+ ExclusionPresets.send(preset)
13
+ end
14
+ else
15
+ {}
16
+ end
17
+
18
+ # Merge custom configuration
19
+ exclusion_config.merge!(custom_config)
20
+
21
+ # Create exclusion filter
22
+ exclusion_filter = ExclusionFilter.new(exclusion_config)
23
+
24
+ # Add to configuration
25
+ config.exclusion_filter = exclusion_filter
26
+
27
+ exclusion_filter
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lapsoss
4
+ # Error exclusion system for filtering exception types
5
+ class ExclusionFilter
6
+ def initialize(configuration = {})
7
+ @excluded_exceptions = configuration[:excluded_exceptions] || []
8
+ @excluded_patterns = configuration[:excluded_patterns] || []
9
+ @excluded_messages = configuration[:excluded_messages] || []
10
+ @excluded_environments = configuration[:excluded_environments] || []
11
+ @custom_filters = configuration[:custom_filters] || []
12
+ @inclusion_overrides = configuration[:inclusion_overrides] || []
13
+ end
14
+
15
+ def should_exclude?(event)
16
+ # Check inclusion overrides first - these take precedence
17
+ return false if should_include_override?(event)
18
+
19
+ # Apply exclusion filters
20
+ return true if excluded_by_exception_type?(event)
21
+ return true if excluded_by_pattern?(event)
22
+ return true if excluded_by_message?(event)
23
+ return true if excluded_by_environment?(event)
24
+ return true if excluded_by_custom_filter?(event)
25
+
26
+ false
27
+ end
28
+
29
+ def add_exclusion(type, value)
30
+ case type
31
+ when :exception
32
+ @excluded_exceptions << value
33
+ when :pattern
34
+ @excluded_patterns << value
35
+ when :message
36
+ @excluded_messages << value
37
+ when :environment
38
+ @excluded_environments << value
39
+ when :custom
40
+ @custom_filters << value
41
+ else
42
+ raise ArgumentError, "Unknown exclusion type: #{type}"
43
+ end
44
+ end
45
+
46
+ def add_inclusion_override(filter)
47
+ @inclusion_overrides << filter
48
+ end
49
+
50
+ def clear_exclusions(type = nil)
51
+ if type
52
+ case type
53
+ when :exception then @excluded_exceptions.clear
54
+ when :pattern then @excluded_patterns.clear
55
+ when :message then @excluded_messages.clear
56
+ when :environment then @excluded_environments.clear
57
+ when :custom then @custom_filters.clear
58
+ else raise ArgumentError, "Unknown exclusion type: #{type}"
59
+ end
60
+ else
61
+ @excluded_exceptions.clear
62
+ @excluded_patterns.clear
63
+ @excluded_messages.clear
64
+ @excluded_environments.clear
65
+ @custom_filters.clear
66
+ end
67
+ end
68
+
69
+ def exclusion_stats
70
+ {
71
+ excluded_exceptions: @excluded_exceptions.length,
72
+ excluded_patterns: @excluded_patterns.length,
73
+ excluded_messages: @excluded_messages.length,
74
+ excluded_environments: @excluded_environments.length,
75
+ custom_filters: @custom_filters.length,
76
+ inclusion_overrides: @inclusion_overrides.length
77
+ }
78
+ end
79
+
80
+ private
81
+
82
+ def should_include_override?(event)
83
+ @inclusion_overrides.any? { |filter| filter.call(event) }
84
+ end
85
+
86
+ def excluded_by_exception_type?(event)
87
+ return false unless event.exception
88
+
89
+ exception_class = event.exception.class
90
+
91
+ @excluded_exceptions.any? do |excluded|
92
+ case excluded
93
+ when Class
94
+ exception_class <= excluded
95
+ when String
96
+ exception_class.name == excluded || exception_class.name.include?(excluded)
97
+ when Regexp
98
+ exception_class.name.match?(excluded)
99
+ else
100
+ false
101
+ end
102
+ end
103
+ end
104
+
105
+ def excluded_by_pattern?(event)
106
+ return false unless event.exception
107
+
108
+ exception_class = event.exception.class
109
+ exception_message = event.exception.message
110
+
111
+ @excluded_patterns.any? do |pattern|
112
+ case pattern
113
+ when Regexp
114
+ exception_class.name.match?(pattern) || exception_message.match?(pattern)
115
+ when String
116
+ exception_class.name.include?(pattern) || exception_message.include?(pattern)
117
+ else
118
+ false
119
+ end
120
+ end
121
+ end
122
+
123
+ def excluded_by_message?(event)
124
+ return false unless event.exception
125
+
126
+ exception_message = event.exception.message
127
+
128
+ @excluded_messages.any? do |excluded_message|
129
+ case excluded_message
130
+ when Regexp
131
+ exception_message.match?(excluded_message)
132
+ when String
133
+ exception_message.include?(excluded_message)
134
+ else
135
+ false
136
+ end
137
+ end
138
+ end
139
+
140
+ def excluded_by_environment?(event)
141
+ return false if @excluded_environments.empty?
142
+
143
+ environment = event.context[:environment] ||
144
+ event.context.dig(:tags, :environment) ||
145
+ Lapsoss.configuration.environment
146
+
147
+ return false unless environment
148
+
149
+ @excluded_environments.include?(environment.to_s)
150
+ end
151
+
152
+ def excluded_by_custom_filter?(event)
153
+ @custom_filters.any? { |filter| filter.call(event) }
154
+ end
155
+ end
156
+ end
@@ -1,159 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lapsoss
4
- # Error exclusion system for filtering exception types
5
- class ExclusionFilter
6
- def initialize(configuration = {})
7
- @excluded_exceptions = configuration[:excluded_exceptions] || []
8
- @excluded_patterns = configuration[:excluded_patterns] || []
9
- @excluded_messages = configuration[:excluded_messages] || []
10
- @excluded_environments = configuration[:excluded_environments] || []
11
- @custom_filters = configuration[:custom_filters] || []
12
- @inclusion_overrides = configuration[:inclusion_overrides] || []
13
- end
14
-
15
- def should_exclude?(event)
16
- # Check inclusion overrides first - these take precedence
17
- return false if should_include_override?(event)
18
-
19
- # Apply exclusion filters
20
- return true if excluded_by_exception_type?(event)
21
- return true if excluded_by_pattern?(event)
22
- return true if excluded_by_message?(event)
23
- return true if excluded_by_environment?(event)
24
- return true if excluded_by_custom_filter?(event)
25
-
26
- false
27
- end
28
-
29
- def add_exclusion(type, value)
30
- case type
31
- when :exception
32
- @excluded_exceptions << value
33
- when :pattern
34
- @excluded_patterns << value
35
- when :message
36
- @excluded_messages << value
37
- when :environment
38
- @excluded_environments << value
39
- when :custom
40
- @custom_filters << value
41
- else
42
- raise ArgumentError, "Unknown exclusion type: #{type}"
43
- end
44
- end
45
-
46
- def add_inclusion_override(filter)
47
- @inclusion_overrides << filter
48
- end
49
-
50
- def clear_exclusions(type = nil)
51
- if type
52
- case type
53
- when :exception then @excluded_exceptions.clear
54
- when :pattern then @excluded_patterns.clear
55
- when :message then @excluded_messages.clear
56
- when :environment then @excluded_environments.clear
57
- when :custom then @custom_filters.clear
58
- else raise ArgumentError, "Unknown exclusion type: #{type}"
59
- end
60
- else
61
- @excluded_exceptions.clear
62
- @excluded_patterns.clear
63
- @excluded_messages.clear
64
- @excluded_environments.clear
65
- @custom_filters.clear
66
- end
67
- end
68
-
69
- def exclusion_stats
70
- {
71
- excluded_exceptions: @excluded_exceptions.length,
72
- excluded_patterns: @excluded_patterns.length,
73
- excluded_messages: @excluded_messages.length,
74
- excluded_environments: @excluded_environments.length,
75
- custom_filters: @custom_filters.length,
76
- inclusion_overrides: @inclusion_overrides.length
77
- }
78
- end
79
-
80
- private
81
-
82
- def should_include_override?(event)
83
- @inclusion_overrides.any? { |filter| filter.call(event) }
84
- end
85
-
86
- def excluded_by_exception_type?(event)
87
- return false unless event.exception
88
-
89
- exception_class = event.exception.class
90
-
91
- @excluded_exceptions.any? do |excluded|
92
- case excluded
93
- when Class
94
- exception_class <= excluded
95
- when String
96
- exception_class.name == excluded || exception_class.name.include?(excluded)
97
- when Regexp
98
- exception_class.name.match?(excluded)
99
- else
100
- false
101
- end
102
- end
103
- end
104
-
105
- def excluded_by_pattern?(event)
106
- return false unless event.exception
107
-
108
- exception_class = event.exception.class
109
- exception_message = event.exception.message
110
-
111
- @excluded_patterns.any? do |pattern|
112
- case pattern
113
- when Regexp
114
- exception_class.name.match?(pattern) || exception_message.match?(pattern)
115
- when String
116
- exception_class.name.include?(pattern) || exception_message.include?(pattern)
117
- else
118
- false
119
- end
120
- end
121
- end
122
-
123
- def excluded_by_message?(event)
124
- return false unless event.exception
125
-
126
- exception_message = event.exception.message
127
-
128
- @excluded_messages.any? do |excluded_message|
129
- case excluded_message
130
- when Regexp
131
- exception_message.match?(excluded_message)
132
- when String
133
- exception_message.include?(excluded_message)
134
- else
135
- false
136
- end
137
- end
138
- end
139
-
140
- def excluded_by_environment?(event)
141
- return false if @excluded_environments.empty?
142
-
143
- environment = event.context[:environment] ||
144
- event.context.dig(:tags, :environment) ||
145
- Lapsoss.configuration.environment
146
-
147
- return false unless environment
148
-
149
- @excluded_environments.include?(environment.to_s)
150
- end
151
-
152
- def excluded_by_custom_filter?(event)
153
- @custom_filters.any? { |filter| filter.call(event) }
154
- end
155
- end
156
-
157
4
  # Predefined exclusion configurations for common use cases
158
5
  class ExclusionPresets
159
6
  def self.development
@@ -315,7 +162,7 @@ module Lapsoss
315
162
  custom_filters: [
316
163
  # Exclude high-frequency errors during peak times
317
164
  lambda do |event|
318
- now = Time.now
165
+ now = Time.zone.now
319
166
  peak_hours = (9..17).cover?(now.hour) && (1..5).cover?(now.wday)
320
167
 
321
168
  if peak_hours
@@ -399,31 +246,4 @@ module Lapsoss
399
246
  combined_config
400
247
  end
401
248
  end
402
-
403
- # Configuration helper for exclusions
404
- module ExclusionConfiguration
405
- def self.configure_exclusions(config, preset: nil, **custom_config)
406
- exclusion_config = if preset
407
- case preset
408
- when Array
409
- ExclusionPresets.combined(preset)
410
- else
411
- ExclusionPresets.send(preset)
412
- end
413
- else
414
- {}
415
- end
416
-
417
- # Merge custom configuration
418
- exclusion_config.merge!(custom_config)
419
-
420
- # Create exclusion filter
421
- exclusion_filter = ExclusionFilter.new(exclusion_config)
422
-
423
- # Add to configuration
424
- config.exclusion_filter = exclusion_filter
425
-
426
- exclusion_filter
427
- end
428
- end
429
249
  end