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.
- checksums.yaml +4 -4
- data/README.md +153 -733
- data/lib/lapsoss/adapters/appsignal_adapter.rb +7 -8
- data/lib/lapsoss/adapters/base.rb +0 -3
- data/lib/lapsoss/adapters/bugsnag_adapter.rb +12 -0
- data/lib/lapsoss/adapters/insight_hub_adapter.rb +102 -101
- data/lib/lapsoss/adapters/logger_adapter.rb +7 -7
- data/lib/lapsoss/adapters/rollbar_adapter.rb +93 -54
- data/lib/lapsoss/adapters/sentry_adapter.rb +11 -17
- data/lib/lapsoss/backtrace_frame.rb +35 -214
- data/lib/lapsoss/backtrace_frame_factory.rb +228 -0
- data/lib/lapsoss/backtrace_processor.rb +37 -37
- data/lib/lapsoss/client.rb +2 -6
- data/lib/lapsoss/configuration.rb +25 -22
- data/lib/lapsoss/current.rb +9 -1
- data/lib/lapsoss/event.rb +30 -6
- data/lib/lapsoss/exception_backtrace_frame.rb +39 -0
- data/lib/lapsoss/exclusion_configuration.rb +30 -0
- data/lib/lapsoss/exclusion_filter.rb +156 -0
- data/lib/lapsoss/{exclusions.rb → exclusion_presets.rb} +1 -181
- data/lib/lapsoss/fingerprinter.rb +9 -13
- data/lib/lapsoss/http_client.rb +42 -8
- data/lib/lapsoss/merged_scope.rb +63 -0
- data/lib/lapsoss/middleware/base.rb +15 -0
- data/lib/lapsoss/middleware/conditional_filter.rb +18 -0
- data/lib/lapsoss/middleware/event_enricher.rb +19 -0
- data/lib/lapsoss/middleware/event_transformer.rb +19 -0
- data/lib/lapsoss/middleware/exception_filter.rb +43 -0
- data/lib/lapsoss/middleware/metrics_collector.rb +44 -0
- data/lib/lapsoss/middleware/rate_limiter.rb +31 -0
- data/lib/lapsoss/middleware/release_tracker.rb +117 -0
- data/lib/lapsoss/middleware/sample_filter.rb +23 -0
- data/lib/lapsoss/middleware/sampling_middleware.rb +18 -0
- data/lib/lapsoss/middleware/user_context_enhancer.rb +46 -0
- data/lib/lapsoss/middleware.rb +0 -347
- data/lib/lapsoss/pipeline.rb +1 -73
- data/lib/lapsoss/pipeline_builder.rb +69 -0
- data/lib/lapsoss/rails_error_subscriber.rb +42 -0
- data/lib/lapsoss/rails_middleware.rb +78 -0
- data/lib/lapsoss/railtie.rb +22 -50
- data/lib/lapsoss/registry.rb +34 -20
- data/lib/lapsoss/release_providers.rb +110 -0
- data/lib/lapsoss/release_tracker.rb +112 -207
- data/lib/lapsoss/router.rb +3 -5
- data/lib/lapsoss/sampling/adaptive_sampler.rb +46 -0
- data/lib/lapsoss/sampling/base.rb +11 -0
- data/lib/lapsoss/sampling/composite_sampler.rb +26 -0
- data/lib/lapsoss/sampling/consistent_hash_sampler.rb +30 -0
- data/lib/lapsoss/sampling/exception_type_sampler.rb +44 -0
- data/lib/lapsoss/sampling/health_based_sampler.rb +19 -0
- data/lib/lapsoss/sampling/rate_limiter.rb +32 -0
- data/lib/lapsoss/sampling/sampling_factory.rb +69 -0
- data/lib/lapsoss/sampling/time_based_sampler.rb +44 -0
- data/lib/lapsoss/sampling/uniform_sampler.rb +15 -0
- data/lib/lapsoss/sampling/user_based_sampler.rb +42 -0
- data/lib/lapsoss/sampling.rb +0 -326
- data/lib/lapsoss/scope.rb +17 -57
- data/lib/lapsoss/scrubber.rb +16 -18
- data/lib/lapsoss/user_context.rb +18 -198
- data/lib/lapsoss/user_context_integrations.rb +39 -0
- data/lib/lapsoss/user_context_middleware.rb +50 -0
- data/lib/lapsoss/user_context_provider.rb +93 -0
- data/lib/lapsoss/utils.rb +13 -0
- data/lib/lapsoss/validators.rb +14 -27
- data/lib/lapsoss/version.rb +1 -1
- data/lib/lapsoss.rb +12 -25
- metadata +106 -21
data/lib/lapsoss/client.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
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
|
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,
|
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
|
data/lib/lapsoss/current.rb
CHANGED
@@ -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,
|
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 =
|
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
|