lapsoss 0.2.0 → 0.3.1

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +153 -733
  3. data/lib/lapsoss/adapters/appsignal_adapter.rb +22 -22
  4. data/lib/lapsoss/adapters/base.rb +0 -3
  5. data/lib/lapsoss/adapters/insight_hub_adapter.rb +108 -104
  6. data/lib/lapsoss/adapters/logger_adapter.rb +1 -1
  7. data/lib/lapsoss/adapters/rollbar_adapter.rb +108 -68
  8. data/lib/lapsoss/adapters/sentry_adapter.rb +24 -24
  9. data/lib/lapsoss/backtrace_frame.rb +37 -206
  10. data/lib/lapsoss/backtrace_frame_factory.rb +228 -0
  11. data/lib/lapsoss/backtrace_processor.rb +27 -23
  12. data/lib/lapsoss/client.rb +2 -4
  13. data/lib/lapsoss/configuration.rb +28 -32
  14. data/lib/lapsoss/current.rb +10 -2
  15. data/lib/lapsoss/event.rb +28 -5
  16. data/lib/lapsoss/exception_backtrace_frame.rb +39 -0
  17. data/lib/lapsoss/exclusion_configuration.rb +30 -0
  18. data/lib/lapsoss/exclusion_filter.rb +0 -273
  19. data/lib/lapsoss/exclusion_presets.rb +249 -0
  20. data/lib/lapsoss/fingerprinter.rb +28 -28
  21. data/lib/lapsoss/http_client.rb +8 -8
  22. data/lib/lapsoss/merged_scope.rb +63 -0
  23. data/lib/lapsoss/middleware/base.rb +15 -0
  24. data/lib/lapsoss/middleware/conditional_filter.rb +18 -0
  25. data/lib/lapsoss/middleware/event_enricher.rb +19 -0
  26. data/lib/lapsoss/middleware/event_transformer.rb +19 -0
  27. data/lib/lapsoss/middleware/exception_filter.rb +43 -0
  28. data/lib/lapsoss/middleware/metrics_collector.rb +44 -0
  29. data/lib/lapsoss/middleware/rate_limiter.rb +31 -0
  30. data/lib/lapsoss/middleware/release_tracker.rb +117 -0
  31. data/lib/lapsoss/middleware/sample_filter.rb +23 -0
  32. data/lib/lapsoss/middleware/sampling_middleware.rb +18 -0
  33. data/lib/lapsoss/middleware/user_context_enhancer.rb +46 -0
  34. data/lib/lapsoss/pipeline.rb +0 -68
  35. data/lib/lapsoss/pipeline_builder.rb +69 -0
  36. data/lib/lapsoss/rails_error_subscriber.rb +42 -0
  37. data/lib/lapsoss/rails_middleware.rb +78 -0
  38. data/lib/lapsoss/railtie.rb +22 -50
  39. data/lib/lapsoss/registry.rb +18 -5
  40. data/lib/lapsoss/release_providers.rb +110 -0
  41. data/lib/lapsoss/release_tracker.rb +159 -232
  42. data/lib/lapsoss/sampling/adaptive_sampler.rb +46 -0
  43. data/lib/lapsoss/sampling/base.rb +11 -0
  44. data/lib/lapsoss/sampling/composite_sampler.rb +26 -0
  45. data/lib/lapsoss/sampling/consistent_hash_sampler.rb +30 -0
  46. data/lib/lapsoss/sampling/exception_type_sampler.rb +44 -0
  47. data/lib/lapsoss/sampling/health_based_sampler.rb +19 -0
  48. data/lib/lapsoss/sampling/rate_limiter.rb +32 -0
  49. data/lib/lapsoss/sampling/sampling_factory.rb +69 -0
  50. data/lib/lapsoss/sampling/time_based_sampler.rb +44 -0
  51. data/lib/lapsoss/sampling/uniform_sampler.rb +15 -0
  52. data/lib/lapsoss/sampling/user_based_sampler.rb +42 -0
  53. data/lib/lapsoss/scope.rb +12 -48
  54. data/lib/lapsoss/scrubber.rb +7 -7
  55. data/lib/lapsoss/user_context.rb +30 -203
  56. data/lib/lapsoss/user_context_integrations.rb +39 -0
  57. data/lib/lapsoss/user_context_middleware.rb +50 -0
  58. data/lib/lapsoss/user_context_provider.rb +93 -0
  59. data/lib/lapsoss/utils.rb +13 -0
  60. data/lib/lapsoss/validators.rb +15 -15
  61. data/lib/lapsoss/version.rb +1 -1
  62. data/lib/lapsoss.rb +3 -3
  63. metadata +60 -7
  64. data/lib/lapsoss/middleware.rb +0 -345
  65. data/lib/lapsoss/sampling.rb +0 -328
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lapsoss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -16,6 +16,9 @@ dependencies:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
18
  version: '7.2'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9.0'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -23,20 +26,23 @@ dependencies:
