lapsoss 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/lapsoss/adapters/appsignal_adapter.rb +25 -26
- data/lib/lapsoss/adapters/base.rb +2 -2
- data/lib/lapsoss/adapters/bugsnag_adapter.rb +12 -0
- data/lib/lapsoss/adapters/insight_hub_adapter.rb +29 -32
- data/lib/lapsoss/adapters/logger_adapter.rb +8 -8
- data/lib/lapsoss/adapters/rollbar_adapter.rb +27 -28
- data/lib/lapsoss/adapters/sentry_adapter.rb +23 -29
- data/lib/lapsoss/backtrace_frame.rb +21 -31
- data/lib/lapsoss/backtrace_processor.rb +28 -31
- data/lib/lapsoss/client.rb +4 -6
- data/lib/lapsoss/configuration.rb +48 -41
- data/lib/lapsoss/current.rb +1 -1
- data/lib/lapsoss/event.rb +6 -5
- data/lib/lapsoss/{exclusions.rb → exclusion_filter.rb} +56 -56
- data/lib/lapsoss/fingerprinter.rb +33 -37
- data/lib/lapsoss/http_client.rb +45 -11
- data/lib/lapsoss/middleware.rb +35 -43
- data/lib/lapsoss/pipeline.rb +6 -10
- data/lib/lapsoss/railtie.rb +9 -9
- data/lib/lapsoss/registry.rb +23 -22
- data/lib/lapsoss/release_tracker.rb +160 -182
- data/lib/lapsoss/router.rb +3 -5
- data/lib/lapsoss/sampling.rb +49 -53
- data/lib/lapsoss/scope.rb +6 -10
- data/lib/lapsoss/scrubber.rb +22 -24
- data/lib/lapsoss/user_context.rb +78 -85
- data/lib/lapsoss/validators.rb +13 -26
- data/lib/lapsoss/version.rb +1 -1
- data/lib/lapsoss.rb +12 -25
- metadata +59 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 387dbefc113baa7968d4343b8b5135dca057581757056acf740a4234d8fcab4a
|
4
|
+
data.tar.gz: 3d304210fbe0a71c3679be91cd4e867a33966dab2b8f10e441c11f7a3465d9c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a166ab065134b2a6f84d17de91a7e67b64b8131e5a1d2380e4411cc21eb34692e5da56a531c89b04b9a093782b3b9773ad338769e384adb90155bb1ddceeb9cc
|
7
|
+
data.tar.gz: e5f4cf7fcc987d357a7d03b426bd120090c6190cbe37808139b53b3e939d6e27ecd75a72cef6e4d1ddff14bfb9d5c6848ca0b24f3b752b705a58aca9dde64a9c
|
@@ -1,21 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require_relative "../http_client"
|
3
|
+
require 'json'
|
4
|
+
require 'socket'
|
6
5
|
|
7
6
|
module Lapsoss
|
8
7
|
module Adapters
|
9
8
|
class AppsignalAdapter < Base
|
10
|
-
PUSH_API_URI =
|
11
|
-
ERRORS_API_URI =
|
12
|
-
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'
|
13
12
|
|
14
13
|
def initialize(name, settings = {})
|
15
|
-
super
|
16
|
-
@push_api_key = settings[:push_api_key] || ENV
|
17
|
-
@frontend_api_key = settings[:frontend_api_key] || ENV
|
18
|
-
@app_name = settings[:app_name] || ENV
|
14
|
+
super
|
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)
|
19
18
|
@environment = Lapsoss.configuration.environment
|
20
19
|
|
21
20
|
validate_settings!
|
@@ -31,7 +30,7 @@ module Lapsoss
|
|
31
30
|
return unless payload
|
32
31
|
|
33
32
|
path = "/errors?api_key=#{@frontend_api_key}"
|
34
|
-
headers = {
|
33
|
+
headers = { 'Content-Type' => JSON_CONTENT_TYPE }
|
35
34
|
|
36
35
|
begin
|
37
36
|
@errors_client.post(path, body: JSON.generate(payload), headers: headers)
|
@@ -65,7 +64,7 @@ module Lapsoss
|
|
65
64
|
def build_exception_payload(event)
|
66
65
|
{
|
67
66
|
timestamp: event.timestamp.to_i,
|
68
|
-
namespace: event.context[:namespace] ||
|
67
|
+
namespace: event.context[:namespace] || 'backend',
|
69
68
|
error: {
|
70
69
|
name: event.exception.class.name,
|
71
70
|
message: event.exception.message,
|
@@ -83,24 +82,24 @@ module Lapsoss
|
|
83
82
|
# Instead of creating fake exceptions, we'll structure the message properly
|
84
83
|
# but clearly indicate it's a log message, not an exception
|
85
84
|
|
86
|
-
unless [
|
85
|
+
unless %i[error fatal critical].include?(event.level)
|
87
86
|
# Log when messages are dropped due to level filtering
|
88
87
|
Lapsoss.configuration.logger&.debug(
|
89
88
|
"[Lapsoss::AppsignalAdapter] Dropping message with level '#{event.level}' - " \
|
90
|
-
|
89
|
+
'AppSignal only supports :error, :fatal, and :critical levels for messages'
|
91
90
|
)
|
92
91
|
return nil
|
93
92
|
end
|
94
93
|
|
95
94
|
{
|
96
|
-
action: event.context[:action] ||
|
97
|
-
path: event.context[:path] ||
|
95
|
+
action: event.context[:action] || 'log_message',
|
96
|
+
path: event.context[:path] || '/',
|
98
97
|
exception: {
|
99
98
|
# AppSignal requires exception format for messages - this isn't a real exception
|
100
99
|
# but rather a way to send structured log messages through their error API
|
101
|
-
name:
|
100
|
+
name: 'LogMessage', # Clear indication this is a log message
|
102
101
|
message: event.message,
|
103
|
-
backtrace: []
|
102
|
+
backtrace: [] # No fake backtrace for log messages
|
104
103
|
},
|
105
104
|
tags: stringify_hash(event.context[:tags]),
|
106
105
|
params: stringify_hash(event.context[:params]),
|
@@ -111,9 +110,9 @@ module Lapsoss
|
|
111
110
|
|
112
111
|
def build_environment_context(event)
|
113
112
|
{
|
114
|
-
|
115
|
-
|
116
|
-
|
113
|
+
'hostname' => Socket.gethostname,
|
114
|
+
'app_name' => @app_name,
|
115
|
+
'environment' => @environment
|
117
116
|
}.merge(stringify_hash(event.context[:environment] || {}))
|
118
117
|
end
|
119
118
|
|
@@ -123,13 +122,13 @@ module Lapsoss
|
|
123
122
|
|
124
123
|
def validate_settings!
|
125
124
|
unless @push_api_key || @frontend_api_key
|
126
|
-
raise ValidationError,
|
125
|
+
raise ValidationError, 'AppSignal API key is required (either push_api_key or frontend_api_key)'
|
127
126
|
end
|
128
127
|
|
129
|
-
validate_api_key!(@push_api_key,
|
130
|
-
validate_api_key!(@frontend_api_key,
|
131
|
-
validate_presence!(@app_name,
|
132
|
-
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
|
133
132
|
end
|
134
133
|
end
|
135
134
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lapsoss
|
4
|
+
module Adapters
|
5
|
+
# Bugsnag adapter - backwards compatibility with InsightHub adapter
|
6
|
+
# This allows users to configure with :bugsnag type but uses InsightHub implementation
|
7
|
+
# The InsightHub adapter already checks for BUGSNAG_API_KEY environment variable
|
8
|
+
class BugsnagAdapter < InsightHubAdapter
|
9
|
+
# Inherits all functionality from InsightHubAdapter
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -1,25 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require_relative "../http_client"
|
6
|
-
require_relative "../backtrace_processor"
|
3
|
+
require 'json'
|
4
|
+
require 'socket'
|
7
5
|
|
8
6
|
module Lapsoss
|
9
7
|
module Adapters
|
10
8
|
# Adapter for Insight Hub (formerly Bugsnag)
|
11
9
|
# Note: The API endpoints still use bugsnag.com domains for backwards compatibility
|
12
10
|
class InsightHubAdapter < Base
|
13
|
-
NOTIFY_URI =
|
14
|
-
SESSION_URI =
|
15
|
-
JSON_CONTENT_TYPE =
|
11
|
+
NOTIFY_URI = 'https://notify.bugsnag.com'
|
12
|
+
SESSION_URI = 'https://sessions.bugsnag.com'
|
13
|
+
JSON_CONTENT_TYPE = 'application/json'
|
16
14
|
|
17
15
|
def initialize(name, settings = {})
|
18
|
-
super
|
19
|
-
@api_key = settings[:api_key] || ENV[
|
16
|
+
super
|
17
|
+
@api_key = settings[:api_key] || ENV['INSIGHT_HUB_API_KEY'] || ENV.fetch('BUGSNAG_API_KEY', nil)
|
20
18
|
@release_stage = settings[:release_stage] || Lapsoss.configuration.environment
|
21
19
|
@app_version = settings[:app_version]
|
22
|
-
@app_type = settings[:app_type] ||
|
20
|
+
@app_type = settings[:app_type] || 'ruby'
|
23
21
|
|
24
22
|
validate_settings!
|
25
23
|
|
@@ -35,14 +33,14 @@ module Lapsoss
|
|
35
33
|
return unless payload
|
36
34
|
|
37
35
|
headers = {
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
36
|
+
'Content-Type' => JSON_CONTENT_TYPE,
|
37
|
+
'Bugsnag-Api-Key' => @api_key,
|
38
|
+
'Bugsnag-Payload-Version' => '5.0',
|
39
|
+
'Bugsnag-Sent-At' => Time.now.utc.iso8601
|
42
40
|
}
|
43
41
|
|
44
42
|
begin
|
45
|
-
@notify_client.post(
|
43
|
+
@notify_client.post('/', body: JSON.generate(payload), headers: headers)
|
46
44
|
rescue DeliveryError => e
|
47
45
|
# Log the error and potentially notify error handler
|
48
46
|
Lapsoss.configuration.logger&.error("[Lapsoss::InsightHubAdapter] Failed to deliver event: #{e.message}")
|
@@ -72,11 +70,11 @@ module Lapsoss
|
|
72
70
|
def build_payload(event)
|
73
71
|
{
|
74
72
|
apiKey: @api_key,
|
75
|
-
payloadVersion:
|
73
|
+
payloadVersion: '5.0',
|
76
74
|
notifier: {
|
77
|
-
name:
|
75
|
+
name: 'Lapsoss',
|
78
76
|
version: Lapsoss::VERSION,
|
79
|
-
url:
|
77
|
+
url: 'https://github.com/seuros/lapsoss'
|
80
78
|
},
|
81
79
|
events: [build_event(event)]
|
82
80
|
}
|
@@ -98,7 +96,7 @@ module Lapsoss
|
|
98
96
|
severity: map_severity(event.level),
|
99
97
|
unhandled: event.context[:unhandled] || false,
|
100
98
|
severityReason: {
|
101
|
-
type: event.context[:unhandled] ?
|
99
|
+
type: event.context[:unhandled] ? 'unhandledException' : 'handledException'
|
102
100
|
},
|
103
101
|
user: build_user(event),
|
104
102
|
context: event.context[:context],
|
@@ -121,7 +119,7 @@ module Lapsoss
|
|
121
119
|
errorClass: event.exception.class.name,
|
122
120
|
message: event.exception.message,
|
123
121
|
stacktrace: build_stacktrace(event.exception),
|
124
|
-
type:
|
122
|
+
type: 'ruby'
|
125
123
|
}]
|
126
124
|
}
|
127
125
|
end
|
@@ -131,10 +129,10 @@ module Lapsoss
|
|
131
129
|
exceptions: [{
|
132
130
|
# Insight Hub (Bugsnag) requires exception format for messages - this isn't a real exception
|
133
131
|
# but rather a way to send structured log messages through their error API
|
134
|
-
errorClass:
|
132
|
+
errorClass: 'LogMessage', # Clear indication this is a log message
|
135
133
|
message: event.message,
|
136
134
|
stacktrace: [], # No fake backtrace for log messages
|
137
|
-
type:
|
135
|
+
type: 'log' # Mark as log type, not ruby exception
|
138
136
|
}]
|
139
137
|
}
|
140
138
|
end
|
@@ -146,14 +144,13 @@ module Lapsoss
|
|
146
144
|
@backtrace_processor.format_frames(frames, :bugsnag)
|
147
145
|
end
|
148
146
|
|
149
|
-
|
150
147
|
def build_breadcrumbs(event)
|
151
148
|
breadcrumbs = event.context[:breadcrumbs] || []
|
152
149
|
breadcrumbs.map do |crumb|
|
153
150
|
{
|
154
151
|
timestamp: crumb[:timestamp]&.iso8601 || Time.now.utc.iso8601,
|
155
152
|
name: crumb[:message] || crumb[:name],
|
156
|
-
type: crumb[:type] ||
|
153
|
+
type: crumb[:type] || 'manual',
|
157
154
|
metaData: crumb[:data] || {}
|
158
155
|
}
|
159
156
|
end
|
@@ -172,18 +169,18 @@ module Lapsoss
|
|
172
169
|
|
173
170
|
def map_severity(level)
|
174
171
|
case level
|
175
|
-
when :debug, :info then
|
176
|
-
when :warning, :warn then
|
177
|
-
when :error, :fatal, :critical then
|
178
|
-
else
|
172
|
+
when :debug, :info then 'info'
|
173
|
+
when :warning, :warn then 'warning'
|
174
|
+
when :error, :fatal, :critical then 'error'
|
175
|
+
else 'error'
|
179
176
|
end
|
180
177
|
end
|
181
178
|
|
182
179
|
def validate_settings!
|
183
|
-
validate_presence!(@api_key,
|
184
|
-
validate_api_key!(@api_key,
|
185
|
-
validate_environment!(@app_version,
|
186
|
-
validate_type!(@release_stage, [String, Symbol],
|
180
|
+
validate_presence!(@api_key, 'Insight Hub API key')
|
181
|
+
validate_api_key!(@api_key, 'Insight Hub API key', format: :alphanumeric) if @api_key
|
182
|
+
validate_environment!(@app_version, 'Insight Hub app version') if @app_version
|
183
|
+
validate_type!(@release_stage, [String, Symbol], 'Insight Hub release stage') if @release_stage
|
187
184
|
end
|
188
185
|
end
|
189
186
|
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'logger'
|
4
4
|
|
5
5
|
module Lapsoss
|
6
6
|
module Adapters
|
7
7
|
class LoggerAdapter < Base
|
8
8
|
def initialize(name, settings = {})
|
9
9
|
@logger = settings[:logger] || Logger.new($stdout)
|
10
|
-
super
|
10
|
+
super
|
11
11
|
end
|
12
12
|
|
13
13
|
def capabilities
|
@@ -28,12 +28,12 @@ module Lapsoss
|
|
28
28
|
end
|
29
29
|
|
30
30
|
# Log breadcrumbs if present in the event context
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
31
|
+
return unless event.context[:breadcrumbs]&.any?
|
32
|
+
|
33
|
+
event.context[:breadcrumbs].each do |breadcrumb|
|
34
|
+
breadcrumb_msg = "[BREADCRUMB] [#{breadcrumb[:type].upcase}] #{breadcrumb[:message]}"
|
35
|
+
breadcrumb_msg += " | #{breadcrumb[:metadata].inspect}" unless breadcrumb[:metadata].empty?
|
36
|
+
@logger.debug(breadcrumb_msg)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -1,22 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require_relative "../http_client"
|
7
|
-
require_relative "../backtrace_processor"
|
3
|
+
require 'json'
|
4
|
+
require 'socket'
|
5
|
+
require 'securerandom'
|
8
6
|
|
9
7
|
module Lapsoss
|
10
8
|
module Adapters
|
11
9
|
class RollbarAdapter < Base
|
12
|
-
API_URI =
|
13
|
-
API_VERSION =
|
14
|
-
JSON_CONTENT_TYPE =
|
10
|
+
API_URI = 'https://api.rollbar.com'
|
11
|
+
API_VERSION = '1'
|
12
|
+
JSON_CONTENT_TYPE = 'application/json'
|
15
13
|
|
16
14
|
def initialize(name, settings = {})
|
17
|
-
super
|
18
|
-
@access_token = settings[:access_token] || ENV
|
19
|
-
@environment = settings[:environment] || Lapsoss.configuration.environment ||
|
15
|
+
super
|
16
|
+
@access_token = settings[:access_token] || ENV.fetch('ROLLBAR_ACCESS_TOKEN', nil)
|
17
|
+
@environment = settings[:environment] || Lapsoss.configuration.environment || 'development'
|
20
18
|
|
21
19
|
validate_settings!
|
22
20
|
|
@@ -32,8 +30,8 @@ module Lapsoss
|
|
32
30
|
|
33
31
|
path = "/api/#{API_VERSION}/item/"
|
34
32
|
headers = {
|
35
|
-
|
36
|
-
|
33
|
+
'Content-Type' => JSON_CONTENT_TYPE,
|
34
|
+
'X-Rollbar-Access-Token' => @access_token
|
37
35
|
}
|
38
36
|
|
39
37
|
begin
|
@@ -67,8 +65,8 @@ module Lapsoss
|
|
67
65
|
body: build_body(event),
|
68
66
|
level: map_level(event.level),
|
69
67
|
timestamp: event.timestamp.to_i,
|
70
|
-
platform:
|
71
|
-
language:
|
68
|
+
platform: 'ruby',
|
69
|
+
language: 'ruby',
|
72
70
|
framework: detect_framework,
|
73
71
|
context: event.context[:context],
|
74
72
|
request: event.context[:request],
|
@@ -77,7 +75,7 @@ module Lapsoss
|
|
77
75
|
title: event.message || (event.exception&.message if event.type == :exception),
|
78
76
|
uuid: SecureRandom.uuid,
|
79
77
|
notifier: {
|
80
|
-
name:
|
78
|
+
name: 'lapsoss',
|
81
79
|
version: Lapsoss::VERSION
|
82
80
|
}
|
83
81
|
}
|
@@ -132,25 +130,26 @@ module Lapsoss
|
|
132
130
|
|
133
131
|
def map_level(level)
|
134
132
|
case level
|
135
|
-
when :debug then
|
136
|
-
when :info then
|
137
|
-
when :warning, :warn then
|
138
|
-
when :error then
|
139
|
-
when :fatal, :critical then
|
140
|
-
else
|
133
|
+
when :debug then 'debug'
|
134
|
+
when :info then 'info'
|
135
|
+
when :warning, :warn then 'warning'
|
136
|
+
when :error then 'error'
|
137
|
+
when :fatal, :critical then 'critical'
|
138
|
+
else 'error'
|
141
139
|
end
|
142
140
|
end
|
143
141
|
|
144
142
|
def detect_framework
|
145
|
-
return
|
146
|
-
return
|
147
|
-
|
143
|
+
return 'rails' if defined?(Rails)
|
144
|
+
return 'sinatra' if defined?(Sinatra)
|
145
|
+
|
146
|
+
'ruby'
|
148
147
|
end
|
149
148
|
|
150
149
|
def validate_settings!
|
151
|
-
validate_presence!(@access_token,
|
152
|
-
validate_api_key!(@access_token,
|
153
|
-
validate_environment!(@environment,
|
150
|
+
validate_presence!(@access_token, 'Rollbar access token')
|
151
|
+
validate_api_key!(@access_token, 'Rollbar access token', format: :alphanumeric) if @access_token
|
152
|
+
validate_environment!(@environment, 'Rollbar environment') if @environment
|
154
153
|
end
|
155
154
|
end
|
156
155
|
end
|
@@ -1,19 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require_relative "../http_client"
|
5
|
-
require_relative "../backtrace_processor"
|
3
|
+
require 'securerandom'
|
6
4
|
|
7
5
|
module Lapsoss
|
8
6
|
module Adapters
|
9
7
|
class SentryAdapter < Base
|
10
8
|
PROTOCOL_VERSION = 7
|
11
|
-
CONTENT_TYPE =
|
9
|
+
CONTENT_TYPE = 'application/x-sentry-envelope'
|
12
10
|
GZIP_THRESHOLD = 1024 * 30 # 30KB
|
13
|
-
USER_AGENT = "lapsoss/#{Lapsoss::VERSION}"
|
11
|
+
USER_AGENT = "lapsoss/#{Lapsoss::VERSION}".freeze
|
14
12
|
|
15
13
|
def initialize(name, settings = {})
|
16
|
-
super
|
14
|
+
super
|
17
15
|
validate_settings!
|
18
16
|
return unless settings[:dsn]
|
19
17
|
|
@@ -62,11 +60,11 @@ module Lapsoss
|
|
62
60
|
header = {
|
63
61
|
event_id: event.context[:event_id] || SecureRandom.uuid,
|
64
62
|
sent_at: Time.now.iso8601,
|
65
|
-
sdk: { name:
|
63
|
+
sdk: { name: 'lapsoss', version: Lapsoss::VERSION }
|
66
64
|
}
|
67
65
|
|
68
|
-
item_type = event.type == :transaction ?
|
69
|
-
item_header = { type: item_type, content_type:
|
66
|
+
item_type = event.type == :transaction ? 'transaction' : 'event'
|
67
|
+
item_header = { type: item_type, content_type: 'application/json' }
|
70
68
|
item_payload = build_event_payload(event)
|
71
69
|
|
72
70
|
[header, item_header, item_payload]
|
@@ -90,7 +88,7 @@ module Lapsoss
|
|
90
88
|
|
91
89
|
def build_event_payload(event)
|
92
90
|
{
|
93
|
-
platform:
|
91
|
+
platform: 'ruby',
|
94
92
|
level: map_level(event.level),
|
95
93
|
timestamp: event.timestamp.to_f,
|
96
94
|
environment: @settings[:environment],
|
@@ -123,9 +121,9 @@ module Lapsoss
|
|
123
121
|
|
124
122
|
def build_headers(compressed)
|
125
123
|
{
|
126
|
-
|
127
|
-
|
128
|
-
|
124
|
+
'Content-Type' => CONTENT_TYPE,
|
125
|
+
'X-Sentry-Auth' => auth_header,
|
126
|
+
'Content-Encoding' => ('gzip' if compressed)
|
129
127
|
}.compact
|
130
128
|
end
|
131
129
|
|
@@ -144,16 +142,14 @@ module Lapsoss
|
|
144
142
|
# - Custom service: https://public_key@custom.com/api/v1/errors -> /api/v1/errors
|
145
143
|
|
146
144
|
# Extract project ID for auth header (usually the last path segment)
|
147
|
-
path_parts = uri.path.split(
|
148
|
-
project_id = path_parts.last ||
|
145
|
+
path_parts = uri.path.split('/').reject(&:empty?)
|
146
|
+
project_id = path_parts.last || 'unknown'
|
149
147
|
|
150
148
|
# Use the DSN path directly - this is what the service expects
|
151
149
|
api_path = uri.path
|
152
150
|
|
153
151
|
# For standard Sentry DSNs (just /project_id), build the envelope path
|
154
|
-
if path_parts.length == 1 && project_id.match?(/^\d+$/)
|
155
|
-
api_path = "/api/#{project_id}/envelope/"
|
156
|
-
end
|
152
|
+
api_path = "/api/#{project_id}/envelope/" if path_parts.length == 1 && project_id.match?(/^\d+$/)
|
157
153
|
|
158
154
|
{
|
159
155
|
public_key: uri.user,
|
@@ -176,21 +172,19 @@ module Lapsoss
|
|
176
172
|
|
177
173
|
def map_level(level)
|
178
174
|
case level
|
179
|
-
when :debug then
|
180
|
-
when :info then
|
181
|
-
when :warn, :warning then
|
182
|
-
when :error then
|
183
|
-
when :fatal then
|
184
|
-
else
|
175
|
+
when :debug then 'debug'
|
176
|
+
when :info then 'info'
|
177
|
+
when :warn, :warning then 'warning'
|
178
|
+
when :error then 'error'
|
179
|
+
when :fatal then 'fatal'
|
180
|
+
else 'info'
|
185
181
|
end
|
186
182
|
end
|
187
183
|
|
188
184
|
def validate_settings!
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
raise ValidationError, "Sentry DSN is required"
|
193
|
-
end
|
185
|
+
raise ValidationError, 'Sentry DSN is required' unless @settings[:dsn]
|
186
|
+
|
187
|
+
validate_dsn!(@settings[:dsn], 'Sentry DSN')
|
194
188
|
end
|
195
189
|
end
|
196
190
|
end
|
@@ -53,12 +53,12 @@ module Lapsoss
|
|
53
53
|
'/System/Library/Frameworks'
|
54
54
|
].freeze
|
55
55
|
|
56
|
-
attr_reader :filename, :line_number, :method_name, :in_app, :raw_line
|
57
|
-
|
56
|
+
attr_reader :filename, :line_number, :method_name, :in_app, :raw_line, :function, :module_name, :code_context,
|
57
|
+
:block_info
|
58
58
|
|
59
59
|
# Backward compatibility aliases
|
60
|
-
|
61
|
-
|
60
|
+
alias lineno line_number
|
61
|
+
alias raw raw_line
|
62
62
|
|
63
63
|
def initialize(raw_line, in_app_patterns: [], exclude_patterns: [], load_paths: [])
|
64
64
|
@raw_line = raw_line.to_s.strip
|
@@ -123,7 +123,7 @@ module Lapsoss
|
|
123
123
|
# Try to make path relative to load paths
|
124
124
|
@load_paths.each do |load_path|
|
125
125
|
if @filename.start_with?(load_path)
|
126
|
-
relative = @filename.sub(
|
126
|
+
relative = @filename.sub(%r{^#{Regexp.escape(load_path)}/?}, '')
|
127
127
|
return relative unless relative.empty?
|
128
128
|
end
|
129
129
|
end
|
@@ -134,23 +134,21 @@ module Lapsoss
|
|
134
134
|
private
|
135
135
|
|
136
136
|
def parse_backtrace_line
|
137
|
-
BACKTRACE_PATTERNS.each_with_index do |pattern,
|
137
|
+
BACKTRACE_PATTERNS.each_with_index do |pattern, _pattern_index|
|
138
138
|
match = @raw_line.match(pattern)
|
139
139
|
next unless match
|
140
140
|
|
141
141
|
@filename = match[:filename]
|
142
142
|
# Handle malformed line numbers - convert invalid numbers to 0
|
143
|
-
if match.names.include?('line') && match[:line]
|
144
|
-
|
145
|
-
|
146
|
-
@line_number = nil
|
147
|
-
end
|
143
|
+
@line_number = if match.names.include?('line') && match[:line]
|
144
|
+
match[:line].match?(/^\d+$/) ? match[:line].to_i : 0
|
145
|
+
end
|
148
146
|
@method_name = match.names.include?('method') ? match[:method] : nil
|
149
147
|
@native_gem = match.names.include?('native_gem') ? match[:native_gem] : nil
|
150
148
|
@block_level = match.names.include?('block_level') ? match[:block_level] : nil
|
151
149
|
|
152
150
|
# Set default method name for lines without methods (top-level execution)
|
153
|
-
@method_name =
|
151
|
+
@method_name = '<main>' if @method_name.nil?
|
154
152
|
|
155
153
|
process_method_info
|
156
154
|
return
|
@@ -159,7 +157,7 @@ module Lapsoss
|
|
159
157
|
# Fallback: treat entire line as filename if no pattern matches
|
160
158
|
@filename = @raw_line
|
161
159
|
@line_number = nil
|
162
|
-
@method_name =
|
160
|
+
@method_name = '<main>'
|
163
161
|
end
|
164
162
|
|
165
163
|
def process_method_info
|
@@ -199,9 +197,7 @@ module Lapsoss
|
|
199
197
|
}
|
200
198
|
|
201
199
|
# Extract the method that contains the block
|
202
|
-
if @method_name
|
203
|
-
@block_info[:in_method] = $1
|
204
|
-
end
|
200
|
+
@block_info[:in_method] = ::Regexp.last_match(1) if @method_name =~ /block (?:\([^)]+\)\s+)?in (.+)/
|
205
201
|
end
|
206
202
|
|
207
203
|
def determine_app_status
|
@@ -223,7 +219,7 @@ module Lapsoss
|
|
223
219
|
end
|
224
220
|
|
225
221
|
# Default heuristics: check for library indicators
|
226
|
-
@in_app =
|
222
|
+
@in_app = LIBRARY_INDICATORS.none? { |indicator| @filename.include?(indicator) }
|
227
223
|
|
228
224
|
# Special cases
|
229
225
|
@in_app = false if @native_gem # Native extensions are not app code
|
@@ -234,25 +230,19 @@ module Lapsoss
|
|
234
230
|
return unless @filename
|
235
231
|
|
236
232
|
# Expand relative paths
|
237
|
-
if @filename.start_with?('./')
|
238
|
-
@filename = File.expand_path(@filename)
|
239
|
-
end
|
233
|
+
@filename = File.expand_path(@filename) if @filename.start_with?('./')
|
240
234
|
|
241
235
|
# Handle Windows paths on Unix systems (for cross-platform stack traces)
|
242
|
-
if @filename.include?('\\') &&
|
243
|
-
@filename = @filename.tr('\\', '/')
|
244
|
-
end
|
236
|
+
@filename = @filename.tr('\\', '/') if @filename.include?('\\') && @filename.exclude?('/')
|
245
237
|
|
246
238
|
# Strip load paths to make traces more readable
|
247
|
-
|
248
|
-
original = @filename
|
249
|
-
@filename = relative_filename
|
239
|
+
return unless @load_paths.any?
|
250
240
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
241
|
+
original = @filename
|
242
|
+
@filename = relative_filename
|
243
|
+
|
244
|
+
# Keep absolute path if relative didn't work well
|
245
|
+
@filename = original if @filename.empty? || @filename == '.'
|
256
246
|
end
|
257
247
|
end
|
258
248
|
end
|