lapsoss 0.1.0 → 0.2.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.
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
- require "active_support/cache"
5
- require "active_support/core_ext/numeric/time"
6
- require_relative "backtrace_frame"
3
+ require 'active_support/cache'
4
+ require 'active_support/core_ext/numeric/time'
5
+ require_relative 'backtrace_frame'
7
6
 
8
7
  module Lapsoss
9
8
  class BacktraceProcessor
@@ -33,20 +32,20 @@ module Lapsoss
33
32
 
34
33
  def initialize(config = {})
35
34
  # Handle different config formats for backward compatibility
36
- if config.respond_to?(:backtrace_context_lines)
37
- # Configuration object passed
38
- config_hash = {
39
- context_lines: config.backtrace_context_lines,
40
- max_frames: config.backtrace_max_frames,
41
- enable_code_context: config.backtrace_enable_code_context,
42
- in_app_patterns: config.backtrace_in_app_patterns,
43
- exclude_patterns: config.backtrace_exclude_patterns,
44
- strip_load_path: config.backtrace_strip_load_path
45
- }
46
- else
47
- # Hash passed
48
- config_hash = config
49
- end
35
+ config_hash = if config.respond_to?(:backtrace_context_lines)
36
+ # Configuration object passed
37
+ {
38
+ context_lines: config.backtrace_context_lines,
39
+ max_frames: config.backtrace_max_frames,
40
+ enable_code_context: config.backtrace_enable_code_context,
41
+ in_app_patterns: config.backtrace_in_app_patterns,
42
+ exclude_patterns: config.backtrace_exclude_patterns,
43
+ strip_load_path: config.backtrace_strip_load_path
44
+ }
45
+ else
46
+ # Hash passed
47
+ config
48
+ end
50
49
 
51
50
  @config = DEFAULT_CONFIG.merge(config_hash)
