lapsoss 0.1.0 → 0.3.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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +153 -733
  3. data/lib/lapsoss/adapters/appsignal_adapter.rb +7 -8
  4. data/lib/lapsoss/adapters/base.rb +0 -3
  5. data/lib/lapsoss/adapters/bugsnag_adapter.rb +12 -0
  6. data/lib/lapsoss/adapters/insight_hub_adapter.rb +102 -101
  7. data/lib/lapsoss/adapters/logger_adapter.rb +7 -7
  8. data/lib/lapsoss/adapters/rollbar_adapter.rb +93 -54
  9. data/lib/lapsoss/adapters/sentry_adapter.rb +11 -17
  10. data/lib/lapsoss/backtrace_frame.rb +35 -214
  11. data/lib/lapsoss/backtrace_frame_factory.rb +228 -0
  12. data/lib/lapsoss/backtrace_processor.rb +37 -37
  13. data/lib/lapsoss/client.rb +2 -6
  14. data/lib/lapsoss/configuration.rb +25 -22
  15. data/lib/lapsoss/current.rb +9 -1
  16. data/lib/lapsoss/event.rb +30 -6
  17. data/lib/lapsoss/exception_backtrace_frame.rb +39 -0
  18. data/lib/lapsoss/exclusion_configuration.rb +30 -0
  19. data/lib/lapsoss/exclusion_filter.rb +156 -0
  20. data/lib/lapsoss/{exclusions.rb → exclusion_presets.rb} +1 -181
  21. data/lib/lapsoss/fingerprinter.rb +9 -13
  22. data/lib/lapsoss/http_client.rb +42 -8
  23. data/lib/lapsoss/merged_scope.rb +63 -0
  24. data/lib/lapsoss/middleware/base.rb +15 -0
  25. data/lib/lapsoss/middleware/conditional_filter.rb +18 -0
  26. data/lib/lapsoss/middleware/event_enricher.rb +19 -0
  27. data/lib/lapsoss/middleware/event_transformer.rb +19 -0
  28. data/lib/lapsoss/middleware/exception_filter.rb +43 -0
  29. data/lib/lapsoss/middleware/metrics_collector.rb +44 -0
  30. data/lib/lapsoss/middleware/rate_limiter.rb +31 -0
  31. data/lib/lapsoss/middleware/release_tracker.rb +117 -0
  32. data/lib/lapsoss/middleware/sample_filter.rb +23 -0
  33. data/lib/lapsoss/middleware/sampling_middleware.rb +18 -0
  34. data/lib/lapsoss/middleware/user_context_enhancer.rb +46 -0
  35. data/lib/lapsoss/middleware.rb +0 -347
  36. data/lib/lapsoss/pipeline.rb +1 -73
  37. data/lib/lapsoss/pipeline_builder.rb +69 -0
  38. data/lib/lapsoss/rails_error_subscriber.rb +42 -0
  39. data/lib/lapsoss/rails_middleware.rb +78 -0
  40. data/lib/lapsoss/railtie.rb +22 -50
  41. data/lib/lapsoss/registry.rb +34 -20
  42. data/lib/lapsoss/release_providers.rb +110 -0
  43. data/lib/lapsoss/release_tracker.rb +112 -207
  44. data/lib/lapsoss/router.rb +3 -5
  45. data/lib/lapsoss/sampling/adaptive_sampler.rb +46 -0
  46. data/lib/lapsoss/sampling/base.rb +11 -0
  47. data/lib/lapsoss/sampling/composite_sampler.rb +26 -0
  48. data/lib/lapsoss/sampling/consistent_hash_sampler.rb +30 -0
  49. data/lib/lapsoss/sampling/exception_type_sampler.rb +44 -0
  50. data/lib/lapsoss/sampling/health_based_sampler.rb +19 -0
  51. data/lib/lapsoss/sampling/rate_limiter.rb +32 -0
  52. data/lib/lapsoss/sampling/sampling_factory.rb +69 -0
  53. data/lib/lapsoss/sampling/time_based_sampler.rb +44 -0
  54. data/lib/lapsoss/sampling/uniform_sampler.rb +15 -0
  55. data/lib/lapsoss/sampling/user_based_sampler.rb +42 -0
  56. data/lib/lapsoss/sampling.rb +0 -326
  57. data/lib/lapsoss/scope.rb +17 -57
  58. data/lib/lapsoss/scrubber.rb +16 -18
  59. data/lib/lapsoss/user_context.rb +18 -198
  60. data/lib/lapsoss/user_context_integrations.rb +39 -0
  61. data/lib/lapsoss/user_context_middleware.rb +50 -0
  62. data/lib/lapsoss/user_context_provider.rb +93 -0
  63. data/lib/lapsoss/utils.rb +13 -0
  64. data/lib/lapsoss/validators.rb +14 -27
  65. data/lib/lapsoss/version.rb +1 -1
  66. data/lib/lapsoss.rb +12 -25
  67. metadata +106 -21