23
26
  - - ">="
24
27
  - !ruby/object:Gem::Version
25
28
  version: '7.2'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9.0'
26
32
  - !ruby/object:Gem::Dependency
27
33
  name: concurrent-ruby
28
34
  requirement: !ruby/object:Gem::Requirement
29
35
  requirements:
30
- - - "~>"
36
+ - - ">="
31
37
  - !ruby/object:Gem::Version
32
- version: '1.0'
38
+ version: 1.3.1
33
39
  type: :runtime
34
40
  prerelease: false
35
41
  version_requirements: !ruby/object:Gem::Requirement
36
42
  requirements:
37
- - - "~>"
43
+ - - ">="
38
44
  - !ruby/object:Gem::Version
39
- version: '1.0'
45
+ version: 1.3.1
40
46
  - !ruby/object:Gem::Dependency
41
47
  name: faraday
42
48
  requirement: !ruby/object:Gem::Requirement
@@ -141,6 +147,20 @@ dependencies:
141
147
  - - "~>"
142
148
  - !ruby/object:Gem::Version
143
149
  version: '1.0'
150
+ - !ruby/object:Gem::Dependency
151
+ name: rubocop-rails-omakase
152
+ requirement: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - "~>"
155
+ - !ruby/object:Gem::Version
156
+ version: '1.0'
157
+ type: :development
158
+ prerelease: false
159
+ version_requirements: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - "~>"
162
+ - !ruby/object:Gem::Version
163
+ version: '1.0'
144
164
  - !ruby/object:Gem::Dependency
145
165
  name: steep
146
166
  requirement: !ruby/object:Gem::Requirement
@@ -204,24 +224,57 @@ files:
204
224
  - lib/lapsoss/adapters/rollbar_adapter.rb
205
225
  - lib/lapsoss/adapters/sentry_adapter.rb
206
226
  - lib/lapsoss/backtrace_frame.rb
227
+ - lib/lapsoss/backtrace_frame_factory.rb
207
228
  - lib/lapsoss/backtrace_processor.rb
208
229
  - lib/lapsoss/client.rb
209
230
  - lib/lapsoss/configuration.rb
210
231
  - lib/lapsoss/current.rb
211
232
  - lib/lapsoss/event.rb
233
+ - lib/lapsoss/exception_backtrace_frame.rb
234
+ - lib/lapsoss/exclusion_configuration.rb
212
235
  - lib/lapsoss/exclusion_filter.rb
236
+ - lib/lapsoss/exclusion_presets.rb
213
237
  - lib/lapsoss/fingerprinter.rb
214
238
  - lib/lapsoss/http_client.rb
