lapsoss 0.4.0 → 0.4.2

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +66 -7
  3. data/lib/lapsoss/client.rb +1 -3
  4. data/lib/lapsoss/configuration.rb +8 -17
  5. data/lib/lapsoss/fingerprinter.rb +52 -47
  6. data/lib/lapsoss/middleware/release_tracker.rb +11 -98
  7. data/lib/lapsoss/pipeline_builder.rb +2 -2
  8. data/lib/lapsoss/rails_middleware.rb +2 -2
  9. data/lib/lapsoss/railtie.rb +13 -2
  10. data/lib/lapsoss/registry.rb +7 -7
  11. data/lib/lapsoss/router.rb +1 -3
  12. data/lib/lapsoss/scrubber.rb +15 -152
  13. data/lib/lapsoss/validators.rb +48 -112
  14. data/lib/lapsoss/version.rb +1 -1
  15. metadata +1 -21
  16. data/lib/lapsoss/exclusion_configuration.rb +0 -30
  17. data/lib/lapsoss/exclusion_presets.rb +0 -249
  18. data/lib/lapsoss/middleware/sample_filter.rb +0 -23
  19. data/lib/lapsoss/middleware/sampling_middleware.rb +0 -18
  20. data/lib/lapsoss/middleware/user_context_enhancer.rb +0 -46
  21. data/lib/lapsoss/release_providers.rb +0 -110
  22. data/lib/lapsoss/sampling/adaptive_sampler.rb +0 -46
  23. data/lib/lapsoss/sampling/composite_sampler.rb +0 -26
  24. data/lib/lapsoss/sampling/consistent_hash_sampler.rb +0 -30
  25. data/lib/lapsoss/sampling/exception_type_sampler.rb +0 -44
  26. data/lib/lapsoss/sampling/health_based_sampler.rb +0 -19
  27. data/lib/lapsoss/sampling/sampling_factory.rb +0 -69
  28. data/lib/lapsoss/sampling/time_based_sampler.rb +0 -44
  29. data/lib/lapsoss/sampling/user_based_sampler.rb +0 -42
  30. data/lib/lapsoss/user_context.rb +0 -185
  31. data/lib/lapsoss/user_context_integrations.rb +0 -39
  32. data/lib/lapsoss/user_context_middleware.rb +0 -50
  33. data/lib/lapsoss/user_context_provider.rb +0 -93
  34. data/lib/lapsoss/utils.rb +0 -11
  35. data/lib/tasks/cassettes.rake +0 -50
@@ -4,169 +4,32 @@ require "active_support/parameter_filter"
4
4
 
5
5
  module Lapsoss
6
6
  class Scrubber