@@ -2,71 +2,43 @@
2
2
 
3
3
  module Lapsoss
4
4
  class Railtie < Rails::Railtie
5
+ Rails.logger.debug "[Lapsoss] Railtie loaded" if ENV["DEBUG_LAPSOSS"]
5
6
  config.lapsoss = ActiveSupport::OrderedOptions.new
6
7
 
7
- initializer "lapsoss.configure" do |app|
8
+ initializer "lapsoss.configure" do |_app|
8
9
  Lapsoss.configure do |config|
9
- config.environment ||= Rails.env
10
+ # Use rails_app_version gem if available, otherwise fallback to Rails defaults
11
+ config.environment ||= if Rails.application.respond_to?(:env)
12
+ Rails.application.env
13
+ else
14
+ Rails.env
15
+ end
16
+
10
17
  config.logger ||= Rails.logger
11
- config.release ||= Rails.application.config.try(:release)
18
+
19
+ config.release ||= if Rails.application.respond_to?(:version)
20
+ Rails.application.version.to_s
21
+ else
22
+ Rails.application.config.try(:release)
23
+ end
12
24
 
13
25
  # Set default tags
14
26
  config.default_tags = {
15
- rails_env: Rails.env,
27
+ rails_env: config.environment,
16
28
  rails_version: Rails.version
17
29
  }
18
30
  end
19
31
  end
20
32
 
21
- initializer "lapsoss.rails_error_subscriber", after: "lapsoss.configure" do |app|
22
- if Rails.version.to_f >= 7.0
23
- app.executor.error_reporter.subscribe(Lapsoss::RailsErrorSubscriber.new)
24
- end
25
- end
26
-
33
+ initializer "lapsoss.add_middleware" do |app|
34
+ require "lapsoss/rails_middleware"
27
35
 
28
-
29
- rake_tasks do
30
- # Add any Lapsoss-specific rake tasks here
36
+ # Use config.middleware to ensure it's added during initialization
37
+ app.config.middleware.use Lapsoss::RailsMiddleware
31
38
  end
32
- end
33
-
34
- class RailsErrorSubscriber
35
- def report(error, handled:, severity:, context:, source: nil)
36
- # Skip certain framework errors
37
- return if skip_error?(error, source)
38
-
39
- level = map_severity(severity)
40
39
 
41
- Lapsoss.capture_exception(
42
- error,
43
- level: level,
44
- tags: {
45
- handled: handled,
46
- source: source || "rails"
47
- },
48
- context: context
49
- )
50
- end
51
-
52
- private
53
-
54
- def skip_error?(error, source)
55
- # Skip cache-related Redis errors if configured to do so
56
- if Lapsoss.configuration.skip_rails_cache_errors
57
- return true if source&.include?("cache") && error.is_a?(Redis::CannotConnectError)
58
- end
59
-
60
- false
61
- end
62
-
63
- def map_severity(severity)
64
- case severity
65
- when :error then :error
66
- when :warning then :warning
67
- when :info then :info
68
- else :error
69
- end
40
+ initializer "lapsoss.rails_error_subscriber", after: "lapsoss.add_middleware" do |app|
41
+ app.executor.error_reporter.subscribe(Lapsoss::RailsErrorSubscriber.new)
70
42
  end
71
43
  end
72
44
  end
@@ -2,12 +2,6 @@
2
2
 
3
3
  require "singleton"
4
4
  require "concurrent"
5
- require_relative "adapters/base"
6
- require_relative "adapters/logger_adapter"
7
- require_relative "adapters/sentry_adapter"
8
- require_relative "adapters/appsignal_adapter"
9
- require_relative "adapters/rollbar_adapter"
10
- require_relative "adapters/insight_hub_adapter"
11
5
 
12
6
  module Lapsoss
13
7
  class Registry
@@ -38,12 +32,30 @@ module Lapsoss
38
32
  adapter
39
33
  end
40
34
 
35
+ # Register an adapter instance directly (for testing)
36
+ #
37
+ # @param adapter [Adapter] The adapter instance to register
38
+ def register_adapter(adapter)
39
+ # Ensure we're getting an adapter instance, not a config hash
40
+ raise ArgumentError, "Expected an adapter instance, got #{adapter.class}" unless adapter.respond_to?(:capture)
41
+
42
+ name = if adapter.respond_to?(:name) && adapter.name
43
+ adapter.name.to_sym
44
+ elsif adapter.class.name
45
+ adapter.class.name.split("::").last.to_sym
46
+ else
47
+ # Generate a unique name if class name is nil (anonymous class)
48
+ :"adapter_#{adapter.object_id}"
49
+ end
50
+ @adapters[name] = adapter
51
+ end
52
+
41
53
  # Unregister an adapter
42
54
  #
43
55
  # @param name [Symbol] The adapter name to remove
44
56
  def unregister(name)
45
57
  adapter = @adapters.delete(name.to_sym)
46
- adapter&.shutdown if adapter&.respond_to?(:shutdown)
58
+ adapter&.shutdown if adapter.respond_to?(:shutdown)
47
59
  adapter
