lapsoss 0.3.1 → 0.4.2

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +71 -7
  3. data/lib/lapsoss/adapters/appsignal_adapter.rb +18 -12
  4. data/lib/lapsoss/adapters/base.rb +19 -0
  5. data/lib/lapsoss/adapters/concerns/envelope_builder.rb +127 -0
  6. data/lib/lapsoss/adapters/concerns/http_delivery.rb +130 -0
  7. data/lib/lapsoss/adapters/concerns/level_mapping.rb +65 -0
  8. data/lib/lapsoss/adapters/insight_hub_adapter.rb +21 -21
  9. data/lib/lapsoss/adapters/rollbar_adapter.rb +64 -122
  10. data/lib/lapsoss/adapters/sentry_adapter.rb +77 -143
  11. data/lib/lapsoss/backtrace_processor.rb +1 -1
  12. data/lib/lapsoss/breadcrumb.rb +59 -0
  13. data/lib/lapsoss/client.rb +3 -5
  14. data/lib/lapsoss/configuration.rb +26 -31
  15. data/lib/lapsoss/event.rb +90 -96
  16. data/lib/lapsoss/fingerprinter.rb +57 -49
  17. data/lib/lapsoss/merged_scope.rb +1 -6
  18. data/lib/lapsoss/middleware/release_tracker.rb +11 -98
  19. data/lib/lapsoss/pipeline_builder.rb +2 -2
  20. data/lib/lapsoss/rails_error_subscriber.rb +3 -4
  21. data/lib/lapsoss/rails_middleware.rb +2 -2
  22. data/lib/lapsoss/railtie.rb +13 -2
  23. data/lib/lapsoss/registry.rb +7 -7
  24. data/lib/lapsoss/router.rb +1 -3
  25. data/lib/lapsoss/scope.rb +1 -6
  26. data/lib/lapsoss/scrubber.rb +15 -148
  27. data/lib/lapsoss/validators.rb +63 -92
  28. data/lib/lapsoss/version.rb +1 -1
  29. metadata +8 -24
  30. data/CHANGELOG.md +0 -5
  31. data/lib/lapsoss/exclusion_configuration.rb +0 -30
  32. data/lib/lapsoss/exclusion_presets.rb +0 -249
  33. data/lib/lapsoss/middleware/sample_filter.rb +0 -23
  34. data/lib/lapsoss/middleware/sampling_middleware.rb +0 -18
  35. data/lib/lapsoss/middleware/user_context_enhancer.rb +0 -46
  36. data/lib/lapsoss/release_providers.rb +0 -110
  37. data/lib/lapsoss/sampling/adaptive_sampler.rb +0 -46
  38. data/lib/lapsoss/sampling/composite_sampler.rb +0 -26
  39. data/lib/lapsoss/sampling/consistent_hash_sampler.rb +0 -30
  40. data/lib/lapsoss/sampling/exception_type_sampler.rb +0 -44
  41. data/lib/lapsoss/sampling/health_based_sampler.rb +0 -19
  42. data/lib/lapsoss/sampling/sampling_factory.rb +0 -69
  43. data/lib/lapsoss/sampling/time_based_sampler.rb +0 -44
  44. data/lib/lapsoss/sampling/user_based_sampler.rb +0 -42
  45. data/lib/lapsoss/user_context.rb +0 -175
  46. data/lib/lapsoss/user_context_integrations.rb +0 -39
  47. data/lib/lapsoss/user_context_middleware.rb +0 -50
  48. data/lib/lapsoss/user_context_provider.rb +0 -93
  49. data/lib/lapsoss/utils.rb +0 -13
@@ -21,7 +21,7 @@ module Lapsoss
21
21
  def initialize
22
22
  @adapter_configs = {}
23
23
  @async = true
24
- @logger = nil
24
+ @logger = Logger.new(nil) # Default null logger
25
25
  @environment = nil
26
26
  @enabled = true
27
27
  @release = nil
@@ -193,17 +193,8 @@ module Lapsoss
193
193
 