7
- DEFAULT_SCRUB_FIELDS = %w[
8
- password passwd pwd secret token key api_key access_token
9
- authorization auth_token session_token csrf_token
10
- credit_card cc_number card_number ssn social_security_number
11
- phone mobile email_address
12
- ].freeze
13
-
14
- PROTECTED_EVENT_FIELDS = %w[
15
- type timestamp level message exception environment context
16
- ].freeze
17
-
18
- ATTACHMENT_CLASSES = %w[
19
- ActionDispatch::Http::UploadedFile
20
- Rack::Multipart::UploadedFile
21
- Tempfile
7
+ # Match Rails conventions - these are only used when Rails is not available
8
+ # Rails uses partial matching, so 'passw' matches 'password'
9
+ DEFAULT_SCRUB_FIELDS = %i[
10
+ passw email secret token _key crypt salt certificate otp ssn cvv cvc
22
11
  ].freeze
23
12
 
24
13
  def initialize(config = {})
25
- @rails_parameter_filter = rails_parameter_filter
26
-
27
- # Only use custom scrubbing if Rails parameter filter is not available
28
- return if @rails_parameter_filter
29
-
30
- @scrub_fields = Array(config[:scrub_fields] || DEFAULT_SCRUB_FIELDS)
31
- @scrub_all = config[:scrub_all] || false
32
- @whitelist_fields = Array(config[:whitelist_fields] || [])
33
- @randomize_scrub_length = config[:randomize_scrub_length] || false
34
- @scrub_value = config[:scrub_value] || "**SCRUBBED**"
35
- end
36
-
37
- def scrub(data)
38
- return data if data.nil?
39
-
40
- # If Rails parameter filter is available, use it exclusively
41
- return @rails_parameter_filter.filter(data) if @rails_parameter_filter
42
-
43
- # Fallback to custom scrubbing logic only if Rails filter is not available
44
- @scrubbed_objects = {}.compare_by_identity
45
- scrub_recursive(data)
46
- end
47
-
48
- private
49
-
50
- def scrub_recursive(data)
51
- return data if @scrubbed_objects.key?(data)
52
-
53
- @scrubbed_objects[data] = true
54
-
55
- case data
56
- in Hash => hash
57
- scrub_hash(hash)
58
- in Array => array
59
- scrub_array(array)
14
+ # Combine: Rails filter parameters + custom fields (if provided)
15
+ base_params = if defined?(Rails) && Rails.respond_to?(:application) && Rails.application
16
+ Rails.application.config.filter_parameters.presence || DEFAULT_SCRUB_FIELDS
60
17
  else
61
- scrub_value(data)
18
+ DEFAULT_SCRUB_FIELDS
62
19
  end
63
- end
64
20
 
65
- def scrub_hash(hash)
66
- hash.each_with_object({}) do |(key, value), result|
67
- key_string = key.to_s.downcase
68
-
69
- result[key] = if should_scrub_field?(key_string)
70
- generate_scrub_value(value)
71
- else
72
- case value
73
- in Hash => h
74
- scrub_recursive(h)
75
- in Array => a
76
- scrub_array(a)
77
- else
78
- scrub_value(value)
79
- end
80
- end
81
- end
82
- end
83
-
84
- def scrub_array(array)
85
- array.map do |item|
86
- scrub_recursive(item)
87
- end
88
- end
89
-
90
- def scrub_value(value)
91
- if attachment_value?(value)
92
- scrub_attachment(value)
21
+ filter_params = if config[:scrub_fields]
22
+ Array(base_params) + Array(config[:scrub_fields])
93
23
  else
94
- value
24
+ base_params
95
25
  end
96
- end
97
-
98
- def should_scrub_field?(field_name)
99
- return false if whitelisted_field?(field_name)
100
- return false if protected_event_field?(field_name)
101
- return true if @scrub_all
102
26
 
103
- @scrub_fields.any? { |pattern| field_matches_pattern?(field_name, pattern) }
27
+ @filter = ActiveSupport::ParameterFilter.new(filter_params)
104
28
  end
105
29
 
106
- def field_matches_pattern?(field_name, pattern)
107
- case pattern
108
- in Regexp => regex
109
- regex.match?(field_name)
110
- else
111
- field_name.include?(pattern.to_s.downcase)
112
- end
113
- end
114
-
115
- def whitelisted_field?(field_name)
116
- @whitelist_fields.any? { |pattern| field_matches_pattern?(field_name, pattern) }
117
- end
118
-
119
- def protected_event_field?(field_name)
120
- PROTECTED_EVENT_FIELDS.include?(field_name.to_s)
121
- end
122
-
123
- def whitelisted_value?(_value)
124
- # Basic implementation - could be extended
125
- false
126
- end
127
-
128
- def attachment_value?(value)
129
- return false unless value.respond_to?(:class)
130
-
131
- ATTACHMENT_CLASSES.include?(value.class.name)
132
- end
133
-
134
- def scrub_attachment(attachment)
135
- {
136
- __attachment__: true,
137
- content_type: safe_call(attachment, :content_type),
138
- original_filename: safe_call(attachment, :original_filename),
139
- size: safe_call(attachment, :size) || safe_call(attachment, :tempfile, :size)
140
- }
141
- rescue StandardError => e
142
- { __attachment__: true, error: "Failed to extract attachment info: #{e.message}" }
143
- end
144
-
145
- def safe_call(object, *methods)
146
- methods.reduce(object) do |obj, method|
147
- obj.respond_to?(method) ? obj.public_send(method) : nil
148
- end
149
- end
150
-
151
- def generate_scrub_value(_original_value)
152
- if @randomize_scrub_length
153
- "*" * rand(6..12)
154
- else
155
- @scrub_value
156
- end
157
- end
158
-
159
- def rails_parameter_filter
160
- return nil unless defined?(Rails) && Rails.respond_to?(:application) && Rails.application
161
- return nil unless defined?(ActiveSupport::ParameterFilter)
162
-
163
- filter_params = Rails.application.config.filter_parameters
164
- return nil if filter_params.empty?
165
-
166
- ActiveSupport::ParameterFilter.new(filter_params)
167
- rescue StandardError
168
- # Fallback silently if Rails config is not available
169
- nil
30
+ def scrub(data)
31
+ return data if data.nil?
32
+ @filter.filter(data)
170
33
  end
171
34
  end
172
35
  end
@@ -1,164 +1,100 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/object/blank"
4
+
3
5
  module Lapsoss
4
6
  module Validators
5
- class ValidationError < StandardError; end
7
+ extend ActiveSupport::Concern
6
8
 
7
9
  module_function
8
10
 
9
- # Simple presence check - just ensure it's not blank
10
- def validate_presence!(value, name)
11
- return unless value.blank?
11
+ def logger
12
+ Lapsoss.configuration.logger
13
+ end
12
14
 
13
- Lapsoss.configuration.logger&.warn "[Lapsoss] #{name} is missing or blank"
15
+ # Simple presence check using AS blank?
16
+ def validate_presence!(value, name)
17
+ return true if value.present?
18
+ logger.warn "#{name} is missing or blank"
14
19
  false
15
20
  end
16
21
 
17
- # Check if callable, log warning if not
22
+ # Check if callable
18
23
  def validate_callable!(value, name)
19
24
  return true if value.nil? || value.respond_to?(:call)
20
-
21
- Lapsoss.configuration.logger&.warn "[Lapsoss] #{name} should be callable but got #{value.class}"
25
+ logger.warn "#{name} should be callable but got #{value.class}"
22
26
  false
23
27
  end
24
28
 
25
- # Just log DSN issues, don't fail
29
+ # DSN validation - just log issues
26
30
  def validate_dsn!(dsn_string, name = "DSN")
27
31
  return true if dsn_string.blank?
28
32
 
29
- begin
30
- uri = URI.parse(dsn_string)
31
-
32
- if uri.user.blank?
33
- Lapsoss.configuration.logger&.warn "[Lapsoss] #{name} appears to be missing public key"
34
- end
35
-
36
- if uri.host.blank?
37
- Lapsoss.configuration.logger&.warn "[Lapsoss] #{name} appears to be missing host"
38
- end
39
-
40
- true
41
- rescue URI::InvalidURIError => e
42
- Lapsoss.configuration.logger&.error "[Lapsoss] #{name} couldn't be parsed: #{e.message}"
43
- false
44
- end
33
+ uri = URI.parse(dsn_string)
34
+ logger.warn "#{name} appears to be missing public key" if uri.user.blank?
35
+ logger.warn "#{name} appears to be missing host" if uri.host.blank?
36
+ true
37
+ rescue URI::InvalidURIError => e
38
+ logger.error "#{name} couldn't be parsed: #{e.message}"
39
+ false
45
40
  end
46
41
 
47
- # Validate sample rate is between 0 and 1
42
+ # Validate numeric ranges using AS Range#cover?
48
43
  def validate_sample_rate!(value, name)
49
44
  return true if value.nil?
50
-
51
- if value < 0 || value > 1
52
- Lapsoss.configuration.logger&.warn "[Lapsoss] #{name} should be between 0 and 1, got #{value}"
53
- end
54
- true
45
+ return true if (0..1).cover?(value)
46
+ logger.warn "#{name} should be between 0 and 1, got #{value}"
47
+ false
55
48
  end
56
49
 
57
- # Validate timeout values
58
50
  def validate_timeout!(value, name)
59
51
  return true if value.nil?
60
-
61
- if value <= 0
62
- Lapsoss.configuration.logger&.warn "[Lapsoss] #{name} should be positive, got #{value}"
63
- end
64
- true
52
+ return true if value.positive?
53
+ logger.warn "#{name} should be positive, got #{value}"
54
+ false
65
55
  end
66
56
 
67
- # Validate retry count
68
57
  def validate_retries!(value, name)
69
58
  return true if value.nil?
70
-
71
- if value < 0
72
- Lapsoss.configuration.logger&.warn "[Lapsoss] #{name} should be non-negative, got #{value}"
73
- end
74
- true
75
- end
76
-
77
- # Validate environment string
78
- def validate_environment!(value, name)
79
- return true if value.nil?
80
-
81
- if value.to_s.strip.empty?
82
- Lapsoss.configuration.logger&.warn "[Lapsoss] #{name} should not be empty"
83
- end
84
- true
85
- end
86
-
87
- # Validate type
88
- def validate_type!(value, expected_types, name)
89
- return true if value.nil?
90
-
91
- unless expected_types.any? { |type| value.is_a?(type) }
92
- Lapsoss.configuration.logger&.warn "[Lapsoss] #{name} should be one of #{expected_types.join(', ')}, got #{value.class}"
93
- end
94
- true
59
+ return true if value >= 0
60
+ logger.warn "#{name} should be non-negative, got #{value}"
61
+ false
95
62
  end
96
63
 
97
- # Validate numeric range
98
- def validate_numeric_range!(value, range, name)
99
- return true if value.nil?
100
-
101
- unless range.include?(value)
102
- Lapsoss.configuration.logger&.warn "[Lapsoss] #{name} should be within #{range}, got #{value}"
103
- end
104
- true
105
- end
64
+ # Environment validation using AS presence
65
+ def validate_environment!(value, name = "environment")
66
+ return true if value.blank?
106
67
 
107
- # Validate boolean
108
- def validate_boolean!(value, name)
109
- return true if value.nil?
68
+ value_str = value.to_s.strip
69
+ return true if value_str.present?
110
70
 
111
- unless [ true, false ].include?(value)
112
- Lapsoss.configuration.logger&.warn "[Lapsoss] #{name} should be true or false, got #{value}"
113
- end
114
- true
71
+ logger.warn "#{name} should not be empty"
72
+ false
115
73
  end
116
74
 
117
- # Just check presence, don't validate format
75
+ # API key validation using AS blank?
118
76
  def validate_api_key!(value, name, format: nil)
119
- if value.blank?
120
- Lapsoss.configuration.logger&.warn "[Lapsoss] #{name} is missing"
121
- return false
122
- end
77
+ return false if value.blank? && logger.warn("#{name} is missing")
123
78
 
124
- # Optional format hint for logging only
79
+ # Optional format hints
125
80
  case format
126
81
  when :uuid
127
- unless value.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i)
128
- Lapsoss.configuration.logger&.info "[Lapsoss] #{name} doesn't look like a UUID, but continuing anyway"
129
- end
82
+ logger.info "#{name} doesn't look like a UUID, but continuing anyway" unless value.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i)
130
83
  when :alphanumeric