48
60
  end
49
61
 
@@ -79,7 +91,7 @@ module Lapsoss
79
91
 
80
92
  # Clear all adapters
81
93
  def clear!
82
- @adapters.values.each do |adapter|
94
+ @adapters.each_value do |adapter|
83
95
  adapter.shutdown if adapter.respond_to?(:shutdown)
84
96
  end
85
97
  @adapters.clear
@@ -92,23 +104,25 @@ module Lapsoss
92
104
  @adapters.keys
93
105
  end
94
106
 
107
+ # Get all registered adapters (alias for all)
108
+ #
109
+ # @return [Array<Adapter>] All adapter instances
110
+ def adapters
111
+ all
112
+ end
113
+
95
114
  private
96
115
 
97
116
  # Resolve adapter type to class
98
117
  def resolve_adapter_class(type)
99
- adapter_map[type.to_sym] || raise(AdapterNotFoundError, "Unknown adapter type: #{type}")
100
- end
118
+ # Try to get the class by convention: Adapters::{Type}Adapter
119
+ class_name = "#{type.to_s.split('_').map(&:capitalize).join}Adapter"
101
120
 
102
- # Map of adapter types to classes
103
- def adapter_map
104
- {
105
- logger: Adapters::LoggerAdapter,
106
- sentry: Adapters::SentryAdapter,
107
- appsignal: Adapters::AppsignalAdapter,
108
- rollbar: Adapters::RollbarAdapter,
109
- insight_hub: Adapters::InsightHubAdapter,
110
- bugsnag: Adapters::InsightHubAdapter # Backwards compatibility
111
- }
121
+ begin
122
+ Adapters.const_get(class_name)
123
+ rescue NameError
124
+ raise AdapterNotFoundError, "Unknown adapter type: #{type}. Expected class: Lapsoss::Adapters::#{class_name}"
125
+ end
112
126
  end
113
127
  end
114
128
  end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Lapsoss
6
+ # Built-in release providers for common scenarios
7
+ class ReleaseProviders
8
+ def self.from_file(file_path)
9
+ lambda do
10
+ return nil unless File.exist?(file_path)
11
+
12
+ content = File.read(file_path).strip
13
+ return nil if content.empty?
14
+
15
+ # Try to parse as JSON first
16
+ begin
17
+ JSON.parse(content)
18
+ rescue JSON::ParserError
19
+ # Treat as plain text version
20
+ { version: content }
21
+ end
22
+ end
23
+ end
24
+
25
+ def self.from_ruby_constant(constant_name)
26
+ lambda do
27
+ constant = Object.const_get(constant_name)
28
+ { version: constant.to_s }
29
+ rescue NameError
30
+ nil
31
+ end
32
+ end
33
+
34
+ def self.from_gemfile_lock
35
+ lambda do
36
+ return nil unless File.exist?("Gemfile.lock")
37
+
38
+ content = File.read("Gemfile.lock")
39
+
40
+ # Extract gems with versions
41
+ gems = {}
42
+ content.scan(/^\s{4}(\w+)\s+\(([^)]+)\)/).each do |name, version|
43
+ gems[name] = version
44
+ end
45
+
46
+ { gems: gems }
47
+ end
48
+ end
49
+
50
+ def self.from_package_json
51
+ lambda do
52
+ return nil unless File.exist?("package.json")
53
+
54
+ begin
55
+ package_info = JSON.parse(File.read("package.json"))
56
+ {
57
+ version: package_info["version"],
58
+ name: package_info["name"],
59
+ dependencies: package_info["dependencies"]&.keys
60
+ }.compact
61
+ rescue JSON::ParserError
62
+ nil
63
+ end
64
+ end
65
+ end
66
+
67
+ def self.from_rails_application
68
+ lambda do
69
+ return nil unless defined?(Rails) && Rails.respond_to?(:application)
70
+
71
+ app = Rails.application
72
+ return nil unless app
73
+
74
+ info = {
75
+ rails_version: Rails.version,
76
+ environment: Rails.env,
77
+ root: Rails.root.to_s
78
+ }
79
+
80
+ # Get application version if defined
81
+ info[:app_version] = app.class.version if app.class.respond_to?(:version)
82
+
83
+ # Get application name
84
+ info[:app_name] = app.class.name if app.class.respond_to?(:name)
85
+
86
+ info
87
+ end
88
+ end
89
+
90
+ def self.from_capistrano
91
+ lambda do
92
+ # Check for Capistrano deployment files
93
+ %w[REVISION current/REVISION].each do |file|
94
+ next unless File.exist?(file)
95
+
96
+ revision = File.read(file).strip
97
+ next if revision.empty?
98
+
99
+ return {
100
+ revision: revision,
101
+ deployed_at: File.mtime(file),
102
+ deployment_method: "capistrano"
103
+ }
104
+ end
105
+
106
+ nil
107
+ end
108
+ end
109
+ end
110
+ end