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.
- checksums.yaml +4 -4
- data/README.md +153 -733
- data/lib/lapsoss/adapters/appsignal_adapter.rb +22 -22
- data/lib/lapsoss/adapters/base.rb +0 -3
- data/lib/lapsoss/adapters/insight_hub_adapter.rb +108 -104
- data/lib/lapsoss/adapters/logger_adapter.rb +1 -1
- data/lib/lapsoss/adapters/rollbar_adapter.rb +108 -68
- data/lib/lapsoss/adapters/sentry_adapter.rb +24 -24
- data/lib/lapsoss/backtrace_frame.rb +37 -206
- data/lib/lapsoss/backtrace_frame_factory.rb +228 -0
- data/lib/lapsoss/backtrace_processor.rb +26 -23
- data/lib/lapsoss/client.rb +2 -4
- data/lib/lapsoss/configuration.rb +28 -32
- data/lib/lapsoss/current.rb +10 -2
- data/lib/lapsoss/event.rb +28 -5
- data/lib/lapsoss/exception_backtrace_frame.rb +39 -0
- data/lib/lapsoss/exclusion_configuration.rb +30 -0
- data/lib/lapsoss/exclusion_filter.rb +0 -273
- data/lib/lapsoss/exclusion_presets.rb +249 -0
- data/lib/lapsoss/fingerprinter.rb +28 -28
- data/lib/lapsoss/http_client.rb +8 -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 -339
- data/lib/lapsoss/pipeline.rb +0 -68
- 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 +18 -5
- data/lib/lapsoss/release_providers.rb +110 -0
- data/lib/lapsoss/release_tracker.rb +159 -232
- 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 -322
- data/lib/lapsoss/scope.rb +12 -48
- data/lib/lapsoss/scrubber.rb +7 -7
- data/lib/lapsoss/user_context.rb +30 -203
- 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 +15 -15
- data/lib/lapsoss/version.rb +1 -1
- data/lib/lapsoss.rb +3 -3
- metadata +54 -5
data/lib/lapsoss/scrubber.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "active_support/parameter_filter"
|
4
4
|
|
5
5
|
module Lapsoss
|
6
6
|
class Scrubber
|
@@ -31,7 +31,7 @@ module Lapsoss
|
|
31
31
|
@scrub_all = config[:scrub_all] || false
|
32
32
|
@whitelist_fields = Array(config[:whitelist_fields] || [])
|
33
33
|
@randomize_scrub_length = config[:randomize_scrub_length] || false
|
34
|
-
@scrub_value = config[:scrub_value] ||
|
34
|
+
@scrub_value = config[:scrub_value] || "**SCRUBBED**"
|
35
35
|
end
|
36
36
|
|
37
37
|
def scrub(data)
|
@@ -68,13 +68,13 @@ module Lapsoss
|
|
68
68
|
|
69
69
|
result[key] = if should_scrub_field?(key_string)
|
70
70
|
generate_scrub_value(value)
|
71
|
-
|
71
|
+
elsif value.is_a?(Hash)
|
72
72
|
scrub_recursive(value)
|
73
|
-
|
73
|
+
elsif value.is_a?(Array)
|
74
74
|
scrub_array(value)
|
75
|
-
|
75
|
+
else
|
76
76
|
scrub_value(value)
|
77
|
-
|
77
|
+
end
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
@@ -146,7 +146,7 @@ module Lapsoss
|
|
146
146
|
|
147
147
|
def generate_scrub_value(_original_value)
|
148
148
|
if @randomize_scrub_length
|
149
|
-
|
149
|
+
"*" * rand(6..12)
|
150
150
|
else
|
151
151
|
@scrub_value
|
152
152
|
end
|
data/lib/lapsoss/user_context.rb
CHANGED
@@ -35,9 +35,9 @@ module Lapsoss
|
|
35
35
|
# Apply privacy filtering
|
36
36
|
processed[key] = if @privacy_mode && sensitive_field?(key_sym)
|
37
37
|
apply_privacy_filter(key_sym, value)
|
38
|
-
|
38
|
+
else
|
39
39
|
transform_field(key_sym, value)
|
40
|
-
|
40
|
+
end
|
41
41
|
end
|
42
42
|
|
43
43
|
processed
|
@@ -68,18 +68,18 @@ module Lapsoss
|
|
68
68
|
segments = {}
|
69
69
|
|
70
70
|
# Check for common user segments
|
71
|
-
segments[:internal] = !(user_data[:internal] || user_data[
|
72
|
-
segments[:premium] = !(user_data[:premium] || user_data[
|
73
|
-
segments[:beta] = !(user_data[:beta] || user_data[
|
74
|
-
segments[:admin] = !(user_data[:admin] || user_data[
|
71
|
+
segments[:internal] = !(user_data[:internal] || user_data["internal"]).nil?
|
72
|
+
segments[:premium] = !(user_data[:premium] || user_data["premium"]).nil?
|
73
|
+
segments[:beta] = !(user_data[:beta] || user_data["beta"]).nil?
|
74
|
+
segments[:admin] = !(user_data[:admin] || user_data["admin"]).nil?
|
75
75
|
|
76
76
|
# Check role-based segments
|
77
|
-
if role = user_data[:role] || user_data[
|
77
|
+
if role = user_data[:role] || user_data["role"]
|
78
78
|
segments[:role] = role.to_s.downcase
|
79
79
|
end
|
80
80
|
|
81
81
|
# Check plan-based segments
|
82
|
-
if plan = user_data[:plan] || user_data[
|
82
|
+
if plan = user_data[:plan] || user_data["plan"]
|
83
83
|
segments[:plan] = plan.to_s.downcase
|
84
84
|
end
|
85
85
|
|
@@ -95,14 +95,14 @@ module Lapsoss
|
|
95
95
|
key_sym = key.to_sym
|
96
96
|
|
97
97
|
sanitized[key] = if sensitive_field?(key_sym)
|
98
|
-
|
99
|
-
|
98
|
+
"[REDACTED]"
|
99
|
+
elsif value.is_a?(Hash)
|
100
100
|
sanitize_for_logging(value)
|
101
|
-
|
101
|
+
elsif value.is_a?(Array)
|
102
102
|
value.map { |v| v.is_a?(Hash) ? sanitize_for_logging(v) : v }
|
103
|
-
|
103
|
+
else
|
104
104
|
value
|
105
|
-
|
105
|
+
end
|
106
106
|
end
|
107
107
|
|
108
108
|
sanitized
|
@@ -123,7 +123,7 @@ module Lapsoss
|
|
123
123
|
when :ip_address, :last_login_ip
|
124
124
|
mask_ip(value)
|
125
125
|
else
|
126
|
-
|
126
|
+
"[FILTERED]"
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
@@ -136,212 +136,39 @@ module Lapsoss
|
|
136
136
|
end
|
137
137
|
|
138
138
|
def mask_email(email)
|
139
|
-
return
|
139
|
+
return "[INVALID_EMAIL]" unless email.is_a?(String) && email.include?("@")
|
140
140
|
|
141
|
-
local, domain = email.split(
|
142
|
-
masked_local = local.length > 2 ? "#{local[0]}***#{local[-1]}" :
|
141
|
+
local, domain = email.split("@", 2)
|
142
|
+
masked_local = local.length > 2 ? "#{local[0]}***#{local[-1]}" : "***"
|
143
143
|
"#{masked_local}@#{domain}"
|
144
144
|
end
|
145
145
|
|
146
146
|
def mask_phone(phone)
|
147
|
-
return
|
147
|
+
return "[INVALID_PHONE]" unless phone.is_a?(String)
|
148
148
|
|
149
149
|
# Remove all non-digits
|
150
|
-
digits = phone.gsub(/\D/,
|
151
|
-
return
|
150
|
+
digits = phone.gsub(/\D/, "")
|
151
|
+
return "[INVALID_PHONE]" if digits.length < 4
|
152
152
|
|
153
153
|
# Show last 4 digits
|
154
|
-
(
|
154
|
+
("*" * (digits.length - 4)) + digits[-4..]
|
155
155
|
end
|
156
156
|
|
157
157
|
def mask_ip(ip)
|
158
|
-
return
|
158
|
+
return "[INVALID_IP]" unless ip.is_a?(String)
|
159
159
|
|
160
|
-
if ip.include?(
|
160
|
+
if ip.include?(":")
|
161
161
|
# IPv6 - mask last 4 groups
|
162
|
-
parts = ip.split(
|
163
|
-
parts[-4..-1] = [
|
164
|
-
parts.join(
|
162
|
+
parts = ip.split(":")
|
163
|
+
parts[-4..-1] = [ "****" ] * 4 if parts.length >= 4
|
164
|
+
parts.join(":")
|
165
165
|
else
|
166
166
|
# IPv4 - mask last octet
|
167
|
-
parts = ip.split(
|
168
|
-
return
|
169
|
-
|
170
|
-
parts[-1] = '***'
|
171
|
-
parts.join('.')
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
# User context provider that integrates with various authentication systems
|
177
|
-
class UserContextProvider
|
178
|
-
def initialize(providers: {})
|
179
|
-
@providers = providers
|
180
|
-
end
|
181
|
-
|
182
|
-
def get_user_context(event, hint = {})
|
183
|
-
context = {}
|
184
|
-
|
185
|
-
# Try each provider in order
|
186
|
-
@providers.each do |name, provider|
|
187
|
-
if provider_context = provider.call(event, hint)
|
188
|
-
context.merge!(provider_context)
|
189
|
-
end
|
190
|
-
rescue StandardError => e
|
191
|
-
# Log provider error but don't fail
|
192
|
-
warn "User context provider #{name} failed: #{e.message}"
|
193
|
-
end
|
194
|
-
|
195
|
-
context
|
196
|
-
end
|
197
|
-
|
198
|
-
# Built-in providers for common authentication systems
|
199
|
-
def self.devise_provider
|
200
|
-
lambda do |_event, hint|
|
201
|
-
return {} unless defined?(Devise) && defined?(Warden)
|
202
|
-
|
203
|
-
# Try to get user from Warden (used by Devise)
|
204
|
-
if request = hint[:request]
|
205
|
-
user = request.env['warden']&.user
|
206
|
-
return {} unless user
|
207
|
-
|
208
|
-
{
|
209
|
-
id: user.id,
|
210
|
-
email: user.email,
|
211
|
-
username: user.respond_to?(:username) ? user.username : nil,
|
212
|
-
created_at: user.created_at,
|
213
|
-
role: user.respond_to?(:role) ? user.role : nil
|
214
|
-
}.compact
|
215
|
-
end
|
216
|
-
|
217
|
-
{}
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
def self.omniauth_provider
|
222
|
-
lambda do |_event, hint|
|
223
|
-
return {} unless defined?(OmniAuth)
|
224
|
-
|
225
|
-
if (request = hint[:request]) && (auth_info = request.env['omniauth.auth'])
|
226
|
-
{
|
227
|
-
provider: auth_info['provider'],
|
228
|
-
uid: auth_info['uid'],
|
229
|
-
name: auth_info.dig('info', 'name'),
|
230
|
-
email: auth_info.dig('info', 'email'),
|
231
|
-
username: auth_info.dig('info', 'nickname')
|
232
|
-
}.compact
|
233
|
-
end
|
234
|
-
|
235
|
-
{}
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
def self.session_provider
|
240
|
-
lambda do |_event, hint|
|
241
|
-
return {} unless hint[:request]
|
242
|
-
|
243
|
-
request = hint[:request]
|
244
|
-
session = begin
|
245
|
-
request.session
|
246
|
-
rescue StandardError
|
247
|
-
{}
|
248
|
-
end
|
249
|
-
|
250
|
-
{
|
251
|
-
session_id: session[:session_id] || session['session_id'],
|
252
|
-
user_id: session[:user_id] || session['user_id'],
|
253
|
-
csrf_token: session[:_csrf_token] || session['_csrf_token']
|
254
|
-
}.compact
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
def self.thread_local_provider
|
259
|
-
lambda do |_event, _hint|
|
260
|
-
# Get user from thread-local storage
|
261
|
-
Thread.current[:current_user] || {}
|
262
|
-
end
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
# Integration with popular authentication gems
|
267
|
-
module UserContextIntegrations
|
268
|
-
def self.setup_devise_integration
|
269
|
-
return unless defined?(Devise)
|
270
|
-
|
271
|
-
# Add middleware to capture user context
|
272
|
-
Rails.application.config.middleware.use(UserContextMiddleware) if defined?(Rails)
|
273
|
-
end
|
274
|
-
|
275
|
-
def self.setup_clearance_integration
|
276
|
-
return unless defined?(Clearance)
|
277
|
-
|
278
|
-
# Clearance integration
|
279
|
-
return unless defined?(Rails)
|
280
|
-
|
281
|
-
Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
|
282
|
-
middleware.user_provider = lambda do |request|
|
283
|
-
request.env[:clearance].current_user if request.env[:clearance]
|
284
|
-
end
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
def self.setup_authlogic_integration
|
289
|
-
return unless defined?(Authlogic)
|
290
|
-
|
291
|
-
# Authlogic integration
|
292
|
-
return unless defined?(Rails)
|
293
|
-
|
294
|
-
Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
|
295
|
-
middleware.user_provider = lambda do |_request|
|
296
|
-
UserSession.find&.user if defined?(UserSession)
|
297
|
-
end
|
298
|
-
end
|
299
|
-
end
|
300
|
-
end
|
301
|
-
|
302
|
-
# Middleware to automatically capture user context
|
303
|
-
class UserContextMiddleware
|
304
|
-
def initialize(app, user_provider: nil)
|
305
|
-
@app = app
|
306
|
-
@user_provider = user_provider
|
307
|
-
end
|
308
|
-
|
309
|
-
def call(env)
|
310
|
-
request = Rack::Request.new(env)
|
311
|
-
|
312
|
-
# Capture user context
|
313
|
-
user_context = extract_user_context(request)
|
314
|
-
|
315
|
-
# Store in thread-local for access during request processing
|
316
|
-
Thread.current[:lapsoss_user_context] = user_context
|
317
|
-
|
318
|
-
@app.call(env)
|
319
|
-
ensure
|
320
|
-
Thread.current[:lapsoss_user_context] = nil
|
321
|
-
end
|
322
|
-
|
323
|
-
private
|
324
|
-
|
325
|
-
def extract_user_context(request)
|
326
|
-
if @user_provider
|
327
|
-
user = @user_provider.call(request)
|
328
|
-
return {} unless user
|
167
|
+
parts = ip.split(".")
|
168
|
+
return "[INVALID_IP]" if parts.length != 4
|
329
169
|
|
330
|
-
|
331
|
-
|
332
|
-
email: user.email,
|
333
|
-
username: user.respond_to?(:username) ? user.username : nil
|
334
|
-
}
|
335
|
-
|
336
|
-
# Add role information if available
|
337
|
-
context[:role] = user.role if user.respond_to?(:role)
|
338
|
-
|
339
|
-
# Add plan information if available
|
340
|
-
context[:plan] = user.plan if user.respond_to?(:plan)
|
341
|
-
|
342
|
-
context.compact
|
343
|
-
else
|
344
|
-
{}
|
170
|
+
parts[-1] = "***"
|
171
|
+
parts.join(".")
|
345
172
|
end
|
346
173
|
end
|
347
174
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lapsoss
|
4
|
+
# Integration with popular authentication gems
|
5
|
+
module UserContextIntegrations
|
6
|
+
def self.setup_devise_integration
|
7
|
+
return unless defined?(Devise)
|
8
|
+
|
9
|
+
# Add middleware to capture user context
|
10
|
+
Rails.application.config.middleware.use(UserContextMiddleware) if defined?(Rails)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.setup_clearance_integration
|
14
|
+
return unless defined?(Clearance)
|
15
|
+
|
16
|
+
# Clearance integration
|
17
|
+
return unless defined?(Rails)
|
18
|
+
|
19
|
+
Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
|
20
|
+
middleware.user_provider = lambda do |request|
|
21
|
+
request.env[:clearance].current_user if request.env[:clearance]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.setup_authlogic_integration
|
27
|
+
return unless defined?(Authlogic)
|
28
|
+
|
29
|
+
# Authlogic integration
|
30
|
+
return unless defined?(Rails)
|
31
|
+
|
32
|
+
Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
|
33
|
+
middleware.user_provider = lambda do |_request|
|
34
|
+
UserSession.find&.user if defined?(UserSession)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lapsoss
|
4
|
+
# Middleware to automatically capture user context
|
5
|
+
class UserContextMiddleware
|
6
|
+
def initialize(app, user_provider: nil)
|
7
|
+
@app = app
|
8
|
+
@user_provider = user_provider
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
request = Rack::Request.new(env)
|
13
|
+
|
14
|
+
# Capture user context
|
15
|
+
user_context = extract_user_context(request)
|
16
|
+
|
17
|
+
# Store in thread-local for access during request processing
|
18
|
+
Thread.current[:lapsoss_user_context] = user_context
|
19
|
+
|
20
|
+
@app.call(env)
|
21
|
+
ensure
|
22
|
+
Thread.current[:lapsoss_user_context] = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def extract_user_context(request)
|
28
|
+
if @user_provider
|
29
|
+
user = @user_provider.call(request)
|
30
|
+
return {} unless user
|
31
|
+
|
32
|
+
context = {
|
33
|
+
id: user.id,
|
34
|
+
email: user.email,
|
35
|
+
username: user.respond_to?(:username) ? user.username : nil
|
36
|
+
}
|
37
|
+
|
38
|
+
# Add role information if available
|
39
|
+
context[:role] = user.role if user.respond_to?(:role)
|
40
|
+
|
41
|
+
# Add plan information if available
|
42
|
+
context[:plan] = user.plan if user.respond_to?(:plan)
|
43
|
+
|
44
|
+
context.compact
|
45
|
+
else
|
46
|
+
{}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lapsoss
|
4
|
+
# User context provider that integrates with various authentication systems
|
5
|
+
class UserContextProvider
|
6
|
+
def initialize(providers: {})
|
7
|
+
@providers = providers
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_user_context(event, hint = {})
|
11
|
+
context = {}
|
12
|
+
|
13
|
+
# Try each provider in order
|
14
|
+
@providers.each do |name, provider|
|
15
|
+
if provider_context = provider.call(event, hint)
|
16
|
+
context.merge!(provider_context)
|
17
|
+
end
|
18
|
+
rescue StandardError => e
|
19
|
+
# Log provider error but don't fail
|
20
|
+
warn "User context provider #{name} failed: #{e.message}"
|
21
|
+
end
|
22
|
+
|
23
|
+
context
|
24
|
+
end
|
25
|
+
|
26
|
+
# Built-in providers for common authentication systems
|
27
|
+
def self.devise_provider
|
28
|
+
lambda do |_event, hint|
|
29
|
+
return {} unless defined?(Devise) && defined?(Warden)
|
30
|
+
|
31
|
+
# Try to get user from Warden (used by Devise)
|
32
|
+
if request = hint[:request]
|
33
|
+
user = request.env["warden"]&.user
|
34
|
+
return {} unless user
|
35
|
+
|
36
|
+
{
|
37
|
+
id: user.id,
|
38
|
+
email: user.email,
|
39
|
+
username: user.respond_to?(:username) ? user.username : nil,
|
40
|
+
created_at: user.created_at,
|
41
|
+
role: user.respond_to?(:role) ? user.role : nil
|
42
|
+
}.compact
|
43
|
+
end
|
44
|
+
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.omniauth_provider
|
50
|
+
lambda do |_event, hint|
|
51
|
+
return {} unless defined?(OmniAuth)
|
52
|
+
|
53
|
+
if (request = hint[:request]) && (auth_info = request.env["omniauth.auth"])
|
54
|
+
{
|
55
|
+
provider: auth_info["provider"],
|
56
|
+
uid: auth_info["uid"],
|
57
|
+
name: auth_info.dig("info", "name"),
|
58
|
+
email: auth_info.dig("info", "email"),
|
59
|
+
username: auth_info.dig("info", "nickname")
|
60
|
+
}.compact
|
61
|
+
end
|
62
|
+
|
63
|
+
{}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.session_provider
|
68
|
+
lambda do |_event, hint|
|
69
|
+
return {} unless hint[:request]
|
70
|
+
|
71
|
+
request = hint[:request]
|
72
|
+
session = begin
|
73
|
+
request.session
|
74
|
+
rescue StandardError
|
75
|
+
{}
|
76
|
+
end
|
77
|
+
|
78
|
+
{
|
79
|
+
session_id: session[:session_id] || session["session_id"],
|
80
|
+
user_id: session[:user_id] || session["user_id"],
|
81
|
+
csrf_token: session[:_csrf_token] || session["_csrf_token"]
|
82
|
+
}.compact
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.thread_local_provider
|
87
|
+
lambda do |_event, _hint|
|
88
|
+
# Get user from thread-local storage
|
89
|
+
Thread.current[:current_user] || {}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/lapsoss/validators.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "uri"
|
4
4
|
|
5
5
|
module Lapsoss
|
6
6
|
module Validators
|
@@ -17,7 +17,7 @@ module Lapsoss
|
|
17
17
|
def validate_type!(value, types, name)
|
18
18
|
types = Array(types)
|
19
19
|
unless types.any? { |type| value.is_a?(type) }
|
20
|
-
type_names = types.map(&:name).join(
|
20
|
+
type_names = types.map(&:name).join(" or ")
|
21
21
|
raise ValidationError, "#{name} must be a #{type_names}, got #{value.class} (#{value.inspect})"
|
22
22
|
end
|
23
23
|
end
|
@@ -29,7 +29,7 @@ module Lapsoss
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def validate_numeric_range!(value, range, name)
|
32
|
-
validate_type!(value, [Numeric], name)
|
32
|
+
validate_type!(value, [ Numeric ], name)
|
33
33
|
unless range.cover?(value)
|
34
34
|
raise ValidationError, "#{name} must be between #{range.min} and #{range.max}, got #{value}"
|
35
35
|
end
|
@@ -38,7 +38,7 @@ module Lapsoss
|
|
38
38
|
def validate_url!(value, name)
|
39
39
|
return if value.nil?
|
40
40
|
|
41
|
-
validate_type!(value, [String], name)
|
41
|
+
validate_type!(value, [ String ], name)
|
42
42
|
|
43
43
|
begin
|
44
44
|
uri = URI.parse(value)
|
@@ -49,10 +49,10 @@ module Lapsoss
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
-
def validate_dsn!(dsn_string, name =
|
52
|
+
def validate_dsn!(dsn_string, name = "DSN")
|
53
53
|
return if dsn_string.nil?
|
54
54
|
|
55
|
-
validate_type!(dsn_string, [String], name)
|
55
|
+
validate_type!(dsn_string, [ String ], name)
|
56
56
|
|
57
57
|
begin
|
58
58
|
uri = URI.parse(dsn_string)
|
@@ -66,7 +66,7 @@ module Lapsoss
|
|
66
66
|
raise ValidationError, "#{name} must use http or https scheme" unless %w[http https].include?(uri.scheme)
|
67
67
|
|
68
68
|
# Extract project ID from path
|
69
|
-
path_parts = uri.path&.split(
|
69
|
+
path_parts = uri.path&.split("/") || []
|
70
70
|
project_id = path_parts.last
|
71
71
|
validate_presence!(project_id, "#{name} project ID")
|
72
72
|
|
@@ -81,7 +81,7 @@ module Lapsoss
|
|
81
81
|
|
82
82
|
def validate_api_key!(value, name, format: nil)
|
83
83
|
validate_presence!(value, name)
|
84
|
-
validate_type!(value, [String], name)
|
84
|
+
validate_type!(value, [ String ], name)
|
85
85
|
|
86
86
|
case format
|
87
87
|
when :uuid
|
@@ -95,10 +95,10 @@ module Lapsoss
|
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
98
|
-
def validate_environment!(value, name =
|
98
|
+
def validate_environment!(value, name = "environment")
|
99
99
|
return if value.nil?
|
100
100
|
|
101
|
-
validate_type!(value, [String, Symbol], name)
|
101
|
+
validate_type!(value, [ String, Symbol ], name)
|
102
102
|
|
103
103
|
env_string = value.to_s
|
104
104
|
unless env_string.match?(/\A[a-z0-9_-]+\z/i)
|
@@ -106,23 +106,23 @@ module Lapsoss
|
|
106
106
|
end
|
107
107
|
end
|
108
108
|
|
109
|
-
def validate_sample_rate!(value, name =
|
109
|
+
def validate_sample_rate!(value, name = "sample_rate")
|
110
110
|
return if value.nil?
|
111
111
|
|
112
112
|
validate_numeric_range!(value, 0.0..1.0, name)
|
113
113
|
end
|
114
114
|
|
115
|
-
def validate_timeout!(value, name =
|
115
|
+
def validate_timeout!(value, name = "timeout")
|
116
116
|
return if value.nil?
|
117
117
|
|
118
|
-
validate_type!(value, [Numeric], name)
|
118
|
+
validate_type!(value, [ Numeric ], name)
|
119
119
|
raise ValidationError, "#{name} must be positive, got #{value}" if value <= 0
|
120
120
|
end
|
121
121
|
|
122
|
-
def validate_retries!(value, name =
|
122
|
+
def validate_retries!(value, name = "max_retries")
|
123
123
|
return if value.nil?
|
124
124
|
|
125
|
-
validate_type!(value, [Integer], name)
|
125
|
+
validate_type!(value, [ Integer ], name)
|
126
126
|
validate_numeric_range!(value, 0..10, name)
|
127
127
|
end
|
128
128
|
end
|
data/lib/lapsoss/version.rb
CHANGED
data/lib/lapsoss.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "zeitwerk"
|
4
|
+
require "active_support/core_ext/module/delegation"
|
5
5
|
|
6
6
|
loader = Zeitwerk::Loader.for_gem
|
7
7
|
loader.do_not_eager_load("#{__dir__}/lapsoss/adapters")
|
8
8
|
loader.setup
|
9
9
|
|
10
|
-
require_relative
|
10
|
+
require_relative "lapsoss/railtie" if defined?(Rails::Railtie)
|
11
11
|
|
12
12
|
module Lapsoss
|
13
13
|
class Error < StandardError; end
|