lapsoss 0.4.6 → 0.4.10
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 +77 -21
- data/lib/lapsoss/adapters/sentry_adapter.rb +2 -1
- data/lib/lapsoss/adapters/telebugs_adapter.rb +42 -3
- data/lib/lapsoss/client.rb +37 -10
- data/lib/lapsoss/configuration.rb +7 -3
- data/lib/lapsoss/event.rb +4 -2
- data/lib/lapsoss/merged_scope.rb +22 -0
- data/lib/lapsoss/rails_controller_context.rb +36 -0
- data/lib/lapsoss/rails_controller_transaction.rb +33 -0
- data/lib/lapsoss/railtie.rb +13 -19
- data/lib/lapsoss/router.rb +6 -1
- data/lib/lapsoss/scope.rb +10 -0
- data/lib/lapsoss/version.rb +1 -1
- data/lib/lapsoss.rb +2 -0
- metadata +3 -2
- data/lib/lapsoss/rails_middleware.rb +0 -78
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 76c4609d94f1d9463922da6d1850b7b618977075dd0d96fa7d94d58186cd64b9
|
|
4
|
+
data.tar.gz: 8d5db54e63ef1e6a0d73962261bd13ce4cfca305c63570bf879c4dbf85bb3110
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ac8bc565e2d54b1c3f600de8b2a6fedc0e2da481ce3c9003c587b81bd4134209618ba77dd3ae1019188537217c09c2f8317640ea36d46b91a229116eeaafd032
|
|
7
|
+
data.tar.gz: eee117f27719db7cfe019466cfbe5ec1210e86c0cfb0a7bff6764984acb148fb2233dd0c84fb7029e0e643e38f2147989f41172035a41dbd79a35eecad79763f
|
data/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Lapsoss - Vendor-Neutral Error Reporting for Rails
|
|
2
2
|
|
|
3
|
+
[](https://github.com/seuros/lapsoss/actions/workflows/ci.yml)
|
|
4
|
+
[](https://badge.fury.io/rb/lapsoss)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://rubygems.org/gems/lapsoss)
|
|
7
|
+
|
|
3
8
|
## The Problem We All Face
|
|
4
9
|
|
|
5
10
|
You're 6 months into production with Bugsnag. The CFO says "costs are too high, switch to Sentry."
|
|
@@ -23,10 +28,10 @@ Lapsoss.configure do |config|
|
|
|
23
28
|
# Monday: Using Bugsnag
|
|
24
29
|
config.use_bugsnag(api_key: ENV['BUGSNAG_KEY'])
|
|
25
30
|
|
|
26
|
-
# Tuesday: Add
|
|
27
|
-
config.
|
|
31
|
+
# Tuesday: Add Telebugs for comparison
|
|
32
|
+
config.use_telebugs(dsn: ENV['TELEBUGS_DSN'])
|
|
28
33
|
|
|
29
|
-
# Wednesday: Drop Bugsnag, keep
|
|
34
|
+
# Wednesday: Drop Bugsnag, keep Telebugs
|
|
30
35
|
# Just remove the line. Zero code changes.
|
|
31
36
|
end
|
|
32
37
|
```
|
|
@@ -64,7 +69,7 @@ That's it. No 500-line examples needed.
|
|
|
64
69
|
|
|
65
70
|
## Built for Rails, Not Around It
|
|
66
71
|
|
|
67
|
-
Lapsoss integrates with Rails' native error reporting API introduced in Rails 7. No monkey-patching, no
|
|
72
|
+
Lapsoss integrates with Rails' native error reporting API introduced in Rails 7. No monkey-patching, no global error handlers:
|
|
68
73
|
|
|
69
74
|
```ruby
|
|
70
75
|
# It just works with Rails.error:
|
|
@@ -74,6 +79,56 @@ end
|
|
|
74
79
|
# Automatically captured by whatever service you configured
|
|
75
80
|
```
|
|
76
81
|
|
|
82
|
+
### Rails Integration Options
|
|
83
|
+
|
|
84
|
+
**Option 1: Automatic Rails.error Integration (Recommended)**
|
|
85
|
+
```ruby
|
|
86
|
+
# config/initializers/lapsoss.rb
|
|
87
|
+
Lapsoss.configure do |config|
|
|
88
|
+
config.use_appsignal(push_api_key: ENV['APPSIGNAL_KEY'])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# That's it! All Rails errors are automatically captured
|
|
92
|
+
# Works with Rails.error.handle, Rails.error.record, Rails.error.report
|
|
93
|
+
# No code changes needed - just configure and go
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Option 2: Add Controller Context (Optional)**
|
|
97
|
+
```ruby
|
|
98
|
+
# app/controllers/application_controller.rb
|
|
99
|
+
class ApplicationController < ActionController::Base
|
|
100
|
+
include Lapsoss::RailsControllerContext
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Now all errors include controller/action context:
|
|
104
|
+
# { controller: "users", action: "show", controller_class: "UsersController" }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Option 3: Manual Error Reporting (Your Choice)**
|
|
108
|
+
```ruby
|
|
109
|
+
# In controllers, jobs, or anywhere you want explicit control
|
|
110
|
+
begin
|
|
111
|
+
process_payment
|
|
112
|
+
rescue => e
|
|
113
|
+
Lapsoss.capture_exception(e, user_id: current_user.id)
|
|
114
|
+
# Handle gracefully...
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Or use Rails.error directly with your configured services
|
|
118
|
+
Rails.error.report(e, context: { user_id: current_user.id })
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### No Global Patching Philosophy
|
|
122
|
+
|
|
123
|
+
Unlike other gems, Lapsoss **never** automatically captures all exceptions. You stay in control:
|
|
124
|
+
|
|
125
|
+
- ✅ **Rails.error integration only** - Uses Rails' official API
|
|
126
|
+
- ✅ **Explicit error handling** - You choose what to capture
|
|
127
|
+
- ✅ **No global hooks** - Your app behavior never changes
|
|
128
|
+
- ✅ **Optional controller context** - Include if you want it
|
|
129
|
+
|
|
130
|
+
This means your application behaves exactly the same with or without Lapsoss. No surprises, no changed behavior, no conflicts with other gems.
|
|
131
|
+
|
|
77
132
|
## Zero-Downtime Vendor Migration
|
|
78
133
|
|
|
79
134
|
```ruby
|
|
@@ -84,7 +139,7 @@ gem 'bugsnag' # Keep your existing gem for now
|
|
|
84
139
|
# Step 2: Configure dual reporting
|
|
85
140
|
Lapsoss.configure do |config|
|
|
86
141
|
config.use_bugsnag(api_key: ENV['BUGSNAG_KEY'])
|
|
87
|
-
config.
|
|
142
|
+
config.use_telebugs(dsn: ENV['TELEBUGS_DSN'])
|
|
88
143
|
end
|
|
89
144
|
|
|
90
145
|
# Step 3: Gradually replace Bugsnag calls
|
|
@@ -92,13 +147,14 @@ end
|
|
|
92
147
|
# New: Lapsoss.capture_exception(e)
|
|
93
148
|
|
|
94
149
|
# Step 4: Remove bugsnag gem when ready
|
|
95
|
-
# Your app keeps running, now on
|
|
150
|
+
# Your app keeps running, now on Telebugs
|
|
96
151
|
```
|
|
97
152
|
|
|
98
153
|
## Why Not Just Use Vendor SDKs?
|
|
99
154
|
|
|
100
155
|
**Vendor SDKs monkey-patch your application:**
|
|
101
156
|
- Sentry patches Net::HTTP, Redis, and 20+ other gems
|
|
157
|
+
- Bugsnag patches ActionController, ActiveJob, and more
|
|
102
158
|
- Each vendor races to patch the same methods
|
|
103
159
|
- Multiple SDKs = multiple layers of patches
|
|
104
160
|
- Your app behavior changes based on load order
|
|
@@ -114,8 +170,8 @@ end
|
|
|
114
170
|
### GDPR Compliance
|
|
115
171
|
```ruby
|
|
116
172
|
# Route EU data to EU servers, US data to US servers
|
|
117
|
-
config.use_sentry(name: :us, dsn: ENV['
|
|
118
|
-
config.
|
|
173
|
+
config.use_sentry(name: :us, dsn: ENV['US_SENTRY_DSN'])
|
|
174
|
+
config.use_telebugs(name: :eu, dsn: ENV['EU_TELEBUGS_DSN'])
|
|
119
175
|
```
|
|
120
176
|
|
|
121
177
|
### A/B Testing Error Services
|
|
@@ -128,8 +184,8 @@ config.use_sentry(name: :candidate, dsn: ENV['SENTRY_DSN'])
|
|
|
128
184
|
### High Availability
|
|
129
185
|
```ruby
|
|
130
186
|
# Multiple providers for redundancy
|
|
131
|
-
config.
|
|
132
|
-
config.
|
|
187
|
+
config.use_telebugs(name: :primary, dsn: ENV['PRIMARY_DSN'])
|
|
188
|
+
config.use_appsignal(name: :backup, push_api_key: ENV['APPSIGNAL_KEY'])
|
|
133
189
|
```
|
|
134
190
|
|
|
135
191
|
## Yes, We Require ActiveSupport
|
|
@@ -172,7 +228,7 @@ All adapters are pure Ruby implementations with no external SDK dependencies:
|
|
|
172
228
|
```ruby
|
|
173
229
|
# config/initializers/lapsoss.rb
|
|
174
230
|
Lapsoss.configure do |config|
|
|
175
|
-
config.
|
|
231
|
+
config.use_telebugs(dsn: ENV["TELEBUGS_DSN"])
|
|
176
232
|
end
|
|
177
233
|
```
|
|
178
234
|
|
|
@@ -203,7 +259,7 @@ end
|
|
|
203
259
|
```ruby
|
|
204
260
|
Lapsoss.configure do |config|
|
|
205
261
|
# Adapter setup
|
|
206
|
-
config.
|
|
262
|
+
config.use_rollbar(access_token: ENV['ROLLBAR_TOKEN'])
|
|
207
263
|
|
|
208
264
|
# Data scrubbing (uses Rails filter_parameters automatically)
|
|
209
265
|
config.scrub_fields = %w[password credit_card ssn] # Or leave nil to use Rails defaults
|
|
@@ -213,7 +269,7 @@ Lapsoss.configure do |config|
|
|
|
213
269
|
|
|
214
270
|
# Sampling (see docs/sampling_strategies.md for advanced examples)
|
|
215
271
|
config.sample_rate = Rails.env.production? ? 0.25 : 1.0
|
|
216
|
-
|
|
272
|
+
|
|
217
273
|
# Transport settings
|
|
218
274
|
config.transport_timeout = 10 # seconds
|
|
219
275
|
config.transport_max_retries = 3
|
|
@@ -232,7 +288,7 @@ Lapsoss.configure do |config|
|
|
|
232
288
|
return nil if event.exception.is_a?(ActiveRecord::RecordNotFound)
|
|
233
289
|
event
|
|
234
290
|
end
|
|
235
|
-
|
|
291
|
+
|
|
236
292
|
# Or use the exclusion filter for more complex rules
|
|
237
293
|
config.exclusion_filter = Lapsoss::ExclusionFilter.new(
|
|
238
294
|
# Exclude specific exception types
|
|
@@ -240,20 +296,20 @@ Lapsoss.configure do |config|
|
|
|
240
296
|
"ActionController::RoutingError", # Your choice
|
|
241
297
|
"ActiveRecord::RecordNotFound" # Your decision
|
|
242
298
|
],
|
|
243
|
-
|
|
299
|
+
|
|
244
300
|
# Exclude by pattern matching
|
|
245
301
|
excluded_patterns: [
|
|
246
302
|
/timeout/i, # If timeouts are expected in your app
|
|
247
303
|
/user not found/i # If these are normal in your workflow
|
|
248
304
|
],
|
|
249
|
-
|
|
305
|
+
|
|
250
306
|
# Exclude specific error messages
|
|
251
307
|
excluded_messages: [
|
|
252
308
|
"No route matches",
|
|
253
309
|
"Invalid authenticity token"
|
|
254
310
|
]
|
|
255
311
|
)
|
|
256
|
-
|
|
312
|
+
|
|
257
313
|
# Add custom exclusion logic
|
|
258
314
|
config.exclusion_filter.add_exclusion(:custom, lambda do |event|
|
|
259
315
|
# Your business logic here
|
|
@@ -342,7 +398,7 @@ class Liberation
|
|
|
342
398
|
end
|
|
343
399
|
puts "✅ Continued execution after error"
|
|
344
400
|
end
|
|
345
|
-
|
|
401
|
+
|
|
346
402
|
def self.revolt!
|
|
347
403
|
Rails.error.record do
|
|
348
404
|
raise RuntimeError, "Revolution cannot be stopped!"
|
|
@@ -413,7 +469,7 @@ end
|
|
|
413
469
|
|
|
414
470
|
# These methods mirror Rails.error exactly:
|
|
415
471
|
# - Lapsoss.handle → Rails.error.handle
|
|
416
|
-
# - Lapsoss.record → Rails.error.record
|
|
472
|
+
# - Lapsoss.record → Rails.error.record
|
|
417
473
|
# - Lapsoss.report → Rails.error.report
|
|
418
474
|
```
|
|
419
475
|
|
|
@@ -441,9 +497,9 @@ class TelebugsAdapter < Lapsoss::Adapters::SentryAdapter
|
|
|
441
497
|
def initialize(name = :telebugs, settings = {})
|
|
442
498
|
super(name, settings)
|
|
443
499
|
end
|
|
444
|
-
|
|
500
|
+
|
|
445
501
|
private
|
|
446
|
-
|
|
502
|
+
|
|
447
503
|
def build_headers(public_key)
|
|
448
504
|
super(public_key).merge(
|
|
449
505
|
"X-Telebugs-Client" => "lapsoss/#{Lapsoss::VERSION}"
|
|
@@ -110,8 +110,9 @@ module Lapsoss
|
|
|
110
110
|
user: event.user_context.presence,
|
|
111
111
|
extra: event.extra.presence,
|
|
112
112
|
breadcrumbs: format_breadcrumbs(event.breadcrumbs),
|
|
113
|
+
transaction: event.transaction,
|
|
113
114
|
sdk: {
|
|
114
|
-
name: "
|
|
115
|
+
name: "lapsoss.ruby",
|
|
115
116
|
version: Lapsoss::VERSION
|
|
116
117
|
}
|
|
117
118
|
}.compact_blank
|
|
@@ -8,20 +8,25 @@ module Lapsoss
|
|
|
8
8
|
# Telebugs is compatible with Sentry's API, so we inherit from SentryAdapter
|
|
9
9
|
class TelebugsAdapter < SentryAdapter
|
|
10
10
|
def initialize(name = :telebugs, settings = {})
|
|
11
|
+
debug_log "[TELEBUGS INIT] Initializing with settings: #{settings.inspect}"
|
|
11
12
|
super(name, settings)
|
|
13
|
+
debug_log "[TELEBUGS INIT] Initialization complete, enabled: #{@enabled}"
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
private
|
|
15
17
|
|
|
16
18
|
# Override to parse Telebugs DSN format
|
|
17
19
|
def parse_dsn(dsn_string)
|
|
20
|
+
debug_log "[TELEBUGS DSN] Parsing DSN: #{dsn_string}"
|
|
18
21
|
uri = URI.parse(dsn_string)
|
|
19
|
-
{
|
|
22
|
+
parsed = {
|
|
20
23
|
public_key: uri.user,
|
|
21
24
|
project_id: uri.path.split("/").last,
|
|
22
25
|
host: uri.host,
|
|
23
26
|
path: uri.path
|
|
24
27
|
}
|
|
28
|
+
debug_log "[TELEBUGS DSN] Parsed: #{parsed.inspect}"
|
|
29
|
+
parsed
|
|
25
30
|
end
|
|
26
31
|
|
|
27
32
|
# Override to build Telebugs-specific API path
|
|
@@ -42,8 +47,42 @@ module Lapsoss
|
|
|
42
47
|
uri = URI.parse(@settings[:dsn])
|
|
43
48
|
# For Telebug, we use the full URL without port (unless non-standard)
|
|
44
49
|
port = (uri.port == 443 || uri.port == 80) ? "" : ":#{uri.port}"
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
endpoint = "#{uri.scheme}://#{uri.host}#{port}"
|
|
51
|
+
api_path = build_api_path(uri)
|
|
52
|
+
|
|
53
|
+
debug_log "[TELEBUGS ENDPOINT] Setting endpoint: #{endpoint}"
|
|
54
|
+
debug_log "[TELEBUGS ENDPOINT] Setting API path: #{api_path}"
|
|
55
|
+
|
|
56
|
+
self.class.api_endpoint = endpoint
|
|
57
|
+
self.class.api_path = api_path
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
public
|
|
61
|
+
|
|
62
|
+
# Override capture to add debug logging
|
|
63
|
+
def capture(event)
|
|
64
|
+
debug_log "[TELEBUGS DEBUG] Capture called for event: #{event.type}"
|
|
65
|
+
debug_log "[TELEBUGS DEBUG] DSN configured: #{@dsn.inspect}"
|
|
66
|
+
debug_log "[TELEBUGS DEBUG] Endpoint: #{self.class.api_endpoint}"
|
|
67
|
+
debug_log "[TELEBUGS DEBUG] API Path: #{self.class.api_path}"
|
|
68
|
+
|
|
69
|
+
result = super(event)
|
|
70
|
+
debug_log "[TELEBUGS DEBUG] Event sent successfully, response: #{result.inspect}"
|
|
71
|
+
result
|
|
72
|
+
rescue => e
|
|
73
|
+
debug_log "[TELEBUGS ERROR] Failed to send: #{e.message}", :error
|
|
74
|
+
debug_log "[TELEBUGS ERROR] Backtrace: #{e.backtrace.first(5).join("\n")}", :error
|
|
75
|
+
raise
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def debug_log(message, level = :info)
|
|
79
|
+
return unless @debug
|
|
80
|
+
|
|
81
|
+
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
82
|
+
Rails.logger.public_send(level, message)
|
|
83
|
+
elsif @logger
|
|
84
|
+
@logger.public_send(level, message)
|
|
85
|
+
end
|
|
47
86
|
end
|
|
48
87
|
|
|
49
88
|
# Override headers builder to add Telebugs-specific headers
|
data/lib/lapsoss/client.rb
CHANGED
|
@@ -6,32 +6,35 @@ module Lapsoss
|
|
|
6
6
|
class Client
|
|
7
7
|
def initialize(configuration)
|
|
8
8
|
@configuration = configuration
|
|
9
|
-
|
|
9
|
+
# Note: We're using Thread.new directly for async mode instead of a thread pool
|
|
10
|
+
# The Concurrent::FixedThreadPool had issues in Rails development mode
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
def capture_exception(exception, **context)
|
|
13
|
-
return unless @configuration.enabled
|
|
14
|
+
return nil unless @configuration.enabled
|
|
14
15
|
|
|
15
16
|
with_scope(context) do |scope|
|
|
16
17
|
event = Event.build(
|
|
17
18
|
type: :exception,
|
|
18
19
|
level: :error,
|
|
19
20
|
exception: exception,
|
|
20
|
-
context: scope_to_context(scope)
|
|
21
|
+
context: scope_to_context(scope),
|
|
22
|
+
transaction: scope.transaction_name
|
|
21
23
|
)
|
|
22
24
|
capture_event(event)
|
|
23
25
|
end
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
def capture_message(message, level: :info, **context)
|
|
27
|
-
return unless @configuration.enabled
|
|
29
|
+
return nil unless @configuration.enabled
|
|
28
30
|
|
|
29
31
|
with_scope(context) do |scope|
|
|
30
32
|
event = Event.build(
|
|
31
33
|
type: :message,
|
|
32
34
|
level: level,
|
|
33
35
|
message: message,
|
|
34
|
-
context: scope_to_context(scope)
|
|
36
|
+
context: scope_to_context(scope),
|
|
37
|
+
transaction: scope.transaction_name
|
|
35
38
|
)
|
|
36
39
|
capture_event(event)
|
|
37
40
|
end
|
|
@@ -58,30 +61,54 @@ module Lapsoss
|
|
|
58
61
|
end
|
|
59
62
|
|
|
60
63
|
def flush(timeout: 2)
|
|
61
|
-
|
|
64
|
+
@configuration.logger.debug("[LAPSOSS] Flush called with timeout: #{timeout}")
|
|
65
|
+
# Give threads a moment to complete
|
|
66
|
+
sleep(0.5) if @configuration.async
|
|
67
|
+
|
|
68
|
+
# Flush individual adapters if they support it
|
|
69
|
+
Registry.instance.active.each do |adapter|
|
|
70
|
+
adapter.flush(timeout: timeout) if adapter.respond_to?(:flush)
|
|
71
|
+
end
|
|
62
72
|
end
|
|
63
73
|
|
|
64
74
|
def shutdown
|
|
65
|
-
@executor&.shutdown
|
|
66
75
|
Registry.instance.shutdown
|
|
67
76
|
end
|
|
68
77
|
|
|
69
78
|
private
|
|
70
79
|
|
|
71
80
|
def capture_event(event)
|
|
81
|
+
@configuration.logger.debug("[LAPSOSS] capture_event called, async: #{@configuration.async}, executor: #{@executor.inspect}")
|
|
82
|
+
|
|
72
83
|
# Apply pipeline processing if enabled
|
|
73
84
|
if @configuration.enable_pipeline && @configuration.pipeline
|
|
74
85
|
event = @configuration.pipeline.call(event)
|
|
75
|
-
return unless event
|
|
86
|
+
return nil unless event
|
|
76
87
|
end
|
|
77
88
|
|
|
78
89
|
event = run_before_send(event)
|
|
79
|
-
return unless event
|
|
90
|
+
return nil unless event
|
|
80
91
|
|
|
81
92
|
if @configuration.async
|
|
82
|
-
@
|
|
93
|
+
@configuration.logger.debug("[LAPSOSS ASYNC] About to process event asynchronously")
|
|
94
|
+
|
|
95
|
+
# Use Thread.new for now - the executor pool seems to have issues in Rails dev mode
|
|
96
|
+
thread = Thread.new do
|
|
97
|
+
begin
|
|
98
|
+
@configuration.logger.debug("[LAPSOSS ASYNC] Background thread started")
|
|
99
|
+
Router.process_event(event)
|
|
100
|
+
@configuration.logger.debug("[LAPSOSS ASYNC] Background thread completed")
|
|
101
|
+
rescue => e
|
|
102
|
+
@configuration.logger.error("[LAPSOSS ASYNC ERROR] Failed in background: #{e.message}")
|
|
103
|
+
@configuration.logger.error(e.backtrace.join("\n")) if @configuration.debug
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
thread
|
|
83
108
|
else
|
|
109
|
+
@configuration.logger.debug("[LAPSOSS SYNC] Processing event synchronously")
|
|
84
110
|
Router.process_event(event)
|
|
111
|
+
nil
|
|
85
112
|
end
|
|
86
113
|
rescue StandardError => e
|
|
87
114
|
handle_capture_error(e)
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "logger"
|
|
4
|
-
require "active_support/configurable"
|
|
5
4
|
|
|
6
5
|
module Lapsoss
|
|
7
6
|
class Configuration
|
|
8
7
|
include Validators
|
|
9
|
-
include ActiveSupport::Configurable
|
|
10
8
|
|
|
11
9
|
attr_accessor :async, :logger, :enabled, :release, :debug,
|
|
12
10
|
:scrub_fields, :scrub_all, :whitelist_fields, :randomize_scrub_length,
|
|
@@ -59,6 +57,7 @@ module Lapsoss
|
|
|
59
57
|
# Pipeline settings
|
|
60
58
|
@enable_pipeline = true
|
|
61
59
|
@pipeline_builder = nil
|
|
60
|
+
@pipeline = nil
|
|
62
61
|
@sampling_strategy = nil
|
|
63
62
|
# Rails error filtering
|
|
64
63
|
@skip_rails_cache_errors = true
|
|
@@ -185,7 +184,12 @@ module Lapsoss
|
|
|
185
184
|
end
|
|
186
185
|
|
|
187
186
|
def pipeline
|
|
188
|
-
@pipeline_builder&.pipeline
|
|
187
|
+
@pipeline || @pipeline_builder&.pipeline
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def pipeline=(value)
|
|
191
|
+
validate_callable!(value, "pipeline") if value
|
|
192
|
+
@pipeline = value
|
|
189
193
|
end
|
|
190
194
|
|
|
191
195
|
# Sampling configuration
|
data/lib/lapsoss/event.rb
CHANGED
|
@@ -16,7 +16,8 @@ module Lapsoss
|
|
|
16
16
|
:context,
|
|
17
17
|
:environment,
|
|
18
18
|
:fingerprint,
|
|
19
|
-
:backtrace_frames
|
|
19
|
+
:backtrace_frames,
|
|
20
|
+
:transaction # Controller#action or task name where event occurred
|
|
20
21
|
) do
|
|
21
22
|
# Factory method with smart defaults
|
|
22
23
|
def self.build(type:, level: :info, **attributes)
|
|
@@ -43,7 +44,8 @@ module Lapsoss
|
|
|
43
44
|
context: context,
|
|
44
45
|
environment: environment,
|
|
45
46
|
fingerprint: fingerprint,
|
|
46
|
-
backtrace_frames: backtrace_frames
|
|
47
|
+
backtrace_frames: backtrace_frames,
|
|
48
|
+
transaction: attributes[:transaction]
|
|
47
49
|
)
|
|
48
50
|
end
|
|
49
51
|
|
data/lib/lapsoss/merged_scope.rb
CHANGED
|
@@ -25,6 +25,24 @@ module Lapsoss
|
|
|
25
25
|
@breadcrumbs ||= merge_breadcrumbs
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def transaction_name
|
|
29
|
+
# Check scope stack first (most recent wins)
|
|
30
|
+
@scope_stack.reverse_each do |context|
|
|
31
|
+
return context[:transaction_name] if context[:transaction_name]
|
|
32
|
+
end
|
|
33
|
+
# Fall back to base scope
|
|
34
|
+
@base_scope.transaction_name
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def transaction_source
|
|
38
|
+
# Check scope stack first (most recent wins)
|
|
39
|
+
@scope_stack.reverse_each do |context|
|
|
40
|
+
return context[:transaction_source] if context[:transaction_source]
|
|
41
|
+
end
|
|
42
|
+
# Fall back to base scope
|
|
43
|
+
@base_scope.transaction_source
|
|
44
|
+
end
|
|
45
|
+
|
|
28
46
|
def add_breadcrumb(message, type: :default, **metadata)
|
|
29
47
|
breadcrumb = Breadcrumb.build(message, type: type, metadata: metadata)
|
|
30
48
|
@own_breadcrumbs << breadcrumb
|
|
@@ -34,6 +52,10 @@ module Lapsoss
|
|
|
34
52
|
@breadcrumbs = nil
|
|
35
53
|
end
|
|
36
54
|
|
|
55
|
+
def set_transaction_name(name, source: nil)
|
|
56
|
+
@base_scope.set_transaction_name(name, source: source)
|
|
57
|
+
end
|
|
58
|
+
|
|
37
59
|
private
|
|
38
60
|
|
|
39
61
|
def merge_hash_contexts(key)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lapsoss
|
|
4
|
+
# Optional concern to add controller and action context to Rails.error
|
|
5
|
+
# Include this in ApplicationController or specific controllers to get more detailed context
|
|
6
|
+
#
|
|
7
|
+
# Example:
|
|
8
|
+
# class ApplicationController < ActionController::Base
|
|
9
|
+
# include Lapsoss::RailsControllerContext
|
|
10
|
+
# end
|
|
11
|
+
module RailsControllerContext
|
|
12
|
+
extend ActiveSupport::Concern
|
|
13
|
+
|
|
14
|
+
included do
|
|
15
|
+
prepend_before_action :set_lapsoss_controller_context
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def set_lapsoss_controller_context
|
|
21
|
+
# Set context in Lapsoss scope if available
|
|
22
|
+
Lapsoss::Current.scope&.set_context("controller", {
|
|
23
|
+
controller: controller_name,
|
|
24
|
+
action: action_name,
|
|
25
|
+
controller_class: self.class.name
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
# Set context in Rails.error for ecosystem-wide availability
|
|
29
|
+
Rails.error.set_context(
|
|
30
|
+
controller: controller_name,
|
|
31
|
+
action: action_name,
|
|
32
|
+
controller_class: self.class.name
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lapsoss
|
|
4
|
+
module RailsControllerTransaction
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
around_action :lapsoss_capture_transaction
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def lapsoss_capture_transaction
|
|
14
|
+
if Lapsoss.client
|
|
15
|
+
transaction_name = "#{self.class.name}##{action_name}"
|
|
16
|
+
|
|
17
|
+
# Set the transaction name in the current scope
|
|
18
|
+
Lapsoss::Current.scope.set_transaction_name(transaction_name, source: :view)
|
|
19
|
+
|
|
20
|
+
# Add breadcrumb for the action
|
|
21
|
+
Lapsoss::Current.scope.add_breadcrumb(
|
|
22
|
+
"Processing #{transaction_name}",
|
|
23
|
+
type: :navigation,
|
|
24
|
+
controller: self.class.name,
|
|
25
|
+
action: action_name,
|
|
26
|
+
params: request.filtered_parameters
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
yield
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/lapsoss/railtie.rb
CHANGED
|
@@ -2,13 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Lapsoss
|
|
4
4
|
class Railtie < Rails::Railtie
|
|
5
|
-
|
|
6
|
-
if Rails.logger.respond_to?(:tagged)
|
|
7
|
-
Rails.logger.tagged("Lapsoss") { Rails.logger.debug "Railtie loaded" }
|
|
8
|
-
else
|
|
9
|
-
Rails.logger.debug "[Lapsoss] Railtie loaded"
|
|
10
|
-
end
|
|
11
|
-
end
|
|
5
|
+
# Debug logging removed - will be handled by the configured logger
|
|
12
6
|
config.lapsoss = ActiveSupport::OrderedOptions.new
|
|
13
7
|
|
|
14
8
|
initializer "lapsoss.configure" do |_app|
|
|
@@ -20,12 +14,11 @@ module Lapsoss
|
|
|
20
14
|
Rails.env
|
|
21
15
|
end
|
|
22
16
|
|
|
23
|
-
# Use
|
|
24
|
-
config.logger ||=
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
end
|
|
17
|
+
# Use Rails logger if available
|
|
18
|
+
config.logger ||= Rails.logger
|
|
19
|
+
|
|
20
|
+
# Set debug level in development
|
|
21
|
+
config.debug = Rails.env.development?
|
|
29
22
|
|
|
30
23
|
config.release ||= if Rails.application.respond_to?(:version)
|
|
31
24
|
Rails.application.version.to_s
|
|
@@ -41,15 +34,16 @@ module Lapsoss
|
|
|
41
34
|
end
|
|
42
35
|
end
|
|
43
36
|
|
|
44
|
-
initializer "lapsoss.add_middleware" do |app|
|
|
45
|
-
require "lapsoss/rails_middleware"
|
|
46
37
|
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
initializer "lapsoss.rails_error_subscriber" do |app|
|
|
39
|
+
Rails.error.subscribe(Lapsoss::RailsErrorSubscriber.new)
|
|
49
40
|
end
|
|
50
41
|
|
|
51
|
-
initializer "lapsoss.
|
|
52
|
-
|
|
42
|
+
initializer "lapsoss.controller_transaction" do
|
|
43
|
+
ActiveSupport.on_load(:action_controller) do
|
|
44
|
+
require "lapsoss/rails_controller_transaction"
|
|
45
|
+
include Lapsoss::RailsControllerTransaction
|
|
46
|
+
end
|
|
53
47
|
end
|
|
54
48
|
end
|
|
55
49
|
end
|
data/lib/lapsoss/router.rb
CHANGED
|
@@ -8,8 +8,13 @@ module Lapsoss
|
|
|
8
8
|
#
|
|
9
9
|
# @param event [Lapsoss::Event] The event to process.
|
|
10
10
|
def process_event(event)
|
|
11
|
-
Registry.instance.active
|
|
11
|
+
adapters = Registry.instance.active
|
|
12
|
+
Lapsoss.configuration.logger.debug("[LAPSOSS ROUTER] Processing event to #{adapters.length} adapters: #{adapters.map(&:name).join(', ')}")
|
|
13
|
+
|
|
14
|
+
adapters.each do |adapter|
|
|
15
|
+
Lapsoss.configuration.logger.info("[LAPSOSS ROUTER] About to call #{adapter.name}.capture")
|
|
12
16
|
adapter.capture(event)
|
|
17
|
+
Lapsoss.configuration.logger.info("[LAPSOSS ROUTER] Adapter #{adapter.name} completed")
|
|
13
18
|
rescue StandardError => e
|
|
14
19
|
handle_adapter_error(adapter, event, e)
|
|
15
20
|
end
|
data/lib/lapsoss/scope.rb
CHANGED
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
module Lapsoss
|
|
4
4
|
class Scope
|
|
5
5
|
attr_reader :breadcrumbs, :tags, :user, :extra
|
|
6
|
+
attr_accessor :transaction_name, :transaction_source
|
|
6
7
|
|
|
7
8
|
def initialize
|
|
8
9
|
@breadcrumbs = []
|
|
9
10
|
@tags = {}
|
|
10
11
|
@user = {}
|
|
11
12
|
@extra = {}
|
|
13
|
+
@transaction_name = nil
|
|
14
|
+
@transaction_source = nil
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
def add_breadcrumb(message, type: :default, **metadata)
|
|
@@ -36,6 +39,8 @@ module Lapsoss
|
|
|
36
39
|
@tags.clear
|
|
37
40
|
@user.clear
|
|
38
41
|
@extra.clear
|
|
42
|
+
@transaction_name = nil
|
|
43
|
+
@transaction_source = nil
|
|
39
44
|
end
|
|
40
45
|
|
|
41
46
|
def set_context(key, value)
|
|
@@ -61,5 +66,10 @@ module Lapsoss
|
|
|
61
66
|
def set_extras(extras)
|
|
62
67
|
@extra.merge!(extras)
|
|
63
68
|
end
|
|
69
|
+
|
|
70
|
+
def set_transaction_name(name, source: nil)
|
|
71
|
+
@transaction_name = name
|
|
72
|
+
@transaction_source = source if source
|
|
73
|
+
end
|
|
64
74
|
end
|
|
65
75
|
end
|
data/lib/lapsoss/version.rb
CHANGED
data/lib/lapsoss.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.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Abdelkader Boudih
|
|
@@ -249,8 +249,9 @@ files:
|
|
|
249
249
|
- lib/lapsoss/middleware/release_tracker.rb
|
|
250
250
|
- lib/lapsoss/pipeline.rb
|
|
251
251
|
- lib/lapsoss/pipeline_builder.rb
|
|
252
|
+
- lib/lapsoss/rails_controller_context.rb
|
|
253
|
+
- lib/lapsoss/rails_controller_transaction.rb
|
|
252
254
|
- lib/lapsoss/rails_error_subscriber.rb
|
|
253
|
-
- lib/lapsoss/rails_middleware.rb
|
|
254
255
|
- lib/lapsoss/railtie.rb
|
|
255
256
|
- lib/lapsoss/registry.rb
|
|
256
257
|
- lib/lapsoss/release_tracker.rb
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Lapsoss
|
|
4
|
-
class RailsMiddleware
|
|
5
|
-
def initialize(app)
|
|
6
|
-
@app = app
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def call(env)
|
|
10
|
-
Lapsoss::Current.with_clean_scope do
|
|
11
|
-
# Add request context to current scope
|
|
12
|
-
if Lapsoss.configuration.capture_request_context
|
|
13
|
-
Rails.logger.tagged("Lapsoss") { Rails.logger.debug "Adding request context" } if Rails.env.test?
|
|
14
|
-
add_request_context(env)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
begin
|
|
18
|
-
@app.call(env)
|
|
19
|
-
rescue Exception => e
|
|
20
|
-
Rails.logger.tagged("Lapsoss") { Rails.logger.debug "Capturing exception: #{e.class} - #{e.message}" } if Rails.env.test?
|
|
21
|
-
# Capture the exception
|
|
22
|
-
Lapsoss.capture_exception(e)
|
|
23
|
-
# Re-raise the exception to maintain Rails error handling
|
|
24
|
-
raise
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
def add_request_context(env)
|
|
32
|
-
request = Rack::Request.new(env)
|
|
33
|
-
|
|
34
|
-
return unless Lapsoss::Current.scope
|
|
35
|
-
|
|
36
|
-
Lapsoss::Current.scope.set_context("request", {
|
|
37
|
-
method: request.request_method,
|
|
38
|
-
url: request.url,
|
|
39
|
-
path: request.path,
|
|
40
|
-
query_string: request.query_string,
|
|
41
|
-
headers: extract_headers(env),
|
|
42
|
-
ip: request.ip,
|
|
43
|
-
user_agent: request.user_agent,
|
|
44
|
-
referer: request.referer,
|
|
45
|
-
request_id: env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"]
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
# Add user context if available
|
|
49
|
-
return unless env["warden"]&.user
|
|
50
|
-
|
|
51
|
-
user = env["warden"].user
|
|
52
|
-
Lapsoss::Current.scope.set_user(
|
|
53
|
-
id: user.id,
|
|
54
|
-
email: user.respond_to?(:email) ? user.email : nil
|
|
55
|
-
)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def extract_headers(env)
|
|
59
|
-
headers = {}
|
|
60
|
-
|
|
61
|
-
env.each do |key, value|
|
|
62
|
-
if key.start_with?("HTTP_") && FILTERED_HEADERS.exclude?(key)
|
|
63
|
-
header_name = key.sub(/^HTTP_/, "").split("_").map(&:capitalize).join("-")
|
|
64
|
-
headers[header_name] = value
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
headers
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
FILTERED_HEADERS = %w[
|
|
72
|
-
HTTP_AUTHORIZATION
|
|
73
|
-
HTTP_COOKIE
|
|
74
|
-
HTTP_X_API_KEY
|
|
75
|
-
HTTP_X_AUTH_TOKEN
|
|
76
|
-
].freeze
|
|
77
|
-
end
|
|
78
|
-
end
|