131
- unless value.match?(/\A[a-z0-9]+\z/i)
132
- Lapsoss.configuration.logger&.info "[Lapsoss] #{name} contains special characters, but continuing anyway"
133
- end
134
- end
135
-
136
- true
137
- end
138
-
139
- # Environment validation - just log if unusual
140
- def validate_environment!(value, name = "environment")
141
- return true if value.blank?
142
-
143
- common_envs = %w[development test staging production]
144
- unless common_envs.include?(value.to_s.downcase)
145
- Lapsoss.configuration.logger&.info "[Lapsoss] #{name} '#{value}' is non-standard (expected one of: #{common_envs.join(', ')})"
84
+ logger.info "#{name} contains special characters, but continuing anyway" unless value.match?(/\A[a-z0-9]+\z/i)
146
85
  end
147
86
 
148
87
  true
149
88
  end
150
89
 
151
- # URL validation - just check parsability
90
+ # URL validation
152
91
  def validate_url!(value, name)
153
92
  return true if value.nil?
154
-
155
- begin
156
- URI.parse(value)
157
- true
158
- rescue URI::InvalidURIError => e
159
- Lapsoss.configuration.logger&.warn "[Lapsoss] #{name} couldn't be parsed as URL: #{e.message}"
160
- false
161
- end
93
+ URI.parse(value)
94
+ true
95
+ rescue URI::InvalidURIError => e
96
+ logger.warn "#{name} couldn't be parsed as URL: #{e.message}"
97
+ false
162
98
  end
