lapsoss 0.2.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 +22 -22
- data/lib/lapsoss/adapters/base.rb +0 -3
- data/lib/lapsoss/adapters/insight_hub_adapter.rb +108 -104
- data/lib/lapsoss/adapters/logger_adapter.rb +1 -1
- data/lib/lapsoss/adapters/rollbar_adapter.rb +108 -68
- data/lib/lapsoss/adapters/sentry_adapter.rb +24 -24
- data/lib/lapsoss/backtrace_frame.rb +37 -206
- data/lib/lapsoss/backtrace_frame_factory.rb +228 -0
- data/lib/lapsoss/backtrace_processor.rb +26 -23
- data/lib/lapsoss/client.rb +2 -4
- data/lib/lapsoss/configuration.rb +28 -32
- data/lib/lapsoss/current.rb +10 -2
- data/lib/lapsoss/event.rb +28 -5
- data/lib/lapsoss/exception_backtrace_frame.rb +39 -0
- data/lib/lapsoss/exclusion_configuration.rb +30 -0
- data/lib/lapsoss/exclusion_filter.rb +0 -273
- data/lib/lapsoss/exclusion_presets.rb +249 -0
- data/lib/lapsoss/fingerprinter.rb +28 -28
- data/lib/lapsoss/http_client.rb +8 -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 -339
- data/lib/lapsoss/pipeline.rb +0 -68
- 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 +18 -5
- data/lib/lapsoss/release_providers.rb +110 -0
- data/lib/lapsoss/release_tracker.rb +159 -232
- 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 -322
- data/lib/lapsoss/scope.rb +12 -48
- data/lib/lapsoss/scrubber.rb +7 -7
- data/lib/lapsoss/user_context.rb +30 -203
- 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 +15 -15
- data/lib/lapsoss/version.rb +1 -1
- data/lib/lapsoss.rb +3 -3
- metadata +54 -5
@@ -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
|
# Release and version tracking system
|
@@ -13,6 +13,9 @@ 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
|
@@ -69,44 +72,57 @@ module Lapsoss
|
|
69
72
|
@cache_timestamp = nil
|
70
73
|
end
|
71
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
|
+
|
72
88
|
private
|
73
89
|
|
74
90
|
def detect_git_info
|
75
|
-
return nil unless File.exist?(
|
91
|
+
return nil unless File.exist?(".git")
|
76
92
|
|
77
93
|
git_info = {}
|
78
94
|
|
79
95
|
begin
|
80
96
|
# Get current commit SHA
|
81
|
-
commit_sha = execute_git_command(
|
97
|
+
commit_sha = execute_git_command("rev-parse HEAD")
|
82
98
|
git_info[:commit_sha] = commit_sha if commit_sha
|
83
99
|
|
84
100
|
# Get short commit SHA
|
85
|
-
short_sha = execute_git_command(
|
101
|
+
short_sha = execute_git_command("rev-parse --short HEAD")
|
86
102
|
git_info[:short_sha] = short_sha if short_sha
|
87
103
|
|
88
104
|
# Get branch name
|
89
|
-
branch = execute_git_command(
|
90
|
-
git_info[:branch] = branch if branch && branch !=
|
105
|
+
branch = execute_git_command("rev-parse --abbrev-ref HEAD")
|
106
|
+
git_info[:branch] = branch if branch && branch != "HEAD"
|
91
107
|
|
92
108
|
# Get commit timestamp
|
93
|
-
commit_timestamp = execute_git_command(
|
109
|
+
commit_timestamp = execute_git_command("log -1 --format=%ct")
|
94
110
|
git_info[:commit_timestamp] = Time.zone.at(commit_timestamp.to_i) if commit_timestamp.present?
|
95
111
|
|
96
112
|
# Get commit message
|
97
|
-
commit_message = execute_git_command(
|
113
|
+
commit_message = execute_git_command("log -1 --format=%s")
|
98
114
|
git_info[:commit_message] = commit_message if commit_message
|
99
115
|
|
100
116
|
# Get committer info
|
101
|
-
committer = execute_git_command(
|
117
|
+
committer = execute_git_command("log -1 --format=%cn")
|
102
118
|
git_info[:committer] = committer if committer
|
103
119
|
|
104
120
|
# Get tag if on a tag
|
105
|
-
tag = execute_git_command(
|
121
|
+
tag = execute_git_command("describe --exact-match --tags HEAD 2>/dev/null")
|
106
122
|
git_info[:tag] = tag if tag.present?
|
107
123
|
|
108
124
|
# Get latest tag
|
109
|
-
latest_tag = execute_git_command(
|
125
|
+
latest_tag = execute_git_command("describe --tags --abbrev=0 2>/dev/null")
|
110
126
|
git_info[:latest_tag] = latest_tag if latest_tag.present?
|
111
127
|
|
112
128
|
# Get commits since latest tag
|
@@ -116,11 +132,11 @@ module Lapsoss
|
|
116
132
|
end
|
117
133
|
|
118
134
|
# Check if working directory is dirty
|
119
|
-
git_status = execute_git_command(
|
135
|
+
git_status = execute_git_command("status --porcelain")
|
120
136
|
git_info[:dirty] = !git_status.empty? if git_status
|
121
137
|
|
122
138
|
# Get remote URL
|
123
|
-
remote_url = execute_git_command(
|
139
|
+
remote_url = execute_git_command("config --get remote.origin.url")
|
124
140
|
git_info[:remote_url] = sanitize_remote_url(remote_url) if remote_url
|
125
141
|
|
126
142
|
git_info
|
@@ -133,20 +149,33 @@ module Lapsoss
|
|
133
149
|
def detect_environment_info
|
134
150
|
env_info = {}
|
135
151
|
|
136
|
-
#
|
137
|
-
|
138
|
-
|
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
|
139
168
|
|
140
169
|
# Environment detection
|
141
170
|
env_info[:environment] = detect_environment
|
142
171
|
|
143
172
|
# Application name
|
144
|
-
env_info[:app_name] = ENV[
|
173
|
+
env_info[:app_name] = ENV["APP_NAME"] if ENV["APP_NAME"]
|
145
174
|
|
146
175
|
# Build information
|
147
|
-
env_info[:build_number] = ENV[
|
148
|
-
env_info[:build_id] = ENV[
|
149
|
-
env_info[:build_url] = ENV[
|
176
|
+
env_info[:build_number] = ENV["BUILD_NUMBER"] if ENV["BUILD_NUMBER"]
|
177
|
+
env_info[:build_id] = ENV["BUILD_ID"] if ENV["BUILD_ID"]
|
178
|
+
env_info[:build_url] = ENV["BUILD_URL"] if ENV["BUILD_URL"]
|
150
179
|
|
151
180
|
# CI/CD information
|
152
181
|
env_info[:ci] = detect_ci_info
|
@@ -158,14 +187,14 @@ module Lapsoss
|
|
158
187
|
deployment_info = {}
|
159
188
|
|
160
189
|
# Deployment timestamp
|
161
|
-
if ENV[
|
162
|
-
deployment_info[:deployment_time] = parse_time(ENV[
|
163
|
-
elsif ENV[
|
164
|
-
deployment_info[:deployment_time] = parse_time(ENV[
|
190
|
+
if ENV["DEPLOYMENT_TIME"]
|
191
|
+
deployment_info[:deployment_time] = parse_time(ENV["DEPLOYMENT_TIME"])
|
192
|
+
elsif ENV["DEPLOYED_AT"]
|
193
|
+
deployment_info[:deployment_time] = parse_time(ENV["DEPLOYED_AT"])
|
165
194
|
end
|
166
195
|
|
167
196
|
# Deployment ID
|
168
|
-
deployment_info[:deployment_id] = ENV[
|
197
|
+
deployment_info[:deployment_id] = ENV["DEPLOYMENT_ID"] if ENV["DEPLOYMENT_ID"]
|
169
198
|
|
170
199
|
# Platform-specific detection
|
171
200
|
deployment_info.merge!(detect_heroku_info)
|
@@ -179,111 +208,114 @@ module Lapsoss
|
|
179
208
|
end
|
180
209
|
|
181
210
|
def detect_environment
|
182
|
-
|
183
|
-
return
|
184
|
-
|
185
|
-
return ENV[
|
186
|
-
return ENV[
|
211
|
+
# Try rails_app_version first if available
|
212
|
+
return Rails.application.env if defined?(Rails) && Rails.application.respond_to?(:env)
|
213
|
+
|
214
|
+
return ENV["RAILS_ENV"] if ENV["RAILS_ENV"]
|
215
|
+
return ENV["RACK_ENV"] if ENV["RACK_ENV"]
|
216
|
+
return ENV["NODE_ENV"] if ENV["NODE_ENV"]
|
217
|
+
return ENV["ENVIRONMENT"] if ENV["ENVIRONMENT"]
|
218
|
+
return ENV["ENV"] if ENV["ENV"]
|
187
219
|
|
188
220
|
# Try to detect from Rails if available
|
189
221
|
return Rails.env.to_s if defined?(Rails) && Rails.respond_to?(:env)
|
190
222
|
|
191
223
|
# Default fallback
|
192
|
-
|
224
|
+
"unknown"
|
193
225
|
end
|
194
226
|
|
195
227
|
def detect_ci_info
|
196
228
|
ci_info = {}
|
197
229
|
|
198
230
|
# GitHub Actions
|
199
|
-
if ENV[
|
200
|
-
ci_info[:provider] =
|
201
|
-
ci_info[:run_id] = ENV.fetch(
|
202
|
-
ci_info[:run_number] = ENV.fetch(
|
203
|
-
ci_info[:workflow] = ENV.fetch(
|
204
|
-
ci_info[:actor] = ENV.fetch(
|
205
|
-
ci_info[:repository] = ENV.fetch(
|
206
|
-
ci_info[:ref] = ENV.fetch(
|
207
|
-
ci_info[:sha] = ENV.fetch(
|
231
|
+
if ENV["GITHUB_ACTIONS"]
|
232
|
+
ci_info[:provider] = "github_actions"
|
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)
|
208
240
|
end
|
209
241
|
|
210
242
|
# GitLab CI
|
211
|
-
if ENV[
|
212
|
-
ci_info[:provider] =
|
213
|
-
ci_info[:pipeline_id] = ENV.fetch(
|
214
|
-
ci_info[:job_id] = ENV.fetch(
|
215
|
-
ci_info[:job_name] = ENV.fetch(
|
216
|
-
ci_info[:commit_sha] = ENV.fetch(
|
217
|
-
ci_info[:commit_ref] = ENV.fetch(
|
218
|
-
ci_info[:project_url] = ENV.fetch(
|
243
|
+
if ENV["GITLAB_CI"]
|
244
|
+
ci_info[:provider] = "gitlab_ci"
|
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)
|
219
251
|
end
|
220
252
|
|
221
253
|
# Jenkins
|
222
|
-
if ENV[
|
223
|
-
ci_info[:provider] =
|
224
|
-
ci_info[:build_number] = ENV.fetch(
|
225
|
-
ci_info[:build_id] = ENV.fetch(
|
226
|
-
ci_info[:job_name] = ENV.fetch(
|
227
|
-
ci_info[:build_url] = ENV.fetch(
|
228
|
-
ci_info[:git_commit] = ENV.fetch(
|
229
|
-
ci_info[:git_branch] = ENV.fetch(
|
254
|
+
if ENV["JENKINS_URL"]
|
255
|
+
ci_info[:provider] = "jenkins"
|
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)
|
230
262
|
end
|
231
263
|
|
232
264
|
# CircleCI
|
233
|
-
if ENV[
|
234
|
-
ci_info[:provider] =
|
235
|
-
ci_info[:build_num] = ENV.fetch(
|
236
|
-
ci_info[:workflow_id] = ENV.fetch(
|
237
|
-
ci_info[:job] = ENV.fetch(
|
238
|
-
ci_info[:project_reponame] = ENV.fetch(
|
239
|
-
ci_info[:sha1] = ENV.fetch(
|
240
|
-
ci_info[:branch] = ENV.fetch(
|
265
|
+
if ENV["CIRCLECI"]
|
266
|
+
ci_info[:provider] = "circleci"
|
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)
|
241
273
|
end
|
242
274
|
|
243
275
|
# Travis CI
|
244
|
-
if ENV[
|
245
|
-
ci_info[:provider] =
|
246
|
-
ci_info[:build_id] = ENV.fetch(
|
247
|
-
ci_info[:build_number] = ENV.fetch(
|
248
|
-
ci_info[:job_id] = ENV.fetch(
|
249
|
-
ci_info[:commit] = ENV.fetch(
|
250
|
-
ci_info[:branch] = ENV.fetch(
|
251
|
-
ci_info[:tag] = ENV.fetch(
|
276
|
+
if ENV["TRAVIS"]
|
277
|
+
ci_info[:provider] = "travis"
|
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)
|
252
284
|
end
|
253
285
|
|
254
286
|
ci_info
|
255
287
|
end
|
256
288
|
|
257
289
|
def detect_heroku_info
|
258
|
-
return {} unless ENV[
|
290
|
+
return {} unless ENV["HEROKU_APP_NAME"]
|
259
291
|
|
260
292
|
{
|
261
|
-
platform:
|
262
|
-
app_name: ENV.fetch(
|
263
|
-
dyno: ENV.fetch(
|
264
|
-
slug_commit: ENV.fetch(
|
265
|
-
release_version: ENV.fetch(
|
266
|
-
slug_description: ENV.fetch(
|
293
|
+
platform: "heroku",
|
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)
|
267
299
|
}
|
268
300
|
end
|
269
301
|
|
270
302
|
def detect_aws_info
|
271
303
|
info = {}
|
272
304
|
|
273
|
-
if ENV[
|
274
|
-
info[:platform] =
|
275
|
-
info[:execution_env] = ENV[
|
276
|
-
info[:region] = ENV[
|
277
|
-
info[:function_name] = ENV.fetch(
|
278
|
-
info[:function_version] = ENV.fetch(
|
305
|
+
if ENV["AWS_EXECUTION_ENV"]
|
306
|
+
info[:platform] = "aws"
|
307
|
+
info[:execution_env] = ENV["AWS_EXECUTION_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)
|
279
311
|
end
|
280
312
|
|
281
313
|
# EC2 metadata (if available)
|
282
|
-
if ENV[
|
283
|
-
info[:platform] =
|
284
|
-
info[:instance_id] = ENV[
|
285
|
-
info[:instance_type] = ENV.fetch(
|
286
|
-
info[:availability_zone] = ENV.fetch(
|
314
|
+
if ENV["EC2_INSTANCE_ID"]
|
315
|
+
info[:platform] = "aws_ec2"
|
316
|
+
info[:instance_id] = ENV["EC2_INSTANCE_ID"]
|
317
|
+
info[:instance_type] = ENV.fetch("EC2_INSTANCE_TYPE", nil)
|
318
|
+
info[:availability_zone] = ENV.fetch("EC2_AVAILABILITY_ZONE", nil)
|
287
319
|
end
|
288
320
|
|
289
321
|
info
|
@@ -292,21 +324,21 @@ module Lapsoss
|
|
292
324
|
def detect_gcp_info
|
293
325
|
info = {}
|
294
326
|
|
295
|
-
if ENV[
|
296
|
-
info[:platform] =
|
297
|
-
info[:project] = ENV[
|
298
|
-
info[:region] = ENV.fetch(
|
299
|
-
info[:function_name] = ENV.fetch(
|
300
|
-
info[:function_signature_type] = ENV.fetch(
|
327
|
+
if ENV["GOOGLE_CLOUD_PROJECT"]
|
328
|
+
info[:platform] = "gcp"
|
329
|
+
info[:project] = ENV["GOOGLE_CLOUD_PROJECT"]
|
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)
|
301
333
|
end
|
302
334
|
|
303
335
|
# App Engine
|
304
|
-
if ENV[
|
305
|
-
info[:platform] =
|
306
|
-
info[:application] = ENV[
|
307
|
-
info[:service] = ENV.fetch(
|
308
|
-
info[:version] = ENV.fetch(
|
309
|
-
info[:runtime] = ENV.fetch(
|
336
|
+
if ENV["GAE_APPLICATION"]
|
337
|
+
info[:platform] = "gcp_app_engine"
|
338
|
+
info[:application] = ENV["GAE_APPLICATION"]
|
339
|
+
info[:service] = ENV.fetch("GAE_SERVICE", nil)
|
340
|
+
info[:version] = ENV.fetch("GAE_VERSION", nil)
|
341
|
+
info[:runtime] = ENV.fetch("GAE_RUNTIME", nil)
|
310
342
|
end
|
311
343
|
|
312
344
|
info
|
@@ -315,12 +347,12 @@ module Lapsoss
|
|
315
347
|
def detect_azure_info
|
316
348
|
info = {}
|
317
349
|
|
318
|
-
if ENV[
|
319
|
-
info[:platform] =
|
320
|
-
info[:site_name] = ENV[
|
321
|
-
info[:resource_group] = ENV.fetch(
|
322
|
-
info[:subscription_id] = ENV.fetch(
|
323
|
-
info[:sku] = ENV.fetch(
|
350
|
+
if ENV["WEBSITE_SITE_NAME"]
|
351
|
+
info[:platform] = "azure"
|
352
|
+
info[:site_name] = ENV["WEBSITE_SITE_NAME"]
|
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)
|
324
356
|
end
|
325
357
|
|
326
358
|
info
|
@@ -329,11 +361,11 @@ module Lapsoss
|
|
329
361
|
def detect_docker_info
|
330
362
|
info = {}
|
331
363
|
|
332
|
-
if ENV[
|
333
|
-
info[:platform] =
|
334
|
-
info[:container_id] = ENV[
|
335
|
-
info[:image] = ENV.fetch(
|
336
|
-
info[:tag] = ENV.fetch(
|
364
|
+
if ENV["DOCKER_CONTAINER_ID"] || File.exist?("/.dockerenv")
|
365
|
+
info[:platform] = "docker"
|
366
|
+
info[:container_id] = ENV["DOCKER_CONTAINER_ID"]
|
367
|
+
info[:image] = ENV.fetch("DOCKER_IMAGE", nil)
|
368
|
+
info[:tag] = ENV.fetch("DOCKER_TAG", nil)
|
337
369
|
end
|
338
370
|
|
339
371
|
info
|
@@ -342,13 +374,13 @@ module Lapsoss
|
|
342
374
|
def detect_kubernetes_info
|
343
375
|
info = {}
|
344
376
|
|
345
|
-
if ENV[
|
346
|
-
info[:platform] =
|
347
|
-
info[:namespace] = ENV.fetch(
|
348
|
-
info[:pod_name] = ENV.fetch(
|
349
|
-
info[:service_account] = ENV.fetch(
|
350
|
-
info[:cluster_name] = ENV.fetch(
|
351
|
-
info[:node_name] = ENV.fetch(
|
377
|
+
if ENV["KUBERNETES_SERVICE_HOST"]
|
378
|
+
info[:platform] = "kubernetes"
|
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)
|
352
384
|
end
|
353
385
|
|
354
386
|
info
|
@@ -363,7 +395,7 @@ module Lapsoss
|
|
363
395
|
|
364
396
|
def sanitize_remote_url(url)
|
365
397
|
# Remove credentials from Git URLs
|
366
|
-
url.gsub(%r{://[^@/]+@},
|
398
|
+
url.gsub(%r{://[^@/]+@}, "://")
|
367
399
|
end
|
368
400
|
|
369
401
|
def parse_time(time_str)
|
@@ -371,11 +403,11 @@ module Lapsoss
|
|
371
403
|
|
372
404
|
# Try different time formats
|
373
405
|
formats = [
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
406
|
+
"%Y-%m-%dT%H:%M:%S%z", # ISO 8601 with timezone
|
407
|
+
"%Y-%m-%dT%H:%M:%SZ", # ISO 8601 UTC
|
408
|
+
"%Y-%m-%d %H:%M:%S %z", # Standard format with timezone
|
409
|
+
"%Y-%m-%d %H:%M:%S", # Standard format without timezone
|
410
|
+
"%s" # Unix timestamp
|
379
411
|
]
|
380
412
|
|
381
413
|
formats.each do |format|
|
@@ -413,7 +445,7 @@ module Lapsoss
|
|
413
445
|
|
414
446
|
# If we have components, join them
|
415
447
|
if components.any?
|
416
|
-
release_id = components.join(
|
448
|
+
release_id = components.join("-")
|
417
449
|
# Truncate if too long
|
418
450
|
release_id.length > 64 ? release_id[0, 64] : release_id
|
419
451
|
else
|
@@ -423,109 +455,4 @@ module Lapsoss
|
|
423
455
|
end
|
424
456
|
end
|
425
457
|
end
|
426
|
-
|
427
|
-
# Built-in release providers for common scenarios
|
428
|
-
class ReleaseProviders
|
429
|
-
def self.from_file(file_path)
|
430
|
-
lambda do
|
431
|
-
return nil unless File.exist?(file_path)
|
432
|
-
|
433
|
-
content = File.read(file_path).strip
|
434
|
-
return nil if content.empty?
|
435
|
-
|
436
|
-
# Try to parse as JSON first
|
437
|
-
begin
|
438
|
-
JSON.parse(content)
|
439
|
-
rescue JSON::ParserError
|
440
|
-
# Treat as plain text version
|
441
|
-
{ version: content }
|
442
|
-
end
|
443
|
-
end
|
444
|
-
end
|
445
|
-
|
446
|
-
def self.from_ruby_constant(constant_name)
|
447
|
-
lambda do
|
448
|
-
constant = Object.const_get(constant_name)
|
449
|
-
{ version: constant.to_s }
|
450
|
-
rescue NameError
|
451
|
-
nil
|
452
|
-
end
|
453
|
-
end
|
454
|
-
|
455
|
-
def self.from_gemfile_lock
|
456
|
-
lambda do
|
457
|
-
return nil unless File.exist?('Gemfile.lock')
|
458
|
-
|
459
|
-
content = File.read('Gemfile.lock')
|
460
|
-
|
461
|
-
# Extract gems with versions
|
462
|
-
gems = {}
|
463
|
-
content.scan(/^\s{4}(\w+)\s+\(([^)]+)\)/).each do |name, version|
|
464
|
-
gems[name] = version
|
465
|
-
end
|
466
|
-
|
467
|
-
{ gems: gems }
|
468
|
-
end
|
469
|
-
end
|
470
|
-
|
471
|
-
def self.from_package_json
|
472
|
-
lambda do
|
473
|
-
return nil unless File.exist?('package.json')
|
474
|
-
|
475
|
-
begin
|
476
|
-
package_info = JSON.parse(File.read('package.json'))
|
477
|
-
{
|
478
|
-
version: package_info['version'],
|
479
|
-
name: package_info['name'],
|
480
|
-
dependencies: package_info['dependencies']&.keys
|
481
|
-
}.compact
|
482
|
-
rescue JSON::ParserError
|
483
|
-
nil
|
484
|
-
end
|
485
|
-
end
|
486
|
-
end
|
487
|
-
|
488
|
-
def self.from_rails_application
|
489
|
-
lambda do
|
490
|
-
return nil unless defined?(Rails) && Rails.respond_to?(:application)
|
491
|
-
|
492
|
-
app = Rails.application
|
493
|
-
return nil unless app
|
494
|
-
|
495
|
-
info = {
|
496
|
-
rails_version: Rails.version,
|
497
|
-
environment: Rails.env,
|
498
|
-
root: Rails.root.to_s
|
499
|
-
}
|
500
|
-
|
501
|
-
# Get application version if defined
|
502
|
-
info[:app_version] = app.class.version if app.class.respond_to?(:version)
|
503
|
-
|
504
|
-
# Get application name
|
505
|
-
info[:app_name] = app.class.name if app.class.respond_to?(:name)
|
506
|
-
|
507
|
-
info
|
508
|
-
end
|
509
|
-
end
|
510
|
-
|
511
|
-
def self.from_capistrano
|
512
|
-
lambda do
|
513
|
-
# Check for Capistrano deployment files
|
514
|
-
%w[REVISION current/REVISION].each do |file|
|
515
|
-
next unless File.exist?(file)
|
516
|
-
|
517
|
-
revision = File.read(file).strip
|
518
|
-
next if revision.empty?
|
519
|
-
|
520
|
-
return {
|
521
|
-
revision: revision,
|
522
|
-
deployed_at: File.mtime(file),
|
523
|
-
deployment_method: 'capistrano'
|
524
|
-
}
|
525
|
-
end
|
526
|
-
|
527
|
-
nil
|
528
|
-
end
|
529
|
-
end
|
530
|
-
end
|
531
458
|
end
|
@@ -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
|