lapsoss 0.3.1 → 0.4.2
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 +71 -7
- data/lib/lapsoss/adapters/appsignal_adapter.rb +18 -12
- data/lib/lapsoss/adapters/base.rb +19 -0
- data/lib/lapsoss/adapters/concerns/envelope_builder.rb +127 -0
- data/lib/lapsoss/adapters/concerns/http_delivery.rb +130 -0
- data/lib/lapsoss/adapters/concerns/level_mapping.rb +65 -0
- data/lib/lapsoss/adapters/insight_hub_adapter.rb +21 -21
- data/lib/lapsoss/adapters/rollbar_adapter.rb +64 -122
- data/lib/lapsoss/adapters/sentry_adapter.rb +77 -143
- data/lib/lapsoss/backtrace_processor.rb +1 -1
- data/lib/lapsoss/breadcrumb.rb +59 -0
- data/lib/lapsoss/client.rb +3 -5
- data/lib/lapsoss/configuration.rb +26 -31
- data/lib/lapsoss/event.rb +90 -96
- data/lib/lapsoss/fingerprinter.rb +57 -49
- data/lib/lapsoss/merged_scope.rb +1 -6
- data/lib/lapsoss/middleware/release_tracker.rb +11 -98
- data/lib/lapsoss/pipeline_builder.rb +2 -2
- data/lib/lapsoss/rails_error_subscriber.rb +3 -4
- data/lib/lapsoss/rails_middleware.rb +2 -2
- data/lib/lapsoss/railtie.rb +13 -2
- data/lib/lapsoss/registry.rb +7 -7
- data/lib/lapsoss/router.rb +1 -3
- data/lib/lapsoss/scope.rb +1 -6
- data/lib/lapsoss/scrubber.rb +15 -148
- data/lib/lapsoss/validators.rb +63 -92
- data/lib/lapsoss/version.rb +1 -1
- metadata +8 -24
- data/CHANGELOG.md +0 -5
- data/lib/lapsoss/exclusion_configuration.rb +0 -30
- data/lib/lapsoss/exclusion_presets.rb +0 -249
- data/lib/lapsoss/middleware/sample_filter.rb +0 -23
- data/lib/lapsoss/middleware/sampling_middleware.rb +0 -18
- data/lib/lapsoss/middleware/user_context_enhancer.rb +0 -46
- data/lib/lapsoss/release_providers.rb +0 -110
- data/lib/lapsoss/sampling/adaptive_sampler.rb +0 -46
- data/lib/lapsoss/sampling/composite_sampler.rb +0 -26
- data/lib/lapsoss/sampling/consistent_hash_sampler.rb +0 -30
- data/lib/lapsoss/sampling/exception_type_sampler.rb +0 -44
- data/lib/lapsoss/sampling/health_based_sampler.rb +0 -19
- data/lib/lapsoss/sampling/sampling_factory.rb +0 -69
- data/lib/lapsoss/sampling/time_based_sampler.rb +0 -44
- data/lib/lapsoss/sampling/user_based_sampler.rb +0 -42
- data/lib/lapsoss/user_context.rb +0 -175
- data/lib/lapsoss/user_context_integrations.rb +0 -39
- data/lib/lapsoss/user_context_middleware.rb +0 -50
- data/lib/lapsoss/user_context_provider.rb +0 -93
- data/lib/lapsoss/utils.rb +0 -13
@@ -1,41 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "active_support/core_ext/object/blank"
|
4
4
|
require "socket"
|
5
|
-
require "securerandom"
|
6
5
|
|
7
6
|
module Lapsoss
|
8
7
|
module Adapters
|
9
8
|
class RollbarAdapter < Base
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
include Concerns::LevelMapping
|
10
|
+
include Concerns::EnvelopeBuilder
|
11
|
+
include Concerns::HttpDelivery
|
12
|
+
|
13
|
+
self.level_mapping_type = :rollbar
|
14
|
+
self.api_endpoint = "https://api.rollbar.com"
|
15
|
+
self.api_path = "/api/1/item/"
|
13
16
|
|
14
17
|
def initialize(name, settings = {})
|
15
18
|
super
|
16
|
-
@access_token = settings[:access_token] || ENV
|
17
|
-
@environment = settings[:environment] || Lapsoss.configuration.environment || "development"
|
18
|
-
|
19
|
-
validate_settings!
|
19
|
+
@access_token = settings[:access_token].presence || ENV["ROLLBAR_ACCESS_TOKEN"]
|
20
20
|
|
21
|
-
@
|
22
|
-
|
21
|
+
if @access_token.blank?
|
22
|
+
Lapsoss.configuration.logger&.warn "[Lapsoss::RollbarAdapter] No access token provided, adapter disabled"
|
23
|
+
@enabled = false
|
24
|
+
else
|
25
|
+
validate_api_key!(@access_token, "Rollbar access token", format: :alphanumeric)
|
26
|
+
end
|
23
27
|
end
|
24
28
|
|
25
29
|
def capture(event)
|
26
|
-
|
27
|
-
|
28
|
-
payload = build_payload(event)
|
29
|
-
return unless payload
|
30
|
-
|
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
|
-
})
|
35
|
-
|
36
|
-
handle_response(response, event)
|
37
|
-
rescue StandardError => e
|
38
|
-
handle_delivery_error(e)
|
30
|
+
deliver(event.scrubbed)
|
39
31
|
end
|
40
32
|
|
41
33
|
def capabilities
|
@@ -47,149 +39,99 @@ module Lapsoss
|
|
47
39
|
)
|
48
40
|
end
|
49
41
|
|
50
|
-
def validate!
|
51
|
-
validate_settings!
|
52
|
-
true
|
53
|
-
end
|
54
|
-
|
55
42
|
private
|
56
43
|
|
57
44
|
def build_payload(event)
|
58
45
|
{
|
59
46
|
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] || {}
|
73
|
-
}
|
47
|
+
data: build_rollbar_data(event)
|
74
48
|
}
|
75
49
|
end
|
76
50
|
|
77
|
-
def
|
51
|
+
def build_rollbar_data(event)
|
52
|
+
{
|
53
|
+
environment: event.environment.presence || @settings[:environment] || "production",
|
54
|
+
body: build_rollbar_body(event),
|
55
|
+
level: map_level(event.level),
|
56
|
+
timestamp: event.timestamp.to_i,
|
57
|
+
code_version: @settings[:release].presence || git_sha,
|
58
|
+
platform: "ruby",
|
59
|
+
language: "ruby",
|
60
|
+
framework: detect_framework,
|
61
|
+
server: {
|
62
|
+
host: Socket.gethostname,
|
63
|
+
root: Rails.root.to_s.presence || Dir.pwd,
|
64
|
+
branch: git_branch,
|
65
|
+
code_version: git_sha
|
66
|
+
}.compact_blank,
|
67
|
+
person: build_person_data(event),
|
68
|
+
request: event.request_context,
|
69
|
+
custom: event.extra
|
70
|
+
}.compact_blank
|
71
|
+
end
|
72
|
+
|
73
|
+
def build_rollbar_body(event)
|
78
74
|
case event.type
|
79
|
-
|
75
|
+
in :exception if event.has_exception?
|
80
76
|
{
|
81
77
|
trace: {
|
82
|
-
frames:
|
78
|
+
frames: format_backtrace_frames(event),
|
83
79
|
exception: {
|
84
80
|
class: event.exception_type,
|
85
|
-
message: event.
|
81
|
+
message: event.exception_message,
|
86
82
|
description: event.exception.to_s
|
87
83
|
}
|
88
84
|
}
|
89
85
|
}
|
90
|
-
|
86
|
+
in :message
|
91
87
|
{
|
92
88
|
message: {
|
93
89
|
body: event.message
|
94
90
|
}
|
95
91
|
}
|
92
|
+
else
|
93
|
+
{ message: { body: event.message || "Unknown event" } }
|
96
94
|
end
|
97
95
|
end
|
98
96
|
|
99
|
-
def
|
100
|
-
return [] unless
|
97
|
+
def format_backtrace_frames(event)
|
98
|
+
return [] unless event.has_backtrace?
|
101
99
|
|
102
|
-
frames
|
103
|
-
|
104
|
-
|
105
|
-
|
100
|
+
# Rollbar expects frames in reverse order
|
101
|
+
event.backtrace_frames.map do |frame|
|
102
|
+
{
|
103
|
+
filename: frame.filename,
|
104
|
+
lineno: frame.lineno,
|
105
|
+
method: frame.method_name,
|
106
|
+
code: frame.code_context && frame.code_context[:context_line]
|
107
|
+
}.compact_blank
|
108
|
+
end.reverse
|
106
109
|
end
|
107
110
|
|
108
111
|
def build_person_data(event)
|
109
|
-
user = event.
|
110
|
-
return nil
|
112
|
+
user = event.user_context
|
113
|
+
return nil if user.blank?
|
111
114
|
|
112
115
|
{
|
113
116
|
id: user[:id]&.to_s,
|
114
117
|
username: user[:username],
|
115
118
|
email: user[:email]
|
116
|
-
}.
|
117
|
-
end
|
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
|
-
|
128
|
-
def map_level(level)
|
129
|
-
case level
|
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"
|
136
|
-
end
|
119
|
+
}.compact_blank.presence
|
137
120
|
end
|
138
121
|
|
139
122
|
def detect_framework
|
140
123
|
return "rails" if defined?(Rails)
|
141
124
|
return "sinatra" if defined?(Sinatra)
|
142
|
-
|
143
125
|
"ruby"
|
144
126
|
end
|
145
127
|
|
146
|
-
def
|
147
|
-
|
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
|
157
|
-
|
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)
|
128
|
+
def adapter_specific_headers
|
129
|
+
{ "X-Rollbar-Access-Token" => @access_token }
|
182
130
|
end
|
183
131
|
|
132
|
+
# No longer need strict validation
|
184
133
|
def validate_settings!
|
185
|
-
|
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)
|
134
|
+
# Validation moved to initialize with logging
|
193
135
|
end
|
194
136
|
end
|
195
137
|
end
|
@@ -1,190 +1,124 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "active_support/core_ext/object/blank"
|
4
|
+
require "uri"
|
4
5
|
|
5
6
|
module Lapsoss
|
6
7
|
module Adapters
|
7
8
|
class SentryAdapter < Base
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
include Concerns::LevelMapping
|
10
|
+
include Concerns::EnvelopeBuilder
|
11
|
+
include Concerns::HttpDelivery
|
12
|
+
|
13
|
+
self.level_mapping_type = :sentry
|
14
|
+
self.envelope_format = :sentry_envelope
|
12
15
|
|
13
16
|
def initialize(name, settings = {})
|
14
17
|
super
|
15
|
-
validate_settings!
|
16
|
-
return unless settings[:dsn]
|
17
|
-
|
18
|
-
@dsn = parse_dsn(settings[:dsn])
|
19
|
-
@protocol_version = settings[:protocol_version] || PROTOCOL_VERSION
|
20
|
-
@client = create_http_client(sentry_api_uri)
|
21
|
-
@backtrace_processor = BacktraceProcessor.new
|
22
|
-
end
|
23
|
-
|
24
|
-
def capture(event)
|
25
|
-
return unless @client
|
26
|
-
|
27
|
-
envelope = build_envelope(event)
|
28
|
-
body, compressed = serialize_envelope(envelope)
|
29
|
-
|
30
|
-
headers = build_headers(compressed)
|
31
18
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
Lapsoss.configuration.error_handler&.call(e)
|
19
|
+
if settings[:dsn].blank?
|
20
|
+
Lapsoss.configuration.logger&.warn "[Lapsoss::SentryAdapter] No DSN provided, adapter disabled"
|
21
|
+
@enabled = false
|
22
|
+
return
|
23
|
+
end
|
38
24
|
|
39
|
-
|
40
|
-
|
25
|
+
if validate_dsn!(settings[:dsn], "Sentry DSN")
|
26
|
+
@dsn = parse_dsn(settings[:dsn])
|
27
|
+
setup_endpoint
|
28
|
+
else
|
29
|
+
@enabled = false
|
41
30
|
end
|
42
31
|
end
|
43
32
|
|
44
|
-
def
|
45
|
-
|
46
|
-
super
|
33
|
+
def capture(event)
|
34
|
+
deliver(event.scrubbed)
|
47
35
|
end
|
48
36
|
|
49
37
|
def capabilities
|
50
38
|
super.merge(
|
51
39
|
code_context: true,
|
52
|
-
breadcrumbs: true
|
40
|
+
breadcrumbs: true,
|
41
|
+
data_scrubbing: true
|
53
42
|
)
|
54
43
|
end
|
55
44
|
|
56
45
|
private
|
57
46
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
sent_at: Time.now.iso8601,
|
63
|
-
sdk: { name: "lapsoss", version: Lapsoss::VERSION }
|
64
|
-
}
|
65
|
-
|
66
|
-
item_type = event.type == :transaction ? "transaction" : "event"
|
67
|
-
item_header = { type: item_type, content_type: "application/json" }
|
68
|
-
item_payload = build_event_payload(event)
|
69
|
-
|
70
|
-
[ header, item_header, item_payload ]
|
71
|
-
end
|
72
|
-
|
73
|
-
def serialize_envelope(envelope)
|
74
|
-
header, item_header, item_payload = envelope
|
75
|
-
|
76
|
-
body = [
|
77
|
-
JSON.generate(header),
|
78
|
-
JSON.generate(item_header),
|
79
|
-
JSON.generate(item_payload)
|
80
|
-
].join("\n")
|
81
|
-
|
82
|
-
if body.bytesize >= GZIP_THRESHOLD
|
83
|
-
[ Zlib.gzip(body), true ]
|
84
|
-
else
|
85
|
-
[ body, false ]
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def build_event_payload(event)
|
90
|
-
{
|
91
|
-
platform: "ruby",
|
92
|
-
level: map_level(event.level),
|
93
|
-
timestamp: event.timestamp.to_f,
|
94
|
-
environment: @settings[:environment],
|
95
|
-
release: @settings[:release],
|
96
|
-
tags: event.context[:tags],
|
97
|
-
user: event.context[:user],
|
98
|
-
extra: event.context[:extra],
|
99
|
-
breadcrumbs: { values: event.context[:breadcrumbs] || [] }
|
100
|
-
}.merge(event_specific_payload(event))
|
101
|
-
end
|
102
|
-
|
103
|
-
def event_specific_payload(event)
|
104
|
-
case event.type
|
105
|
-
when :exception
|
106
|
-
{
|
107
|
-
exception: {
|
108
|
-
values: [ {
|
109
|
-
type: event.exception.class.name,
|
110
|
-
value: event.exception.message,
|
111
|
-
stacktrace: { frames: parse_backtrace(event.exception.backtrace) }
|
112
|
-
} ]
|
113
|
-
}
|
114
|
-
}
|
115
|
-
when :message
|
116
|
-
{ message: event.message }
|
117
|
-
else
|
118
|
-
{}
|
119
|
-
end
|
47
|
+
def setup_endpoint
|
48
|
+
uri = URI.parse(@settings[:dsn])
|
49
|
+
self.class.api_endpoint = "#{uri.scheme}://#{uri.host}:#{uri.port}"
|
50
|
+
self.class.api_path = build_api_path(uri)
|
120
51
|
end
|
121
52
|
|
122
|
-
def
|
123
|
-
|
124
|
-
|
125
|
-
"X-Sentry-Auth" => auth_header,
|
126
|
-
"Content-Encoding" => ("gzip" if compressed)
|
127
|
-
}.compact
|
128
|
-
end
|
53
|
+
def build_api_path(uri)
|
54
|
+
# Extract project ID from DSN path
|
55
|
+
project_id = uri.path.split("/").last
|
129
56
|
|
130
|
-
|
131
|
-
|
132
|
-
"Sentry sentry_version=#{@protocol_version}, sentry_client=#{USER_AGENT}, sentry_timestamp=#{timestamp}, sentry_key=#{@dsn[:public_key]}"
|
57
|
+
# Standard Sentry envelope endpoint
|
58
|
+
"/api/#{project_id}/envelope/"
|
133
59
|
end
|
134
60
|
|
135
61
|
def parse_dsn(dsn_string)
|
136
62
|
uri = URI.parse(dsn_string)
|
63
|
+
{
|
64
|
+
public_key: uri.user,
|
65
|
+
project_id: uri.path.split("/").last
|
66
|
+
}
|
67
|
+
end
|
137
68
|
|
138
|
-
|
139
|
-
|
140
|
-
#
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
project_id = path_parts.last || "unknown"
|
69
|
+
# Build Sentry-specific payload format
|
70
|
+
def build_payload(event)
|
71
|
+
# Sentry uses envelope format with headers and items
|
72
|
+
envelope_header = {
|
73
|
+
event_id: event.fingerprint.presence || SecureRandom.uuid,
|
74
|
+
sent_at: format_timestamp(event.timestamp),
|
75
|
+
sdk: sdk_info
|
76
|
+
}
|
147
77
|
|
148
|
-
|
149
|
-
|
78
|
+
item_header = {
|
79
|
+
type: event.type == :transaction ? "transaction" : "event",
|
80
|
+
content_type: "application/json"
|
81
|
+
}
|
150
82
|
|
151
|
-
|
152
|
-
api_path = "/api/#{project_id}/envelope/" if path_parts.length == 1 && project_id.match?(/^\d+$/)
|
83
|
+
item_payload = build_envelope_wrapper(event)
|
153
84
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
85
|
+
# Sentry envelope is newline-delimited JSON
|
86
|
+
[
|
87
|
+
ActiveSupport::JSON.encode(envelope_header),
|
88
|
+
ActiveSupport::JSON.encode(item_header),
|
89
|
+
ActiveSupport::JSON.encode(item_payload)
|
90
|
+
].join("\n")
|
159
91
|
end
|
160
92
|
|
161
|
-
|
162
|
-
|
163
|
-
|
93
|
+
# Override serialization for Sentry's envelope format
|
94
|
+
def serialize_payload(envelope_string)
|
95
|
+
# Sentry envelopes are already formatted, just compress if needed
|
96
|
+
if envelope_string.bytesize >= compress_threshold
|
97
|
+
[ ActiveSupport::Gzip.compress(envelope_string), true ]
|
98
|
+
else
|
99
|
+
[ envelope_string, false ]
|
100
|
+
end
|
164
101
|
end
|
165
102
|
|
166
|
-
def
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
103
|
+
def adapter_specific_headers
|
104
|
+
timestamp = Time.current.to_i
|
105
|
+
{
|
106
|
+
"X-Sentry-Auth" => [
|
107
|
+
"Sentry sentry_version=7",
|
108
|
+
"sentry_client=#{user_agent}",
|
109
|
+
"sentry_timestamp=#{timestamp}",
|
110
|
+
"sentry_key=#{@dsn[:public_key]}"
|
111
|
+
].join(", ")
|
112
|
+
}
|
171
113
|
end
|
172
114
|
|
173
|
-
def
|
174
|
-
|
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"
|
181
|
-
end
|
115
|
+
def build_delivery_headers(compressed: false, content_type: nil)
|
116
|
+
super(compressed: compressed, content_type: "application/x-sentry-envelope")
|
182
117
|
end
|
183
118
|
|
119
|
+
# No longer need strict validation
|
184
120
|
def validate_settings!
|
185
|
-
|
186
|
-
|
187
|
-
validate_dsn!(@settings[:dsn], "Sentry DSN")
|
121
|
+
# Validation moved to initialize with logging
|
188
122
|
end
|
189
123
|
end
|
190
124
|
end
|
@@ -338,7 +338,7 @@ module Lapsoss
|
|
338
338
|
paths << Dir.pwd
|
339
339
|
|
340
340
|
# Add gem paths
|
341
|
-
paths.concat(Gem.path.map {
|
341
|
+
paths.concat(Gem.path.map { File.join(_1, "gems") }) if defined?(Gem)
|
342
342
|
|
343
343
|
# Sort by length (longest first) for better matching
|
344
344
|
paths.uniq.sort_by(&:length).reverse
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lapsoss
|
4
|
+
module Breadcrumb
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# Canonical breadcrumb builder used by SDK
|
8
|
+
# message: String, type: Symbol, metadata: Hash, timestamp: Time (UTC)
|
9
|
+
def build(message, type: :default, metadata: {})
|
10
|
+
{
|
11
|
+
message: message.to_s,
|
12
|
+
type: type.to_sym,
|
13
|
+
metadata: metadata || {},
|
14
|
+
timestamp: Time.now.utc
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Normalize an incoming breadcrumb-like hash to the canonical structure
|
19
|
+
def normalize(crumb)
|
20
|
+
msg = crumb[:message] || crumb["message"]
|
21
|
+
type = crumb[:type] || crumb["type"] || :default
|
22
|
+
metadata = crumb[:metadata] || crumb["metadata"] || crumb[:data] || crumb["data"] || {}
|
23
|
+
ts = crumb[:timestamp] || crumb["timestamp"]
|
24
|
+
ts = ts.utc if ts.respond_to?(:utc)
|
25
|
+
|
26
|
+
{
|
27
|
+
message: msg.to_s,
|
28
|
+
type: type.to_sym,
|
29
|
+
metadata: metadata.is_a?(Hash) ? metadata : Hash(metadata),
|
30
|
+
timestamp: ts || Time.now.utc
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Adapter conversions
|
35
|
+
def for_sentry(crumbs)
|
36
|
+
crumbs.map do |c|
|
37
|
+
c = normalize(c)
|
38
|
+
{
|
39
|
+
timestamp: c[:timestamp].utc.iso8601,
|
40
|
+
message: c[:message],
|
41
|
+
type: c[:type].to_s,
|
42
|
+
data: c[:metadata]
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def for_insight_hub(crumbs)
|
48
|
+
crumbs.map do |c|
|
49
|
+
c = normalize(c)
|
50
|
+
{
|
51
|
+
timestamp: c[:timestamp].utc.iso8601,
|
52
|
+
name: c[:message],
|
53
|
+
type: c[:type].to_s,
|
54
|
+
metaData: c[:metadata]
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/lapsoss/client.rb
CHANGED
@@ -13,7 +13,7 @@ module Lapsoss
|
|
13
13
|
return unless @configuration.enabled
|
14
14
|
|
15
15
|
with_scope(context) do |scope|
|
16
|
-
event = Event.
|
16
|
+
event = Event.build(
|
17
17
|
type: :exception,
|
18
18
|
level: :error,
|
19
19
|
exception: exception,
|
@@ -27,7 +27,7 @@ module Lapsoss
|
|
27
27
|
return unless @configuration.enabled
|
28
28
|
|
29
29
|
with_scope(context) do |scope|
|
30
|
-
event = Event.
|
30
|
+
event = Event.build(
|
31
31
|
type: :message,
|
32
32
|
level: level,
|
33
33
|
message: message,
|
@@ -103,9 +103,7 @@ module Lapsoss
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def handle_capture_error(error)
|
106
|
-
|
107
|
-
|
108
|
-
@configuration.logger.error("[Lapsoss] Failed to capture event: #{error.message}")
|
106
|
+
@configuration.logger.error("Failed to capture event: #{error.message}")
|
109
107
|
end
|
110
108
|
end
|
111
109
|
end
|