215
- - lib/lapsoss/middleware.rb
239
+ - lib/lapsoss/merged_scope.rb
240
+ - lib/lapsoss/middleware/base.rb
241
+ - lib/lapsoss/middleware/conditional_filter.rb
242
+ - lib/lapsoss/middleware/event_enricher.rb
243
+ - lib/lapsoss/middleware/event_transformer.rb
244
+ - lib/lapsoss/middleware/exception_filter.rb
245
+ - lib/lapsoss/middleware/metrics_collector.rb
246
+ - lib/lapsoss/middleware/rate_limiter.rb
247
+ - lib/lapsoss/middleware/release_tracker.rb
248
+ - lib/lapsoss/middleware/sample_filter.rb
249
+ - lib/lapsoss/middleware/sampling_middleware.rb
250
+ - lib/lapsoss/middleware/user_context_enhancer.rb
216
251
  - lib/lapsoss/pipeline.rb
252
+ - lib/lapsoss/pipeline_builder.rb
253
+ - lib/lapsoss/rails_error_subscriber.rb
254
+ - lib/lapsoss/rails_middleware.rb
217
255
  - lib/lapsoss/railtie.rb
218
256
  - lib/lapsoss/registry.rb
257
+ - lib/lapsoss/release_providers.rb
219
258
  - lib/lapsoss/release_tracker.rb
220
259
  - lib/lapsoss/router.rb
221
- - lib/lapsoss/sampling.rb
260
+ - lib/lapsoss/sampling/adaptive_sampler.rb
261
+ - lib/lapsoss/sampling/base.rb
262
+ - lib/lapsoss/sampling/composite_sampler.rb
263
+ - lib/lapsoss/sampling/consistent_hash_sampler.rb
264
+ - lib/lapsoss/sampling/exception_type_sampler.rb
265
+ - lib/lapsoss/sampling/health_based_sampler.rb
266
+ - lib/lapsoss/sampling/rate_limiter.rb
267
+ - lib/lapsoss/sampling/sampling_factory.rb
268
+ - lib/lapsoss/sampling/time_based_sampler.rb
269
+ - lib/lapsoss/sampling/uniform_sampler.rb
270
+ - lib/lapsoss/sampling/user_based_sampler.rb
222
271
  - lib/lapsoss/scope.rb
223
272
  - lib/lapsoss/scrubber.rb
224
273
  - lib/lapsoss/user_context.rb
274
+ - lib/lapsoss/user_context_integrations.rb
275
+ - lib/lapsoss/user_context_middleware.rb
276
+ - lib/lapsoss/user_context_provider.rb
277
+ - lib/lapsoss/utils.rb
225
278
  - lib/lapsoss/validators.rb
226
279
  - lib/lapsoss/version.rb
227
280
  homepage: https://github.com/seuros/lapsoss
