Skip to content

Commit a0626b6

Browse files
committed
fix: prevent SDK crash when SDK logging fails
custom SDK loggers can crash the SDK, we guard against that by returning the exception to stderr
1 parent 287f2e5 commit a0626b6

File tree

2 files changed

+110
-4
lines changed

2 files changed

+110
-4
lines changed

sentry-ruby/lib/sentry/utils/logging_helper.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,25 @@ module LoggingHelper
66
# @!visibility private
77
def log_error(message, exception, debug: false)
88
message = "#{message}: #{exception.message}"
9-
message += "\n#{exception.backtrace.join("\n")}" if debug
9+
message += "\n#{exception.backtrace.join("\n")}" if debug && exception.backtrace
1010

11-
sdk_logger&.error(LOGGER_PROGNAME) do
12-
message
13-
end
11+
sdk_logger&.error(LOGGER_PROGNAME) { message }
12+
rescue StandardError => e
13+
$stderr.puts "Sentry SDK logging failed (#{e.class}: #{message})"
1414
end
1515

1616
# @!visibility private
1717
def log_debug(message)
1818
sdk_logger&.debug(LOGGER_PROGNAME) { message }
19+
rescue StandardError => e
20+
$stderr.puts "Sentry SDK logging failed (#{e.class}: #{message})"
1921
end
2022

2123
# @!visibility private
2224
def log_warn(message)
2325
sdk_logger&.warn(LOGGER_PROGNAME) { message }
26+
rescue StandardError => e
27+
$stderr.puts "Sentry SDK logging failed (#{e.class}: #{message})"
2428
end
2529

2630
# @!visibility private
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe Sentry::LoggingHelper do
4+
let(:string_io) { StringIO.new }
5+
let(:logger) { Logger.new(string_io) }
6+
7+
let(:helper_class) do
8+
Class.new do
9+
include Sentry::LoggingHelper
10+
attr_accessor :sdk_logger
11+
12+
def initialize(sdk_logger)
13+
@sdk_logger = sdk_logger
14+
end
15+
end
16+
end
17+
18+
let(:logger_helper) { helper_class.new(logger) }
19+
20+
describe "#log_error" do
21+
it "logs exception message with description" do
22+
exception = StandardError.new("Something went wrong")
23+
logger_helper.log_error("test_error", exception)
24+
25+
expect(string_io.string).to include("test_error: Something went wrong")
26+
end
27+
28+
it "includes backtrace when debug is true" do
29+
exception = StandardError.new("Error")
30+
exception.set_backtrace(["it_broke.rb:1"])
31+
32+
logger_helper.log_error("test_error", exception, debug: true)
33+
34+
expect(string_io.string).to include("it_broke.rb:1")
35+
end
36+
end
37+
38+
describe "stderr fallback when logger fails" do
39+
shared_examples "falls back to stderr" do |method_name, *args|
40+
it "outputs to stderr with error class and message" do
41+
broken_logger = Class.new do
42+
def error(*); raise IOError, "oops"; end
43+
def debug(*); raise IOError, "oops"; end
44+
def warn(*); raise IOError, "oops"; end
45+
end.new
46+
47+
helper = helper_class.new(broken_logger)
48+
49+
expect($stderr).to receive(:puts).with(/Sentry SDK logging failed \(IOError: /)
50+
expect { helper.public_send(method_name, *args) }.not_to raise_error
51+
end
52+
end
53+
54+
context "#log_error" do
55+
include_examples "falls back to stderr", :log_error, "Test", StandardError.new("Error")
56+
end
57+
58+
context "#log_debug" do
59+
include_examples "falls back to stderr", :log_debug, "Debug message"
60+
end
61+
62+
context "#log_warn" do
63+
include_examples "falls back to stderr", :log_warn, "Warning message"
64+
end
65+
end
66+
67+
describe "custom JSON logger with encoding errors" do
68+
# Custom logger from GitHub issue #2805
69+
let(:json_logger) do
70+
Class.new(::Logger) do
71+
class JsonFormatter
72+
def call(level, _, _, m)
73+
message = { message: m.message, backtrace: m.backtrace.first(5).join("\n") } if m.is_a?(Exception)
74+
message = { message: m } if m.is_a?(String)
75+
{ severity: level }.merge(message).to_json << "\n"
76+
end
77+
end
78+
79+
def initialize(*)
80+
super
81+
self.formatter = JsonFormatter.new
82+
end
83+
end.new(StringIO.new)
84+
end
85+
86+
let(:logger_helper) { helper_class.new(json_logger) }
87+
88+
it "falls back to stderr when JSON encoding fails on invalid UTF-8" do
89+
exception = StandardError.new("Error \x92".dup.force_encoding("UTF-8"))
90+
91+
# Capture the stderr message
92+
stderr_message = nil
93+
expect($stderr).to receive(:puts) { |msg| stderr_message = msg }
94+
95+
expect { logger_helper.log_error("Event sending failed", exception) }.not_to raise_error
96+
97+
# Verify the error message contains the key information
98+
expect(stderr_message).to include("Sentry SDK logging failed")
99+
expect(stderr_message).to include("JSON::GeneratorError")
100+
end
101+
end
102+
end

0 commit comments

Comments
 (0)