194
194
  def create_sampling_strategy
195
195
  case @sampling_strategy
196
- when Symbol
197
- case @sampling_strategy
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
196
+ when Numeric
197
+ Sampling::UniformSampler.new(@sampling_strategy)
207
198
  when Proc
208
199
  @sampling_strategy
209
200
  when nil
@@ -272,42 +263,46 @@ module Lapsoss
272
263
  @fingerprint_callback = value
273
264
  end
274
265
 
275
- # Configuration validation
266
+ # Configuration validation - just log warnings, don't fail
276
267
  def validate!
277
- validate_sample_rate!(@sample_rate, "sample_rate") if @sample_rate
268
+ # Check sample rate is between 0 and 1
269
+ if @sample_rate && (@sample_rate < 0 || @sample_rate > 1)
270
+ logger.warn "sample_rate should be between 0 and 1, got #{@sample_rate}"
271
+ end
272
+
273
+ # Check callables
278
274
  validate_callable!(@before_send, "before_send")
279
275
  validate_callable!(@error_handler, "error_handler")
280
276
  validate_callable!(@fingerprint_callback, "fingerprint_callback")
277
+
278
+ # Log if environment looks unusual
281
279
  validate_environment!(@environment, "environment") if @environment
282
280
 
283
- # Validate transport settings
284
- validate_timeout!(@transport_timeout, "transport_timeout")
285
- validate_retries!(@transport_max_retries, "transport_max_retries")
286
- validate_timeout!(@transport_initial_backoff, "transport_initial_backoff")
287
- validate_timeout!(@transport_max_backoff, "transport_max_backoff")
281
+ # Just log if transport settings look unusual
282
+ if @transport_timeout && @transport_timeout <= 0
283
+ logger.warn "transport_timeout should be positive, got #{@transport_timeout}"
284
+ end
288
285
 
289
- if @transport_backoff_multiplier
290
- validate_type!(@transport_backoff_multiplier, [ Numeric ], "transport_backoff_multiplier")
291
- validate_numeric_range!(@transport_backoff_multiplier, 1.0..10.0, "transport_backoff_multiplier")
286
+ if @transport_max_retries && @transport_max_retries < 0
287
+ logger.warn "transport_max_retries should be non-negative, got #{@transport_max_retries}"
292
288
  end
293
289
 
294
- # Validate that initial backoff is less than max backoff
295
290
  if @transport_initial_backoff && @transport_max_backoff && @transport_initial_backoff > @transport_max_backoff
296
- raise ValidationError,
297
- "transport_initial_backoff (#{@transport_initial_backoff}) must be less than transport_max_backoff (#{@transport_max_backoff})"
291
+ logger.warn "transport_initial_backoff (#{@transport_initial_backoff}) should be less than transport_max_backoff (#{@transport_max_backoff})"
298
292
  end
299
293
 
300
- # Validate adapter configurations
294
+ # Validate adapter configurations exist
301
295
  @adapter_configs.each do |name, config|
302
- validate_adapter_config!(name, config)
296
+ if config[:type].blank?
297
+ logger.warn "Adapter '#{name}' has no type specified"
298
+ end
303
299
  end
300
+
301
+ true
304
302
  end
305
303
 
306
304
  private
307
305
 
308
- def validate_adapter_config!(name, config)
309
- validate_presence!(config[:type], "adapter type for '#{name}'")
310
- validate_type!(config[:settings], [ Hash ], "adapter settings for '#{name}'")
311
- end
306
+ # Adapter config validation moved to inline logging
312
307
  end
313
308
  end
