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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34f511accdaa033204fa71e2a498af0a795cc29071afa227c4d59bd224b81bd8
4
- data.tar.gz: 2cc9bcd3d87c9cc8ccf026106c5a9414cfd42fbb896de95df5bcf53814007842
3
+ metadata.gz: 387dbefc113baa7968d4343b8b5135dca057581757056acf740a4234d8fcab4a
4
+ data.tar.gz: 3d304210fbe0a71c3679be91cd4e867a33966dab2b8f10e441c11f7a3465d9c2
5
5
  SHA512:
6
- metadata.gz: dc1f028a9f02b5862f458794e26144d571ccb902f963aecdfda2d11e12346b57f46a8ef3fc9ea4c9a5ebf193a8fc4f6ca02988acb7dc518e5e248f20a4621918
7
- data.tar.gz: a2bb32697511091f34fe8736261bea39c78e070c31c2ef35dc1b2a20c865844ac1ec0e986729d80c17a31535de1e5bf7c22a030788898c2b7fa35b5d49bee517
6
+ metadata.gz: a166ab065134b2a6f84d17de91a7e67b64b8131e5a1d2380e4411cc21eb34692e5da56a531c89b04b9a093782b3b9773ad338769e384adb90155bb1ddceeb9cc
7
+ data.tar.gz: e5f4cf7fcc987d357a7d03b426bd120090c6190cbe37808139b53b3e939d6e27ecd75a72cef6e4d1ddff14bfb9d5c6848ca0b24f3b752b705a58aca9dde64a9c
@@ -1,21 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
- require "socket"
5
- require_relative "../http_client"
3
+ require 'json'
4
+ require 'socket'
6
5
 
7
6
  module Lapsoss
8
7
  module Adapters
9
8
  class AppsignalAdapter < Base
10
- PUSH_API_URI = "https://push.appsignal.com"
11
- ERRORS_API_URI = "https://appsignal-endpoint.net"
12
- JSON_CONTENT_TYPE = "application/json; charset=UTF-8"
9
+ PUSH_API_URI = 'https://push.appsignal.com'
10
+ ERRORS_API_URI = 'https://appsignal-endpoint.net'
11
+ JSON_CONTENT_TYPE = 'application/json; charset=UTF-8'
13
12
 
14
13
  def initialize(name, settings = {})
15
- super(name, settings)
16
- @push_api_key = settings[:push_api_key] || ENV["APPSIGNAL_PUSH_API_KEY"]
17
- @frontend_api_key = settings[:frontend_api_key] || ENV["APPSIGNAL_FRONTEND_API_KEY"]
18
- @app_name = settings[:app_name] || ENV["APPSIGNAL_APP_NAME"]
14
+ super
15
+ @push_api_key = settings[:push_api_key] || ENV.fetch('APPSIGNAL_PUSH_API_KEY', nil)
16
+ @frontend_api_key = settings[:frontend_api_key] || ENV.fetch('APPSIGNAL_FRONTEND_API_KEY', nil)
17
+ @app_name = settings[:app_name] || ENV.fetch('APPSIGNAL_APP_NAME', nil)
19
18
  @environment = Lapsoss.configuration.environment
20
19
 
21
20
  validate_settings!
@@ -31,7 +30,7 @@ module Lapsoss
31
30
  return unless payload
32
31
 
33
32
  path = "/errors?api_key=#{@frontend_api_key}"
34
- headers = { "Content-Type" => JSON_CONTENT_TYPE }
33
+ headers = { 'Content-Type' => JSON_CONTENT_TYPE }
35
34
 
36
35
  begin
37
36
  @errors_client.post(path, body: JSON.generate(payload), headers: headers)
@@ -65,7 +64,7 @@ module Lapsoss
65
64
  def build_exception_payload(event)
