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.
- 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 +27 -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/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/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 +60 -7
- data/lib/lapsoss/middleware.rb +0 -345
- data/lib/lapsoss/sampling.rb +0 -328
@@ -1,20 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "json"
|
4
|
+
require "socket"
|
5
5
|
|
6
6
|
module Lapsoss
|
7
7
|
module Adapters
|
8
8
|
class AppsignalAdapter < Base
|
9
|
-
PUSH_API_URI =
|
10
|
-
ERRORS_API_URI =
|
11
|
-
JSON_CONTENT_TYPE =
|
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"
|
12
12
|
|
13
13
|
def initialize(name, settings = {})
|
14
14
|
super
|
15
|
-
@push_api_key = settings[:push_api_key] || ENV.fetch(
|
16
|
-
@frontend_api_key = settings[:frontend_api_key] || ENV.fetch(
|
17
|
-
@app_name = settings[:app_name] || ENV.fetch(
|
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)
|
18
18
|
@environment = Lapsoss.configuration.environment
|
19
19
|
|
20
20
|
validate_settings!
|
@@ -30,7 +30,7 @@ module Lapsoss
|
|
30
30
|
return unless payload
|
31
31
|
|
32
32
|
path = "/errors?api_key=#{@frontend_api_key}"
|
33
|
-
headers = {
|
33
|
+
headers = { "Content-Type" => JSON_CONTENT_TYPE }
|
34
34
|
|
35
35
|
begin
|
36
36
|
@errors_client.post(path, body: JSON.generate(payload), headers: headers)
|
@@ -64,7 +64,7 @@ module Lapsoss
|
|
64
64
|
def build_exception_payload(event)
|
65
65
|
{
|
66
66
|
timestamp: event.timestamp.to_i,
|
67
|
-
namespace: event.context[:namespace] ||
|
67
|
+
namespace: event.context[:namespace] || "backend",
|
68
68
|
error: {
|
69
69
|
name: event.exception.class.name,
|
70
70
|
message: event.exception.message,
|
@@ -86,18 +86,18 @@ module Lapsoss
|
|
86
86
|
# Log when messages are dropped due to level filtering
|
87
87
|
Lapsoss.configuration.logger&.debug(
|
88
88
|
"[Lapsoss::AppsignalAdapter] Dropping message with level '#{event.level}' - " \
|
89
|
-
|
89
|
+
"AppSignal only supports :error, :fatal, and :critical levels for messages"
|
90
90
|
)
|
91
91
|
return nil
|
92
92
|
end
|
93
93
|
|
94
94
|
{
|
95
|
-
action: event.context[:action] ||
|
96
|
-
path: event.context[:path] ||
|
95
|
+
action: event.context[:action] || "log_message",
|
96
|
+
path: event.context[:path] || "/",
|
97
97
|
exception: {
|
98
98
|
# AppSignal requires exception format for messages - this isn't a real exception
|
99
99
|
# but rather a way to send structured log messages through their error API
|
100
|
-
name:
|
100
|
+
name: "LogMessage", # Clear indication this is a log message
|
101
101
|
message: event.message,
|
102
102
|
backtrace: [] # No fake backtrace for log messages
|
103
103
|
},
|
@@ -110,9 +110,9 @@ module Lapsoss
|
|
110
110
|
|
111
111
|
def build_environment_context(event)
|
112
112
|
{
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
"hostname" => Socket.gethostname,
|
114
|
+
"app_name" => @app_name,
|
115
|
+
"environment" => @environment
|
116
116
|
}.merge(stringify_hash(event.context[:environment] || {}))
|
117
117
|
end
|
118
118
|
|
@@ -122,13 +122,13 @@ module Lapsoss
|
|
122
122
|
|
123
123
|
def validate_settings!
|
124
124
|
unless @push_api_key || @frontend_api_key
|
125
|
-
raise ValidationError,
|
125
|
+
raise ValidationError, "AppSignal API key is required (either push_api_key or frontend_api_key)"
|
126
126
|
end
|
127
127
|
|
128
|
-
validate_api_key!(@push_api_key,
|
129
|
-
validate_api_key!(@frontend_api_key,
|
130
|
-
validate_presence!(@app_name,
|
131
|
-
validate_environment!(@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
|
132
132
|
end
|
133
133
|
end
|
134
134
|
end
|
@@ -1,28 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require 'socket'
|
3
|
+
require "json"
|
5
4
|
|
6
5
|
module Lapsoss
|
7
6
|
module Adapters
|
8
|
-
# Adapter for Insight Hub (formerly Bugsnag)
|
9
|
-
# Note: The API endpoints still use bugsnag.com domains for backwards compatibility
|
10
7
|
class InsightHubAdapter < Base
|
11
|
-
|
12
|
-
|
13
|
-
JSON_CONTENT_TYPE = 'application/json'
|
8
|
+
API_URI = "https://notify.bugsnag.com"
|
9
|
+
JSON_CONTENT_TYPE = "application/json"
|
14
10
|
|
15
11
|
def initialize(name, settings = {})
|
16
12
|
super
|
17
|
-
@api_key = settings[:api_key] || ENV
|
18
|
-
@release_stage = settings[:release_stage] || Lapsoss.configuration.environment
|
19
|
-
@app_version = settings[:app_version]
|
20
|
-
@app_type = settings[:app_type] || 'ruby'
|
13
|
+
@api_key = settings[:api_key] || ENV.fetch("INSIGHT_HUB_API_KEY", nil)
|
21
14
|
|
22
15
|
validate_settings!
|
23
16
|
|
24
|
-
@
|
25
|
-
@session_client = create_http_client(SESSION_URI) if settings[:enable_sessions]
|
17
|
+
@client = create_http_client(API_URI)
|
26
18
|
@backtrace_processor = BacktraceProcessor.new
|
27
19
|
end
|
28
20
|
|
@@ -32,133 +24,113 @@ module Lapsoss
|
|
32
24
|
payload = build_payload(event)
|
33
25
|
return unless payload
|
34
26
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
}
|
41
|
-
|
42
|
-
begin
|
43
|
-
@notify_client.post('/', body: JSON.generate(payload), headers: headers)
|
44
|
-
rescue DeliveryError => e
|
45
|
-
# Log the error and potentially notify error handler
|
46
|
-
Lapsoss.configuration.logger&.error("[Lapsoss::InsightHubAdapter] Failed to deliver event: #{e.message}")
|
47
|
-
Lapsoss.configuration.error_handler&.call(e)
|
48
|
-
|
49
|
-
# Re-raise to let the caller know delivery failed
|
50
|
-
raise
|
51
|
-
end
|
52
|
-
end
|
27
|
+
response = @client.post("/", body: payload.to_json, headers: {
|
28
|
+
"Content-Type" => JSON_CONTENT_TYPE,
|
29
|
+
"Bugsnag-Api-Key" => @api_key,
|
30
|
+
"Bugsnag-Payload-Version" => "5"
|
31
|
+
})
|
53
32
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
super
|
33
|
+
handle_response(response, event)
|
34
|
+
rescue StandardError => e
|
35
|
+
handle_delivery_error(e)
|
58
36
|
end
|
59
37
|
|
60
38
|
def capabilities
|
61
39
|
super.merge(
|
62
|
-
code_context: true,
|
63
40
|
breadcrumbs: true,
|
41
|
+
user_tracking: true,
|
42
|
+
custom_context: true,
|
43
|
+
release_tracking: true,
|
64
44
|
sessions: true
|
65
45
|
)
|
66
46
|
end
|
67
47
|
|
48
|
+
def validate!
|
49
|
+
validate_settings!
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
68
53
|
private
|
69
54
|
|
70
55
|
def build_payload(event)
|
71
56
|
{
|
72
57
|
apiKey: @api_key,
|
73
|
-
payloadVersion:
|
58
|
+
payloadVersion: "5",
|
74
59
|
notifier: {
|
75
|
-
name:
|
60
|
+
name: "Lapsoss Ruby",
|
76
61
|
version: Lapsoss::VERSION,
|
77
|
-
url:
|
62
|
+
url: "https://github.com/yourusername/lapsoss"
|
78
63
|
},
|
79
|
-
events: [build_event(event)]
|
64
|
+
events: [ build_event(event) ]
|
80
65
|
}
|
81
66
|
end
|
82
67
|
|
83
68
|
def build_event(event)
|
84
|
-
|
85
|
-
app:
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
runtimeVersions: {
|
93
|
-
ruby: RUBY_VERSION
|
94
|
-
}
|
95
|
-
},
|
69
|
+
{
|
70
|
+
app: build_app_data(event),
|
71
|
+
device: build_device_data,
|
72
|
+
exceptions: build_exceptions(event),
|
73
|
+
breadcrumbs: build_breadcrumbs(event),
|
74
|
+
request: event.request_context,
|
75
|
+
user: build_user_data(event),
|
76
|
+
context: event.context[:custom]&.dig(:context) || "production",
|
96
77
|
severity: map_severity(event.level),
|
97
78
|
unhandled: event.context[:unhandled] || false,
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
user: build_user(event),
|
102
|
-
context: event.context[:context],
|
103
|
-
groupingHash: event.context[:fingerprint],
|
104
|
-
breadcrumbs: build_breadcrumbs(event),
|
105
|
-
metaData: event.context[:extra] || {}
|
106
|
-
}
|
79
|
+
metaData: event.context[:custom] || {}
|
80
|
+
}.compact
|
81
|
+
end
|
107
82
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
83
|
+
def build_exceptions(event)
|
84
|
+
return [] unless event.type == :exception && event.exception
|
85
|
+
|
86
|
+
[ {
|
87
|
+
errorClass: event.exception_type,
|
88
|
+
message: event.message,
|
89
|
+
stacktrace: build_stacktrace(event.exception),
|
90
|
+
type: "ruby"
|
91
|
+
} ]
|
114
92
|
end
|
115
93
|
|
116
|
-
def
|
117
|
-
|
118
|
-
|
119
|
-
errorClass: event.exception.class.name,
|
120
|
-
message: event.exception.message,
|
121
|
-
stacktrace: build_stacktrace(event.exception),
|
122
|
-
type: 'ruby'
|
123
|
-
}]
|
124
|
-
}
|
94
|
+
def build_stacktrace(exception)
|
95
|
+
frames = @backtrace_processor.process_exception(exception, follow_cause: true)
|
96
|
+
@backtrace_processor.format_frames(frames, :bugsnag)
|
125
97
|
end
|
126
98
|
|
127
|
-
def
|
99
|
+
def build_app_data(event)
|
128
100
|
{
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
stacktrace: [], # No fake backtrace for log messages
|
135
|
-
type: 'log' # Mark as log type, not ruby exception
|
136
|
-
}]
|
137
|
-
}
|
101
|
+
id: event.context[:app]&.dig(:id),
|
102
|
+
version: event.context[:release]&.dig(:version),
|
103
|
+
releaseStage: @environment || "production",
|
104
|
+
type: detect_app_type
|
105
|
+
}.compact
|
138
106
|
end
|
139
107
|
|
140
|
-
def
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
108
|
+
def build_device_data
|
109
|
+
{
|
110
|
+
hostname: Socket.gethostname,
|
111
|
+
osName: RUBY_PLATFORM,
|
112
|
+
runtimeVersions: {
|
113
|
+
ruby: RUBY_VERSION
|
114
|
+
}
|
115
|
+
}
|
145
116
|
end
|
146
117
|
|
147
118
|
def build_breadcrumbs(event)
|
148
119
|
breadcrumbs = event.context[:breadcrumbs] || []
|
120
|
+
|
149
121
|
breadcrumbs.map do |crumb|
|
150
122
|
{
|
151
|
-
timestamp: crumb[:timestamp]&.iso8601
|
152
|
-
name: crumb[:message]
|
153
|
-
type: crumb[:type] ||
|
123
|
+
timestamp: crumb[:timestamp]&.iso8601,
|
124
|
+
name: crumb[:message],
|
125
|
+
type: crumb[:type] || "manual",
|
154
126
|
metaData: crumb[:data] || {}
|
155
127
|
}
|
156
128
|
end
|
157
129
|
end
|
158
130
|
|
159
|
-
def
|
131
|
+
def build_user_data(event)
|
160
132
|
user = event.context[:user]
|
161
|
-
return unless user
|
133
|
+
return nil unless user
|
162
134
|
|
163
135
|
{
|
164
136
|
id: user[:id]&.to_s,
|
@@ -169,18 +141,50 @@ module Lapsoss
|
|
169
141
|
|
170
142
|
def map_severity(level)
|
171
143
|
case level
|
172
|
-
when :debug, :info then
|
173
|
-
when :warning
|
174
|
-
when :error, :fatal
|
175
|
-
else
|
144
|
+
when :debug, :info then "info"
|
145
|
+
when :warning then "warning"
|
146
|
+
when :error, :fatal then "error"
|
147
|
+
else "error"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def detect_app_type
|
152
|
+
return "rails" if defined?(Rails)
|
153
|
+
return "rack" if defined?(Rack)
|
154
|
+
|
155
|
+
"ruby"
|
156
|
+
end
|
157
|
+
|
158
|
+
def handle_response(response, _event)
|
159
|
+
case response.status
|
160
|
+
when 200
|
161
|
+
true
|
162
|
+
when 400
|
163
|
+
body = begin
|
164
|
+
JSON.parse(response.body)
|
165
|
+
rescue
|
166
|
+
{}
|
167
|
+
end
|
168
|
+
raise DeliveryError.new("Bad request: #{body['errors']&.join(', ')}", response: response)
|
169
|
+
when 401
|
170
|
+
raise DeliveryError.new("Unauthorized: Invalid API key", response: response)
|
171
|
+
when 413
|
172
|
+
raise DeliveryError.new("Payload too large", response: response)
|
173
|
+
when 429
|
174
|
+
raise DeliveryError.new("Rate limit exceeded", response: response)
|
175
|
+
else
|
176
|
+
raise DeliveryError.new("Unexpected response: #{response.status}", response: response)
|
176
177
|
end
|
177
178
|
end
|
178
179
|
|
179
180
|
def validate_settings!
|
180
|
-
validate_presence!(@api_key,
|
181
|
-
validate_api_key!(@api_key,
|
182
|
-
|
183
|
-
|
181
|
+
validate_presence!(@api_key, "Insight Hub API key")
|
182
|
+
validate_api_key!(@api_key, "Insight Hub API key", format: :alphanumeric) if @api_key
|
183
|
+
end
|
184
|
+
|
185
|
+
def handle_delivery_error(error, response = nil)
|
186
|
+
message = "Insight Hub delivery failed: #{error.message}"
|
187
|
+
raise DeliveryError.new(message, response: response, cause: error)
|
184
188
|
end
|
185
189
|
end
|
186
190
|
end
|
@@ -1,20 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "json"
|
4
|
+
require "socket"
|
5
|
+
require "securerandom"
|
6
6
|
|
7
7
|
module Lapsoss
|
8
8
|
module Adapters
|
9
9
|
class RollbarAdapter < Base
|
10
|
-
API_URI =
|
11
|
-
API_VERSION =
|
12
|
-
JSON_CONTENT_TYPE =
|
10
|
+
API_URI = "https://api.rollbar.com"
|
11
|
+
API_VERSION = "1"
|
12
|
+
JSON_CONTENT_TYPE = "application/json"
|
13
13
|
|
14
14
|
def initialize(name, settings = {})
|
15
15
|
super
|
16
|
-
@access_token = settings[:access_token] || ENV.fetch(
|
17
|
-
@environment = settings[:environment] || Lapsoss.configuration.environment ||
|
16
|
+
@access_token = settings[:access_token] || ENV.fetch("ROLLBAR_ACCESS_TOKEN", nil)
|
17
|
+
@environment = settings[:environment] || Lapsoss.configuration.environment || "development"
|
18
18
|
|
19
19
|
validate_settings!
|
20
20
|
|
@@ -28,62 +28,50 @@ module Lapsoss
|
|
28
28
|
payload = build_payload(event)
|
29
29
|
return unless payload
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
}
|
36
|
-
|
37
|
-
begin
|
38
|
-
@client.post(path, body: JSON.generate(payload), headers: headers)
|
39
|
-
rescue DeliveryError => e
|
40
|
-
# Log the error and potentially notify error handler
|
41
|
-
Lapsoss.configuration.logger&.error("[Lapsoss::RollbarAdapter] Failed to deliver event: #{e.message}")
|
42
|
-
Lapsoss.configuration.error_handler&.call(e)
|
43
|
-
|
44
|
-
# Re-raise to let the caller know delivery failed
|
45
|
-
raise
|
46
|
-
end
|
47
|
-
end
|
31
|
+
response = @client.post("/api/#{API_VERSION}/item/", body: payload.to_json, headers: {
|
32
|
+
"Content-Type" => JSON_CONTENT_TYPE,
|
33
|
+
"X-Rollbar-Access-Token" => @access_token
|
34
|
+
})
|
48
35
|
|
49
|
-
|
50
|
-
|
51
|
-
|
36
|
+
handle_response(response, event)
|
37
|
+
rescue StandardError => e
|
38
|
+
handle_delivery_error(e)
|
52
39
|
end
|
53
40
|
|
54
41
|
def capabilities
|
55
42
|
super.merge(
|
56
|
-
|
43
|
+
breadcrumbs: true,
|
44
|
+
user_tracking: true,
|
45
|
+
custom_context: true,
|
46
|
+
release_tracking: true
|
57
47
|
)
|
58
48
|
end
|
59
49
|
|
50
|
+
def validate!
|
51
|
+
validate_settings!
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
60
55
|
private
|
61
56
|
|
62
57
|
def build_payload(event)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
name: 'lapsoss',
|
79
|
-
version: Lapsoss::VERSION
|
58
|
+
{
|
59
|
+
access_token: @access_token,
|
60
|
+
data: {
|
61
|
+
environment: @environment,
|
62
|
+
body: build_body(event),
|
63
|
+
level: map_level(event.level),
|
64
|
+
timestamp: event.timestamp.to_i,
|
65
|
+
code_version: event.context[:release]&.dig(:commit_sha),
|
66
|
+
platform: "ruby",
|
67
|
+
language: "ruby",
|
68
|
+
framework: detect_framework,
|
69
|
+
server: build_server_data,
|
70
|
+
person: build_person_data(event),
|
71
|
+
request: event.request_context,
|
72
|
+
custom: event.context[:custom] || {}
|
80
73
|
}
|
81
74
|
}
|
82
|
-
|
83
|
-
# Only add fingerprint if it exists
|
84
|
-
data[:fingerprint] = event.context[:fingerprint].to_s if event.context[:fingerprint]
|
85
|
-
|
86
|
-
{ data: data }
|
87
75
|
end
|
88
76
|
|
89
77
|
def build_body(event)
|
@@ -93,9 +81,9 @@ module Lapsoss
|
|
93
81
|
trace: {
|
94
82
|
frames: build_backtrace_frames(event.exception),
|
95
83
|
exception: {
|
96
|
-
class: event.
|
97
|
-
message: event.
|
98
|
-
description: event.exception.
|
84
|
+
class: event.exception_type,
|
85
|
+
message: event.message,
|
86
|
+
description: event.exception.to_s
|
99
87
|
}
|
100
88
|
}
|
101
89
|
}
|
@@ -117,9 +105,9 @@ module Lapsoss
|
|
117
105
|
formatted_frames.reverse
|
118
106
|
end
|
119
107
|
|
120
|
-
def
|
108
|
+
def build_person_data(event)
|
121
109
|
user = event.context[:user]
|
122
|
-
return unless user
|
110
|
+
return nil unless user
|
123
111
|
|
124
112
|
{
|
125
113
|
id: user[:id]&.to_s,
|
@@ -128,28 +116,80 @@ module Lapsoss
|
|
128
116
|
}.compact
|
129
117
|
end
|
130
118
|
|
119
|
+
def build_server_data
|
120
|
+
{
|
121
|
+
host: Socket.gethostname,
|
122
|
+
root: defined?(Rails) ? Rails.root.to_s : Dir.pwd,
|
123
|
+
branch: git_branch,
|
124
|
+
code_version: git_sha
|
125
|
+
}.compact
|
126
|
+
end
|
127
|
+
|
131
128
|
def map_level(level)
|
132
129
|
case level
|
133
|
-
when :debug then
|
134
|
-
when :info then
|
135
|
-
when :warning
|
136
|
-
when :error then
|
137
|
-
when :fatal
|
138
|
-
else
|
130
|
+
when :debug then "debug"
|
131
|
+
when :info then "info"
|
132
|
+
when :warning then "warning"
|
133
|
+
when :error then "error"
|
134
|
+
when :fatal then "critical"
|
135
|
+
else "error"
|
139
136
|
end
|
140
137
|
end
|
141
138
|
|
142
139
|
def detect_framework
|
143
|
-
return
|
144
|
-
return
|
140
|
+
return "rails" if defined?(Rails)
|
141
|
+
return "sinatra" if defined?(Sinatra)
|
142
|
+
|
143
|
+
"ruby"
|
144
|
+
end
|
145
|
+
|
146
|
+
def git_branch
|
147
|
+
`git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip.presence
|
148
|
+
rescue StandardError
|
149
|
+
nil
|
150
|
+
end
|
151
|
+
|
152
|
+
def git_sha
|
153
|
+
`git rev-parse HEAD 2>/dev/null`.strip.presence
|
154
|
+
rescue StandardError
|
155
|
+
nil
|
156
|
+
end
|
145
157
|
|
146
|
-
|
158
|
+
def handle_response(response, event)
|
159
|
+
case response.code.to_i
|
160
|
+
when 200
|
161
|
+
true
|
162
|
+
when 400
|
163
|
+
handle_client_error(response, event)
|
164
|
+
when 401
|
165
|
+
raise DeliveryError.new("Unauthorized: Invalid access token", response: response)
|
166
|
+
when 429
|
167
|
+
raise DeliveryError.new("Rate limit exceeded", response: response)
|
168
|
+
else
|
169
|
+
raise DeliveryError.new("Unexpected response: #{response.code}", response: response)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def handle_client_error(response, _event)
|
174
|
+
body = begin
|
175
|
+
JSON.parse(response.body)
|
176
|
+
rescue
|
177
|
+
{}
|
178
|
+
end
|
179
|
+
error_msg = body["message"] || "Bad request"
|
180
|
+
|
181
|
+
raise DeliveryError.new("Client error: #{error_msg}", response: response)
|
147
182
|
end
|
148
183
|
|
149
184
|
def validate_settings!
|
150
|
-
validate_presence!(@access_token,
|
151
|
-
validate_api_key!(@access_token,
|
152
|
-
validate_environment!(@environment,
|
185
|
+
validate_presence!(@access_token, "Rollbar access token")
|
186
|
+
validate_api_key!(@access_token, "Rollbar access token", format: :alphanumeric) if @access_token
|
187
|
+
validate_environment!(@environment, "Rollbar environment") if @environment
|
188
|
+
end
|
189
|
+
|
190
|
+
def handle_delivery_error(error, response = nil)
|
191
|
+
message = "Rollbar delivery failed: #{error.message}"
|
192
|
+
raise DeliveryError.new(message, response: response, cause: error)
|
153
193
|
end
|
154
194
|
end
|
155
195
|
end
|