Skip to content

Commit 2e67a9a

Browse files
committed
Add Sentry::DebugTransport for testing/debugging
1 parent aebfd6d commit 2e67a9a

File tree

6 files changed

+159
-0
lines changed

6 files changed

+159
-0
lines changed

sentry-ruby/lib/sentry/configuration.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,11 @@ def capture_exception_frame_locals=(value)
196196
# @return [Logger]
197197
attr_accessor :sdk_logger
198198

199+
# File path for DebugTransport to log events to. If not set, defaults to a temporary file.
200+
# This is useful for debugging and testing purposes.
201+
# @return [String, nil]
202+
attr_accessor :sdk_debug_transport_log_file
203+
199204
# @deprecated Use {#sdk_logger=} instead.
200205
def logger=(logger)
201206
warn "[sentry] `config.logger=` is deprecated. Please use `config.sdk_logger=` instead."

sentry-ruby/lib/sentry/test_helper.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ module Sentry
44
module TestHelper
55
DUMMY_DSN = "http://12345:67890@sentry.localdomain/sentry/42"
66

7+
# Not really real, but it will be resolved as a non-local for testing needs
8+
REAL_DSN = "https://user:pass@getsentry.io/project/42"
9+
710
# Alters the existing SDK configuration with test-suitable options. Mainly:
811
# - Sets a dummy DSN instead of `nil` or an actual DSN.
912
# - Sets the transport to DummyTransport, which allows easy access to the captured events.
@@ -46,6 +49,11 @@ def setup_sentry_test(&block)
4649
def teardown_sentry_test
4750
return unless Sentry.initialized?
4851

52+
transport = Sentry.get_current_client&.transport
53+
if transport.is_a?(Sentry::DebugTransport)
54+
transport.clear
55+
end
56+
4957
# pop testing layer created by `setup_sentry_test`
5058
# but keep the base layer to avoid nil-pointer errors
5159
# TODO: find a way to notify users if they somehow popped the test layer before calling this method

sentry-ruby/lib/sentry/transport.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,4 @@ def reject_rate_limited_items(envelope)
223223
require "sentry/transport/dummy_transport"
224224
require "sentry/transport/http_transport"
225225
require "sentry/transport/spotlight_transport"
226+
require "sentry/transport/debug_transport"
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# frozen_string_literal: true
2+
3+
require "json"
4+
require "fileutils"
5+
require "pathname"
6+
require "delegate"
7+
8+
module Sentry
9+
# DebugTransport is a transport that logs events to a file for debugging purposes.
10+
#
11+
# It can optionally also send events to Sentry via HTTP transport if a real DSN
12+
# is provided.
13+
class DebugTransport < SimpleDelegator
14+
DEFAULT_LOG_FILE_PATH = File.join("log", "sentry_debug_events.log")
15+
16+
attr_reader :log_file, :backend
17+
18+
alias_method :__getobj__, :backend
19+
20+
def initialize(configuration)
21+
@log_file = initialize_log_file(configuration)
22+
@backend = initialize_backend(configuration)
23+
24+
super(@backend)
25+
end
26+
27+
def send_event(event)
28+
log_envelope(envelope_from_event(event))
29+
backend.send_event(event)
30+
end
31+
32+
def log_envelope(envelope)
33+
envelope_json = {
34+
timestamp: Time.now.utc.iso8601,
35+
envelope_headers: envelope.headers,
36+
items: envelope.items.map do |item|
37+
{
38+
headers: item.headers,
39+
payload: item.payload
40+
}
41+
end
42+
}
43+
44+
File.open(log_file, "a") { |file| file << JSON.dump(envelope_json) << "\n" }
45+
end
46+
47+
def logged_envelopes
48+
return [] unless File.exist?(log_file)
49+
50+
File.readlines(log_file).map do |line|
51+
JSON.parse(line)
52+
end
53+
end
54+
55+
def clear
56+
File.write(log_file, "")
57+
log_debug("DebugTransport: Cleared events from #{log_file}")
58+
end
59+
60+
private
61+
62+
def initialize_backend(configuration)
63+
backend = configuration.dsn.local? ? DummyTransport : HTTPTransport
64+
backend.new(configuration)
65+
end
66+
67+
def initialize_log_file(configuration)
68+
log_file = Pathname(configuration.sdk_debug_transport_log_file || DEFAULT_LOG_FILE_PATH)
69+
70+
FileUtils.mkdir_p(log_file.dirname) unless log_file.dirname.exist?
71+
72+
log_file
73+
end
74+
end
75+
end
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe Sentry do
4+
let(:client) { Sentry.get_current_client }
5+
let(:transport) { Sentry.get_current_client.transport }
6+
let(:error) { StandardError.new("test error") }
7+
8+
before do
9+
perform_basic_setup
10+
11+
setup_sentry_test do |config|
12+
config.dsn = dsn
13+
config.transport.transport_class = Sentry::DebugTransport
14+
config.debug = true
15+
end
16+
end
17+
18+
after do
19+
teardown_sentry_test
20+
end
21+
22+
context "with local DSN for testing" do
23+
let(:dsn) { Sentry::TestHelper::DUMMY_DSN }
24+
25+
describe ".capture_exception with debug transport" do
26+
it "logs envelope data and stores an event internally" do
27+
Sentry.capture_exception(error)
28+
29+
expect(transport.events.count).to be(1)
30+
expect(transport.backend.events.count).to be(1)
31+
expect(transport.backend.envelopes.count).to be(1)
32+
33+
event = transport.logged_envelopes.last
34+
item = event["items"].first
35+
payload = item["payload"]
36+
37+
expect(payload["exception"]["values"].first["value"]).to include("test error")
38+
end
39+
end
40+
end
41+
42+
context "with a real DSN for testing" do
43+
let(:dsn) { Sentry::TestHelper::REAL_DSN }
44+
45+
describe ".capture_exception with debug transport" do
46+
it "sends an event and logs envelope" do
47+
stub_request(:post, "https://getsentry.io/project/api/42/envelope/")
48+
.to_return(status: 200, body: "", headers: {})
49+
50+
Sentry.capture_exception(error)
51+
52+
expect(transport.logged_envelopes.count).to be(1)
53+
54+
event = transport.logged_envelopes.last
55+
item = event["items"].first
56+
payload = item["payload"]
57+
58+
expect(payload["exception"]["values"].first["value"]).to include("test error")
59+
end
60+
end
61+
end
62+
end

sentry-ruby/spec/spec_helper.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@
7979
end
8080

8181
config.after(:each) do
82+
if Sentry.initialized?
83+
transport = Sentry.get_current_client&.transport
84+
85+
if transport.is_a?(Sentry::DebugTransport)
86+
transport.clear
87+
end
88+
end
89+
8290
reset_sentry_globals!
8391
end
8492

0 commit comments

Comments
 (0)