66
65
  {
67
66
  timestamp: event.timestamp.to_i,
68
- namespace: event.context[:namespace] || "backend",
67
+ namespace: event.context[:namespace] || 'backend',
69
68
  error: {
70
69
  name: event.exception.class.name,
71
70
  message: event.exception.message,
@@ -83,24 +82,24 @@ module Lapsoss
83
82
  # Instead of creating fake exceptions, we'll structure the message properly
84
83
  # but clearly indicate it's a log message, not an exception
85
84
 
86
- unless [:error, :fatal, :critical].include?(event.level)
85
+ unless %i[error fatal critical].include?(event.level)
87
86
  # Log when messages are dropped due to level filtering
88
87
  Lapsoss.configuration.logger&.debug(
89
88
  "[Lapsoss::AppsignalAdapter] Dropping message with level '#{event.level}' - " \
90
- "AppSignal only supports :error, :fatal, and :critical levels for messages"
89
+ 'AppSignal only supports :error, :fatal, and :critical levels for messages'
91
90
  )
92
91
  return nil
93
92
  end
94
93
 
95
94
  {
96
- action: event.context[:action] || "log_message",
97
- path: event.context[:path] || "/",
95
+ action: event.context[:action] || 'log_message',
96
+ path: event.context[:path] || '/',
98
97
  exception: {
99
98
  # AppSignal requires exception format for messages - this isn't a real exception
100
99
  # but rather a way to send structured log messages through their error API
101
- name: "LogMessage", # Clear indication this is a log message
100
+ name: 'LogMessage', # Clear indication this is a log message
102
101
  message: event.message,
103
- backtrace: [] # No fake backtrace for log messages
102
+ backtrace: [] # No fake backtrace for log messages
104
103
  },
105
104
  tags: stringify_hash(event.context[:tags]),
106
105
  params: stringify_hash(event.context[:params]),
@@ -111,9 +110,9 @@ module Lapsoss
111
110
 
112
111
  def build_environment_context(event)
113
112
  {
114
- "hostname" => Socket.gethostname,
115
- "app_name" => @app_name,
116
- "environment" => @environment
113
+ 'hostname' => Socket.gethostname,
114
+ 'app_name' => @app_name,
115
+ 'environment' => @environment
117
116
  }.merge(stringify_hash(event.context[:environment] || {}))
118
117
  end
119
118
 
@@ -123,13 +122,13 @@ module Lapsoss
123
122
 
124
123
  def validate_settings!
125
124
  unless @push_api_key || @frontend_api_key
126
- raise ValidationError, "AppSignal API key is required (either push_api_key or frontend_api_key)"
125
+ raise ValidationError, 'AppSignal API key is required (either push_api_key or frontend_api_key)'
127
126
  end
128
127
 
129
- validate_api_key!(@push_api_key, "AppSignal push API key", format: :uuid) if @push_api_key
130
- validate_api_key!(@frontend_api_key, "AppSignal frontend API key", format: :uuid) if @frontend_api_key
131
- validate_presence!(@app_name, "AppSignal app name") if @app_name
132
- validate_environment!(@environment, "AppSignal environment") if @environment
128
+ validate_api_key!(@push_api_key, 'AppSignal push API key', format: :uuid) if @push_api_key
129
+ validate_api_key!(@frontend_api_key, 'AppSignal frontend API key', format: :uuid) if @frontend_api_key
130
+ validate_presence!(@app_name, 'AppSignal app name') if @app_name
131
+ validate_environment!(@environment, 'AppSignal environment') if @environment
133
132
  end
134
133
  end
135
134
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../http_client"
4
- require_relative "../validators"
3
+ require_relative '../http_client'
4
+ require_relative '../validators'
5
5
 
6
6
  module Lapsoss
7
7
  module Adapters
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lapsoss
4
+ module Adapters
5
+ # Bugsnag adapter - backwards compatibility with InsightHub adapter
6
+ # This allows users to configure with :bugsnag type but uses InsightHub implementation
7
+ # The InsightHub adapter already checks for BUGSNAG_API_KEY environment variable
8
+ class BugsnagAdapter < InsightHubAdapter
9
+ # Inherits all functionality from InsightHubAdapter
10
+ end
11
+ end
12
+ end
@@ -1,25 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
- require "socket"
5
- require_relative "../http_client"
6
- require_relative "../backtrace_processor"
3
+ require 'json'
4
+ require 'socket'
7
5
 
8
6
  module Lapsoss
9
7
  module Adapters
10
8
  # Adapter for Insight Hub (formerly Bugsnag)
11
9
  # Note: The API endpoints still use bugsnag.com domains for backwards compatibility
12
10
  class InsightHubAdapter < Base
13
- NOTIFY_URI = "https://notify.bugsnag.com"
14
- SESSION_URI = "https://sessions.bugsnag.com"
15
- JSON_CONTENT_TYPE = "application/json"
11
+ NOTIFY_URI = 'https://notify.bugsnag.com'
12
+ SESSION_URI = 'https://sessions.bugsnag.com'
13
+ JSON_CONTENT_TYPE = 'application/json'
16
14
 
17
15
  def initialize(name, settings = {})
18
- super(name, settings)
19
- @api_key = settings[:api_key] || ENV["INSIGHT_HUB_API_KEY"] || ENV["BUGSNAG_API_KEY"]
16
+ super
17
+ @api_key = settings[:api_key] || ENV['INSIGHT_HUB_API_KEY'] || ENV.fetch('BUGSNAG_API_KEY', nil)
20
18
  @release_stage = settings[:release_stage] || Lapsoss.configuration.environment
21
19
  @app_version = settings[:app_version]
22
- @app_type = settings[:app_type] || "ruby"
20
+ @app_type = settings[:app_type] || 'ruby'
23
21
 
24
22
  validate_settings!
25
23
 
@@ -35,14 +33,14 @@ module Lapsoss
35
33
  return unless payload
36
34
 
37
35
  headers = {
38
- "Content-Type" => JSON_CONTENT_TYPE,
39
- "Bugsnag-Api-Key" => @api_key,
40
- "Bugsnag-Payload-Version" => "5.0",
41
- "Bugsnag-Sent-At" => Time.now.utc.iso8601
36
+ 'Content-Type' => JSON_CONTENT_TYPE,
37
+ 'Bugsnag-Api-Key' => @api_key,
38
+ 'Bugsnag-Payload-Version' => '5.0',
39
+ 'Bugsnag-Sent-At' => Time.now.utc.iso8601
42
40
  }
43
41
 
44
42
  begin
45
- @notify_client.post("/", body: JSON.generate(payload), headers: headers)
43
+ @notify_client.post('/', body: JSON.generate(payload), headers: headers)
46
44
  rescue DeliveryError => e
47
45
  # Log the error and potentially notify error handler
48
46
  Lapsoss.configuration.logger&.error("[Lapsoss::InsightHubAdapter] Failed to deliver event: #{e.message}")
@@ -72,11 +70,11 @@ module Lapsoss
72
70
  def build_payload(event)
73
71
  {
74
72
  apiKey: @api_key,
75
- payloadVersion: "5.0",
73
+ payloadVersion: '5.0',
76
74
  notifier: {
77
- name: "Lapsoss",
75
+ name: 'Lapsoss',
78
76
  version: Lapsoss::VERSION,
79
- url: "https://github.com/seuros/lapsoss"
77
+ url: 'https://github.com/seuros/lapsoss'
80
78
  },
81
79
  events: [build_event(event)]
82
80
  }
@@ -98,7 +96,7 @@ module Lapsoss
98
96
  severity: map_severity(event.level),
99
97
  unhandled: event.context[:unhandled] || false,
100
98
  severityReason: {
101
- type: event.context[:unhandled] ? "unhandledException" : "handledException"
99
+ type: event.context[:unhandled] ? 'unhandledException' : 'handledException'
102
100
  },
103
101
  user: build_user(event),
104
102
  context: event.context[:context],
@@ -121,7 +119,7 @@ module Lapsoss
121
119
  errorClass: event.exception.class.name,
122
120
  message: event.exception.message,
123
121
  stacktrace: build_stacktrace(event.exception),
124
- type: "ruby"
122
+ type: 'ruby'
125
123
  }]
126
124
  }
