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.
- checksums.yaml +4 -4
- data/README.md +66 -7
- data/lib/lapsoss/client.rb +1 -3
- data/lib/lapsoss/configuration.rb +8 -17
- data/lib/lapsoss/fingerprinter.rb +52 -47
- data/lib/lapsoss/middleware/release_tracker.rb +11 -98
- data/lib/lapsoss/pipeline_builder.rb +2 -2
- data/lib/lapsoss/rails_middleware.rb +2 -2
- data/lib/lapsoss/railtie.rb +13 -2
- data/lib/lapsoss/registry.rb +7 -7
- data/lib/lapsoss/router.rb +1 -3
- data/lib/lapsoss/scrubber.rb +15 -152
- data/lib/lapsoss/validators.rb +48 -112
- data/lib/lapsoss/version.rb +1 -1
- metadata +1 -21
- data/lib/lapsoss/exclusion_configuration.rb +0 -30
- data/lib/lapsoss/exclusion_presets.rb +0 -249
- data/lib/lapsoss/middleware/sample_filter.rb +0 -23
- data/lib/lapsoss/middleware/sampling_middleware.rb +0 -18
- data/lib/lapsoss/middleware/user_context_enhancer.rb +0 -46
- data/lib/lapsoss/release_providers.rb +0 -110
- data/lib/lapsoss/sampling/adaptive_sampler.rb +0 -46
- data/lib/lapsoss/sampling/composite_sampler.rb +0 -26
- data/lib/lapsoss/sampling/consistent_hash_sampler.rb +0 -30
- data/lib/lapsoss/sampling/exception_type_sampler.rb +0 -44
- data/lib/lapsoss/sampling/health_based_sampler.rb +0 -19
- data/lib/lapsoss/sampling/sampling_factory.rb +0 -69
- data/lib/lapsoss/sampling/time_based_sampler.rb +0 -44
- data/lib/lapsoss/sampling/user_based_sampler.rb +0 -42
- data/lib/lapsoss/user_context.rb +0 -185
- data/lib/lapsoss/user_context_integrations.rb +0 -39
- data/lib/lapsoss/user_context_middleware.rb +0 -50
- data/lib/lapsoss/user_context_provider.rb +0 -93
- data/lib/lapsoss/utils.rb +0 -11
- data/lib/tasks/cassettes.rake +0 -50
data/lib/lapsoss/scrubber.rb
CHANGED
@@ -4,169 +4,32 @@ require "active_support/parameter_filter"
|
|
4
4
|
|
5
5
|
module Lapsoss
|
6
6
|
class Scrubber
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
18
|
+
DEFAULT_SCRUB_FIELDS
|
62
19
|
end
|
63
|
-
end
|
64
20
|
|
65
|
-
|
66
|
-
|
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
|
-
|
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
|
-
@
|
27
|
+
@filter = ActiveSupport::ParameterFilter.new(filter_params)
|
104
28
|
end
|
105
29
|
|
106
|
-
def
|
107
|
-
|
108
|
-
|
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
|
data/lib/lapsoss/validators.rb
CHANGED
@@ -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
|
-
|
7
|
+
extend ActiveSupport::Concern
|
6
8
|
|
7
9
|
module_function
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
def logger
|
12
|
+
Lapsoss.configuration.logger
|
13
|
+
end
|
12
14
|
|
13
|
-
|
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
|
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
|
-
#
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
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
|
-
|
52
|
-
|
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
|
-
|
62
|
-
|
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
|
-
|
72
|
-
|
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
|
-
#
|
98
|
-
def
|
99
|
-
return true if value.
|
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
|
-
|
108
|
-
|
109
|
-
return true if value.nil?
|
68
|
+
value_str = value.to_s.strip
|
69
|
+
return true if value_str.present?
|
110
70
|
|
111
|
-
|
112
|
-
|
113
|
-
end
|
114
|
-
true
|
71
|
+
logger.warn "#{name} should not be empty"
|
72
|
+
false
|
115
73
|
end
|
116
74
|
|
117
|
-
#
|
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
|
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
|
90
|
+
# URL validation
|
152
91
|
def validate_url!(value, name)
|
153
92
|
return true if value.nil?
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
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
|
data/lib/lapsoss/version.rb
CHANGED
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.
|
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
|