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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c9422cb2f9fa0e1ae582a771fd337d61177d9236db6cd3dfc96edf3fefc7777
4
- data.tar.gz: d1dc595b8fcd5e00feb9f01087e77700bb49452a6b358a309ce630d6b29bd203
3
+ metadata.gz: bc2f5c5b2a06b525bb921e683c87a201e93507c795b6cff7e4cddc33d3716880
4
+ data.tar.gz: 3d5bce60161f233758b0f39fdf54197ac7c5b0db921709ac3e1a4e54531bf669
5
5
  SHA512:
6
- metadata.gz: 218bbe054b15df5457996cfda82aa6227477446dc0a5be24680acbae4c4ff3edfaf7217adfc96814aba94fe09331300253f4a499a85055b2fbac8df83a8336fb
7
- data.tar.gz: d5b0f10802bfe6c938cf90e8a322b0bc940b11d3bca9fde5efcaf96964c5753c09e04ae453087844cb455056b2cd6fd242e846e99ce59270d25962367daa7bda
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
- - **Telebug** - Sentry-compatible protocol (perfect for self-hosted alternatives)
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
- # Telebug, Glitchtip, or any Sentry-compatible service
193
+ # Telebugs, Glitchtip, or any Sentry-compatible service
194
194
  Lapsoss.configure do |config|
195
- config.use_telebug(dsn: ENV['TELEBUG_DSN'])
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 TelebugAdapter < Lapsoss::Adapters::SentryAdapter
441
- def initialize(name = :telebug, settings = {})
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-Telebug-Client" => "lapsoss/#{Lapsoss::VERSION}"
449
+ "X-Telebugs-Client" => "lapsoss/#{Lapsoss::VERSION}"
450
450
  )
451
451
  end
452
452
  end
@@ -122,7 +122,7 @@ module Lapsoss
122
122
  end
123
123
 
124
124
  def user_agent
125
- "Lapsoss/#{Lapsoss::VERSION} Ruby/#{RUBY_VERSION} Rails/#{Rails.version if defined?(Rails)}"
125
+ Base::USER_AGENT
126
126
  end
127
127
  end
128
128
  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 = build_envelope_wrapper(event)
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
- # Telebug adapter - uses Sentry protocol with Telebug endpoints
8
- # Telebug is compatible with Sentry's API, so we inherit from SentryAdapter
9
- class TelebugAdapter < SentryAdapter
10
- def initialize(name = :telebug, settings = {})
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 Telebug DSN format
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 Telebug-specific API path
27
+ # Override to build Telebugs-specific API path
28
28
  def build_api_path(uri)
29
- # Telebug uses: https://[key]@[host]/api/v1/sentry_errors/[project_id]
30
- # The path is already complete: /api/v1/sentry_errors/4
31
- # Unlike Sentry which needs /api/[project_id]/envelope/
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 Telebug endpoint
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 Telebug-specific headers
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-Telebug-Client" => "lapsoss/#{Lapsoss::VERSION}"
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 filename && line_number && File.exist?(filename)
35
+ return unless line_number
34
36
 
35
- with(code_context: processor.get_code_context(filename, line_number, context_lines))
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
- filename = normalize_path(filename) if filename
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: 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 && File.exist?(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 Telebug (Sentry-compatible)
89
- def use_telebug(name: :telebug, **settings)
90
- register_adapter(name, :telebug, **settings)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lapsoss
4
- VERSION = "0.4.4"
4
+ VERSION = "0.4.6"
5
5
  end
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
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/telebug_adapter.rb
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