127
125
  end
@@ -131,10 +129,10 @@ module Lapsoss
131
129
  exceptions: [{
132
130
  # Insight Hub (Bugsnag) requires exception format for messages - this isn't a real exception
133
131
  # but rather a way to send structured log messages through their error API
134
- errorClass: "LogMessage", # Clear indication this is a log message
132
+ errorClass: 'LogMessage', # Clear indication this is a log message
135
133
  message: event.message,
136
134
  stacktrace: [], # No fake backtrace for log messages
137
- type: "log" # Mark as log type, not ruby exception
135
+ type: 'log' # Mark as log type, not ruby exception
138
136
  }]
139
137
  }
140
138
  end
@@ -146,14 +144,13 @@ module Lapsoss
146
144
  @backtrace_processor.format_frames(frames, :bugsnag)
147
145
  end
148
146
 
149
-
150
147
  def build_breadcrumbs(event)
151
148
  breadcrumbs = event.context[:breadcrumbs] || []
152
149
  breadcrumbs.map do |crumb|
153
150
  {
154
151
  timestamp: crumb[:timestamp]&.iso8601 || Time.now.utc.iso8601,
155
152
  name: crumb[:message] || crumb[:name],
156
- type: crumb[:type] || "manual",
153
+ type: crumb[:type] || 'manual',
157
154
  metaData: crumb[:data] || {}
158
155
  }
