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 +4 -4
- data/lib/lapsoss/adapters/appsignal_adapter.rb +25 -26
- data/lib/lapsoss/adapters/base.rb +2 -2
- data/lib/lapsoss/adapters/bugsnag_adapter.rb +12 -0
- data/lib/lapsoss/adapters/insight_hub_adapter.rb +29 -32
- data/lib/lapsoss/adapters/logger_adapter.rb +8 -8
- data/lib/lapsoss/adapters/rollbar_adapter.rb +27 -28
- data/lib/lapsoss/adapters/sentry_adapter.rb +23 -29
- data/lib/lapsoss/backtrace_frame.rb +21 -31
- data/lib/lapsoss/backtrace_processor.rb +28 -31
- data/lib/lapsoss/client.rb +4 -6
- data/lib/lapsoss/configuration.rb +48 -41
- data/lib/lapsoss/current.rb +1 -1
- data/lib/lapsoss/event.rb +6 -5
- data/lib/lapsoss/{exclusions.rb → exclusion_filter.rb} +56 -56
- data/lib/lapsoss/fingerprinter.rb +33 -37
- data/lib/lapsoss/http_client.rb +45 -11
- data/lib/lapsoss/middleware.rb +35 -43
- data/lib/lapsoss/pipeline.rb +6 -10
- data/lib/lapsoss/railtie.rb +9 -9
- data/lib/lapsoss/registry.rb +23 -22
- data/lib/lapsoss/release_tracker.rb +160 -182
- data/lib/lapsoss/router.rb +3 -5
- data/lib/lapsoss/sampling.rb +49 -53
- data/lib/lapsoss/scope.rb +6 -10
- data/lib/lapsoss/scrubber.rb +22 -24
- data/lib/lapsoss/user_context.rb +78 -85
- data/lib/lapsoss/validators.rb +13 -26
- data/lib/lapsoss/version.rb +1 -1
- data/lib/lapsoss.rb +12 -25
- metadata +59 -23
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'digest'
|
4
4
|
|
5
5
|
module Lapsoss
|
6
6
|
class Fingerprinter
|
@@ -8,61 +8,61 @@ module Lapsoss
|
|
8
8
|
# User/ID normalization patterns
|
9
9
|
{
|
10
10
|
pattern: /User \d+ (not found|invalid|missing)/i,
|
11
|
-
fingerprint:
|
11
|
+
fingerprint: 'user-lookup-error'
|
12
12
|
},
|
13
13
|
{
|
14
14
|
pattern: /Record \d+ (not found|invalid|missing)/i,
|
15
|
-
fingerprint:
|
15
|
+
fingerprint: 'record-lookup-error'
|
16
16
|
},
|
17
17
|
|
18
18
|
# URL/Path normalization patterns
|
19
19
|
{
|
20
20
|
pattern: %r{/users/\d+(/.*)?},
|
21
|
-
fingerprint:
|
21
|
+
fingerprint: 'users-id-endpoint'
|
22
22
|
},
|
23
23
|
{
|
24
24
|
pattern: %r{/api/v\d+/.*},
|
25
|
-
fingerprint:
|
25
|
+
fingerprint: 'api-endpoint'
|
26
26
|
},
|
27
27
|
|
28
28
|
# Database error patterns
|
29
29
|
{
|
30
30
|
pattern: /PG::ConnectionBad|Mysql2::Error|SQLite3::BusyException/,
|
31
|
-
fingerprint:
|
31
|
+
fingerprint: 'database-connection-error'
|
32
32
|
},
|
33
33
|
{
|
34
34
|
pattern: /ActiveRecord::RecordNotFound/,
|
35
|
-
fingerprint:
|
35
|
+
fingerprint: 'record-not-found'
|
36
36
|
},
|
37
37
|
{
|
38
38
|
pattern: /ActiveRecord::StatementInvalid.*timeout/i,
|
39
|
-
fingerprint:
|
39
|
+
fingerprint: 'database-timeout'
|
40
40
|
},
|
41
41
|
|
42
42
|
# Network error patterns
|
43
43
|
{
|
44
44
|
pattern: /Net::(TimeoutError|ReadTimeout|OpenTimeout)/,
|
45
|
-
fingerprint:
|
45
|
+
fingerprint: 'network-timeout'
|
46
46
|
},
|
47
47
|
{
|
48
48
|
pattern: /Errno::(ECONNREFUSED|ECONNRESET|EHOSTUNREACH)/,
|
49
|
-
fingerprint:
|
49
|
+
fingerprint: 'network-connection-error'
|
50
50
|
},
|
51
51
|
|
52
52
|
# File system patterns
|
53
53
|
{
|
54
|
-
pattern:
|
55
|
-
fingerprint:
|
54
|
+
pattern: %r{Errno::(ENOENT|EACCES).*/tmp/},
|
55
|
+
fingerprint: 'tmp-file-error'
|
56
56
|
},
|
57
57
|
{
|
58
58
|
pattern: /No such file or directory.*\.log/,
|
59
|
-
fingerprint:
|
59
|
+
fingerprint: 'log-file-missing'
|
60
60
|
},
|
61
61
|
|
62
62
|
# Memory/Resource patterns
|
63
63
|
{
|
64
64
|
pattern: /NoMemoryError|SystemStackError/,
|
65
|
-
fingerprint:
|
65
|
+
fingerprint: 'memory-resource-error'
|
66
66
|
}
|
67
67
|
].freeze
|
68
68
|
|
@@ -121,11 +121,9 @@ module Lapsoss
|
|
121
121
|
parts << event.message if event.message
|
122
122
|
|
123
123
|
# Include first few backtrace lines for context
|
124
|
-
if event.exception&.backtrace
|
125
|
-
parts.concat(event.exception.backtrace.first(3))
|
126
|
-
end
|
124
|
+
parts.concat(event.exception.backtrace.first(3)) if event.exception&.backtrace
|
127
125
|
|
128
|
-
parts.compact.join(
|
126
|
+
parts.compact.join(' ')
|
129
127
|
end
|
130
128
|
|
131
129
|
def generate_default_fingerprint(event)
|
@@ -143,17 +141,15 @@ module Lapsoss
|
|
143
141
|
primary_location = extract_primary_location(event.exception.backtrace)
|
144
142
|
components << primary_location if primary_location
|
145
143
|
elsif event.message
|
146
|
-
components <<
|
144
|
+
components << 'message'
|
147
145
|
components << normalize_message(event.message)
|
148
146
|
end
|
149
147
|
|
150
148
|
# Include environment if configured
|
151
|
-
if @include_environment && event.environment
|
152
|
-
components << event.environment
|
153
|
-
end
|
149
|
+
components << event.environment if @include_environment && event.environment
|
154
150
|
|
155
151
|
# Generate hash from components
|
156
|
-
content = components.compact.join(
|
152
|
+
content = components.compact.join('|')
|
157
153
|
Digest::SHA256.hexdigest(content)[0, 16] # Use first 16 chars for readability
|
158
154
|
end
|
159
155
|
|
@@ -164,29 +160,29 @@ module Lapsoss
|
|
164
160
|
|
165
161
|
if @normalize_ids
|
166
162
|
# Replace UUIDs first (before numeric IDs)
|
167
|
-
normalized.gsub!(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i,
|
163
|
+
normalized.gsub!(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i, ':uuid')
|
168
164
|
|
169
165
|
# Replace hex hashes with placeholder
|
170
|
-
normalized.gsub!(/\b[0-9a-f]{32,}\b/i,
|
166
|
+
normalized.gsub!(/\b[0-9a-f]{32,}\b/i, ':hash')
|
171
167
|
|
172
168
|
# Replace numeric IDs with placeholder (after UUIDs and hashes)
|
173
|
-
normalized.gsub!(/\b\d{3,}\b/,
|
169
|
+
normalized.gsub!(/\b\d{3,}\b/, ':id')
|
174
170
|
end
|
175
171
|
|
176
172
|
if @normalize_paths
|
177
173
|
# Replace absolute file paths with placeholder
|
178
|
-
normalized.gsub!(%r{/[^/\s]+(?:/[^/\s]+)*\.[a-zA-Z0-9]+},
|
179
|
-
normalized.gsub!(%r{/[^/\s]+(?:/[^/\s]+)+(?:/)?},
|
174
|
+
normalized.gsub!(%r{/[^/\s]+(?:/[^/\s]+)*\.[a-zA-Z0-9]+}, ':filepath')
|
175
|
+
normalized.gsub!(%r{/[^/\s]+(?:/[^/\s]+)+(?:/)?}, ':dirpath')
|
180
176
|
|
181
177
|
# Replace timestamps
|
182
|
-
normalized.gsub!(/\b\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}/,
|
178
|
+
normalized.gsub!(/\b\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}/, ':timestamp')
|
183
179
|
|
184
180
|
# Replace URLs with placeholder
|
185
|
-
normalized.gsub!(%r{https?://[^\s]+},
|
181
|
+
normalized.gsub!(%r{https?://[^\s]+}, ':url')
|
186
182
|
end
|
187
183
|
|
188
184
|
# Clean up extra whitespace
|
189
|
-
normalized.strip.squeeze(
|
185
|
+
normalized.strip.squeeze(' ')
|
190
186
|
end
|
191
187
|
|
192
188
|
def extract_primary_location(backtrace)
|
@@ -194,18 +190,18 @@ module Lapsoss
|
|
194
190
|
|
195
191
|
# Find first non-gem, non-framework line
|
196
192
|
app_line = backtrace.find do |line|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
193
|
+
line.exclude?('/gems/') &&
|
194
|
+
line.exclude?('/ruby/') &&
|
195
|
+
line.exclude?('(eval)') &&
|
196
|
+
!line.start_with?('[')
|
201
197
|
end
|
202
198
|
|
203
199
|
line_to_use = app_line || backtrace.first
|
204
200
|
|
205
201
|
if @normalize_paths
|
206
202
|
# Extract just filename:line_number
|
207
|
-
if line_to_use
|
208
|
-
"#{
|
203
|
+
if line_to_use =~ %r{([^/]+):(\d+)}
|
204
|
+
"#{::Regexp.last_match(1)}:#{::Regexp.last_match(2)}"
|
209
205
|
else
|
210
206
|
line_to_use
|
211
207
|
end
|
data/lib/lapsoss/http_client.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
3
|
+
require 'faraday'
|
4
|
+
require 'faraday/retry'
|
5
|
+
require 'json'
|
6
|
+
require 'zlib'
|
7
7
|
|
8
8
|
module Lapsoss
|
9
9
|
# HTTP client wrapper using Faraday with retry logic
|
10
10
|
class HttpClient
|
11
|
-
USER_AGENT = "lapsoss/#{Lapsoss::VERSION}"
|
11
|
+
USER_AGENT = "lapsoss/#{Lapsoss::VERSION}".freeze
|
12
12
|
|
13
13
|
def initialize(base_url, config = {})
|
14
14
|
@base_url = base_url
|
@@ -53,18 +53,52 @@ module Lapsoss
|
|
53
53
|
conn.options.open_timeout = @config[:timeout] || 5
|
54
54
|
|
55
55
|
# Configure SSL
|
56
|
-
if @config.key?(:ssl_verify)
|
57
|
-
conn.ssl.verify = @config[:ssl_verify]
|
58
|
-
end
|
56
|
+
conn.ssl.verify = @config[:ssl_verify] if @config.key?(:ssl_verify)
|
59
57
|
|
60
58
|
# Set user agent
|
61
|
-
conn.headers[
|
59
|
+
conn.headers['User-Agent'] = USER_AGENT
|
62
60
|
|
63
|
-
#
|
64
|
-
conn.adapter
|
61
|
+
# Auto-detect and use appropriate adapter
|
62
|
+
conn.adapter detect_optimal_adapter
|
65
63
|
end
|
66
64
|
end
|
67
65
|
|
66
|
+
def detect_optimal_adapter
|
67
|
+
if fiber_scheduler_active? && async_adapter_available? && !force_sync_mode?
|
68
|
+
log_adapter_selection(:async)
|
69
|
+
:async_http
|
70
|
+
else
|
71
|
+
log_adapter_selection(:sync)
|
72
|
+
Faraday.default_adapter
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def fiber_scheduler_active?
|
77
|
+
Fiber.current_scheduler != nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def async_adapter_available?
|
81
|
+
require 'async/http/faraday'
|
82
|
+
true
|
83
|
+
rescue LoadError
|
84
|
+
false
|
85
|
+
end
|
86
|
+
|
87
|
+
def force_sync_mode?
|
88
|
+
Lapsoss.configuration.force_sync_http
|
89
|
+
end
|
90
|
+
|
91
|
+
def log_adapter_selection(adapter_type)
|
92
|
+
return unless Lapsoss.configuration.debug?
|
93
|
+
|
94
|
+
Lapsoss.configuration.logger&.debug(
|
95
|
+
"[Lapsoss::HttpClient] Using #{adapter_type} HTTP adapter " \
|
96
|
+
"(fiber_scheduler: #{fiber_scheduler_active?}, " \
|
97
|
+
"async_available: #{async_adapter_available?}, " \
|
98
|
+
"force_sync: #{force_sync_mode?})"
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
68
102
|
def retry_options
|
69
103
|
{
|
70
104
|
max: @config[:max_retries] || 3,
|
data/lib/lapsoss/middleware.rb
CHANGED
@@ -22,14 +22,10 @@ module Lapsoss
|
|
22
22
|
|
23
23
|
def call(event, hint = {})
|
24
24
|
# Apply custom sampling logic first
|
25
|
-
if @sample_callback
|
26
|
-
return nil unless @sample_callback.call(event, hint)
|
27
|
-
end
|
25
|
+
return nil if @sample_callback && !@sample_callback.call(event, hint)
|
28
26
|
|
29
27
|
# Apply rate-based sampling
|
30
|
-
if @sample_rate < 1.0
|
31
|
-
return nil if rand > @sample_rate
|
32
|
-
end
|
28
|
+
return nil if (@sample_rate < 1.0) && (rand > @sample_rate)
|
33
29
|
|
34
30
|
@app.call(event, hint)
|
35
31
|
end
|
@@ -44,6 +40,7 @@ module Lapsoss
|
|
44
40
|
|
45
41
|
def call(event, hint = {})
|
46
42
|
return nil if should_exclude?(event)
|
43
|
+
|
47
44
|
@app.call(event, hint)
|
48
45
|
end
|
49
46
|
|
@@ -95,22 +92,18 @@ module Lapsoss
|
|
95
92
|
enhanced_user = existing_user.merge(user_data)
|
96
93
|
|
97
94
|
# Apply privacy filtering if enabled
|
98
|
-
if @privacy_mode
|
99
|
-
enhanced_user = apply_privacy_filtering(enhanced_user)
|
100
|
-
end
|
95
|
+
enhanced_user = apply_privacy_filtering(enhanced_user) if @privacy_mode
|
101
96
|
|
102
97
|
event.context[:user] = enhanced_user unless enhanced_user.empty?
|
103
98
|
end
|
104
99
|
|
105
100
|
def apply_privacy_filtering(user_data)
|
106
101
|
# Remove sensitive fields in privacy mode
|
107
|
-
sensitive_fields = [
|
102
|
+
sensitive_fields = %i[email phone address ssn credit_card]
|
108
103
|
filtered = user_data.dup
|
109
104
|
|
110
105
|
sensitive_fields.each do |field|
|
111
|
-
if filtered.key?(field)
|
112
|
-
filtered[field] = "[FILTERED]"
|
113
|
-
end
|
106
|
+
filtered[field] = '[FILTERED]' if filtered.key?(field)
|
114
107
|
end
|
115
108
|
|
116
109
|
filtered
|
@@ -133,9 +126,7 @@ module Lapsoss
|
|
133
126
|
def add_release_info(event, hint)
|
134
127
|
release_info = @release_provider&.call(event, hint) || auto_detect_release
|
135
128
|
|
136
|
-
if release_info
|
137
|
-
event.context[:release] = release_info
|
138
|
-
end
|
129
|
+
event.context[:release] = release_info if release_info
|
139
130
|
end
|
140
131
|
|
141
132
|
def auto_detect_release
|
@@ -155,7 +146,7 @@ module Lapsoss
|
|
155
146
|
end
|
156
147
|
|
157
148
|
def detect_git_info
|
158
|
-
return nil unless File.exist?(
|
149
|
+
return nil unless File.exist?('.git')
|
159
150
|
|
160
151
|
begin
|
161
152
|
# Get current commit SHA
|
@@ -164,11 +155,11 @@ module Lapsoss
|
|
164
155
|
|
165
156
|
# Get branch name
|
166
157
|
branch = `git rev-parse --abbrev-ref HEAD`.strip
|
167
|
-
branch = nil if branch.empty? || branch ==
|
158
|
+
branch = nil if branch.empty? || branch == 'HEAD'
|
168
159
|
|
169
160
|
# Get commit timestamp
|
170
161
|
commit_time = `git log -1 --format=%ct`.strip
|
171
|
-
commit_timestamp = commit_time.empty? ? nil : Time.at(commit_time.to_i)
|
162
|
+
commit_timestamp = commit_time.empty? ? nil : Time.zone.at(commit_time.to_i)
|
172
163
|
|
173
164
|
# Get tag if on a tag
|
174
165
|
tag = `git describe --exact-match --tags HEAD 2>/dev/null`.strip
|
@@ -189,43 +180,43 @@ module Lapsoss
|
|
189
180
|
info = {}
|
190
181
|
|
191
182
|
# Check common deployment environment variables
|
192
|
-
info[:deployment_id] = ENV[
|
193
|
-
info[:build_number] = ENV[
|
194
|
-
info[:deployment_time] = parse_deployment_time(ENV[
|
183
|
+
info[:deployment_id] = ENV['DEPLOYMENT_ID'] if ENV['DEPLOYMENT_ID']
|
184
|
+
info[:build_number] = ENV['BUILD_NUMBER'] if ENV['BUILD_NUMBER']
|
185
|
+
info[:deployment_time] = parse_deployment_time(ENV['DEPLOYMENT_TIME']) if ENV['DEPLOYMENT_TIME']
|
195
186
|
|
196
187
|
# Check Heroku
|
197
|
-
if ENV[
|
198
|
-
info[:platform] =
|
199
|
-
info[:app_name] = ENV[
|
200
|
-
info[:dyno] = ENV
|
201
|
-
info[:slug_commit] = ENV
|
188
|
+
if ENV['HEROKU_APP_NAME']
|
189
|
+
info[:platform] = 'heroku'
|
190
|
+
info[:app_name] = ENV['HEROKU_APP_NAME']
|
191
|
+
info[:dyno] = ENV.fetch('DYNO', nil)
|
192
|
+
info[:slug_commit] = ENV.fetch('HEROKU_SLUG_COMMIT', nil)
|
202
193
|
end
|
203
194
|
|
204
195
|
# Check AWS
|
205
|
-
if ENV[
|
206
|
-
info[:platform] =
|
207
|
-
info[:execution_env] = ENV[
|
208
|
-
info[:region] = ENV
|
196
|
+
if ENV['AWS_EXECUTION_ENV']
|
197
|
+
info[:platform] = 'aws'
|
198
|
+
info[:execution_env] = ENV['AWS_EXECUTION_ENV']
|
199
|
+
info[:region] = ENV.fetch('AWS_REGION', nil)
|
209
200
|
end
|
210
201
|
|
211
202
|
# Check Docker
|
212
|
-
if ENV[
|
213
|
-
info[:platform] =
|
214
|
-
info[:container_id] = ENV[
|
203
|
+
if ENV['DOCKER_CONTAINER_ID'] || File.exist?('/.dockerenv')
|
204
|
+
info[:platform] = 'docker'
|
205
|
+
info[:container_id] = ENV['DOCKER_CONTAINER_ID']
|
215
206
|
end
|
216
207
|
|
217
208
|
# Check Kubernetes
|
218
|
-
if ENV[
|
219
|
-
info[:platform] =
|
220
|
-
info[:namespace] = ENV
|
221
|
-
info[:pod_name] = ENV
|
209
|
+
if ENV['KUBERNETES_SERVICE_HOST']
|
210
|
+
info[:platform] = 'kubernetes'
|
211
|
+
info[:namespace] = ENV.fetch('KUBERNETES_NAMESPACE', nil)
|
212
|
+
info[:pod_name] = ENV.fetch('HOSTNAME', nil)
|
222
213
|
end
|
223
214
|
|
224
215
|
info.empty? ? nil : info
|
225
216
|
end
|
226
217
|
|
227
218
|
def parse_deployment_time(time_str)
|
228
|
-
Time.parse(time_str)
|
219
|
+
Time.zone.parse(time_str)
|
229
220
|
rescue StandardError
|
230
221
|
nil
|
231
222
|
end
|
@@ -253,6 +244,7 @@ module Lapsoss
|
|
253
244
|
|
254
245
|
def call(event, hint = {})
|
255
246
|
return nil unless @condition.call(event, hint)
|
247
|
+
|
256
248
|
@app.call(event, hint)
|
257
249
|
end
|
258
250
|
end
|
@@ -266,6 +258,7 @@ module Lapsoss
|
|
266
258
|
def call(event, hint = {})
|
267
259
|
transformed_event = @transformer.call(event, hint)
|
268
260
|
return nil unless transformed_event
|
261
|
+
|
269
262
|
@app.call(transformed_event, hint)
|
270
263
|
end
|
271
264
|
end
|
@@ -281,14 +274,12 @@ module Lapsoss
|
|
281
274
|
|
282
275
|
def call(event, hint = {})
|
283
276
|
@mutex.synchronize do
|
284
|
-
now = Time.now
|
277
|
+
now = Time.zone.now
|
285
278
|
# Remove old events outside time window
|
286
279
|
@events.reject! { |timestamp| now - timestamp > @time_window }
|
287
280
|
|
288
281
|
# Check if we're over the limit
|
289
|
-
if @events.length >= @max_events
|
290
|
-
return nil
|
291
|
-
end
|
282
|
+
return nil if @events.length >= @max_events
|
292
283
|
|
293
284
|
# Add current event
|
294
285
|
@events << now
|
@@ -346,6 +337,7 @@ module Lapsoss
|
|
346
337
|
|
347
338
|
def call(event, hint = {})
|
348
339
|
return nil unless @sampler.sample?(event, hint)
|
340
|
+
|
349
341
|
@app.call(event, hint)
|
350
342
|
end
|
351
343
|
end
|
data/lib/lapsoss/pipeline.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative 'middleware'
|
4
4
|
|
5
5
|
module Lapsoss
|
6
6
|
class Pipeline
|
@@ -46,7 +46,7 @@ module Lapsoss
|
|
46
46
|
|
47
47
|
def build_chain
|
48
48
|
# The final app just returns the event
|
49
|
-
final_app =
|
49
|
+
final_app = ->(event, _hint) { event }
|
50
50
|
|
51
51
|
# Build middleware chain from right to left
|
52
52
|
@middlewares.reverse.reduce(final_app) do |app, middleware_config|
|
@@ -115,17 +115,13 @@ module Lapsoss
|
|
115
115
|
self
|
116
116
|
end
|
117
117
|
|
118
|
-
def use_middleware(middleware_class,
|
119
|
-
@pipeline.use(middleware_class,
|
118
|
+
def use_middleware(middleware_class, *, **)
|
119
|
+
@pipeline.use(middleware_class, *, **)
|
120
120
|
self
|
121
121
|
end
|
122
122
|
|
123
|
-
|
124
|
-
@pipeline.build
|
125
|
-
end
|
123
|
+
delegate :build, to: :@pipeline
|
126
124
|
|
127
|
-
|
128
|
-
@pipeline
|
129
|
-
end
|
125
|
+
attr_reader :pipeline
|
130
126
|
end
|
131
127
|
end
|
data/lib/lapsoss/railtie.rb
CHANGED
@@ -4,7 +4,7 @@ module Lapsoss
|
|
4
4
|
class Railtie < Rails::Railtie
|
5
5
|
config.lapsoss = ActiveSupport::OrderedOptions.new
|
6
6
|
|
7
|
-
initializer
|
7
|
+
initializer 'lapsoss.configure' do |_app|
|
8
8
|
Lapsoss.configure do |config|
|
9
9
|
config.environment ||= Rails.env
|
10
10
|
config.logger ||= Rails.logger
|
@@ -18,13 +18,13 @@ module Lapsoss
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
initializer
|
22
|
-
|
23
|
-
app.executor.error_reporter.subscribe(Lapsoss::RailsErrorSubscriber.new)
|
24
|
-
end
|
21
|
+
initializer 'lapsoss.add_middleware', after: 'lapsoss.configure' do |app|
|
22
|
+
app.middleware.insert_before ActionDispatch::ShowExceptions, Lapsoss::Middleware
|
25
23
|
end
|
26
24
|
|
27
|
-
|
25
|
+
initializer 'lapsoss.rails_error_subscriber', after: 'lapsoss.add_middleware' do |app|
|
26
|
+
app.executor.error_reporter.subscribe(Lapsoss::RailsErrorSubscriber.new)
|
27
|
+
end
|
28
28
|
|
29
29
|
rake_tasks do
|
30
30
|
# Add any Lapsoss-specific rake tasks here
|
@@ -43,7 +43,7 @@ module Lapsoss
|
|
43
43
|
level: level,
|
44
44
|
tags: {
|
45
45
|
handled: handled,
|
46
|
-
source: source ||
|
46
|
+
source: source || 'rails'
|
47
47
|
},
|
48
48
|
context: context
|
49
49
|
)
|
@@ -53,8 +53,8 @@ module Lapsoss
|
|
53
53
|
|
54
54
|
def skip_error?(error, source)
|
55
55
|
# Skip cache-related Redis errors if configured to do so
|
56
|
-
if Lapsoss.configuration.skip_rails_cache_errors
|
57
|
-
return true
|
56
|
+
if Lapsoss.configuration.skip_rails_cache_errors && source&.include?('cache') && error.is_a?(Redis::CannotConnectError)
|
57
|
+
return true
|
58
58
|
end
|
59
59
|
|
60
60
|
false
|
data/lib/lapsoss/registry.rb
CHANGED
@@ -1,13 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
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"
|
3
|
+
require 'singleton'
|
4
|
+
require 'concurrent'
|
11
5
|
|
12
6
|
module Lapsoss
|
13
7
|
class Registry
|
@@ -38,12 +32,24 @@ 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
|
+
name = if adapter.respond_to?(:name) && adapter.name
|
40
|
+
adapter.name.to_sym
|
41
|
+
else
|
42
|
+
adapter.class.name.split('::').last.to_sym
|
43
|
+
end
|
44
|
+
@adapters[name] = adapter
|
45
|
+
end
|
46
|
+
|
41
47
|
# Unregister an adapter
|
42
48
|
#
|
43
49
|
# @param name [Symbol] The adapter name to remove
|
44
50
|
def unregister(name)
|
45
51
|
adapter = @adapters.delete(name.to_sym)
|
46
|
-
adapter&.shutdown if adapter
|
52
|
+
adapter&.shutdown if adapter.respond_to?(:shutdown)
|
47
53
|
adapter
|
48
54
|
end
|
49
55
|
|
@@ -79,7 +85,7 @@ module Lapsoss
|
|
79
85
|
|
80
86
|
# Clear all adapters
|
81
87
|
def clear!
|
82
|
-
@adapters.
|
88
|
+
@adapters.each_value do |adapter|
|
83
89
|
adapter.shutdown if adapter.respond_to?(:shutdown)
|
84
90
|
end
|
85
91
|
@adapters.clear
|
@@ -96,19 +102,14 @@ module Lapsoss
|
|
96
102
|
|
97
103
|
# Resolve adapter type to class
|
98
104
|
def resolve_adapter_class(type)
|
99
|
-
|
100
|
-
|
105
|
+
# Try to get the class by convention: Adapters::{Type}Adapter
|
106
|
+
class_name = "#{type.to_s.split('_').map(&:capitalize).join}Adapter"
|
101
107
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
appsignal: Adapters::AppsignalAdapter,
|
108
|
-
rollbar: Adapters::RollbarAdapter,
|
109
|
-
insight_hub: Adapters::InsightHubAdapter,
|
110
|
-
bugsnag: Adapters::InsightHubAdapter # Backwards compatibility
|
111
|
-
}
|
108
|
+
begin
|
109
|
+
Adapters.const_get(class_name)
|
110
|
+
rescue NameError
|
111
|
+
raise AdapterNotFoundError, "Unknown adapter type: #{type}. Expected class: Lapsoss::Adapters::#{class_name}"
|
112
|
+
end
|
112
113
|
end
|
113
114
|
end
|
114
115
|
end
|