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.
- checksums.yaml +4 -4
- data/README.md +153 -733
- data/lib/lapsoss/adapters/appsignal_adapter.rb +7 -8
- data/lib/lapsoss/adapters/base.rb +0 -3
- data/lib/lapsoss/adapters/bugsnag_adapter.rb +12 -0
- data/lib/lapsoss/adapters/insight_hub_adapter.rb +102 -101
- data/lib/lapsoss/adapters/logger_adapter.rb +7 -7
- data/lib/lapsoss/adapters/rollbar_adapter.rb +93 -54
- data/lib/lapsoss/adapters/sentry_adapter.rb +11 -17
- data/lib/lapsoss/backtrace_frame.rb +35 -214
- data/lib/lapsoss/backtrace_frame_factory.rb +228 -0
- data/lib/lapsoss/backtrace_processor.rb +37 -37
- data/lib/lapsoss/client.rb +2 -6
- data/lib/lapsoss/configuration.rb +25 -22
- data/lib/lapsoss/current.rb +9 -1
- data/lib/lapsoss/event.rb +30 -6
- data/lib/lapsoss/exception_backtrace_frame.rb +39 -0
- data/lib/lapsoss/exclusion_configuration.rb +30 -0
- data/lib/lapsoss/exclusion_filter.rb +156 -0
- data/lib/lapsoss/{exclusions.rb → exclusion_presets.rb} +1 -181
- data/lib/lapsoss/fingerprinter.rb +9 -13
- data/lib/lapsoss/http_client.rb +42 -8
- data/lib/lapsoss/merged_scope.rb +63 -0
- data/lib/lapsoss/middleware/base.rb +15 -0
- data/lib/lapsoss/middleware/conditional_filter.rb +18 -0
- data/lib/lapsoss/middleware/event_enricher.rb +19 -0
- data/lib/lapsoss/middleware/event_transformer.rb +19 -0
- data/lib/lapsoss/middleware/exception_filter.rb +43 -0
- data/lib/lapsoss/middleware/metrics_collector.rb +44 -0
- data/lib/lapsoss/middleware/rate_limiter.rb +31 -0
- data/lib/lapsoss/middleware/release_tracker.rb +117 -0
- data/lib/lapsoss/middleware/sample_filter.rb +23 -0
- data/lib/lapsoss/middleware/sampling_middleware.rb +18 -0
- data/lib/lapsoss/middleware/user_context_enhancer.rb +46 -0
- data/lib/lapsoss/middleware.rb +0 -347
- data/lib/lapsoss/pipeline.rb +1 -73
- data/lib/lapsoss/pipeline_builder.rb +69 -0
- data/lib/lapsoss/rails_error_subscriber.rb +42 -0
- data/lib/lapsoss/rails_middleware.rb +78 -0
- data/lib/lapsoss/railtie.rb +22 -50
- data/lib/lapsoss/registry.rb +34 -20
- data/lib/lapsoss/release_providers.rb +110 -0
- data/lib/lapsoss/release_tracker.rb +112 -207
- data/lib/lapsoss/router.rb +3 -5
- data/lib/lapsoss/sampling/adaptive_sampler.rb +46 -0
- data/lib/lapsoss/sampling/base.rb +11 -0
- data/lib/lapsoss/sampling/composite_sampler.rb +26 -0
- data/lib/lapsoss/sampling/consistent_hash_sampler.rb +30 -0
- data/lib/lapsoss/sampling/exception_type_sampler.rb +44 -0
- data/lib/lapsoss/sampling/health_based_sampler.rb +19 -0
- data/lib/lapsoss/sampling/rate_limiter.rb +32 -0
- data/lib/lapsoss/sampling/sampling_factory.rb +69 -0
- data/lib/lapsoss/sampling/time_based_sampler.rb +44 -0
- data/lib/lapsoss/sampling/uniform_sampler.rb +15 -0
- data/lib/lapsoss/sampling/user_based_sampler.rb +42 -0
- data/lib/lapsoss/sampling.rb +0 -326
- data/lib/lapsoss/scope.rb +17 -57
- data/lib/lapsoss/scrubber.rb +16 -18
- data/lib/lapsoss/user_context.rb +18 -198
- data/lib/lapsoss/user_context_integrations.rb +39 -0
- data/lib/lapsoss/user_context_middleware.rb +50 -0
- data/lib/lapsoss/user_context_provider.rb +93 -0
- data/lib/lapsoss/utils.rb +13 -0
- data/lib/lapsoss/validators.rb +14 -27
- data/lib/lapsoss/version.rb +1 -1
- data/lib/lapsoss.rb +12 -25
- metadata +106 -21
@@ -13,10 +13,13 @@ module Lapsoss
|
|
13
13
|
@cache_duration = configuration[:cache_duration] || 300 # 5 minutes
|
14
14
|
@cached_release_info = nil
|
15
15
|
@cache_timestamp = nil
|
16
|
+
|
17
|
+
# Auto-register rails_app_version provider if available
|
18
|
+
add_rails_app_version_provider if defined?(Rails) && Rails.application.respond_to?(:version)
|
16
19
|
end
|
17
20
|
|
18
21
|
def get_release_info
|
19
|
-
now = Time.now
|
22
|
+
now = Time.zone.now
|
20
23
|
|
21
24
|
# Return cached info if still valid
|
22
25
|
if @cached_release_info && @cache_timestamp && (now - @cache_timestamp) < @cache_duration
|
@@ -28,34 +31,26 @@ module Lapsoss
|
|
28
31
|
|
29
32
|
# Add custom version providers
|
30
33
|
@version_providers.each do |provider|
|
31
|
-
|
32
|
-
|
33
|
-
release_info.merge!(provider_info)
|
34
|
-
end
|
35
|
-
rescue StandardError => e
|
36
|
-
warn "Release provider failed: #{e.message}"
|
34
|
+
if provider_info = provider.call
|
35
|
+
release_info.merge!(provider_info)
|
37
36
|
end
|
37
|
+
rescue StandardError => e
|
38
|
+
warn "Release provider failed: #{e.message}"
|
38
39
|
end
|
39
40
|
|
40
41
|
# Add Git information
|
41
|
-
if @git_enabled
|
42
|
-
|
43
|
-
release_info.merge!(git_info)
|
44
|
-
end
|
42
|
+
if @git_enabled && (git_info = detect_git_info)
|
43
|
+
release_info.merge!(git_info)
|
45
44
|
end
|
46
45
|
|
47
46
|
# Add environment information
|
48
|
-
if @environment_enabled
|
49
|
-
|
50
|
-
release_info.merge!(env_info)
|
51
|
-
end
|
47
|
+
if @environment_enabled && (env_info = detect_environment_info)
|
48
|
+
release_info.merge!(env_info)
|
52
49
|
end
|
53
50
|
|
54
51
|
# Add deployment information
|
55
|
-
if @deployment_enabled
|
56
|
-
|
57
|
-
release_info.merge!(deployment_info)
|
58
|
-
end
|
52
|
+
if @deployment_enabled && (deployment_info = detect_deployment_info)
|
53
|
+
release_info.merge!(deployment_info)
|
59
54
|
end
|
60
55
|
|
61
56
|
# Generate release ID if not provided
|
@@ -77,6 +72,19 @@ module Lapsoss
|
|
77
72
|
@cache_timestamp = nil
|
78
73
|
end
|
79
74
|
|
75
|
+
def add_rails_app_version_provider
|
76
|
+
add_version_provider do
|
77
|
+
if defined?(Rails) && Rails.application.respond_to?(:version)
|
78
|
+
version = Rails.application.version
|
79
|
+
{
|
80
|
+
rails_app_version: version.to_s,
|
81
|
+
rails_app_version_cache_key: version.to_cache_key,
|
82
|
+
rails_app_environment: Rails.application.env
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
80
88
|
private
|
81
89
|
|
82
90
|
def detect_git_info
|
@@ -99,9 +107,7 @@ module Lapsoss
|
|
99
107
|
|
100
108
|
# Get commit timestamp
|
101
109
|
commit_timestamp = execute_git_command("log -1 --format=%ct")
|
102
|
-
|
103
|
-
git_info[:commit_timestamp] = Time.at(commit_timestamp.to_i)
|
104
|
-
end
|
110
|
+
git_info[:commit_timestamp] = Time.zone.at(commit_timestamp.to_i) if commit_timestamp.present?
|
105
111
|
|
106
112
|
# Get commit message
|
107
113
|
commit_message = execute_git_command("log -1 --format=%s")
|
@@ -113,11 +119,11 @@ module Lapsoss
|
|
113
119
|
|
114
120
|
# Get tag if on a tag
|
115
121
|
tag = execute_git_command("describe --exact-match --tags HEAD 2>/dev/null")
|
116
|
-
git_info[:tag] = tag if tag
|
122
|
+
git_info[:tag] = tag if tag.present?
|
117
123
|
|
118
124
|
# Get latest tag
|
119
125
|
latest_tag = execute_git_command("describe --tags --abbrev=0 2>/dev/null")
|
120
|
-
git_info[:latest_tag] = latest_tag if latest_tag
|
126
|
+
git_info[:latest_tag] = latest_tag if latest_tag.present?
|
121
127
|
|
122
128
|
# Get commits since latest tag
|
123
129
|
if latest_tag
|
@@ -131,9 +137,7 @@ module Lapsoss
|
|
131
137
|
|
132
138
|
# Get remote URL
|
133
139
|
remote_url = execute_git_command("config --get remote.origin.url")
|
134
|
-
if remote_url
|
135
|
-
git_info[:remote_url] = sanitize_remote_url(remote_url)
|
136
|
-
end
|
140
|
+
git_info[:remote_url] = sanitize_remote_url(remote_url) if remote_url
|
137
141
|
|
138
142
|
git_info
|
139
143
|
rescue StandardError => e
|
@@ -145,9 +149,22 @@ module Lapsoss
|
|
145
149
|
def detect_environment_info
|
146
150
|
env_info = {}
|
147
151
|
|
148
|
-
#
|
149
|
-
|
150
|
-
|
152
|
+
# Try rails_app_version first if available
|
153
|
+
if defined?(Rails) && Rails.application.respond_to?(:version)
|
154
|
+
env_info[:version] = Rails.application.version.to_s
|
155
|
+
env_info[:app_version] = Rails.application.version.to_s
|
156
|
+
|
157
|
+
# Add detailed version info
|
158
|
+
version_obj = Rails.application.version
|
159
|
+
env_info[:version_major] = version_obj.major
|
160
|
+
env_info[:version_minor] = version_obj.minor
|
161
|
+
env_info[:version_patch] = version_obj.patch
|
162
|
+
env_info[:version_prerelease] = version_obj.prerelease? if version_obj.respond_to?(:prerelease?)
|
163
|
+
else
|
164
|
+
# Fallback to environment variables
|
165
|
+
env_info[:app_version] = ENV["APP_VERSION"] if ENV["APP_VERSION"]
|
166
|
+
env_info[:version] = ENV["VERSION"] if ENV["VERSION"]
|
167
|
+
end
|
151
168
|
|
152
169
|
# Environment detection
|
153
170
|
env_info[:environment] = detect_environment
|
@@ -191,6 +208,9 @@ module Lapsoss
|
|
191
208
|
end
|
192
209
|
|
193
210
|
def detect_environment
|
211
|
+
# Try rails_app_version first if available
|
212
|
+
return Rails.application.env if defined?(Rails) && Rails.application.respond_to?(:env)
|
213
|
+
|
194
214
|
return ENV["RAILS_ENV"] if ENV["RAILS_ENV"]
|
195
215
|
return ENV["RACK_ENV"] if ENV["RACK_ENV"]
|
196
216
|
return ENV["NODE_ENV"] if ENV["NODE_ENV"]
|
@@ -198,9 +218,7 @@ module Lapsoss
|
|
198
218
|
return ENV["ENV"] if ENV["ENV"]
|
199
219
|
|
200
220
|
# Try to detect from Rails if available
|
201
|
-
if defined?(Rails) && Rails.respond_to?(:env)
|
202
|
-
return Rails.env.to_s
|
203
|
-
end
|
221
|
+
return Rails.env.to_s if defined?(Rails) && Rails.respond_to?(:env)
|
204
222
|
|
205
223
|
# Default fallback
|
206
224
|
"unknown"
|
@@ -212,57 +230,57 @@ module Lapsoss
|
|
212
230
|
# GitHub Actions
|
213
231
|
if ENV["GITHUB_ACTIONS"]
|
214
232
|
ci_info[:provider] = "github_actions"
|
215
|
-
ci_info[:run_id] = ENV
|
216
|
-
ci_info[:run_number] = ENV
|
217
|
-
ci_info[:workflow] = ENV
|
218
|
-
ci_info[:actor] = ENV
|
219
|
-
ci_info[:repository] = ENV
|
220
|
-
ci_info[:ref] = ENV
|
221
|
-
ci_info[:sha] = ENV
|
233
|
+
ci_info[:run_id] = ENV.fetch("GITHUB_RUN_ID", nil)
|
234
|
+
ci_info[:run_number] = ENV.fetch("GITHUB_RUN_NUMBER", nil)
|
235
|
+
ci_info[:workflow] = ENV.fetch("GITHUB_WORKFLOW", nil)
|
236
|
+
ci_info[:actor] = ENV.fetch("GITHUB_ACTOR", nil)
|
237
|
+
ci_info[:repository] = ENV.fetch("GITHUB_REPOSITORY", nil)
|
238
|
+
ci_info[:ref] = ENV.fetch("GITHUB_REF", nil)
|
239
|
+
ci_info[:sha] = ENV.fetch("GITHUB_SHA", nil)
|
222
240
|
end
|
223
241
|
|
224
242
|
# GitLab CI
|
225
243
|
if ENV["GITLAB_CI"]
|
226
244
|
ci_info[:provider] = "gitlab_ci"
|
227
|
-
ci_info[:pipeline_id] = ENV
|
228
|
-
ci_info[:job_id] = ENV
|
229
|
-
ci_info[:job_name] = ENV
|
230
|
-
ci_info[:commit_sha] = ENV
|
231
|
-
ci_info[:commit_ref] = ENV
|
232
|
-
ci_info[:project_url] = ENV
|
245
|
+
ci_info[:pipeline_id] = ENV.fetch("CI_PIPELINE_ID", nil)
|
246
|
+
ci_info[:job_id] = ENV.fetch("CI_JOB_ID", nil)
|
247
|
+
ci_info[:job_name] = ENV.fetch("CI_JOB_NAME", nil)
|
248
|
+
ci_info[:commit_sha] = ENV.fetch("CI_COMMIT_SHA", nil)
|
249
|
+
ci_info[:commit_ref] = ENV.fetch("CI_COMMIT_REF_NAME", nil)
|
250
|
+
ci_info[:project_url] = ENV.fetch("CI_PROJECT_URL", nil)
|
233
251
|
end
|
234
252
|
|
235
253
|
# Jenkins
|
236
254
|
if ENV["JENKINS_URL"]
|
237
255
|
ci_info[:provider] = "jenkins"
|
238
|
-
ci_info[:build_number] = ENV
|
239
|
-
ci_info[:build_id] = ENV
|
240
|
-
ci_info[:job_name] = ENV
|
241
|
-
ci_info[:build_url] = ENV
|
242
|
-
ci_info[:git_commit] = ENV
|
243
|
-
ci_info[:git_branch] = ENV
|
256
|
+
ci_info[:build_number] = ENV.fetch("BUILD_NUMBER", nil)
|
257
|
+
ci_info[:build_id] = ENV.fetch("BUILD_ID", nil)
|
258
|
+
ci_info[:job_name] = ENV.fetch("JOB_NAME", nil)
|
259
|
+
ci_info[:build_url] = ENV.fetch("BUILD_URL", nil)
|
260
|
+
ci_info[:git_commit] = ENV.fetch("GIT_COMMIT", nil)
|
261
|
+
ci_info[:git_branch] = ENV.fetch("GIT_BRANCH", nil)
|
244
262
|
end
|
245
263
|
|
246
264
|
# CircleCI
|
247
265
|
if ENV["CIRCLECI"]
|
248
266
|
ci_info[:provider] = "circleci"
|
249
|
-
ci_info[:build_num] = ENV
|
250
|
-
ci_info[:workflow_id] = ENV
|
251
|
-
ci_info[:job] = ENV
|
252
|
-
ci_info[:project_reponame] = ENV
|
253
|
-
ci_info[:sha1] = ENV
|
254
|
-
ci_info[:branch] = ENV
|
267
|
+
ci_info[:build_num] = ENV.fetch("CIRCLE_BUILD_NUM", nil)
|
268
|
+
ci_info[:workflow_id] = ENV.fetch("CIRCLE_WORKFLOW_ID", nil)
|
269
|
+
ci_info[:job] = ENV.fetch("CIRCLE_JOB", nil)
|
270
|
+
ci_info[:project_reponame] = ENV.fetch("CIRCLE_PROJECT_REPONAME", nil)
|
271
|
+
ci_info[:sha1] = ENV.fetch("CIRCLE_SHA1", nil)
|
272
|
+
ci_info[:branch] = ENV.fetch("CIRCLE_BRANCH", nil)
|
255
273
|
end
|
256
274
|
|
257
275
|
# Travis CI
|
258
276
|
if ENV["TRAVIS"]
|
259
277
|
ci_info[:provider] = "travis"
|
260
|
-
ci_info[:build_id] = ENV
|
261
|
-
ci_info[:build_number] = ENV
|
262
|
-
ci_info[:job_id] = ENV
|
263
|
-
ci_info[:commit] = ENV
|
264
|
-
ci_info[:branch] = ENV
|
265
|
-
ci_info[:tag] = ENV
|
278
|
+
ci_info[:build_id] = ENV.fetch("TRAVIS_BUILD_ID", nil)
|
279
|
+
ci_info[:build_number] = ENV.fetch("TRAVIS_BUILD_NUMBER", nil)
|
280
|
+
ci_info[:job_id] = ENV.fetch("TRAVIS_JOB_ID", nil)
|
281
|
+
ci_info[:commit] = ENV.fetch("TRAVIS_COMMIT", nil)
|
282
|
+
ci_info[:branch] = ENV.fetch("TRAVIS_BRANCH", nil)
|
283
|
+
ci_info[:tag] = ENV.fetch("TRAVIS_TAG", nil)
|
266
284
|
end
|
267
285
|
|
268
286
|
ci_info
|
@@ -273,11 +291,11 @@ module Lapsoss
|
|
273
291
|
|
274
292
|
{
|
275
293
|
platform: "heroku",
|
276
|
-
app_name: ENV
|
277
|
-
dyno: ENV
|
278
|
-
slug_commit: ENV
|
279
|
-
release_version: ENV
|
280
|
-
slug_description: ENV
|
294
|
+
app_name: ENV.fetch("HEROKU_APP_NAME", nil),
|
295
|
+
dyno: ENV.fetch("DYNO", nil),
|
296
|
+
slug_commit: ENV.fetch("HEROKU_SLUG_COMMIT", nil),
|
297
|
+
release_version: ENV.fetch("HEROKU_RELEASE_VERSION", nil),
|
298
|
+
slug_description: ENV.fetch("HEROKU_SLUG_DESCRIPTION", nil)
|
281
299
|
}
|
282
300
|
end
|
283
301
|
|
@@ -287,17 +305,17 @@ module Lapsoss
|
|
287
305
|
if ENV["AWS_EXECUTION_ENV"]
|
288
306
|
info[:platform] = "aws"
|
289
307
|
info[:execution_env] = ENV["AWS_EXECUTION_ENV"]
|
290
|
-
info[:region] = ENV["AWS_REGION"] || ENV
|
291
|
-
info[:function_name] = ENV
|
292
|
-
info[:function_version] = ENV
|
308
|
+
info[:region] = ENV["AWS_REGION"] || ENV.fetch("AWS_DEFAULT_REGION", nil)
|
309
|
+
info[:function_name] = ENV.fetch("AWS_LAMBDA_FUNCTION_NAME", nil)
|
310
|
+
info[:function_version] = ENV.fetch("AWS_LAMBDA_FUNCTION_VERSION", nil)
|
293
311
|
end
|
294
312
|
|
295
313
|
# EC2 metadata (if available)
|
296
314
|
if ENV["EC2_INSTANCE_ID"]
|
297
315
|
info[:platform] = "aws_ec2"
|
298
316
|
info[:instance_id] = ENV["EC2_INSTANCE_ID"]
|
299
|
-
info[:instance_type] = ENV
|
300
|
-
info[:availability_zone] = ENV
|
317
|
+
info[:instance_type] = ENV.fetch("EC2_INSTANCE_TYPE", nil)
|
318
|
+
info[:availability_zone] = ENV.fetch("EC2_AVAILABILITY_ZONE", nil)
|
301
319
|
end
|
302
320
|
|
303
321
|
info
|
@@ -309,18 +327,18 @@ module Lapsoss
|
|
309
327
|
if ENV["GOOGLE_CLOUD_PROJECT"]
|
310
328
|
info[:platform] = "gcp"
|
311
329
|
info[:project] = ENV["GOOGLE_CLOUD_PROJECT"]
|
312
|
-
info[:region] = ENV
|
313
|
-
info[:function_name] = ENV
|
314
|
-
info[:function_signature_type] = ENV
|
330
|
+
info[:region] = ENV.fetch("GOOGLE_CLOUD_REGION", nil)
|
331
|
+
info[:function_name] = ENV.fetch("FUNCTION_NAME", nil)
|
332
|
+
info[:function_signature_type] = ENV.fetch("FUNCTION_SIGNATURE_TYPE", nil)
|
315
333
|
end
|
316
334
|
|
317
335
|
# App Engine
|
318
336
|
if ENV["GAE_APPLICATION"]
|
319
337
|
info[:platform] = "gcp_app_engine"
|
320
338
|
info[:application] = ENV["GAE_APPLICATION"]
|
321
|
-
info[:service] = ENV
|
322
|
-
info[:version] = ENV
|
323
|
-
info[:runtime] = ENV
|
339
|
+
info[:service] = ENV.fetch("GAE_SERVICE", nil)
|
340
|
+
info[:version] = ENV.fetch("GAE_VERSION", nil)
|
341
|
+
info[:runtime] = ENV.fetch("GAE_RUNTIME", nil)
|
324
342
|
end
|
325
343
|
|
326
344
|
info
|
@@ -332,9 +350,9 @@ module Lapsoss
|
|
332
350
|
if ENV["WEBSITE_SITE_NAME"]
|
333
351
|
info[:platform] = "azure"
|
334
352
|
info[:site_name] = ENV["WEBSITE_SITE_NAME"]
|
335
|
-
info[:resource_group] = ENV
|
336
|
-
info[:subscription_id] = ENV
|
337
|
-
info[:sku] = ENV
|
353
|
+
info[:resource_group] = ENV.fetch("WEBSITE_RESOURCE_GROUP", nil)
|
354
|
+
info[:subscription_id] = ENV.fetch("WEBSITE_OWNER_NAME", nil)
|
355
|
+
info[:sku] = ENV.fetch("WEBSITE_SKU", nil)
|
338
356
|
end
|
339
357
|
|
340
358
|
info
|
@@ -346,8 +364,8 @@ module Lapsoss
|
|
346
364
|
if ENV["DOCKER_CONTAINER_ID"] || File.exist?("/.dockerenv")
|
347
365
|
info[:platform] = "docker"
|
348
366
|
info[:container_id] = ENV["DOCKER_CONTAINER_ID"]
|
349
|
-
info[:image] = ENV
|
350
|
-
info[:tag] = ENV
|
367
|
+
info[:image] = ENV.fetch("DOCKER_IMAGE", nil)
|
368
|
+
info[:tag] = ENV.fetch("DOCKER_TAG", nil)
|
351
369
|
end
|
352
370
|
|
353
371
|
info
|
@@ -358,11 +376,11 @@ module Lapsoss
|
|
358
376
|
|
359
377
|
if ENV["KUBERNETES_SERVICE_HOST"]
|
360
378
|
info[:platform] = "kubernetes"
|
361
|
-
info[:namespace] = ENV
|
362
|
-
info[:pod_name] = ENV
|
363
|
-
info[:service_account] = ENV
|
364
|
-
info[:cluster_name] = ENV
|
365
|
-
info[:node_name] = ENV
|
379
|
+
info[:namespace] = ENV.fetch("KUBERNETES_NAMESPACE", nil)
|
380
|
+
info[:pod_name] = ENV.fetch("HOSTNAME", nil)
|
381
|
+
info[:service_account] = ENV.fetch("KUBERNETES_SERVICE_ACCOUNT", nil)
|
382
|
+
info[:cluster_name] = ENV.fetch("CLUSTER_NAME", nil)
|
383
|
+
info[:node_name] = ENV.fetch("NODE_NAME", nil)
|
366
384
|
end
|
367
385
|
|
368
386
|
info
|
@@ -393,16 +411,14 @@ module Lapsoss
|
|
393
411
|
]
|
394
412
|
|
395
413
|
formats.each do |format|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
next
|
400
|
-
end
|
414
|
+
return Time.strptime(time_str, format)
|
415
|
+
rescue ArgumentError
|
416
|
+
next
|
401
417
|
end
|
402
418
|
|
403
419
|
# Try parsing as integer (Unix timestamp)
|
404
420
|
begin
|
405
|
-
return Time.at(time_str.to_i) if time_str.match?(/^\d+$/)
|
421
|
+
return Time.zone.at(time_str.to_i) if time_str.match?(/^\d+$/)
|
406
422
|
rescue ArgumentError
|
407
423
|
nil
|
408
424
|
end
|
@@ -439,115 +455,4 @@ module Lapsoss
|
|
439
455
|
end
|
440
456
|
end
|
441
457
|
end
|
442
|
-
|
443
|
-
# Built-in release providers for common scenarios
|
444
|
-
class ReleaseProviders
|
445
|
-
def self.from_file(file_path)
|
446
|
-
lambda do
|
447
|
-
return nil unless File.exist?(file_path)
|
448
|
-
|
449
|
-
content = File.read(file_path).strip
|
450
|
-
return nil if content.empty?
|
451
|
-
|
452
|
-
# Try to parse as JSON first
|
453
|
-
begin
|
454
|
-
JSON.parse(content)
|
455
|
-
rescue JSON::ParserError
|
456
|
-
# Treat as plain text version
|
457
|
-
{ version: content }
|
458
|
-
end
|
459
|
-
end
|
460
|
-
end
|
461
|
-
|
462
|
-
def self.from_ruby_constant(constant_name)
|
463
|
-
lambda do
|
464
|
-
begin
|
465
|
-
constant = Object.const_get(constant_name)
|
466
|
-
{ version: constant.to_s }
|
467
|
-
rescue NameError
|
468
|
-
nil
|
469
|
-
end
|
470
|
-
end
|
471
|
-
end
|
472
|
-
|
473
|
-
def self.from_gemfile_lock
|
474
|
-
lambda do
|
475
|
-
return nil unless File.exist?("Gemfile.lock")
|
476
|
-
|
477
|
-
content = File.read("Gemfile.lock")
|
478
|
-
|
479
|
-
# Extract gems with versions
|
480
|
-
gems = {}
|
481
|
-
content.scan(/^\s{4}(\w+)\s+\(([^)]+)\)/).each do |name, version|
|
482
|
-
gems[name] = version
|
483
|
-
end
|
484
|
-
|
485
|
-
{ gems: gems }
|
486
|
-
end
|
487
|
-
end
|
488
|
-
|
489
|
-
def self.from_package_json
|
490
|
-
lambda do
|
491
|
-
return nil unless File.exist?("package.json")
|
492
|
-
|
493
|
-
begin
|
494
|
-
package_info = JSON.parse(File.read("package.json"))
|
495
|
-
{
|
496
|
-
version: package_info["version"],
|
497
|
-
name: package_info["name"],
|
498
|
-
dependencies: package_info["dependencies"]&.keys
|
499
|
-
}.compact
|
500
|
-
rescue JSON::ParserError
|
501
|
-
nil
|
502
|
-
end
|
503
|
-
end
|
504
|
-
end
|
505
|
-
|
506
|
-
def self.from_rails_application
|
507
|
-
lambda do
|
508
|
-
return nil unless defined?(Rails) && Rails.respond_to?(:application)
|
509
|
-
|
510
|
-
app = Rails.application
|
511
|
-
return nil unless app
|
512
|
-
|
513
|
-
info = {
|
514
|
-
rails_version: Rails.version,
|
515
|
-
environment: Rails.env,
|
516
|
-
root: Rails.root.to_s
|
517
|
-
}
|
518
|
-
|
519
|
-
# Get application version if defined
|
520
|
-
if app.class.respond_to?(:version)
|
521
|
-
info[:app_version] = app.class.version
|
522
|
-
end
|
523
|
-
|
524
|
-
# Get application name
|
525
|
-
if app.class.respond_to?(:name)
|
526
|
-
info[:app_name] = app.class.name
|
527
|
-
end
|
528
|
-
|
529
|
-
info
|
530
|
-
end
|
531
|
-
end
|
532
|
-
|
533
|
-
def self.from_capistrano
|
534
|
-
lambda do
|
535
|
-
# Check for Capistrano deployment files
|
536
|
-
%w[REVISION current/REVISION].each do |file|
|
537
|
-
next unless File.exist?(file)
|
538
|
-
|
539
|
-
revision = File.read(file).strip
|
540
|
-
next if revision.empty?
|
541
|
-
|
542
|
-
return {
|
543
|
-
revision: revision,
|
544
|
-
deployed_at: File.mtime(file),
|
545
|
-
deployment_method: "capistrano"
|
546
|
-
}
|
547
|
-
end
|
548
|
-
|
549
|
-
nil
|
550
|
-
end
|
551
|
-
end
|
552
|
-
end
|
553
458
|
end
|
data/lib/lapsoss/router.rb
CHANGED
@@ -9,11 +9,9 @@ module Lapsoss
|
|
9
9
|
# @param event [Lapsoss::Event] The event to process.
|
10
10
|
def process_event(event)
|
11
11
|
Registry.instance.active.each do |adapter|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
handle_adapter_error(adapter, event, e)
|
16
|
-
end
|
12
|
+
adapter.capture(event)
|
13
|
+
rescue StandardError => e
|
14
|
+
handle_adapter_error(adapter, event, e)
|
17
15
|
end
|
18
16
|
end
|
19
17
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lapsoss
|
4
|
+
module Sampling
|
5
|
+
class AdaptiveSampler < Base
|
6
|
+
def initialize(target_rate: 1.0, adjustment_period: 60)
|
7
|
+
@target_rate = target_rate
|
8
|
+
@adjustment_period = adjustment_period
|
9
|
+
@current_rate = target_rate
|
10
|
+
@events_count = 0
|
11
|
+
@last_adjustment = Time.zone.now
|
12
|
+
@mutex = Mutex.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def sample?(_event, _hint = {})
|
16
|
+
@mutex.synchronize do
|
17
|
+
@events_count += 1
|
18
|
+
|
19
|
+
# Adjust rate periodically
|
20
|
+
now = Time.zone.now
|
21
|
+
if now - @last_adjustment > @adjustment_period
|
22
|
+
adjust_rate
|
23
|
+
@last_adjustment = now
|
24
|
+
@events_count = 0
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
@current_rate > rand
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :current_rate
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def adjust_rate
|
36
|
+
# Simple adaptive logic - can be enhanced based on system metrics
|
37
|
+
# For now, just ensure we don't drift too far from target
|
38
|
+
if @events_count > 100 # High volume
|
39
|
+
@current_rate = [ @current_rate * 0.9, @target_rate * 0.1 ].max
|
40
|
+
elsif @events_count < 10 # Low volume
|
41
|
+
@current_rate = [ @current_rate * 1.1, @target_rate ].min
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lapsoss
|
4
|
+
module Sampling
|
5
|
+
class CompositeSampler < Base
|
6
|
+
def initialize(app = nil, samplers: [], strategy: :all)
|
7
|
+
@app = app
|
8
|
+
@samplers = samplers
|
9
|
+
@strategy = strategy
|
10
|
+
end
|
11
|
+
|
12
|
+
def sample?(event, hint = {})
|
13
|
+
case @strategy
|
14
|
+
when :all
|
15
|
+
@samplers.all? { |sampler| sampler.sample?(event, hint) }
|
16
|
+
when :any
|
17
|
+
@samplers.any? { |sampler| sampler.sample?(event, hint) }
|
18
|
+
when :first
|
19
|
+
@samplers.first&.sample?(event, hint) || true
|
20
|
+
else
|
21
|
+
raise ArgumentError, "Unknown strategy: #{@strategy}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest"
|
4
|
+
|
5
|
+
module Lapsoss
|
6
|
+
module Sampling
|
7
|
+
class ConsistentHashSampler < Base
|
8
|
+
def initialize(rate:, key_extractor: nil)
|
9
|
+
@rate = rate
|
10
|
+
@key_extractor = key_extractor || method(:default_key_extractor)
|
11
|
+
@threshold = (rate * 0xFFFFFFFF).to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
def sample?(event, hint = {})
|
15
|
+
key = @key_extractor.call(event, hint)
|
16
|
+
return @rate > rand unless key
|
17
|
+
|
18
|
+
hash_value = Digest::MD5.hexdigest(key.to_s)[0, 8].to_i(16)
|
19
|
+
hash_value <= @threshold
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def default_key_extractor(event, _hint)
|
25
|
+
# Use fingerprint for consistent sampling
|
26
|
+
event.fingerprint
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lapsoss
|
4
|
+
module Sampling
|
5
|
+
class ExceptionTypeSampler < Base
|
6
|
+
def initialize(rates: {})
|
7
|
+
@rates = rates
|
8
|
+
@default_rate = rates.fetch(:default, 1.0)
|
9
|
+
end
|
10
|
+
|
11
|
+
def sample?(event, _hint = {})
|
12
|
+
return @default_rate > rand unless event.exception
|
13
|
+
|
14
|
+
exception_class = event.exception.class
|
15
|
+
rate = find_rate_for_exception(exception_class)
|
16
|
+
rate > rand
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def find_rate_for_exception(exception_class)
|
22
|
+
# Check exact class match first
|
23
|
+
return @rates[exception_class] if @rates.key?(exception_class)
|
24
|
+
|
25
|
+
# Check inheritance hierarchy
|
26
|
+
@rates.each do |klass, rate|
|
27
|
+
return rate if klass.is_a?(Class) && exception_class <= klass
|
28
|
+
end
|
29
|
+
|
30
|
+
# Check string/regex patterns
|
31
|
+
@rates.each do |pattern, rate|
|
32
|
+
case pattern
|
33
|
+
when String
|
34
|
+
return rate if exception_class.name.include?(pattern)
|
35
|
+
when Regexp
|
36
|
+
return rate if exception_class.name.match?(pattern)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
@default_rate
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|