159
156
  end
@@ -172,18 +169,18 @@ module Lapsoss
172
169
 
173
170
  def map_severity(level)
174
171
  case level
175
- when :debug, :info then "info"
176
- when :warning, :warn then "warning"
177
- when :error, :fatal, :critical then "error"
178
- else "error"
172
+ when :debug, :info then 'info'
173
+ when :warning, :warn then 'warning'
174
+ when :error, :fatal, :critical then 'error'
175
+ else 'error'
179
176
  end
180
177
  end
181
178
 
182
179
  def validate_settings!
183
- validate_presence!(@api_key, "Insight Hub API key")
184
- validate_api_key!(@api_key, "Insight Hub API key", format: :alphanumeric) if @api_key
185
- validate_environment!(@app_version, "Insight Hub app version") if @app_version
186
- validate_type!(@release_stage, [String, Symbol], "Insight Hub release stage") if @release_stage
180
+ validate_presence!(@api_key, 'Insight Hub API key')
181
+ validate_api_key!(@api_key, 'Insight Hub API key', format: :alphanumeric) if @api_key
182
+ validate_environment!(@app_version, 'Insight Hub app version') if @app_version
183
+ validate_type!(@release_stage, [String, Symbol], 'Insight Hub release stage') if @release_stage
187
184
  end
188
185
  end
189
186
  end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "logger"
3
+ require 'logger'
4
4
 
5
5
  module Lapsoss
6
6
  module Adapters
7
7
  class LoggerAdapter < Base
8
8
  def initialize(name, settings = {})
9
9
  @logger = settings[:logger] || Logger.new($stdout)
10
- super(name, settings)
10
+ super
11
11
  end
12
12
 
13
13
  def capabilities
@@ -28,12 +28,12 @@ module Lapsoss
28
28
  end
29
29
 
30
30
  # Log breadcrumbs if present in the event context
31
- if event.context[:breadcrumbs]&.any?
32
- event.context[:breadcrumbs].each do |breadcrumb|
33
- breadcrumb_msg = "[BREADCRUMB] [#{breadcrumb[:type].upcase}] #{breadcrumb[:message]}"
34
- breadcrumb_msg += " | #{breadcrumb[:metadata].inspect}" unless breadcrumb[:metadata].empty?
35
- @logger.debug(breadcrumb_msg)
36
- end
31
+ return unless event.context[:breadcrumbs]&.any?
32
+
33
+ event.context[:breadcrumbs].each do |breadcrumb|
34
+ breadcrumb_msg = "[BREADCRUMB] [#{breadcrumb[:type].upcase}] #{breadcrumb[:message]}"
35
+ breadcrumb_msg += " | #{breadcrumb[:metadata].inspect}" unless breadcrumb[:metadata].empty?
36
+ @logger.debug(breadcrumb_msg)
37
37
  end
38
38
  end
39
39
 
@@ -1,22 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
- require "socket"
5
- require "securerandom"
6
- require_relative "../http_client"
7
- require_relative "../backtrace_processor"
3
+ require 'json'
4
+ require 'socket'
5
+ require 'securerandom'
8
6
 
9
7
  module Lapsoss
10
8
  module Adapters
11
9
  class RollbarAdapter < Base
12
- API_URI = "https://api.rollbar.com"
13
- API_VERSION = "1"
14
- JSON_CONTENT_TYPE = "application/json"
10
+ API_URI = 'https://api.rollbar.com'
11
+ API_VERSION = '1'
12
+ JSON_CONTENT_TYPE = 'application/json'
15
13
 
16
14
  def initialize(name, settings = {})
17
- super(name, settings)
18
- @access_token = settings[:access_token] || ENV["ROLLBAR_ACCESS_TOKEN"]
19
- @environment = settings[:environment] || Lapsoss.configuration.environment || "development"
15
+ super
16
+ @access_token = settings[:access_token] || ENV.fetch('ROLLBAR_ACCESS_TOKEN', nil)
17
+ @environment = settings[:environment] || Lapsoss.configuration.environment || 'development'
20
18
 