data/lib/lapsoss/event.rb CHANGED
@@ -1,131 +1,125 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Lapsoss
4
- class Event
5
- attr_accessor :type, :timestamp, :level, :message, :exception, :context, :environment, :fingerprint,
6
- :backtrace_frames
7
-
8
- def initialize(type:, level: :info, **attributes)
9
- @type = type
10
- @level = level
11
- @timestamp = Utils.current_time
12
- @context = {}
13
- @environment = Lapsoss.configuration.environment
14
-
15
- attributes.each do |key, value|
16
- instance_variable_set("@#{key}", value) if respond_to?("#{key}=")
17
- end
18
-
19
- # Process backtrace if we have an exception
20
- @backtrace_frames = process_backtrace if @exception
21
-
22
- # Set message from exception if not provided
23
- @message ||= @exception.message if @exception
3
+ require "active_support/core_ext/hash"
4
+ require "active_support/core_ext/object/blank"
5
+ require "active_support/core_ext/time"
6
+ require "active_support/json"
24
7
 
25
- # Generate fingerprint after all attributes are set (unless explicitly set to nil or a value)
26
- @fingerprint = generate_fingerprint if @fingerprint.nil? && !attributes.key?(:fingerprint)
27
- end
8
+ module Lapsoss
9
+ # Immutable event structure using Ruby 3.3 Data class
10
+ Event = Data.define(
11
+ :type, # :exception, :message, :transaction
12
+ :level, # :debug, :info, :warning, :error, :fatal
13
+ :timestamp,
14
+ :message,
15
+ :exception,
16
+ :context,
17
+ :environment,
18
+ :fingerprint,
19
+ :backtrace_frames
20
+ ) do
21
+ # Factory method with smart defaults
22
+ def self.build(type:, level: :info, **attributes)
23
+ timestamp = attributes[:timestamp] || Time.now
24
+ environment = attributes[:environment].presence || Lapsoss.configuration.environment
25
+ context = attributes[:context] || {}
26
+
27
+ # Process exception if present
28
+ exception = attributes[:exception]
29
+ message = attributes[:message].presence || exception&.message
30
+ backtrace_frames = process_backtrace(exception) if exception
31
+
32
+ # Generate fingerprint
33
+ fingerprint = attributes.fetch(:fingerprint) {
34
+ generate_fingerprint(type, message, exception, environment)
35
+ }
28
36
 
29
- def to_h
30
- data = {
37
+ new(
31
38
  type: type,
32
- timestamp: timestamp,
33
39
  level: level,
40
+ timestamp: timestamp,
34
41
  message: message,
35
- exception: exception_data,
36
- backtrace: backtrace_data,
42
+ exception: exception,
37
43
  context: context,
38
44
  environment: environment,
39
- fingerprint: fingerprint
40
- }.compact
41
-
42
- scrub_sensitive_data(data)
45
+ fingerprint: fingerprint,
46
+ backtrace_frames: backtrace_frames
47
+ )
43
48
  end
44
49
 
45
- def scrub_sensitive_data(data)
46
- config = Lapsoss.configuration
47
-
48
- scrubber = Scrubber.new(
49
- scrub_fields: config.scrub_fields,
50
- scrub_all: config.scrub_all,
51
- whitelist_fields: config.whitelist_fields,
52
- randomize_scrub_length: config.randomize_scrub_length
53
- )
50
+ # ActiveSupport::JSON serialization
51
+ def as_json(options = nil)
52
+ to_h.compact_blank.as_json(options)
53
+ end
54
54
 
55
- scrubber.scrub(data)
55
+ def to_json(options = nil)
56
+ ActiveSupport::JSON.encode(as_json(options))
56
57
  end
57
58
 
58
- def generate_fingerprint
59
- config = Lapsoss.configuration
59
+ # Helper methods
60
+ def exception_type = exception&.class&.name
60
61
 
61
- fingerprinter = Fingerprinter.new(
62
- custom_callback: config.fingerprint_callback,
63
- patterns: config.fingerprint_patterns,
64
- normalize_paths: config.normalize_fingerprint_paths,
65
- normalize_ids: config.normalize_fingerprint_ids,
66
- include_environment: config.fingerprint_include_environment
67
- )
62
+ def exception_message = exception&.message
68
63
 
69
- fingerprinter.generate_fingerprint(self)
70
- end
64
+ def has_exception? = exception.present?
71
65
 
