lapsoss 0.4.5 → 0.4.6
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/concerns/http_delivery.rb +1 -1
- data/lib/lapsoss/adapters/sentry_adapter.rb +84 -1
- data/lib/lapsoss/backtrace_frame.rb +8 -2
- data/lib/lapsoss/backtrace_frame_factory.rb +6 -2
- data/lib/lapsoss/backtrace_processor.rb +4 -2
- data/lib/lapsoss/runtime_context.rb +70 -0
- data/lib/lapsoss/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc2f5c5b2a06b525bb921e683c87a201e93507c795b6cff7e4cddc33d3716880
|
4
|
+
data.tar.gz: 3d5bce60161f233758b0f39fdf54197ac7c5b0db921709ac3e1a4e54531bf669
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 569ae7372bbe9f3984c0cb1b13074e1442af9f34cb21e74124f04db1a62227a7ac08c8d856d08c739e9da05a799c288260b72e62e8e7df44f5fa853bfacf5d7b
|
7
|
+
data.tar.gz: 6e17e4ffdd5d757fb70414da87e188ee12dbc1469bb37ac978554c2fc5146e5f86ebb257daefe37748e3129140d402d34c517ac1c756aa56c56defdd41a159e9
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "active_support/core_ext/object/blank"
|
4
4
|
require "uri"
|
5
|
+
require "lapsoss/runtime_context"
|
5
6
|
|
6
7
|
module Lapsoss
|
7
8
|
module Adapters
|
@@ -80,7 +81,7 @@ module Lapsoss
|
|
80
81
|
content_type: "application/json"
|
81
82
|
}
|
82
83
|
|
83
|
-
item_payload =
|
84
|
+
item_payload = build_sentry_event(event)
|
84
85
|
|
85
86
|
# Sentry envelope is newline-delimited JSON
|
86
87
|
[
|
@@ -90,6 +91,88 @@ module Lapsoss
|
|
90
91
|
].join("\n")
|
91
92
|
end
|
92
93
|
|
94
|
+
# Build Sentry-compliant event structure
|
95
|
+
def build_sentry_event(event)
|
96
|
+
context = RuntimeContext.current
|
97
|
+
event_id = event.fingerprint.presence || SecureRandom.uuid
|
98
|
+
|
99
|
+
base_event = {
|
100
|
+
event_id: event_id,
|
101
|
+
timestamp: format_timestamp(event.timestamp),
|
102
|
+
platform: "ruby",
|
103
|
+
level: map_level(event.level),
|
104
|
+
environment: event.environment.presence || "production",
|
105
|
+
release: context.release,
|
106
|
+
server_name: context.server_name,
|
107
|
+
modules: context.modules,
|
108
|
+
contexts: context.to_contexts,
|
109
|
+
tags: event.tags.presence,
|
110
|
+
user: event.user_context.presence,
|
111
|
+
extra: event.extra.presence,
|
112
|
+
breadcrumbs: format_breadcrumbs(event.breadcrumbs),
|
113
|
+
sdk: {
|
114
|
+
name: "sentry.ruby",
|
115
|
+
version: Lapsoss::VERSION
|
116
|
+
}
|
117
|
+
}.compact_blank
|
118
|
+
|
119
|
+
# Add event-specific data
|
120
|
+
case event.type
|
121
|
+
when :exception
|
122
|
+
base_event.merge(build_exception_envelope(event))
|
123
|
+
when :message
|
124
|
+
base_event.merge(
|
125
|
+
message: event.message,
|
126
|
+
level: map_level(event.level)
|
127
|
+
)
|
128
|
+
else
|
129
|
+
base_event
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def build_exception_envelope(event)
|
134
|
+
{
|
135
|
+
exception: {
|
136
|
+
values: [ {
|
137
|
+
type: event.exception_type,
|
138
|
+
value: event.exception_message,
|
139
|
+
module: nil,
|
140
|
+
thread_id: Thread.current.object_id,
|
141
|
+
stacktrace: build_sentry_stacktrace(event),
|
142
|
+
mechanism: { type: "generic", handled: true }
|
143
|
+
} ]
|
144
|
+
},
|
145
|
+
threads: {
|
146
|
+
values: [ {
|
147
|
+
id: Thread.current.object_id,
|
148
|
+
name: Thread.current.name,
|
149
|
+
crashed: true,
|
150
|
+
current: true
|
151
|
+
} ]
|
152
|
+
}
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
def build_sentry_stacktrace(event)
|
157
|
+
return nil unless event.has_backtrace?
|
158
|
+
|
159
|
+
frames = event.backtrace_frames.map do |frame|
|
160
|
+
{
|
161
|
+
filename: frame.filename,
|
162
|
+
abs_path: frame.absolute_path || frame.filename,
|
163
|
+
function: frame.method_name || frame.function,
|
164
|
+
lineno: frame.line_number,
|
165
|
+
in_app: frame.in_app,
|
166
|
+
pre_context: frame.code_context&.dig(:pre_context),
|
167
|
+
context_line: frame.code_context&.dig(:context_line),
|
168
|
+
post_context: frame.code_context&.dig(:post_context)
|
169
|
+
}.compact
|
170
|
+
end
|
171
|
+
|
172
|
+
# Sentry expects frames in reverse order (oldest to newest)
|
173
|
+
{ frames: frames.reverse }
|
174
|
+
end
|
175
|
+
|
93
176
|
# Override serialization for Sentry's envelope format
|
94
177
|
def serialize_payload(envelope_string)
|
95
178
|
# Sentry envelopes are already formatted, just compress if needed
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module Lapsoss
|
4
4
|
BacktraceFrame = Data.define(
|
5
5
|
:filename,
|
6
|
+
:absolute_path,
|
6
7
|
:line_number,
|
7
8
|
:method_name,
|
8
9
|
:in_app,
|
@@ -19,6 +20,7 @@ module Lapsoss
|
|
19
20
|
def to_h
|
20
21
|
{
|
21
22
|
filename: filename,
|
23
|
+
absolute_path: absolute_path,
|
22
24
|
line_number: line_number,
|
23
25
|
method: method_name,
|
24
26
|
function: function,
|
@@ -30,9 +32,13 @@ module Lapsoss
|
|
30
32
|
end
|
31
33
|
|
32
34
|
def add_code_context(processor, context_lines = 3)
|
33
|
-
return unless
|
35
|
+
return unless line_number
|
34
36
|
|
35
|
-
|
37
|
+
# Use absolute path if available, otherwise try filename
|
38
|
+
path_to_read = absolute_path || filename
|
39
|
+
return unless path_to_read
|
40
|
+
|
41
|
+
with(code_context: processor.get_code_context(path_to_read, line_number, context_lines))
|
36
42
|
end
|
37
43
|
|
38
44
|
def valid?
|
@@ -75,10 +75,14 @@ module Lapsoss
|
|
75
75
|
filename, line_number, method_name, function, module_name, block_info = parse_line_components
|
76
76
|
|
77
77
|
in_app = determine_app_status(filename)
|
78
|
-
|
78
|
+
|
79
|
+
# Keep both absolute and normalized paths
|
80
|
+
absolute_path = filename
|
81
|
+
normalized_filename = normalize_path(filename) if filename
|
79
82
|
|
80
83
|
BacktraceFrame.new(
|
81
|
-
filename:
|
84
|
+
filename: normalized_filename,
|
85
|
+
absolute_path: absolute_path,
|
82
86
|
line_number: line_number,
|
83
87
|
method_name: method_name,
|
84
88
|
in_app: in_app,
|
@@ -196,8 +196,7 @@ module Lapsoss
|
|
196
196
|
|
197
197
|
# Get code context around a specific line number using ActiveSupport::Cache
|
198
198
|
def get_code_context(filename, line_number, context_lines = 3)
|
199
|
-
return nil unless filename
|
200
|
-
return nil if File.size(filename) > (@config[:max_file_size] || (1024 * 1024))
|
199
|
+
return nil unless filename
|
201
200
|
|
202
201
|
lines = @file_cache.fetch(filename) do
|
203
202
|
read_file_safely(filename)
|
@@ -229,6 +228,9 @@ module Lapsoss
|
|
229
228
|
private
|
230
229
|
|
231
230
|
def read_file_safely(filename)
|
231
|
+
# Don't read huge files
|
232
|
+
return [] if File.exist?(filename) && File.size(filename) > (@config[:max_file_size] || (1024 * 1024))
|
233
|
+
|
232
234
|
File.readlines(filename, chomp: true)
|
233
235
|
rescue StandardError
|
234
236
|
[]
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rbconfig"
|
4
|
+
require "socket"
|
5
|
+
|
6
|
+
module Lapsoss
|
7
|
+
# Boot-time context collection using Data class
|
8
|
+
RuntimeContext = Data.define(:os, :runtime, :modules, :server_name, :release) do
|
9
|
+
def self.current
|
10
|
+
@current ||= new(
|
11
|
+
os: collect_os_context,
|
12
|
+
runtime: collect_runtime_context,
|
13
|
+
modules: collect_modules,
|
14
|
+
server_name: collect_server_name,
|
15
|
+
release: collect_release
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.collect_os_context
|
20
|
+
{
|
21
|
+
name: RbConfig::CONFIG["host_os"],
|
22
|
+
version: `uname -r 2>/dev/null`.strip.presence,
|
23
|
+
build: `uname -v 2>/dev/null`.strip.presence,
|
24
|
+
kernel_version: `uname -a 2>/dev/null`.strip.presence,
|
25
|
+
machine: RbConfig::CONFIG["host_cpu"]
|
26
|
+
}.compact
|
27
|
+
rescue
|
28
|
+
{ name: RbConfig::CONFIG["host_os"] }
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.collect_runtime_context
|
32
|
+
{
|
33
|
+
name: "ruby",
|
34
|
+
version: RUBY_DESCRIPTION
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.collect_modules
|
39
|
+
return {} unless defined?(Bundler)
|
40
|
+
|
41
|
+
Bundler.load.specs.each_with_object({}) do |spec, h|
|
42
|
+
h[spec.name] = spec.version.to_s
|
43
|
+
end
|
44
|
+
rescue
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.collect_server_name
|
49
|
+
Socket.gethostname
|
50
|
+
rescue
|
51
|
+
"unknown"
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.collect_release
|
55
|
+
# Try to get from git if available
|
56
|
+
if File.exist?(".git")
|
57
|
+
`git rev-parse HEAD 2>/dev/null`.strip.presence
|
58
|
+
end
|
59
|
+
rescue
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_contexts
|
64
|
+
{
|
65
|
+
os: os,
|
66
|
+
runtime: runtime
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/lapsoss/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lapsoss
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
@@ -255,6 +255,7 @@ files:
|
|
255
255
|
- lib/lapsoss/registry.rb
|
256
256
|
- lib/lapsoss/release_tracker.rb
|
257
257
|
- lib/lapsoss/router.rb
|
258
|
+
- lib/lapsoss/runtime_context.rb
|
258
259
|
- lib/lapsoss/sampling/base.rb
|
259
260
|
- lib/lapsoss/sampling/rate_limiter.rb
|
260
261
|
- lib/lapsoss/sampling/uniform_sampler.rb
|