lapsoss 0.4.4 → 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/README.md +6 -6
- data/lib/lapsoss/adapters/concerns/http_delivery.rb +1 -1
- data/lib/lapsoss/adapters/sentry_adapter.rb +84 -1
- data/lib/lapsoss/adapters/{telebug_adapter.rb → telebugs_adapter.rb} +18 -18
- 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/configuration.rb +3 -3
- data/lib/lapsoss/runtime_context.rb +70 -0
- data/lib/lapsoss/version.rb +1 -1
- metadata +3 -2
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
|
data/README.md
CHANGED
@@ -163,7 +163,7 @@ All adapters are pure Ruby implementations with no external SDK dependencies:
|
|
163
163
|
- **Rollbar** - Complete error tracking with grouping
|
164
164
|
- **AppSignal** - Error tracking and deploy markers
|
165
165
|
- **Insight Hub** (formerly Bugsnag) - Error tracking with breadcrumbs
|
166
|
-
- **
|
166
|
+
- **Telebugs** - Sentry-compatible protocol (perfect for self-hosted alternatives)
|
167
167
|
|
168
168
|
## Configuration
|
169
169
|
|
@@ -190,9 +190,9 @@ end
|
|
190
190
|
### Using Sentry-Compatible Services
|
191
191
|
|
192
192
|
```ruby
|
193
|
-
#
|
193
|
+
# Telebugs, Glitchtip, or any Sentry-compatible service
|
194
194
|
Lapsoss.configure do |config|
|
195
|
-
config.
|
195
|
+
config.use_telebugs(dsn: ENV['TELEBUGS_DSN'])
|
196
196
|
# Or use use_sentry with a custom endpoint
|
197
197
|
config.use_sentry(dsn: ENV['SELF_HOSTED_SENTRY_DSN'])
|
198
198
|
end
|
@@ -437,8 +437,8 @@ Lapsoss::Registry.register(:my_service, MyAdapter)
|
|
437
437
|
For Sentry-compatible services, just extend the SentryAdapter:
|
438
438
|
|
439
439
|
```ruby
|
440
|
-
class
|
441
|
-
def initialize(name = :
|
440
|
+
class TelebugsAdapter < Lapsoss::Adapters::SentryAdapter
|
441
|
+
def initialize(name = :telebugs, settings = {})
|
442
442
|
super(name, settings)
|
443
443
|
end
|
444
444
|
|
@@ -446,7 +446,7 @@ class TelebugAdapter < Lapsoss::Adapters::SentryAdapter
|
|
446
446
|
|
447
447
|
def build_headers(public_key)
|
448
448
|
super(public_key).merge(
|
449
|
-
"X-
|
449
|
+
"X-Telebugs-Client" => "lapsoss/#{Lapsoss::VERSION}"
|
450
450
|
)
|
451
451
|
end
|
452
452
|
end
|
@@ -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
|
@@ -4,16 +4,16 @@ require_relative "sentry_adapter"
|
|
4
4
|
|
5
5
|
module Lapsoss
|
6
6
|
module Adapters
|
7
|
-
#
|
8
|
-
#
|
9
|
-
class
|
10
|
-
def initialize(name = :
|
7
|
+
# Telebugs adapter - uses Sentry protocol with Telebugs endpoints
|
8
|
+
# Telebugs is compatible with Sentry's API, so we inherit from SentryAdapter
|
9
|
+
class TelebugsAdapter < SentryAdapter
|
10
|
+
def initialize(name = :telebugs, settings = {})
|
11
11
|
super(name, settings)
|
12
12
|
end
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
-
# Override to parse
|
16
|
+
# Override to parse Telebugs DSN format
|
17
17
|
def parse_dsn(dsn_string)
|
18
18
|
uri = URI.parse(dsn_string)
|
19
19
|
{
|
@@ -24,15 +24,20 @@ module Lapsoss
|
|
24
24
|
}
|
25
25
|
end
|
26
26
|
|
27
|
-
# Override to build
|
27
|
+
# Override to build Telebugs-specific API path
|
28
28
|
def build_api_path(uri)
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
uri.path
|
29
|
+
# Telebugs DSN: https://[key]@[host]/api/v1/sentry_errors/[project_id]
|
30
|
+
# But needs to hit: /api/v1/sentry_errors/api/[project_id]/envelope/
|
31
|
+
# Extract base path without project_id
|
32
|
+
path_parts = uri.path.split("/")
|
33
|
+
project_id = path_parts.last
|
34
|
+
base_path = path_parts[0..-2].join("/")
|
35
|
+
|
36
|
+
# Build the envelope path
|
37
|
+
"#{base_path}/api/#{project_id}/envelope/"
|
33
38
|
end
|
34
39
|
|
35
|
-
# Override to setup
|
40
|
+
# Override to setup Telebugs endpoint
|
36
41
|
def setup_endpoint
|
37
42
|
uri = URI.parse(@settings[:dsn])
|
38
43
|
# For Telebug, we use the full URL without port (unless non-standard)
|
@@ -41,18 +46,13 @@ module Lapsoss
|
|
41
46
|
self.class.api_path = build_api_path(uri)
|
42
47
|
end
|
43
48
|
|
44
|
-
# Override headers builder to add
|
49
|
+
# Override headers builder to add Telebugs-specific headers
|
45
50
|
def headers_for(envelope)
|
46
51
|
base_headers = super(envelope)
|
47
52
|
base_headers.merge(
|
48
|
-
"X-
|
53
|
+
"X-Telebugs-Client" => "lapsoss/#{Lapsoss::VERSION}"
|
49
54
|
)
|
50
55
|
end
|
51
|
-
|
52
|
-
# Override user agent for Telebug
|
53
|
-
def user_agent
|
54
|
-
"lapsoss-telebug/#{Lapsoss::VERSION}"
|
55
|
-
end
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
@@ -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
|
[]
|
@@ -85,9 +85,9 @@ module Lapsoss
|
|
85
85
|
register_adapter(name, :sentry, **settings)
|
86
86
|
end
|
87
87
|
|
88
|
-
# Convenience method for
|
89
|
-
def
|
90
|
-
register_adapter(name, :
|
88
|
+
# Convenience method for Telebugs (Sentry-compatible)
|
89
|
+
def use_telebugs(name: :telebugs, **settings)
|
90
|
+
register_adapter(name, :telebugs, **settings)
|
91
91
|
end
|
92
92
|
|
93
93
|
# Convenience method for AppSignal
|
@@ -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
|
@@ -225,7 +225,7 @@ files:
|
|
225
225
|
- lib/lapsoss/adapters/logger_adapter.rb
|
226
226
|
- lib/lapsoss/adapters/rollbar_adapter.rb
|
227
227
|
- lib/lapsoss/adapters/sentry_adapter.rb
|
228
|
-
- lib/lapsoss/adapters/
|
228
|
+
- lib/lapsoss/adapters/telebugs_adapter.rb
|
229
229
|
- lib/lapsoss/backtrace_frame.rb
|
230
230
|
- lib/lapsoss/backtrace_frame_factory.rb
|
231
231
|
- lib/lapsoss/backtrace_processor.rb
|
@@ -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
|