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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc2f5c5b2a06b525bb921e683c87a201e93507c795b6cff7e4cddc33d3716880
4
- data.tar.gz: 3d5bce60161f233758b0f39fdf54197ac7c5b0db921709ac3e1a4e54531bf669
3
+ metadata.gz: 76c4609d94f1d9463922da6d1850b7b618977075dd0d96fa7d94d58186cd64b9
4
+ data.tar.gz: 8d5db54e63ef1e6a0d73962261bd13ce4cfca305c63570bf879c4dbf85bb3110
5
5
  SHA512:
6
- metadata.gz: 569ae7372bbe9f3984c0cb1b13074e1442af9f34cb21e74124f04db1a62227a7ac08c8d856d08c739e9da05a799c288260b72e62e8e7df44f5fa853bfacf5d7b
7
- data.tar.gz: 6e17e4ffdd5d757fb70414da87e188ee12dbc1469bb37ac978554c2fc5146e5f86ebb257daefe37748e3129140d402d34c517ac1c756aa56c56defdd41a159e9
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
+ [![Ruby CI](https://github.com/seuros/lapsoss/actions/workflows/ci.yml/badge.svg)](https://github.com/seuros/lapsoss/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/lapsoss.svg)](https://badge.fury.io/rb/lapsoss)
5
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+ [![Downloads](https://img.shields.io/gem/dt/lapsoss.svg)](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 Sentry for comparison
27
- config.use_sentry(dsn: ENV['SENTRY_DSN'])
31
+ # Tuesday: Add Telebugs for comparison
32
+ config.use_telebugs(dsn: ENV['TELEBUGS_DSN'])
28
33
 
29
- # Wednesday: Drop Bugsnag, keep Sentry
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 middleware gymnastics:
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.use_sentry(dsn: ENV['SENTRY_DSN'])
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 Sentry
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['US_DSN'])
118
- config.use_sentry(name: :eu, dsn: ENV['EU_DSN'])
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.use_sentry(name: :primary, dsn: ENV['PRIMARY_DSN'])
132
- config.use_rollbar(name: :backup, access_token: ENV['BACKUP_TOKEN'])
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.use_sentry(dsn: ENV["SENTRY_DSN"])
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.use_sentry(dsn: ENV['SENTRY_DSN'])
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: "sentry.ruby",
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
- self.class.api_endpoint = "#{uri.scheme}://#{uri.host}#{port}"
46
- self.class.api_path = build_api_path(uri)
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
@@ -6,32 +6,35 @@ module Lapsoss
6
6
  class Client
7
7
  def initialize(configuration)
8
8
  @configuration = configuration
9
- @executor = Concurrent::FixedThreadPool.new(5) if @configuration.async
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
- Registry.instance.flush(timeout: timeout)
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
- @executor.post { Router.process_event(event) }
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
 
@@ -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
@@ -2,13 +2,7 @@
2
2
 
3
3
  module Lapsoss
4
4
  class Railtie < Rails::Railtie
5
- if ENV["DEBUG_LAPSOSS"]
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 tagged logger for all Lapsoss logs
24
- config.logger ||= if Rails.logger.respond_to?(:tagged)
25
- Rails.logger.tagged("Lapsoss")
26
- else
27
- ActiveSupport::TaggedLogging.new(Rails.logger).tagged("Lapsoss")
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
- # Use config.middleware to ensure it's added during initialization
48
- app.config.middleware.use Lapsoss::RailsMiddleware
38
+ initializer "lapsoss.rails_error_subscriber" do |app|
39
+ Rails.error.subscribe(Lapsoss::RailsErrorSubscriber.new)
49
40
  end
50
41
 
51
- initializer "lapsoss.rails_error_subscriber", after: "lapsoss.add_middleware" do |app|
52
- Rails.error.subscribe(Lapsoss::RailsErrorSubscriber.new)
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
@@ -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.each do |adapter|
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lapsoss
4
- VERSION = "0.4.6"
4
+ VERSION = "0.4.10"
5
5
  end
data/lib/lapsoss.rb CHANGED
@@ -37,6 +37,8 @@ module Lapsoss
37
37
  end
38
38
 
39
39
  def capture_exception(exception, **context)
40
+ configuration.logger.debug "[LAPSOSS] capture_exception called for #{exception.class}"
41
+ return unless client
40
42
  client.capture_exception(exception, **context)
41
43
  end
42
44
 
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.6
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