163
99
  end
164
100
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lapsoss
4
- VERSION = "0.4.0"
4
+ VERSION = "0.4.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lapsoss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -234,9 +234,7 @@ files:
234
234
  - lib/lapsoss/current.rb
235
235
  - lib/lapsoss/event.rb
236
236
  - lib/lapsoss/exception_backtrace_frame.rb
237
- - lib/lapsoss/exclusion_configuration.rb
238
237
  - lib/lapsoss/exclusion_filter.rb
239
- - lib/lapsoss/exclusion_presets.rb
240
238
  - lib/lapsoss/fingerprinter.rb
241
239
  - lib/lapsoss/http_client.rb
242
240
  - lib/lapsoss/merged_scope.rb
@@ -248,39 +246,21 @@ files:
248
246
  - lib/lapsoss/middleware/metrics_collector.rb
249
247
  - lib/lapsoss/middleware/rate_limiter.rb
250
248
  - lib/lapsoss/middleware/release_tracker.rb
251
- - lib/lapsoss/middleware/sample_filter.rb
252
- - lib/lapsoss/middleware/sampling_middleware.rb
253
- - lib/lapsoss/middleware/user_context_enhancer.rb
254
249
  - lib/lapsoss/pipeline.rb
255
250
  - lib/lapsoss/pipeline_builder.rb
