lapsoss 0.4.0 → 0.4.4
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 +195 -7
- data/lib/lapsoss/adapters/concerns/level_mapping.rb +1 -0
- data/lib/lapsoss/adapters/telebug_adapter.rb +58 -0
- data/lib/lapsoss/client.rb +1 -3
- data/lib/lapsoss/configuration.rb +14 -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 +14 -3
- 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
- data/lib/lapsoss.rb +23 -0
- metadata +2 -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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c9422cb2f9fa0e1ae582a771fd337d61177d9236db6cd3dfc96edf3fefc7777
|
4
|
+
data.tar.gz: d1dc595b8fcd5e00feb9f01087e77700bb49452a6b358a309ce630d6b29bd203
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 218bbe054b15df5457996cfda82aa6227477446dc0a5be24680acbae4c4ff3edfaf7217adfc96814aba94fe09331300253f4a499a85055b2fbac8df83a8336fb
|
7
|
+
data.tar.gz: d5b0f10802bfe6c938cf90e8a322b0bc940b11d3bca9fde5efcaf96964c5753c09e04ae453087844cb455056b2cd6fd242e846e99ce59270d25962367daa7bda
|
data/README.md
CHANGED
@@ -163,6 +163,7 @@ All adapters are pure Ruby implementations with no external SDK dependencies:
|
|
163
163
|
- **Rollbar** - Complete error tracking with grouping
|
164
164
|
- **AppSignal** - Error tracking and deploy markers
|
165
165
|
- **Insight Hub** (formerly Bugsnag) - Error tracking with breadcrumbs
|
166
|
+
- **Telebug** - Sentry-compatible protocol (perfect for self-hosted alternatives)
|
166
167
|
|
167
168
|
## Configuration
|
168
169
|
|
@@ -186,6 +187,17 @@ Lapsoss.configure do |config|
|
|
186
187
|
end
|
187
188
|
```
|
188
189
|
|
190
|
+
### Using Sentry-Compatible Services
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
# Telebug, Glitchtip, or any Sentry-compatible service
|
194
|
+
Lapsoss.configure do |config|
|
195
|
+
config.use_telebug(dsn: ENV['TELEBUG_DSN'])
|
196
|
+
# Or use use_sentry with a custom endpoint
|
197
|
+
config.use_sentry(dsn: ENV['SELF_HOSTED_SENTRY_DSN'])
|
198
|
+
end
|
199
|
+
```
|
200
|
+
|
189
201
|
### Advanced Configuration
|
190
202
|
|
191
203
|
```ruby
|
@@ -196,23 +208,82 @@ Lapsoss.configure do |config|
|
|
196
208
|
# Data scrubbing (uses Rails filter_parameters automatically)
|
197
209
|
config.scrub_fields = %w[password credit_card ssn] # Or leave nil to use Rails defaults
|
198
210
|
|
199
|
-
#
|
211
|
+
# Performance
|
212
|
+
config.async = true # Send errors in background
|
213
|
+
|
214
|
+
# Sampling (see docs/sampling_strategies.md for advanced examples)
|
215
|
+
config.sample_rate = Rails.env.production? ? 0.25 : 1.0
|
216
|
+
|
217
|
+
# Transport settings
|
218
|
+
config.transport_timeout = 10 # seconds
|
219
|
+
config.transport_max_retries = 3
|
220
|
+
end
|
221
|
+
```
|
222
|
+
|
223
|
+
### Filtering Errors
|
224
|
+
|
225
|
+
You decide what errors to track. Lapsoss doesn't make assumptions:
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
Lapsoss.configure do |config|
|
229
|
+
# Use the before_send callback for simple filtering
|
200
230
|
config.before_send = lambda do |event|
|
201
231
|
# Return nil to prevent sending
|
202
232
|
return nil if event.exception.is_a?(ActiveRecord::RecordNotFound)
|
203
233
|
event
|
204
234
|
end
|
235
|
+
|
236
|
+
# Or use the exclusion filter for more complex rules
|
237
|
+
config.exclusion_filter = Lapsoss::ExclusionFilter.new(
|
238
|
+
# Exclude specific exception types
|
239
|
+
excluded_exceptions: [
|
240
|
+
"ActionController::RoutingError", # Your choice
|
241
|
+
"ActiveRecord::RecordNotFound" # Your decision
|
242
|
+
],
|
243
|
+
|
244
|
+
# Exclude by pattern matching
|
245
|
+
excluded_patterns: [
|
246
|
+
/timeout/i, # If timeouts are expected in your app
|
247
|
+
/user not found/i # If these are normal in your workflow
|
248
|
+
],
|
249
|
+
|
250
|
+
# Exclude specific error messages
|
251
|
+
excluded_messages: [
|
252
|
+
"No route matches",
|
253
|
+
"Invalid authenticity token"
|
254
|
+
]
|
255
|
+
)
|
256
|
+
|
257
|
+
# Add custom exclusion logic
|
258
|
+
config.exclusion_filter.add_exclusion(:custom, lambda do |event|
|
259
|
+
# Your business logic here
|
260
|
+
event.context[:request]&.dig(:user_agent)&.match?(/bot/i)
|
261
|
+
end)
|
262
|
+
end
|
263
|
+
```
|
205
264
|
|
206
|
-
|
207
|
-
config.sample_rate = Rails.env.production? ? 0.25 : 1.0
|
265
|
+
#### Common Patterns (Your Choice)
|
208
266
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
config.
|
267
|
+
```ruby
|
268
|
+
# Development/Test exclusions
|
269
|
+
if Rails.env.development?
|
270
|
+
config.exclusion_filter.add_exclusion(:exception, "RSpec::Expectations::ExpectationNotMetError")
|
271
|
+
config.exclusion_filter.add_exclusion(:exception, "Minitest::Assertion")
|
213
272
|
end
|
273
|
+
|
274
|
+
# User input errors (if you don't want to track them)
|
275
|
+
config.exclusion_filter.add_exclusion(:exception, "ActiveRecord::RecordInvalid")
|
276
|
+
config.exclusion_filter.add_exclusion(:exception, "ActionController::ParameterMissing")
|
277
|
+
|
278
|
+
# Bot traffic (if you want to exclude it)
|
279
|
+
config.exclusion_filter.add_exclusion(:custom, lambda do |event|
|
280
|
+
request = event.context[:request]
|
281
|
+
request && request[:user_agent]&.match?(/googlebot|bingbot/i)
|
282
|
+
end)
|
214
283
|
```
|
215
284
|
|
285
|
+
Your app, your rules. Lapsoss just provides the mechanism.
|
286
|
+
|
216
287
|
### Data Protection
|
217
288
|
|
218
289
|
Lapsoss automatically integrates with Rails' parameter filtering:
|
@@ -251,6 +322,103 @@ config.transport_timeout = 10
|
|
251
322
|
config.transport_jitter = true # Prevent thundering herd
|
252
323
|
```
|
253
324
|
|
325
|
+
## Testing in Rails Console
|
326
|
+
|
327
|
+
Want to see Lapsoss in action? Try this in your Rails console:
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
# Configure Lapsoss with the logger adapter for immediate visibility
|
331
|
+
Lapsoss.configure do |config|
|
332
|
+
config.use_logger(name: :console_test)
|
333
|
+
config.async = false # Synchronous for immediate output
|
334
|
+
config.debug = true # Verbose logging
|
335
|
+
end
|
336
|
+
|
337
|
+
# Create a class that demonstrates error handling
|
338
|
+
class Liberation
|
339
|
+
def self.liberate!
|
340
|
+
Rails.error.handle do
|
341
|
+
raise StandardError, "Freedom requires breaking chains!"
|
342
|
+
end
|
343
|
+
puts "✅ Continued execution after error"
|
344
|
+
end
|
345
|
+
|
346
|
+
def self.revolt!
|
347
|
+
Rails.error.record do
|
348
|
+
raise RuntimeError, "Revolution cannot be stopped!"
|
349
|
+
end
|
350
|
+
puts "This won't print - error was re-raised"
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Test error capture (error is swallowed)
|
355
|
+
Liberation.liberate!
|
356
|
+
# You'll see the error logged but execution continues
|
357
|
+
|
358
|
+
# Test error recording (error is re-raised)
|
359
|
+
begin
|
360
|
+
Liberation.revolt!
|
361
|
+
rescue => e
|
362
|
+
puts "Caught re-raised error: #{e.message}"
|
363
|
+
end
|
364
|
+
|
365
|
+
# Manual error reporting with context
|
366
|
+
begin
|
367
|
+
1 / 0
|
368
|
+
rescue => e
|
369
|
+
Rails.error.report(e, context: { user_id: 42, action: "console_test" })
|
370
|
+
end
|
371
|
+
|
372
|
+
# Check what was captured
|
373
|
+
puts "\n🎉 Lapsoss captured all errors through Rails.error!"
|
374
|
+
```
|
375
|
+
|
376
|
+
You'll see all errors logged to your console with full backtraces and context. This same integration works automatically for all Rails controllers, jobs, and mailers.
|
377
|
+
|
378
|
+
## Using Lapsoss Outside Rails
|
379
|
+
|
380
|
+
Lapsoss provides the same convenient error handling methods directly, perfect for background jobs, rake tasks, or standalone scripts:
|
381
|
+
|
382
|
+
```ruby
|
383
|
+
# In your Sidekiq job, rake task, or any Ruby code
|
384
|
+
require 'lapsoss'
|
385
|
+
|
386
|
+
Lapsoss.configure do |config|
|
387
|
+
config.use_sentry(dsn: ENV['SENTRY_DSN'])
|
388
|
+
end
|
389
|
+
|
390
|
+
# Handle errors (swallow them)
|
391
|
+
result = Lapsoss.handle do
|
392
|
+
risky_operation
|
393
|
+
end
|
394
|
+
# Returns nil if error occurred, or the block's result
|
395
|
+
|
396
|
+
# Handle with fallback
|
397
|
+
user = Lapsoss.handle(fallback: User.anonymous) do
|
398
|
+
User.find(id)
|
399
|
+
end
|
400
|
+
|
401
|
+
# Record errors (re-raise them)
|
402
|
+
Lapsoss.record do
|
403
|
+
critical_operation # Error is captured then re-raised
|
404
|
+
end
|
405
|
+
|
406
|
+
# Report errors manually
|
407
|
+
begin
|
408
|
+
something_dangerous
|
409
|
+
rescue => e
|
410
|
+
Lapsoss.report(e, user_id: user.id, context: 'background_job')
|
411
|
+
# Continue processing...
|
412
|
+
end
|
413
|
+
|
414
|
+
# These methods mirror Rails.error exactly:
|
415
|
+
# - Lapsoss.handle → Rails.error.handle
|
416
|
+
# - Lapsoss.record → Rails.error.record
|
417
|
+
# - Lapsoss.report → Rails.error.report
|
418
|
+
```
|
419
|
+
|
420
|
+
This means your error handling code works the same way everywhere - in Rails controllers, background jobs, rake tasks, or standalone scripts.
|
421
|
+
|
254
422
|
## Creating Custom Adapters
|
255
423
|
|
256
424
|
```ruby
|
@@ -264,6 +432,26 @@ end
|
|
264
432
|
Lapsoss::Registry.register(:my_service, MyAdapter)
|
265
433
|
```
|
266
434
|
|
435
|
+
### Extending Existing Adapters
|
436
|
+
|
437
|
+
For Sentry-compatible services, just extend the SentryAdapter:
|
438
|
+
|
439
|
+
```ruby
|
440
|
+
class TelebugAdapter < Lapsoss::Adapters::SentryAdapter
|
441
|
+
def initialize(name = :telebug, settings = {})
|
442
|
+
super(name, settings)
|
443
|
+
end
|
444
|
+
|
445
|
+
private
|
446
|
+
|
447
|
+
def build_headers(public_key)
|
448
|
+
super(public_key).merge(
|
449
|
+
"X-Telebug-Client" => "lapsoss/#{Lapsoss::VERSION}"
|
450
|
+
)
|
451
|
+
end
|
452
|
+
end
|
453
|
+
```
|
454
|
+
|
267
455
|
## Contributing
|
268
456
|
|
269
457
|
1. Fork it
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "sentry_adapter"
|
4
|
+
|
5
|
+
module Lapsoss
|
6
|
+
module Adapters
|
7
|
+
# Telebug adapter - uses Sentry protocol with Telebug endpoints
|
8
|
+
# Telebug is compatible with Sentry's API, so we inherit from SentryAdapter
|
9
|
+
class TelebugAdapter < SentryAdapter
|
10
|
+
def initialize(name = :telebug, settings = {})
|
11
|
+
super(name, settings)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# Override to parse Telebug DSN format
|
17
|
+
def parse_dsn(dsn_string)
|
18
|
+
uri = URI.parse(dsn_string)
|
19
|
+
{
|
20
|
+
public_key: uri.user,
|
21
|
+
project_id: uri.path.split("/").last,
|
22
|
+
host: uri.host,
|
23
|
+
path: uri.path
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Override to build Telebug-specific API path
|
28
|
+
def build_api_path(uri)
|
29
|
+
# Telebug uses: https://[key]@[host]/api/v1/sentry_errors/[project_id]
|
30
|
+
# The path is already complete: /api/v1/sentry_errors/4
|
31
|
+
# Unlike Sentry which needs /api/[project_id]/envelope/
|
32
|
+
uri.path
|
33
|
+
end
|
34
|
+
|
35
|
+
# Override to setup Telebug endpoint
|
36
|
+
def setup_endpoint
|
37
|
+
uri = URI.parse(@settings[:dsn])
|
38
|
+
# For Telebug, we use the full URL without port (unless non-standard)
|
39
|
+
port = (uri.port == 443 || uri.port == 80) ? "" : ":#{uri.port}"
|
40
|
+
self.class.api_endpoint = "#{uri.scheme}://#{uri.host}#{port}"
|
41
|
+
self.class.api_path = build_api_path(uri)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Override headers builder to add Telebug-specific headers
|
45
|
+
def headers_for(envelope)
|
46
|
+
base_headers = super(envelope)
|
47
|
+
base_headers.merge(
|
48
|
+
"X-Telebug-Client" => "lapsoss/#{Lapsoss::VERSION}"
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Override user agent for Telebug
|
53
|
+
def user_agent
|
54
|
+
"lapsoss-telebug/#{Lapsoss::VERSION}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/lapsoss/client.rb
CHANGED
@@ -103,9 +103,7 @@ module Lapsoss
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def handle_capture_error(error)
|
106
|
-
|
107
|
-
|
108
|
-
@configuration.logger.error("[Lapsoss] Failed to capture event: #{error.message}")
|
106
|
+
@configuration.logger.error("Failed to capture event: #{error.message}")
|
109
107
|
end
|
110
108
|
end
|
111
109
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "logger"
|
3
4
|
require "active_support/configurable"
|
4
5
|
|
5
6
|
module Lapsoss
|
@@ -21,7 +22,7 @@ module Lapsoss
|
|
21
22
|
def initialize
|
22
23
|
@adapter_configs = {}
|
23
24
|
@async = true
|
24
|
-
@logger = nil
|
25
|
+
@logger = Logger.new(nil) # Default null logger
|
25
26
|
@environment = nil
|
26
27
|
@enabled = true
|
27
28
|
@release = nil
|
@@ -84,6 +85,11 @@ module Lapsoss
|
|
84
85
|
register_adapter(name, :sentry, **settings)
|
85
86
|
end
|
86
87
|
|
88
|
+
# Convenience method for Telebug (Sentry-compatible)
|
89
|
+
def use_telebug(name: :telebug, **settings)
|
90
|
+
register_adapter(name, :telebug, **settings)
|
91
|
+
end
|
92
|
+
|
87
93
|
# Convenience method for AppSignal
|
88
94
|
def use_appsignal(name: :appsignal, **settings)
|
89
95
|
register_adapter(name, :appsignal, **settings)
|
@@ -193,17 +199,8 @@ module Lapsoss
|
|
193
199
|
|
194
200
|
def create_sampling_strategy
|
195
201
|
case @sampling_strategy
|
196
|
-
when
|
197
|
-
|
198
|
-
when :production
|
199
|
-
Sampling::SamplingFactory.create_production_sampling
|
200
|
-
when :development
|
201
|
-
Sampling::SamplingFactory.create_development_sampling
|
202
|
-
when :user_focused
|
203
|
-
Sampling::SamplingFactory.create_user_focused_sampling
|
204
|
-
else
|
205
|
-
Sampling::UniformSampler.new(@sample_rate)
|
206
|
-
end
|
202
|
+
when Numeric
|
203
|
+
Sampling::UniformSampler.new(@sampling_strategy)
|
207
204
|
when Proc
|
208
205
|
@sampling_strategy
|
209
206
|
when nil
|
@@ -276,7 +273,7 @@ module Lapsoss
|
|
276
273
|
def validate!
|
277
274
|
# Check sample rate is between 0 and 1
|
278
275
|
if @sample_rate && (@sample_rate < 0 || @sample_rate > 1)
|
279
|
-
logger
|
276
|
+
logger.warn "sample_rate should be between 0 and 1, got #{@sample_rate}"
|
280
277
|
end
|
281
278
|
|
282
279
|
# Check callables
|
@@ -289,21 +286,21 @@ module Lapsoss
|
|
289
286
|
|
290
287
|
# Just log if transport settings look unusual
|
291
288
|
if @transport_timeout && @transport_timeout <= 0
|
292
|
-
logger
|
289
|
+
logger.warn "transport_timeout should be positive, got #{@transport_timeout}"
|
293
290
|
end
|
294
291
|
|
295
292
|
if @transport_max_retries && @transport_max_retries < 0
|
296
|
-
logger
|
293
|
+
logger.warn "transport_max_retries should be non-negative, got #{@transport_max_retries}"
|
297
294
|
end
|
298
295
|
|
299
296
|
if @transport_initial_backoff && @transport_max_backoff && @transport_initial_backoff > @transport_max_backoff
|
300
|
-
logger
|
297
|
+
logger.warn "transport_initial_backoff (#{@transport_initial_backoff}) should be less than transport_max_backoff (#{@transport_max_backoff})"
|
301
298
|
end
|
302
299
|
|
303
300
|
# Validate adapter configurations exist
|
304
301
|
@adapter_configs.each do |name, config|
|
305
302
|
if config[:type].blank?
|
306
|
-
logger
|
303
|
+
logger.warn "Adapter '#{name}' has no type specified"
|
307
304
|
end
|
308
305
|
end
|
309
306
|
|
@@ -4,8 +4,9 @@ require "digest"
|
|
4
4
|
|
5
5
|
module Lapsoss
|
6
6
|
class Fingerprinter
|
7
|
-
|
8
|
-
|
7
|
+
# Base patterns that are always available
|
8
|
+
BASE_PATTERNS = [
|
9
|
+
# Generic error message normalization
|
9
10
|
{
|
10
11
|
pattern: /User \d+ (not found|invalid|missing)/i,
|
11
12
|
fingerprint: "user-lookup-error"
|
@@ -15,21 +16,25 @@ module Lapsoss
|
|
15
16
|
fingerprint: "record-lookup-error"
|
16
17
|
},
|
17
18
|
|
18
|
-
#
|
19
|
+
# Network error patterns
|
19
20
|
{
|
20
|
-
pattern:
|
21
|
-
fingerprint: "
|
21
|
+
pattern: /Net::(TimeoutError|ReadTimeout|OpenTimeout)/,
|
22
|
+
fingerprint: "network-timeout"
|
22
23
|
},
|
23
24
|
{
|
24
|
-
pattern:
|
25
|
-
fingerprint: "
|
25
|
+
pattern: /Errno::(ECONNREFUSED|ECONNRESET|EHOSTUNREACH)/,
|
26
|
+
fingerprint: "network-connection-error"
|
26
27
|
},
|
27
28
|
|
28
|
-
#
|
29
|
+
# Memory/Resource patterns
|
29
30
|
{
|
30
|
-
pattern: /
|
31
|
-
fingerprint: "
|
32
|
-
}
|
31
|
+
pattern: /NoMemoryError|SystemStackError/,
|
32
|
+
fingerprint: "memory-resource-error"
|
33
|
+
}
|
34
|
+
].freeze
|
35
|
+
|
36
|
+
# ActiveRecord-specific patterns (only loaded if ActiveRecord is defined)
|
37
|
+
ACTIVERECORD_PATTERNS = [
|
33
38
|
{
|
34
39
|
pattern: /ActiveRecord::RecordNotFound/,
|
35
40
|
fingerprint: "record-not-found"
|
@@ -38,38 +43,34 @@ module Lapsoss
|
|
38
43
|
pattern: /ActiveRecord::StatementInvalid.*timeout/i,
|
39
44
|
fingerprint: "database-timeout"
|
40
45
|
},
|
41
|
-
|
42
|
-
# Network error patterns
|
43
|
-
{
|
44
|
-
pattern: /Net::(TimeoutError|ReadTimeout|OpenTimeout)/,
|
45
|
-
fingerprint: "network-timeout"
|
46
|
-
},
|
47
46
|
{
|
48
|
-
pattern: /
|
49
|
-
fingerprint: "
|
50
|
-
}
|
47
|
+
pattern: /ActiveRecord::ConnectionTimeoutError/,
|
48
|
+
fingerprint: "database-connection-timeout"
|
49
|
+
}
|
50
|
+
].freeze
|
51
51
|
|
52
|
-
|
52
|
+
# Database adapter patterns (only loaded if adapters are defined)
|
53
|
+
DATABASE_PATTERNS = [
|
53
54
|
{
|
54
|
-
pattern:
|
55
|
-
fingerprint: "
|
55
|
+
pattern: /PG::ConnectionBad/,
|
56
|
+
fingerprint: "postgres-connection-error",
|
57
|
+
condition: -> { defined?(PG) }
|
56
58
|
},
|
57
59
|
{
|
58
|
-
pattern: /
|
59
|
-
fingerprint: "
|
60
|
+
pattern: /Mysql2::Error/,
|
61
|
+
fingerprint: "mysql-connection-error",
|
62
|
+
condition: -> { defined?(Mysql2) }
|
60
63
|
},
|
61
|
-
|
62
|
-
# Memory/Resource patterns
|
63
64
|
{
|
64
|
-
pattern: /
|
65
|
-
fingerprint: "
|
65
|
+
pattern: /SQLite3::BusyException/,
|
66
|
+
fingerprint: "sqlite-busy-error",
|
67
|
+
condition: -> { defined?(SQLite3) }
|
66
68
|
}
|
67
69
|
].freeze
|
68
70
|
|
69
71
|
def initialize(config = {})
|
70
72
|
@custom_callback = config[:custom_callback]
|
71
|
-
@patterns = config[:patterns]
|
72
|
-
@normalize_paths = config.fetch(:normalize_paths, true)
|
73
|
+
@patterns = build_patterns(config[:patterns])
|
73
74
|
@normalize_ids = config.fetch(:normalize_ids, true)
|
74
75
|
@include_environment = config.fetch(:include_environment, false)
|
75
76
|
end
|
@@ -91,6 +92,23 @@ module Lapsoss
|
|
91
92
|
|
92
93
|
private
|
93
94
|
|
95
|
+
def build_patterns(custom_patterns)
|
96
|
+
return custom_patterns if custom_patterns
|
97
|
+
|
98
|
+
patterns = BASE_PATTERNS.dup
|
99
|
+
|
100
|
+
# Always include ActiveRecord patterns - they match on string names
|
101
|
+
patterns.concat(ACTIVERECORD_PATTERNS)
|
102
|
+
|
103
|
+
# Add database-specific patterns - they also match on string names
|
104
|
+
DATABASE_PATTERNS.each do |pattern_config|
|
105
|
+
# Skip the condition check - just match on error names
|
106
|
+
patterns << pattern_config.except(:condition)
|
107
|
+
end
|
108
|
+
|
109
|
+
patterns
|
110
|
+
end
|
111
|
+
|
94
112
|
def match_patterns(event)
|
95
113
|
full_error_text = build_error_text(event)
|
96
114
|
|
@@ -170,18 +188,9 @@ module Lapsoss
|
|
170
188
|
|
171
189
|
# Replace numeric IDs with placeholder (after UUIDs and hashes)
|
172
190
|
normalized.gsub!(/\b\d{3,}\b/, ":id")
|
173
|
-
end
|
174
|
-
|
175
|
-
if @normalize_paths
|
176
|
-
# Replace absolute file paths with placeholder
|
177
|
-
normalized.gsub!(%r{/[^/\s]+(?:/[^/\s]+)*\.[a-zA-Z0-9]+}, ":filepath")
|
178
|
-
normalized.gsub!(%r{/[^/\s]+(?:/[^/\s]+)+(?:/)?}, ":dirpath")
|
179
191
|
|
180
192
|
# Replace timestamps
|
181
193
|
normalized.gsub!(/\b\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}/, ":timestamp")
|
182
|
-
|
183
|
-
# Replace URLs with placeholder
|
184
|
-
normalized.gsub!(%r{https?://[^\s]+}, ":url")
|
185
194
|
end
|
186
195
|
|
187
196
|
# Clean up extra whitespace
|
@@ -201,13 +210,9 @@ module Lapsoss
|
|
201
210
|
|
202
211
|
line_to_use = app_line || backtrace.first
|
203
212
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
"#{::Regexp.last_match(1)}:#{::Regexp.last_match(2)}"
|
208
|
-
else
|
209
|
-
line_to_use
|
210
|
-
end
|
213
|
+
# Extract just filename:line_number
|
214
|
+
if line_to_use =~ %r{([^/]+):(\d+)}
|
215
|
+
"#{::Regexp.last_match(1)}:#{::Regexp.last_match(2)}"
|
211
216
|
else
|
212
217
|
line_to_use
|
213
218
|
end
|