72
- def process_backtrace
73
- return nil unless @exception&.backtrace
66
+ def has_backtrace? = backtrace_frames.present?
74
67
 
75
- config = Lapsoss.configuration
68
+ def backtrace = exception&.backtrace
76
69
 
77
- processor = BacktraceProcessor.new(
78
- context_lines: config.backtrace_context_lines,
79
- max_frames: config.backtrace_max_frames,
80
- enable_code_context: config.backtrace_enable_code_context,
81
- strip_load_path: config.backtrace_strip_load_path,
82
- in_app_patterns: config.backtrace_in_app_patterns,
83
- exclude_patterns: config.backtrace_exclude_patterns
84
- )
70
+ def request_context = context.dig(:extra, :request) || context.dig(:extra, "request")
85
71
 
86
- processor.process_exception_backtrace(@exception)
87
- end
72
+ def user_context = context[:user]
88
73
 
89
- private
74
+ def tags = context[:tags] || {}
90
75
 
91
- def exception_data
92
- return nil unless exception
76
+ def extra = context[:extra] || {}
93
77
 
94
- {
95
- class: exception.class.name,
96
- message: exception.message,
97
- backtrace: exception.backtrace&.first(20)
98
- }
99
- end
78
+ def breadcrumbs = context[:breadcrumbs] || []
100
79
 
101
- def backtrace_data
102
- return nil unless @backtrace_frames&.any?
80
+ # Apply data scrubbing
81
+ def scrubbed
82
+ scrubber = Scrubber.new(
83
+ scrub_fields: Lapsoss.configuration.scrub_fields,
84
+ scrub_all: Lapsoss.configuration.scrub_all,
85
+ whitelist_fields: Lapsoss.configuration.whitelist_fields,
86
+ randomize_scrub_length: Lapsoss.configuration.randomize_scrub_length
87
+ )
103
88
 
104
- @backtrace_frames.map(&:to_h)
89
+ with(context: scrubber.scrub(context))
105
90
  end
106
91
 
107
- public
92
+ private
108
93
 
109
- def exception_type
110
- exception&.class&.name
111
- end
94
+ def self.process_backtrace(exception)
95
+ return nil unless exception&.backtrace.present?
112
96
 
113
- def backtrace
114
- if @backtrace_frames
115
- @backtrace_frames.map(&:raw_line)
116
- elsif exception
117
- exception.backtrace
118
- else
119
- []
120
- end
97
+ config = Lapsoss.configuration
98
+ processor = BacktraceProcessor.new(config)
99
+ processor.process_exception_backtrace(exception)
121
100
  end
122
101
 
123
- def request_context
124
- # Request context is stored in extra by the middleware
125
- # Check both string and symbol keys for compatibility
126
- return nil unless context[:extra]
102
+ def self.generate_fingerprint(type, message, exception, environment)
103
+ return nil unless Lapsoss.configuration.fingerprint_callback ||
104
+ Lapsoss.configuration.fingerprint_patterns.present?
105
+
106
+ fingerprinter = Fingerprinter.new(
107
+ custom_callback: Lapsoss.configuration.fingerprint_callback,
108
+ patterns: Lapsoss.configuration.fingerprint_patterns,
109
+ normalize_paths: Lapsoss.configuration.normalize_fingerprint_paths,
110
+ normalize_ids: Lapsoss.configuration.normalize_fingerprint_ids,
111
+ include_environment: Lapsoss.configuration.fingerprint_include_environment
112
+ )
113
+
114
+ # Create a temporary event-like object for fingerprinting
115
+ temp_event = Struct.new(:type, :message, :exception, :environment).new(
116
+ type,
117
+ message,
118
+ exception,
119
+ environment
120
+ )
127
121
 
128
- context[:extra]["request"] || context[:extra][:request]
122
+ fingerprinter.generate_fingerprint(temp_event)
129
123
  end
130
124
  end
131
125
  end
@@ -4,8 +4,9 @@ require "digest"
4
4
 
5
5
  module Lapsoss
6
6
  class Fingerprinter