21
19
  validate_settings!
22
20
 
@@ -32,8 +30,8 @@ module Lapsoss
32
30
 
33
31
  path = "/api/#{API_VERSION}/item/"
34
32
  headers = {
35
- "Content-Type" => JSON_CONTENT_TYPE,
36
- "X-Rollbar-Access-Token" => @access_token
33
+ 'Content-Type' => JSON_CONTENT_TYPE,
34
+ 'X-Rollbar-Access-Token' => @access_token
37
35
  }
38
36
 
39
37
  begin
@@ -67,8 +65,8 @@ module Lapsoss
67
65
  body: build_body(event),
68
66
  level: map_level(event.level),
69
67
  timestamp: event.timestamp.to_i,
70
- platform: "ruby",
71
- language: "ruby",
68
+ platform: 'ruby',
69
+ language: 'ruby',
72
70
  framework: detect_framework,
73
71
  context: event.context[:context],
74
72
  request: event.context[:request],
@@ -77,7 +75,7 @@ module Lapsoss
77
75
  title: event.message || (event.exception&.message if event.type == :exception),
78
76
  uuid: SecureRandom.uuid,
79
77
  notifier: {
80
- name: "lapsoss",
78
+ name: 'lapsoss',
81
79
  version: Lapsoss::VERSION
82
80
  }
83
81
  }
@@ -132,25 +130,26 @@ module Lapsoss
132
130
 
133
131
  def map_level(level)
134
132
  case level
135
- when :debug then "debug"
136
- when :info then "info"
137
- when :warning, :warn then "warning"
138
- when :error then "error"
139
- when :fatal, :critical then "critical"
140
- else "error"
133
+ when :debug then 'debug'
134
+ when :info then 'info'
135
+ when :warning, :warn then 'warning'
136
+ when :error then 'error'
137
+ when :fatal, :critical then 'critical'
138
+ else 'error'
141
139
  end
142
140
  end
143
141
 
144
142
  def detect_framework
145
- return "rails" if defined?(Rails)
146
- return "sinatra" if defined?(Sinatra)
147
- "ruby"
143
+ return 'rails' if defined?(Rails)
144
+ return 'sinatra' if defined?(Sinatra)
145
+
146
+ 'ruby'
148
147
  end
149
148
 
150
149
  def validate_settings!
151
- validate_presence!(@access_token, "Rollbar access token")
152
- validate_api_key!(@access_token, "Rollbar access token", format: :alphanumeric) if @access_token
153
- validate_environment!(@environment, "Rollbar environment") if @environment
150
+ validate_presence!(@access_token, 'Rollbar access token')
151
+ validate_api_key!(@access_token, 'Rollbar access token', format: :alphanumeric) if @access_token
152
+ validate_environment!(@environment, 'Rollbar environment') if @environment
154
153
  end
155
154
  end
156
155
  end
@@ -1,19 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "securerandom"
4
- require_relative "../http_client"
5
- require_relative "../backtrace_processor"
3
+ require 'securerandom'
6
4
 
7
5
  module Lapsoss
8
6
  module Adapters
9
7
  class SentryAdapter < Base
10
8
  PROTOCOL_VERSION = 7
11
- CONTENT_TYPE = "application/x-sentry-envelope"
9
+ CONTENT_TYPE = 'application/x-sentry-envelope'
12
10
  GZIP_THRESHOLD = 1024 * 30 # 30KB
13
- USER_AGENT = "lapsoss/#{Lapsoss::VERSION}"
11
+ USER_AGENT = "lapsoss/#{Lapsoss::VERSION}".freeze
14
12
 
15
13
  def initialize(name, settings = {})
16
- super(name, settings)
14
+ super
17
15
  validate_settings!
18
16
  return unless settings[:dsn]
19
17
 
@@ -62,11 +60,11 @@ module Lapsoss
62
60
  header = {
63
61
  event_id: event.context[:event_id] || SecureRandom.uuid,
64
62
  sent_at: Time.now.iso8601,
65
- sdk: { name: "lapsoss", version: Lapsoss::VERSION }
63
+ sdk: { name: 'lapsoss', version: Lapsoss::VERSION }
66
64
  }
