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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5db26196bdbf6e69d90c126041f7310681e240ef22e61f0a3aee12035f7616da
4
- data.tar.gz: 93a2c75b6af04e776aca9c24f3704ddba51b4a2432d5d6db0f736874bef17c69
3
+ metadata.gz: bc2f5c5b2a06b525bb921e683c87a201e93507c795b6cff7e4cddc33d3716880
4
+ data.tar.gz: 3d5bce60161f233758b0f39fdf54197ac7c5b0db921709ac3e1a4e54531bf669
5
5
  SHA512:
6
- metadata.gz: 38d9d2d1c1e2f67997b71cae8776b2c7ef8b3aca5d13d563080304c63b84b1db227eb923618b1ae078223cfc20dfebf5d1620dcf01eed92ee62d53461bf36e1e
7
- data.tar.gz: 1831cda3198ad594c850fd5579fed77aa9097ee4f497dd81374bb265ee237addc4d31fab0447d6164cff6fb64695869eba42366e0820647725d0725566158ff8
6
+ metadata.gz: 569ae7372bbe9f3984c0cb1b13074e1442af9f34cb21e74124f04db1a62227a7ac08c8d856d08c739e9da05a799c288260b72e62e8e7df44f5fa853bfacf5d7b
7
+ data.tar.gz: 6e17e4ffdd5d757fb70414da87e188ee12dbc1469bb37ac978554c2fc5146e5f86ebb257daefe37748e3129140d402d34c517ac1c756aa56c56defdd41a159e9
@@ -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
@@ -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
  []
@@ -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.5"
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.5
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