7
- DEFAULT_PATTERNS = [
8
- # User/ID normalization patterns
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
- # URL/Path normalization patterns
19
+ # Network error patterns
19
20
  {
20
- pattern: %r{/users/\d+(/.*)?},
21
- fingerprint: "users-id-endpoint"
21
+ pattern: /Net::(TimeoutError|ReadTimeout|OpenTimeout)/,
22
+ fingerprint: "network-timeout"
22
23
  },
23
24
  {
24
- pattern: %r{/api/v\d+/.*},
25
- fingerprint: "api-endpoint"
25
+ pattern: /Errno::(ECONNREFUSED|ECONNRESET|EHOSTUNREACH)/,
26
+ fingerprint: "network-connection-error"
26
27
  },
27
28
 
28
- # Database error patterns
29
+ # Memory/Resource patterns
29
30
  {
30
- pattern: /PG::ConnectionBad|Mysql2::Error|SQLite3::BusyException/,
31
- fingerprint: "database-connection-error"
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: /Errno::(ECONNREFUSED|ECONNRESET|EHOSTUNREACH)/,
49
- fingerprint: "network-connection-error"
50
- },
47
+ pattern: /ActiveRecord::ConnectionTimeoutError/,
48
+ fingerprint: "database-connection-timeout"
49
+ }
50
+ ].freeze
51
51
 
52
- # File system patterns
52
+ # Database adapter patterns (only loaded if adapters are defined)
53
+ DATABASE_PATTERNS = [
53
54
  {
54
- pattern: %r{Errno::(ENOENT|EACCES).*/tmp/},
55
- fingerprint: "tmp-file-error"
55
+ pattern: /PG::ConnectionBad/,
56
+ fingerprint: "postgres-connection-error",
57
+ condition: -> { defined?(PG) }
56
58
  },
57
59
  {
58
- pattern: /No such file or directory.*\.log/,
59
- fingerprint: "log-file-missing"
60
+ pattern: /Mysql2::Error/,
61
+ fingerprint: "mysql-connection-error",
62
+ condition: -> { defined?(Mysql2) }
60
63
  },
61
-
62
- # Memory/Resource patterns
63
64
  {
64
- pattern: /NoMemoryError|SystemStackError/,
65
- fingerprint: "memory-resource-error"
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] || DEFAULT_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
 
@@ -98,10 +116,13 @@ module Lapsoss
98
116
  pattern = pattern_config[:pattern]
99
117
  fingerprint = pattern_config[:fingerprint]
100
118
 
101
- if pattern.is_a?(Regexp) && full_error_text.match?(pattern)
119
+ case pattern
120
+ in Regexp if pattern.match?(full_error_text)
102
121
  return fingerprint
103
- elsif pattern.is_a?(String) && full_error_text.include?(pattern)
122
+ in String if full_error_text.include?(pattern)
104
123
  return fingerprint
124
+ else
125
+ # Continue to next pattern
105
126
  end
106
127
  end
107
128
 
@@ -167,18 +188,9 @@ module Lapsoss
167
188
 
168
189
  # Replace numeric IDs with placeholder (after UUIDs and hashes)
169
190
  normalized.gsub!(/\b\d{3,}\b/, ":id")
170
- end
171
-
172
- if @normalize_paths
173
- # Replace absolute file paths with placeholder
174
- normalized.gsub!(%r{/[^/\s]+(?:/[^/\s]+)*\.[a-zA-Z0-9]+}, ":filepath")
175
- normalized.gsub!(%r{/[^/\s]+(?:/[^/\s]+)+(?:/)?}, ":dirpath")
176
191
 
177
192
  # Replace timestamps
178
193
  normalized.gsub!(/\b\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}/, ":timestamp")