67
65
 
68
- item_type = event.type == :transaction ? "transaction" : "event"
69
- item_header = { type: item_type, content_type: "application/json" }
66
+ item_type = event.type == :transaction ? 'transaction' : 'event'
67
+ item_header = { type: item_type, content_type: 'application/json' }
70
68
  item_payload = build_event_payload(event)
71
69
 
72
70
  [header, item_header, item_payload]
@@ -90,7 +88,7 @@ module Lapsoss
90
88
 
91
89
  def build_event_payload(event)
92
90
  {
93
- platform: "ruby",
91
+ platform: 'ruby',
94
92
  level: map_level(event.level),
95
93
  timestamp: event.timestamp.to_f,
96
94
  environment: @settings[:environment],
@@ -123,9 +121,9 @@ module Lapsoss
123
121
 
124
122
  def build_headers(compressed)
125
123
  {
126
- "Content-Type" => CONTENT_TYPE,
127
- "X-Sentry-Auth" => auth_header,
128
- "Content-Encoding" => ("gzip" if compressed)
124
+ 'Content-Type' => CONTENT_TYPE,
125
+ 'X-Sentry-Auth' => auth_header,
126
+ 'Content-Encoding' => ('gzip' if compressed)
129
127
  }.compact
130
128
  end
131
129
 
@@ -144,16 +142,14 @@ module Lapsoss
144
142
  # - Custom service: https://public_key@custom.com/api/v1/errors -> /api/v1/errors
145
143
 
146
144
  # Extract project ID for auth header (usually the last path segment)
147
- path_parts = uri.path.split("/").reject(&:empty?)
148
- project_id = path_parts.last || "unknown"
145
+ path_parts = uri.path.split('/').reject(&:empty?)
146
+ project_id = path_parts.last || 'unknown'
149
147
 
150
148
  # Use the DSN path directly - this is what the service expects
151
149
  api_path = uri.path
152
150
 
153
151
  # For standard Sentry DSNs (just /project_id), build the envelope path
154
- if path_parts.length == 1 && project_id.match?(/^\d+$/)
155
- api_path = "/api/#{project_id}/envelope/"
156
- end
152
+ api_path = "/api/#{project_id}/envelope/" if path_parts.length == 1 && project_id.match?(/^\d+$/)
157
153
 
158
154
  {
159
155
  public_key: uri.user,
@@ -176,21 +172,19 @@ module Lapsoss
176
172
 
177
173
  def map_level(level)
178
174
  case level
179
- when :debug then "debug"
180
- when :info then "info"
181
- when :warn, :warning then "warning"
182
- when :error then "error"
183
- when :fatal then "fatal"
184
- else "info"
175
+ when :debug then 'debug'
176
+ when :info then 'info'
177
+ when :warn, :warning then 'warning'
178
+ when :error then 'error'
179
+ when :fatal then 'fatal'
180
+ else 'info'
185
181
  end
186
182
  end
187
183
 
188
184
  def validate_settings!
189
- if @settings[:dsn]
190
- validate_dsn!(@settings[:dsn], "Sentry DSN")
191
- else
192
- raise ValidationError, "Sentry DSN is required"
193
- end
185
+ raise ValidationError, 'Sentry DSN is required' unless @settings[:dsn]
186
+
187
+ validate_dsn!(@settings[:dsn], 'Sentry DSN')
194
188
  end
195
189
  end
196
190
  end
@@ -53,12 +53,12 @@ module Lapsoss
53
53
  '/System/Library/Frameworks'
54
54
  ].freeze
55
55
 
56
- attr_reader :filename, :line_number, :method_name, :in_app, :raw_line
57
- attr_reader :function, :module_name, :code_context, :block_info
56
+ attr_reader :filename, :line_number, :method_name, :in_app, :raw_line, :function, :module_name, :code_context,
57
+ :block_info
58
58
 
59
59
  # Backward compatibility aliases
60
- alias_method :lineno, :line_number
61
- alias_method :raw, :raw_line
60
+ alias lineno line_number
61
+ alias raw raw_line
62
62
 
63
63
  def initialize(raw_line, in_app_patterns: [], exclude_patterns: [], load_paths: [])
64
64
  @raw_line = raw_line.to_s.strip
@@ -123,7 +123,7 @@ module Lapsoss
123
123
  # Try to make path relative to load paths
124
124
  @load_paths.each do |load_path|
125
125
  if @filename.start_with?(load_path)
126
- relative = @filename.sub(/^#{Regexp.escape(load_path)}\/?/, '')
126
+ relative = @filename.sub(%r{^#{Regexp.escape(load_path)}/?}, '')
127
127
  return relative unless relative.empty?
128
128
  end
129
129
  end
@@ -134,23 +134,21 @@ module Lapsoss
134
134
  private
135
135
 
136
136
  def parse_backtrace_line
137
- BACKTRACE_PATTERNS.each_with_index do |pattern, pattern_index|
137
+ BACKTRACE_PATTERNS.each_with_index do |pattern, _pattern_index|
138
138
  match = @raw_line.match(pattern)
139
139
  next unless match
140
140
 
141
141
  @filename = match[:filename]
142
142
  # Handle malformed line numbers - convert invalid numbers to 0
143
- if match.names.include?('line') && match[:line]
144
- @line_number = match[:line].match?(/^\d+$/) ? match[:line].to_i : 0
145
- else
146
- @line_number = nil
147
- end
143
+ @line_number = if match.names.include?('line') && match[:line]
144
+ match[:line].match?(/^\d+$/) ? match[:line].to_i : 0
145
+ end
148
146
  @method_name = match.names.include?('method') ? match[:method] : nil
149
147
  @native_gem = match.names.include?('native_gem') ? match[:native_gem] : nil
150
148
  @block_level = match.names.include?('block_level') ? match[:block_level] : nil
151
149
 
152
150
  # Set default method name for lines without methods (top-level execution)
153
- @method_name = "<main>" if @method_name.nil?
151
+ @method_name = '<main>' if @method_name.nil?
154
152
 
155
153
  process_method_info
156
154
  return
@@ -159,7 +157,7 @@ module Lapsoss
159
157
  # Fallback: treat entire line as filename if no pattern matches
160
158
  @filename = @raw_line
161
159
  @line_number = nil
162
- @method_name = "<main>"
160
+ @method_name = '<main>'
163
161
  end
164
162
 
165
163
  def process_method_info
@@ -199,9 +197,7 @@ module Lapsoss
199
197
  }
200
198
 
201
199
  # Extract the method that contains the block
202
- if @method_name.match(/block (?:\([^)]+\)\s+)?in (.+)/)
203
- @block_info[:in_method] = $1
204
- end
200
+ @block_info[:in_method] = ::Regexp.last_match(1) if @method_name =~ /block (?:\([^)]+\)\s+)?in (.+)/
205
201
  end
206
202
 
207
203
  def determine_app_status
@@ -223,7 +219,7 @@ module Lapsoss
223
219
  end
224
220
 
225
221
  # Default heuristics: check for library indicators
226
- @in_app = !LIBRARY_INDICATORS.any? { |indicator| @filename.include?(indicator) }
222
+ @in_app = LIBRARY_INDICATORS.none? { |indicator| @filename.include?(indicator) }
227
223
 
228
224
  # Special cases
229
225
  @in_app = false if @native_gem # Native extensions are not app code
@@ -234,25 +230,19 @@ module Lapsoss
234
230
  return unless @filename
235
231
 
236
232
  # Expand relative paths
237
- if @filename.start_with?('./')
238
- @filename = File.expand_path(@filename)
239
- end
233
+ @filename = File.expand_path(@filename) if @filename.start_with?('./')
240
234
 
241
235
  # Handle Windows paths on Unix systems (for cross-platform stack traces)
242
- if @filename.include?('\\') && !@filename.include?('/')
243
- @filename = @filename.tr('\\', '/')
244
- end
236
+ @filename = @filename.tr('\\', '/') if @filename.include?('\\') && @filename.exclude?('/')
245
237
 
246
238
  # Strip load paths to make traces more readable
247
- if @load_paths.any?
248
- original = @filename
249
- @filename = relative_filename
239
+ return unless @load_paths.any?
250
240
 
251
- # Keep absolute path if relative didn't work well
252
- if @filename.empty? || @filename == '.'
253
- @filename = original
254
- end
255
- end
241
+ original = @filename
242
+ @filename = relative_filename
243
+
244
+ # Keep absolute path if relative didn't work well
245
+ @filename = original if @filename.empty? || @filename == '.'
256
246
  end
257
247
  end
258
248
  end