256
251
  - lib/lapsoss/rails_error_subscriber.rb
257
252
  - lib/lapsoss/rails_middleware.rb
258
253
  - lib/lapsoss/railtie.rb
259
254
  - lib/lapsoss/registry.rb
260
- - lib/lapsoss/release_providers.rb
261
255
  - lib/lapsoss/release_tracker.rb
262
256
  - lib/lapsoss/router.rb
263
- - lib/lapsoss/sampling/adaptive_sampler.rb
264
257
  - lib/lapsoss/sampling/base.rb
265
- - lib/lapsoss/sampling/composite_sampler.rb
266
- - lib/lapsoss/sampling/consistent_hash_sampler.rb
267
- - lib/lapsoss/sampling/exception_type_sampler.rb
268
- - lib/lapsoss/sampling/health_based_sampler.rb
269
258
  - lib/lapsoss/sampling/rate_limiter.rb
270
- - lib/lapsoss/sampling/sampling_factory.rb
271
- - lib/lapsoss/sampling/time_based_sampler.rb
272
259
  - lib/lapsoss/sampling/uniform_sampler.rb
273
- - lib/lapsoss/sampling/user_based_sampler.rb
274
260
  - lib/lapsoss/scope.rb
275
261
  - lib/lapsoss/scrubber.rb
276
- - lib/lapsoss/user_context.rb
277
- - lib/lapsoss/user_context_integrations.rb
278
- - lib/lapsoss/user_context_middleware.rb
279
- - lib/lapsoss/user_context_provider.rb
280
- - lib/lapsoss/utils.rb
281
262
  - lib/lapsoss/validators.rb
282
263
  - lib/lapsoss/version.rb
283
- - lib/tasks/cassettes.rake
284
264
  homepage: https://github.com/seuros/lapsoss
285
265
  licenses:
286
266
  - MIT
@@ -1,30 +0,0 @@
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