179
-
180
- # Replace URLs with placeholder
181
- normalized.gsub!(%r{https?://[^\s]+}, ":url")
182
194
  end
183
195
 
184
196
  # Clean up extra whitespace
@@ -198,13 +210,9 @@ module Lapsoss
198
210
 
199
211
  line_to_use = app_line || backtrace.first
200
212
 
201
- if @normalize_paths
202
- # Extract just filename:line_number
203
- if line_to_use =~ %r{([^/]+):(\d+)}
204
- "#{::Regexp.last_match(1)}:#{::Regexp.last_match(2)}"
205
- else
206
- line_to_use
207
- end
213
+ # Extract just filename:line_number
214
+ if line_to_use =~ %r{([^/]+):(\d+)}
215
+ "#{::Regexp.last_match(1)}:#{::Regexp.last_match(2)}"
208
216
  else
209
217
  line_to_use
210
218
  end
@@ -26,12 +26,7 @@ module Lapsoss
26
26
  end
27
27
 
28
28
  def add_breadcrumb(message, type: :default, **metadata)
29
- breadcrumb = {
30
- message: message,
31
- type: type,
32
- metadata: metadata,
33
- timestamp: Time.now.utc
34
- }
29
+ breadcrumb = Breadcrumb.build(message, type: type, metadata: metadata)
35
30
  @own_breadcrumbs << breadcrumb
36
31
  # Keep breadcrumbs to a reasonable limit
37
32
  @own_breadcrumbs.shift if @own_breadcrumbs.length > 20
@@ -3,114 +3,27 @@
3
3
  module Lapsoss
4
4
  module Middleware
5
5
  class ReleaseTracker < Base
6
- def initialize(app, release_provider: nil)
6
+ def initialize(app, release: nil)
7
7
  super(app)
8
- @release_provider = release_provider
8
+ @release = release
9
9
  end
10
10
 
11
11
  def call(event, hint = {})
12
- add_release_info(event, hint)
12
+ if release = detect_release
13
+ event.context[:release] = release
14
+ end
13
15
  @app.call(event, hint)
14
16
  end
15
17
 
16
18
  private
17
19
 
18
- def add_release_info(event, hint)
19
- release_info = @release_provider&.call(event, hint) || auto_detect_release
20
-
21
- event.context[:release] = release_info if release_info
22
- end
23
-
24
- def auto_detect_release
25
- release_info = {}
26
-
27
- # Try to detect Git information
28
- if git_info = detect_git_info
29
- release_info.merge!(git_info)
30
- end
31
-
32
- # Try to detect deployment info
33
- if deployment_info = detect_deployment_info
34
- release_info.merge!(deployment_info)
35
- end
36
-
37
- release_info.empty? ? nil : release_info
38
- end
39
-
40
- def detect_git_info
41
- return nil unless File.exist?(".git")
42
-
43
- begin
44
- # Get current commit SHA
45
- commit_sha = `git rev-parse HEAD`.strip
46
- return nil if commit_sha.empty?
47
-
48
- # Get branch name
49
- branch = `git rev-parse --abbrev-ref HEAD`.strip
50
- branch = nil if branch.empty? || branch == "HEAD"
51
-
52
- # Get commit timestamp
53
- commit_time = `git log -1 --format=%ct`.strip
54
- commit_timestamp = commit_time.empty? ? nil : Time.zone.at(commit_time.to_i)
55
-
56
- # Get tag if on a tag
57
- tag = `git describe --exact-match --tags HEAD 2>/dev/null`.strip
58
- tag = nil if tag.empty?
59
-
60
- {
61
- commit_sha: commit_sha,
62
- branch: branch,
63
- tag: tag,
64
- commit_timestamp: commit_timestamp
65
- }.compact
66
- rescue StandardError
67
- nil
68
- end
69
- end
70
-
71
- def detect_deployment_info
72
- info = {}
73
-
74
- # Check common deployment environment variables
75
- info[:deployment_id] = ENV["DEPLOYMENT_ID"] if ENV["DEPLOYMENT_ID"]
76
- info[:build_number] = ENV["BUILD_NUMBER"] if ENV["BUILD_NUMBER"]
77
- info[:deployment_time] = parse_deployment_time(ENV["DEPLOYMENT_TIME"]) if ENV["DEPLOYMENT_TIME"]
78
-
79
- # Check Heroku
80
- if ENV["HEROKU_APP_NAME"]
81
- info[:platform] = "heroku"
82
- info[:app_name] = ENV["HEROKU_APP_NAME"]
83
- info[:dyno] = ENV.fetch("DYNO", nil)
84
- info[:slug_commit] = ENV.fetch("HEROKU_SLUG_COMMIT", nil)
85
- end
86
-
87
- # Check AWS
88
- if ENV["AWS_EXECUTION_ENV"]
89
- info[:platform] = "aws"
90
- info[:execution_env] = ENV["AWS_EXECUTION_ENV"]
91
- info[:region] = ENV.fetch("AWS_REGION", nil)
92
- end
93
-
94
- # Check Docker
95
- if ENV["DOCKER_CONTAINER_ID"] || File.exist?("/.dockerenv")
96
- info[:platform] = "docker"
97
- info[:container_id] = ENV["DOCKER_CONTAINER_ID"]
98
- end
99
-
100
- # Check Kubernetes
101
- if ENV["KUBERNETES_SERVICE_HOST"]
102
- info[:platform] = "kubernetes"
103
- info[:namespace] = ENV.fetch("KUBERNETES_NAMESPACE", nil)
104
- info[:pod_name] = ENV.fetch("HOSTNAME", nil)
105
- end
106
-
107
- info.empty? ? nil : info
108
- end
20
+ def detect_release
21
+ # Use configured release
22
+ return @release.call if @release.respond_to?(:call)
23
+ return @release if @release.present?
109
24
 
110
- def parse_deployment_time(time_str)
111
- Time.zone.parse(time_str)
112
- rescue StandardError
113
- nil
25
+ # Use rails_app_version gem if available
26
+ Rails.application.version.to_s if defined?(Rails) && Rails.application.respond_to?(:version)
114
27
  end
115
28
  end
116
29
  end
@@ -25,8 +25,8 @@ module Lapsoss
25
25
  self
26
26
  end
27
27
 
28
- def track_releases(provider: nil)
29
- @pipeline.use(Middleware::ReleaseTracker, release_provider: provider)
28
+ def track_releases(release: nil)
29
+ @pipeline.use(Middleware::ReleaseTracker, release: release)
30
30
  self
31
31
  end
32
32
 
@@ -22,10 +22,9 @@ module Lapsoss
22
22
  private
23
23
 
24
24
  def skip_error?(error, source)
25
- # Skip cache-related Redis errors if configured to do so
26
- if Lapsoss.configuration.skip_rails_cache_errors && source&.include?("cache") && defined?(Redis::CannotConnectError) && error.is_a?(Redis::CannotConnectError)
27
- return true
28
- end
25
+ # Skip Rails cache-related errors using Rails error reporter source
26
+ # Avoid referencing backend-specific gems (e.g., Redis)
27
+ return true if Lapsoss.configuration.skip_rails_cache_errors && source&.include?("cache")
29
28
 
30
29
  false
31
30
  end
@@ -10,14 +10,14 @@ module Lapsoss
10
10
  Lapsoss::Current.with_clean_scope do
11
11
  # Add request context to current scope
12
12
  if Lapsoss.configuration.capture_request_context
13
- Rails.logger.debug "[Lapsoss] Adding request context" if Rails.env.test?
13
+ Rails.logger.tagged("Lapsoss") { Rails.logger.debug "Adding request context" } if Rails.env.test?
14
14
  add_request_context(env)
15
15
  end
16
16
 
17
17
  begin
18
18
  @app.call(env)
19
19
  rescue Exception => e
20
- Rails.logger.debug { "[Lapsoss] Capturing exception: #{e.class} - #{e.message}" } if Rails.env.test?
20
+ Rails.logger.tagged("Lapsoss") { Rails.logger.debug "Capturing exception: #{e.class} - #{e.message}" } if Rails.env.test?
21
21
  # Capture the exception
22
22
  Lapsoss.capture_exception(e)
23
23
  # Re-raise the exception to maintain Rails error handling