52
51
  @file_cache = ActiveSupport::Cache::MemoryStore.new(
@@ -77,7 +76,7 @@ module Lapsoss
77
76
  end
78
77
 
79
78
  # Backward compatibility alias
80
- alias_method :process, :process_backtrace
79
+ alias process process_backtrace
81
80
 
82
81
  def process_exception_backtrace(exception, follow_cause: false)
83
82
  return [] unless exception&.backtrace
@@ -86,7 +85,7 @@ module Lapsoss
86
85
 
87
86
  # Add exception-specific context
88
87
  frames.each_with_index do |frame, index|
89
- frame.define_singleton_method(:crash_frame?) { index == 0 }
88
+ frame.define_singleton_method(:crash_frame?) { index.zero? }
90
89
  frame.define_singleton_method(:exception_class) { exception.class.name }
91
90
  end
92
91
 
@@ -100,7 +99,7 @@ module Lapsoss
100
99
  end
101
100
 
102
101
  # Backward compatibility aliases
103
- alias_method :process_exception, :process_exception_backtrace
102
+ alias process_exception process_exception_backtrace
104
103
 
105
104
  def clear_cache!
106
105
  @file_cache.clear
@@ -180,7 +179,7 @@ module Lapsoss
180
179
  file_cache: {
181
180
  # ActiveSupport::Cache::MemoryStore doesn't expose detailed stats
182
181
  # but we can provide basic info
183
- type: "ActiveSupport::Cache::MemoryStore",
182
+ type: 'ActiveSupport::Cache::MemoryStore',
184
183
  configured_size: @file_cache.options[:size]
185
184
  },
186
185
  config: @config,
@@ -205,7 +204,7 @@ module Lapsoss
205
204
 
206
205
  # Convert to 0-based index
207
206
  line_index = line_number - 1
208
- return nil if line_index < 0 || line_index >= lines.length
207
+ return nil if line_index.negative? || line_index >= lines.length
209
208
 
210
209
  # Calculate context range
211
210
  start_line = [0, line_index - context_lines].max
@@ -283,11 +282,11 @@ module Lapsoss
283
282
  head_frames = frames.first(head_count)
284
283
 
285
284
  # Get tail frames (original cause)
286
- tail_frames = if tail_count > 0
287
- frames.last(tail_count)
288
- else
289
- []
290
- end
285
+ tail_frames = if tail_count.positive?
286
+ frames.last(tail_count)
287
+ else
288
+ []
289
+ end
291
290
 
292
291
  head_frames + tail_frames
293
292
  end
@@ -335,9 +334,7 @@ module Lapsoss
335
334
  paths << Dir.pwd
336
335
 
337
336
  # Add gem paths
338
- if defined?(Gem)
339
- paths.concat(Gem.path.map { |p| File.join(p, 'gems') })
340
- end
337
+ paths.concat(Gem.path.map { |p| File.join(p, 'gems') }) if defined?(Gem)
341
338
 
342
339
  # Sort by length (longest first) for better matching
343
340
  paths.uniq.sort_by(&:length).reverse
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "concurrent"
4
- require_relative "scope"
5
- require_relative "current"
3
+ require 'concurrent'
4
+ require_relative 'scope'
5
+ require_relative 'current'
6
6
 
7
7
  module Lapsoss
8
8
  class Client
@@ -70,8 +70,6 @@ module Lapsoss
70
70
 
71
71
  private
72
72
 
73
-
74
-
75
73
  def capture_event(event)
76
74
  # Apply pipeline processing if enabled
77
75
  if @configuration.enable_pipeline && @configuration.pipeline
@@ -87,7 +85,7 @@ module Lapsoss
87
85
  else
88
86
  Router.process_event(event)
89
87
  end
90
- rescue => e
88
+ rescue StandardError => e
91
89
  handle_capture_error(e)
92
90
  end
93
91
 
@@ -1,26 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "validators"
4
- require "active_support/configurable"
3
+ require_relative 'validators'
4
+ require 'active_support/configurable'
5
5
 
6
6
  module Lapsoss
7
7
  class Configuration
8
8
  include Validators
9
9
  include ActiveSupport::Configurable
10
10
 
11
- attr_accessor :async, :logger, :enabled, :release,
11
+ attr_accessor :async, :logger, :enabled, :release, :debug,
12
12
  :scrub_fields, :scrub_all, :whitelist_fields, :randomize_scrub_length,
13
13
  :transport_jitter, :fingerprint_patterns,
14
14
  :normalize_fingerprint_paths, :normalize_fingerprint_ids, :fingerprint_include_environment,
15
15
  :backtrace_context_lines, :backtrace_in_app_patterns, :backtrace_exclude_patterns,
16
16
  :backtrace_strip_load_path, :backtrace_max_frames, :backtrace_enable_code_context,
17
17
  :enable_pipeline, :pipeline_builder, :sampling_strategy,
18
- :skip_rails_cache_errors
19
- attr_reader :fingerprint_callback
20
- attr_reader :environment, :before_send, :sample_rate, :error_handler,
21
- :transport_timeout, :transport_max_retries, :transport_initial_backoff,
22
- :transport_max_backoff, :transport_backoff_multiplier, :transport_ssl_verify
23
- attr_reader :default_context, :adapter_configs
18
+ :skip_rails_cache_errors, :force_sync_http
19
+ attr_reader :fingerprint_callback, :environment, :before_send, :sample_rate, :error_handler, :transport_timeout,
20
+ :transport_max_retries, :transport_initial_backoff, :transport_max_backoff, :transport_backoff_multiplier, :transport_ssl_verify, :default_context, :adapter_configs
24
21
 
25
22
  def initialize
26
23
  @adapter_configs = {}
@@ -29,11 +26,12 @@ module Lapsoss
29
26
  @environment = nil
30
27
  @enabled = true
31
28
  @release = nil
29
+ @debug = false
32
30
  @before_send = nil
33
31
  @sample_rate = 1.0
34
32
  @default_context = {}
35
33
  @error_handler = nil
36
- @scrub_fields = nil # Will use defaults from Scrubber
34
+ @scrub_fields = nil # Will use defaults from Scrubber
37
35
  @scrub_all = false
38
36
  @whitelist_fields = []
39
37
  @randomize_scrub_length = false
@@ -47,7 +45,7 @@ module Lapsoss
47
45
  @transport_ssl_verify = true
48
46
  # Fingerprinting settings
49
47
  @fingerprint_callback = nil
50
- @fingerprint_patterns = nil # Will use defaults from Fingerprinter
48
+ @fingerprint_patterns = nil # Will use defaults from Fingerprinter
51
49
  @normalize_fingerprint_paths = true
52
50
  @normalize_fingerprint_ids = true
53
51
  @fingerprint_include_environment = false
@@ -64,9 +62,10 @@ module Lapsoss
64
62
  @sampling_strategy = nil
65
63
  # Rails error filtering
66
64
  @skip_rails_cache_errors = true
65
+ # HTTP client settings
66
+ @force_sync_http = false
67
67
  end
68
68
 
69
-
70
69
  # Register a named adapter configuration
71
70
  #
72
71
  # @param name [Symbol] Unique name for this adapter instance
@@ -99,7 +98,6 @@ module Lapsoss
99
98
  register_adapter(name, :bugsnag, **settings)
100
99
  end
101
100
 
102
-
103
101
  # Convenience method for Rollbar
104
102
  def use_rollbar(name: :rollbar, **settings)
105
103
  register_adapter(name, :rollbar, **settings)
@@ -164,9 +162,17 @@ module Lapsoss
164
162
  initialize
165
163
  end
166
164
 
165
+ def debug?
166
+ @debug
167
+ end
168
+
169
+ def async?
170
+ @async
171
+ end
172
+
167
173
  # Pipeline configuration
168
- def configure_pipeline(&block)
169
- require_relative "pipeline"
174
+ def configure_pipeline
175
+ require_relative 'pipeline'
170
176
  @pipeline_builder = PipelineBuilder.new
171
177
  yield(@pipeline_builder) if block_given?
172
178
  @pipeline_builder
@@ -178,7 +184,7 @@ module Lapsoss
178
184
 
179
185
  # Sampling configuration
180
186
  def configure_sampling(strategy = nil, &block)
181
- require_relative "sampling"
187
+ require_relative 'sampling'
182
188
 
183
189
  if strategy
184
190
  @sampling_strategy = strategy
@@ -188,7 +194,7 @@ module Lapsoss
188
194
  end
189
195
 
190
196
  def create_sampling_strategy
191
- require_relative "sampling"
197
+ require_relative 'sampling'
192
198
 
193
199
  case @sampling_strategy
194
200
  when Symbol
@@ -213,85 +219,86 @@ module Lapsoss
213
219
 
214
220
  # Validation and setter overrides
215
221
  def sample_rate=(value)
216
- validate_sample_rate!(value, "sample_rate") if value
222
+ validate_sample_rate!(value, 'sample_rate') if value
217
223
  @sample_rate = value
218
224
  end
219
225
 
220
226
  def before_send=(value)
221
- validate_callable!(value, "before_send")
227
+ validate_callable!(value, 'before_send')
222
228
  @before_send = value
223
229
  end
224
230
 
225
231
  def error_handler=(value)
226
- validate_callable!(value, "error_handler")
232
+ validate_callable!(value, 'error_handler')
227
233
  @error_handler = value
228
234
  end
229
235
 
230
236
  def environment=(value)
231
- validate_environment!(value, "environment") if value
237
+ validate_environment!(value, 'environment') if value
232
238
  @environment = value&.to_s
233
239
  end
234
240
 
235
241
  def transport_timeout=(value)
236
- validate_timeout!(value, "transport_timeout") if value
242
+ validate_timeout!(value, 'transport_timeout') if value
237
243
  @transport_timeout = value
238
244
  end
239
245
 
240
246
  def transport_max_retries=(value)
241
- validate_retries!(value, "transport_max_retries") if value
247
+ validate_retries!(value, 'transport_max_retries') if value
242
248
  @transport_max_retries = value
243
249
  end
244
250
 
245
251
  def transport_initial_backoff=(value)
246
- validate_timeout!(value, "transport_initial_backoff") if value
252
+ validate_timeout!(value, 'transport_initial_backoff') if value
247
253
  @transport_initial_backoff = value
248
254
  end
249
255
 
250
256
  def transport_max_backoff=(value)
251
- validate_timeout!(value, "transport_max_backoff") if value
257
+ validate_timeout!(value, 'transport_max_backoff') if value
252
258
  @transport_max_backoff = value
253
259
  end
254
260
 
255
261
  def transport_backoff_multiplier=(value)
256
262
  if value
257
- validate_type!(value, [Numeric], "transport_backoff_multiplier")
258
- validate_numeric_range!(value, 1.0..10.0, "transport_backoff_multiplier")
263
+ validate_type!(value, [Numeric], 'transport_backoff_multiplier')
264
+ validate_numeric_range!(value, 1.0..10.0, 'transport_backoff_multiplier')
259
265
  end
260
266
  @transport_backoff_multiplier = value
261
267
  end
262
268
 
263
269
  def transport_ssl_verify=(value)
264
- validate_boolean!(value, "transport_ssl_verify") if value
270
+ validate_boolean!(value, 'transport_ssl_verify') if value
265
271
  @transport_ssl_verify = value
266
272
  end
267
273
 
268
274
  def fingerprint_callback=(value)
269
- validate_callable!(value, "fingerprint_callback")
275
+ validate_callable!(value, 'fingerprint_callback')
270
276
  @fingerprint_callback = value
271
277
  end
272
278
 
273
279
  # Configuration validation
274
280
  def validate!
275
- validate_sample_rate!(@sample_rate, "sample_rate") if @sample_rate
276
- validate_callable!(@before_send, "before_send")
277
- validate_callable!(@error_handler, "error_handler")
278
- validate_callable!(@fingerprint_callback, "fingerprint_callback")
279
- validate_environment!(@environment, "environment") if @environment
281
+ validate_sample_rate!(@sample_rate, 'sample_rate') if @sample_rate
282
+ validate_callable!(@before_send, 'before_send')
283
+ validate_callable!(@error_handler, 'error_handler')
284
+ validate_callable!(@fingerprint_callback, 'fingerprint_callback')
285
+ validate_environment!(@environment, 'environment') if @environment
280
286
 
281
287
  # Validate transport settings
282
- validate_timeout!(@transport_timeout, "transport_timeout")
283
- validate_retries!(@transport_max_retries, "transport_max_retries")
284
- validate_timeout!(@transport_initial_backoff, "transport_initial_backoff")
285
- validate_timeout!(@transport_max_backoff, "transport_max_backoff")
288
+ validate_timeout!(@transport_timeout, 'transport_timeout')
289
+ validate_retries!(@transport_max_retries, 'transport_max_retries')
290
+ validate_timeout!(@transport_initial_backoff, 'transport_initial_backoff')
291
+ validate_timeout!(@transport_max_backoff, 'transport_max_backoff')
286
292
 
287
293
  if @transport_backoff_multiplier
288
- validate_type!(@transport_backoff_multiplier, [Numeric], "transport_backoff_multiplier")
289
- validate_numeric_range!(@transport_backoff_multiplier, 1.0..10.0, "transport_backoff_multiplier")
294
+ validate_type!(@transport_backoff_multiplier, [Numeric], 'transport_backoff_multiplier')
295
+ validate_numeric_range!(@transport_backoff_multiplier, 1.0..10.0, 'transport_backoff_multiplier')
290
296
  end
291
297
 
292
298
  # Validate that initial backoff is less than max backoff
293
299
  if @transport_initial_backoff && @transport_max_backoff && @transport_initial_backoff > @transport_max_backoff
294
- raise ValidationError, "transport_initial_backoff (#{@transport_initial_backoff}) must be less than transport_max_backoff (#{@transport_max_backoff})"
300
+ raise ValidationError,
301
+ "transport_initial_backoff (#{@transport_initial_backoff}) must be less than transport_max_backoff (#{@transport_max_backoff})"
295
302
  end
296
303
 
297
304
  # Validate adapter configurations
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/all"
3
+ require 'active_support/all'
4
4
 
5
5
  module Lapsoss
6
6
  class Current < ActiveSupport::CurrentAttributes
data/lib/lapsoss/event.rb CHANGED
@@ -1,17 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "scrubber"
4
- require_relative "fingerprinter"
5
- require_relative "backtrace_processor"
3
+ require_relative 'scrubber'
4
+ require_relative 'fingerprinter'
5
+ require_relative 'backtrace_processor'
6
6
 
7
7
  module Lapsoss
8
8
  class Event
9
- attr_accessor :type, :timestamp, :level, :message, :exception, :context, :environment, :fingerprint, :backtrace_frames
9
+ attr_accessor :type, :timestamp, :level, :message, :exception, :context, :environment, :fingerprint,
10
+ :backtrace_frames
10
11
 
11
12
  def initialize(type:, level: :info, **attributes)
12
13
  @type = type
13
14
  @level = level
14
- @timestamp = Time.now
15
+ @timestamp = Time.zone.now
15
16
  @context = {}
16
17
  @environment = Lapsoss.configuration.environment
17
18
 
@@ -141,8 +141,8 @@ module Lapsoss
141
141
  return false if @excluded_environments.empty?
142
142
 
143
143
  environment = event.context[:environment] ||
144
- event.context.dig(:tags, :environment) ||
145
- Lapsoss.configuration.environment
144
+ event.context.dig(:tags, :environment) ||
145
+ Lapsoss.configuration.environment
146
146
 
147
147
  return false unless environment
148
148
 
@@ -160,12 +160,12 @@ module Lapsoss
160
160
  {
161
161
  excluded_exceptions: [
162
162
  # Test-related exceptions
163
- "RSpec::Expectations::ExpectationNotMetError",
164
- "Minitest::Assertion",
163
+ 'RSpec::Expectations::ExpectationNotMetError',
164
+ 'Minitest::Assertion',
165
165
 
166
166
  # Development tools
167
- "Pry::CommandError",
168
- "Byebug::CommandError"
167
+ 'Pry::CommandError',
168
+ 'Byebug::CommandError'
169
169
  ],
170
170
  excluded_patterns: [
171
171
  /test/i,
@@ -181,19 +181,19 @@ module Lapsoss
181
181
  {
182
182
  excluded_exceptions: [
183
183
  # Common Rails exceptions that are usually not actionable
184
- "ActionController::RoutingError",
185
- "ActionController::UnknownFormat",
186
- "ActionController::BadRequest",
187
- "ActionController::ParameterMissing",
184
+ 'ActionController::RoutingError',
185
+ 'ActionController::UnknownFormat',
186
+ 'ActionController::BadRequest',
187
+ 'ActionController::ParameterMissing',
188
188
 
189
189
  # ActiveRecord exceptions for common user errors
190
- "ActiveRecord::RecordNotFound",
191
- "ActiveRecord::RecordInvalid",
190
+ 'ActiveRecord::RecordNotFound',
191
+ 'ActiveRecord::RecordInvalid',
192
192
 
193
193
  # Network timeouts that are expected
194
- "Net::ReadTimeout",
195
- "Net::OpenTimeout",
196
- "Timeout::Error"
194
+ 'Net::ReadTimeout',
195
+ 'Net::OpenTimeout',
196
+ 'Timeout::Error'
197
197
  ],
198
198
  excluded_patterns: [
199
199
  # Bot and crawler patterns
@@ -215,10 +215,10 @@ module Lapsoss
215
215
  ],
216
216
  excluded_messages: [
217
217
  # Common spam/attack messages
218
- "No route matches",
219
- "Invalid authenticity token",
220
- "Forbidden",
221
- "Unauthorized"
218
+ 'No route matches',
219
+ 'Invalid authenticity token',
220
+ 'Forbidden',
221
+ 'Unauthorized'
222
222
  ]
223
223
  }
224
224
  end
@@ -227,8 +227,8 @@ module Lapsoss
227
227
  {
228
228
  excluded_exceptions: [
229
229
  # Test data related errors
230
- "ActiveRecord::RecordNotFound",
231
- "ArgumentError"
230
+ 'ActiveRecord::RecordNotFound',
231
+ 'ArgumentError'
232
232
  ],
233
233
  excluded_patterns: [
234
234
  /test/i,
@@ -267,15 +267,15 @@ module Lapsoss
267
267
  /onerror=/i
268
268
  ],
269
269
  excluded_messages: [
270
- "Invalid authenticity token",
271
- "Forbidden",
272
- "Unauthorized",
273
- "Access denied"
270
+ 'Invalid authenticity token',
271
+ 'Forbidden',
272
+ 'Unauthorized',
273
+ 'Access denied'
274
274
  ],
275
275
  custom_filters: [
276
276
  # Exclude requests from known bot user agents
277
277
  lambda do |event|
278
- user_agent = event.context.dig(:request, :headers, "User-Agent")
278
+ user_agent = event.context.dig(:request, :headers, 'User-Agent')
279
279
  return false unless user_agent
280
280
 
281
281
  bot_patterns = [
@@ -297,14 +297,14 @@ module Lapsoss
297
297
  {
298
298
  excluded_exceptions: [
299
299
  # Timeout exceptions that are expected under load
300
- "Net::ReadTimeout",
301
- "Net::OpenTimeout",
302
- "Timeout::Error",
303
- "Redis::TimeoutError",
300
+ 'Net::ReadTimeout',
301
+ 'Net::OpenTimeout',
302
+ 'Timeout::Error',
303
+ 'Redis::TimeoutError',
304
304
 
305
305
  # Memory and resource limits
306
- "NoMemoryError",
307
- "SystemStackError"
306
+ 'NoMemoryError',
307
+ 'SystemStackError'
308
308
  ],
309
309
  excluded_patterns: [
310
310
  /timeout/i,
@@ -315,7 +315,7 @@ module Lapsoss
315
315
  custom_filters: [
316
316
  # Exclude high-frequency errors during peak times
317
317
  lambda do |event|
318
- now = Time.now
318
+ now = Time.zone.now
319
319
  peak_hours = (9..17).cover?(now.hour) && (1..5).cover?(now.wday)
320
320
 
321
321
  if peak_hours
@@ -334,11 +334,11 @@ module Lapsoss
334
334
  {
335
335
  excluded_exceptions: [
336
336
  # User input validation errors
337
- "ActiveModel::ValidationError",
338
- "ActiveRecord::RecordInvalid",
339
- "ActionController::ParameterMissing",
340
- "ArgumentError",
341
- "TypeError"
337
+ 'ActiveModel::ValidationError',
338
+ 'ActiveRecord::RecordInvalid',
339
+ 'ActionController::ParameterMissing',
340
+ 'ArgumentError',
341
+ 'TypeError'
342
342
  ],
343
343
  excluded_patterns: [
344
344
  /validation/i,
@@ -373,15 +373,15 @@ module Lapsoss
373
373
 
374
374
  presets.each do |preset|
375
375
  config = case preset
376
- when :development then development
377
- when :production then production
378
- when :staging then staging
379
- when :security_focused then security_focused
380
- when :performance_focused then performance_focused
381
- when :user_error_focused then user_error_focused
382
- when Hash then preset
383
- else raise ArgumentError, "Unknown preset: #{preset}"
384
- end
376
+ when :development then development
377
+ when :production then production
378
+ when :staging then staging
379
+ when :security_focused then security_focused
380
+ when :performance_focused then performance_focused
381
+ when :user_error_focused then user_error_focused
382
+ when Hash then preset
383
+ else raise ArgumentError, "Unknown preset: #{preset}"
384
+ end
385
385
 
386
386
  combined_config[:excluded_exceptions].concat(config[:excluded_exceptions] || [])
387
387
  combined_config[:excluded_patterns].concat(config[:excluded_patterns] || [])
@@ -404,15 +404,15 @@ module Lapsoss
404
404
  module ExclusionConfiguration
405
405
  def self.configure_exclusions(config, preset: nil, **custom_config)
406
406
  exclusion_config = if preset
407
- case preset
408
- when Array
409
- ExclusionPresets.combined(preset)
410
- else
411
- ExclusionPresets.send(preset)
412
- end
413
- else
414
- {}
415
- end
407
+ case preset
408
+ when Array
409
+ ExclusionPresets.combined(preset)
410
+ else
411
+ ExclusionPresets.send(preset)
412
+ end
413
+ else
414
+ {}
415
+ end
416
416
 
417
417
  # Merge custom configuration
418
418
  exclusion_config.merge!(custom_config)