@@ -1,345 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lapsoss
4
- module Middleware
5
- class Base
6
- def initialize(app)
7
- @app = app
8
- end
9
-
10
- def call(event, hint = {})
11
- @app.call(event, hint)
12
- end
13
- end
14
-
15
- # Built-in middleware for common functionality
16
- class SampleFilter < Base
17
- def initialize(app, sample_rate: 1.0, sample_callback: nil)
18
- super(app)
19
- @sample_rate = sample_rate
20
- @sample_callback = sample_callback
21
- end
22
-
23
- def call(event, hint = {})
24
- # Apply custom sampling logic first
25
- return nil if @sample_callback && !@sample_callback.call(event, hint)
26
-
27
- # Apply rate-based sampling
28
- return nil if (@sample_rate < 1.0) && (rand > @sample_rate)
29
-
30
- @app.call(event, hint)
31
- end
32
- end
33
-
34
- class ExceptionFilter < Base
35
- def initialize(app, excluded_exceptions: [], excluded_patterns: [])
36
- super(app)
37
- @excluded_exceptions = Array(excluded_exceptions)
38
- @excluded_patterns = Array(excluded_patterns)
39
- end
40
-
41
- def call(event, hint = {})
42
- return nil if should_exclude?(event)
43
-
44
- @app.call(event, hint)
45
- end
46
-
47
- private
48
-
49
- def should_exclude?(event)
50
- return false unless event.exception
51
-
52
- exception_class = event.exception.class
53
- exception_message = event.exception.message
54
-
55
- # Check exact class matches
56
- return true if @excluded_exceptions.any? { |klass| exception_class <= klass }
57
-
58
- # Check pattern matches
59
- @excluded_patterns.any? do |pattern|
60
- case pattern
61
- when Regexp
62
- exception_message&.match?(pattern) || exception_class.name.match?(pattern)
63
- when String
64
- exception_message&.include?(pattern) || exception_class.name.include?(pattern)
65
- else
66
- false
67
- end
68
- end
69
- end
70
- end
71
-
72
- class UserContextEnhancer < Base
73
- def initialize(app, user_provider: nil, privacy_mode: false)
74
- super(app)
75
- @user_provider = user_provider
76
- @privacy_mode = privacy_mode
77
- end
78
-
79
- def call(event, hint = {})
80
- enhance_user_context(event, hint)
81
- @app.call(event, hint)
82
- end
83
-
84
- private
85
-
86
- def enhance_user_context(event, hint)
87
- # Get user from provider if available
88
- user_data = @user_provider&.call(event, hint) || {}
89
-
90
- # Merge with existing user context
91
- existing_user = event.context[:user] || {}
92
- enhanced_user = existing_user.merge(user_data)
93
-
94
- # Apply privacy filtering if enabled
95
- enhanced_user = apply_privacy_filtering(enhanced_user) if @privacy_mode
96
-
97
- event.context[:user] = enhanced_user unless enhanced_user.empty?
98
- end
99
-
100
- def apply_privacy_filtering(user_data)
101
- # Remove sensitive fields in privacy mode
102
- sensitive_fields = %i[email phone address ssn credit_card]
103
- filtered = user_data.dup
104
-
105
- sensitive_fields.each do |field|
106
- filtered[field] = '[FILTERED]' if filtered.key?(field)
107
- end
108
-
109
- filtered
110
- end
111
- end
112
-
113
- class ReleaseTracker < Base
114
- def initialize(app, release_provider: nil)
115
- super(app)
116
- @release_provider = release_provider
117
- end
118
-
119
- def call(event, hint = {})
120
- add_release_info(event, hint)
121
- @app.call(event, hint)
122
- end
123
-
124
- private
125
-
126
- def add_release_info(event, hint)
127
- release_info = @release_provider&.call(event, hint) || auto_detect_release
128
-
129
- event.context[:release] = release_info if release_info
130
- end
131
-
132
- def auto_detect_release
133
- release_info = {}
134
-
135
- # Try to detect Git information
136
- if git_info = detect_git_info
137
- release_info.merge!(git_info)
138
- end
139
-
140
- # Try to detect deployment info
141
- if deployment_info = detect_deployment_info
142
- release_info.merge!(deployment_info)
143
- end
144
-
145
- release_info.empty? ? nil : release_info
146
- end
147
-
148
- def detect_git_info
149
- return nil unless File.exist?('.git')
150
-
151
- begin
152
- # Get current commit SHA
153
- commit_sha = `git rev-parse HEAD`.strip
154
- return nil if commit_sha.empty?
155
-
156
- # Get branch name
157
- branch = `git rev-parse --abbrev-ref HEAD`.strip
158
- branch = nil if branch.empty? || branch == 'HEAD'
159
-
160
- # Get commit timestamp
161
- commit_time = `git log -1 --format=%ct`.strip
162
- commit_timestamp = commit_time.empty? ? nil : Time.zone.at(commit_time.to_i)
163
-
164
- # Get tag if on a tag
165
- tag = `git describe --exact-match --tags HEAD 2>/dev/null`.strip
166
- tag = nil if tag.empty?
167
-
168
- {
169
- commit_sha: commit_sha,
170
- branch: branch,
171
- tag: tag,
172
- commit_timestamp: commit_timestamp
173
- }.compact
174
- rescue StandardError
175
- nil
176
- end
177
- end
178
-
179
- def detect_deployment_info
180
- info = {}
181
-
182
- # Check common deployment environment variables
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']
186
-
187
- # Check Heroku
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)
193
- end
194
-
195
- # Check AWS
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)
200
- end
201
-
202
- # Check Docker
203
- if ENV['DOCKER_CONTAINER_ID'] || File.exist?('/.dockerenv')
204
- info[:platform] = 'docker'
205
- info[:container_id] = ENV['DOCKER_CONTAINER_ID']
206
- end
207
-
208
- # Check Kubernetes
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)
213
- end
214
-
215
- info.empty? ? nil : info
216
- end
217
-
218
- def parse_deployment_time(time_str)
219
- Time.zone.parse(time_str)
220
- rescue StandardError
221
- nil
222
- end
223
- end
224
-
225
- class EventEnricher < Base
226
- def initialize(app, enrichers: [])
227
- super(app)
228
- @enrichers = enrichers
229
- end
230
-
231
- def call(event, hint = {})
232
- @enrichers.each do |enricher|
233
- enricher.call(event, hint)
234
- end
235
- @app.call(event, hint)
236
- end
237
- end
238
-
239
- class ConditionalFilter < Base
240
- def initialize(app, condition)
241
- super(app)
242
- @condition = condition
243
- end
244
-
245
- def call(event, hint = {})
246
- return nil unless @condition.call(event, hint)
247
-
248
- @app.call(event, hint)
249
- end
250
- end
251
-
252
- class EventTransformer < Base
253
- def initialize(app, transformer)
254
- super(app)
255
- @transformer = transformer
256
- end
257
-
258
- def call(event, hint = {})
259
- transformed_event = @transformer.call(event, hint)
260
- return nil unless transformed_event
261
-
262
- @app.call(transformed_event, hint)
263
- end
264
- end
265
-
266
- class RateLimiter < Base
267
- def initialize(app, max_events: 100, time_window: 60)
268
- super(app)
269
- @max_events = max_events
270
- @time_window = time_window
271
- @events = []
272
- @mutex = Mutex.new
273
- end
274
-
275
- def call(event, hint = {})
276
- @mutex.synchronize do
277
- now = Time.zone.now
278
- # Remove old events outside time window
279
- @events.reject! { |timestamp| now - timestamp > @time_window }
280
-
281
- # Check if we're over the limit
282
- return nil if @events.length >= @max_events
283
-
284
- # Add current event
285
- @events << now
286
- end
287
-
288
- @app.call(event, hint)
289
- end
290
- end
291
-
292
- class MetricsCollector < Base
293
- def initialize(app, collector: nil)
294
- super(app)
295
- @collector = collector
296
- @metrics = {
297
- events_processed: 0,
298
- events_dropped: 0,
299
- events_by_type: Hash.new(0),
300
- events_by_level: Hash.new(0)
301
- }
302
- @mutex = Mutex.new
303
- end
304
-
305
- def call(event, hint = {})
306
- @mutex.synchronize do
307
- @metrics[:events_processed] += 1
308
- @metrics[:events_by_type][event.type] += 1
309
- @metrics[:events_by_level][event.level] += 1
310
- end
311
-
312
- result = @app.call(event, hint)
313
-
314
- if result.nil?
315
- @mutex.synchronize do
316
- @metrics[:events_dropped] += 1
317
- end
318
- end
319
-
320
- # Send to external collector if provided
321
- @collector&.call(@metrics.dup, event, hint)
322
-
323
- result
324
- end
325
-
326
- def metrics
327
- @mutex.synchronize { @metrics.dup }
328
- end
329
- end
330
-
331
- # Middleware wrapper for sampling strategies
332
- class SamplingMiddleware < Base
333
- def initialize(app, sampler)
334
- super(app)
335
- @sampler = sampler
336
- end
337
-
338
- def call(event, hint = {})
339
- return nil unless @sampler.sample?(event, hint)
340
-
341
- @app.call(event, hint)
342
- end
343
- end
344
- end
345
- end