Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
Sentry.metrics.count("button.click", 1, attributes: { button_id: "submit" })
Sentry.metrics.distribution("response.time", 120.5, unit: "millisecond")
Sentry.metrics.gauge("cpu.usage", 75.2, unit: "percent")
```
```

- Support for tracing `Sequel` queries ([#2814](https://github.com/getsentry/sentry-ruby/pull/2814))

Expand All @@ -33,6 +33,19 @@

- Add support for OpenTelemetry messaging/queue system spans ([#2685](https://github.com/getsentry/sentry-ruby/pull/2685))

- Add support for `config.std_lib_logger_filter` proc ([#2829](https://github.com/getsentry/sentry-ruby/pull/2829))

```ruby
Sentry.init do |config|
config.std_lib_logger_filter = proc do |logger, message, severity|
# Only send ERROR and above messages
severity == :error || severity == :fatal
end

config.enabled_patches = [:std_lib_logger]
end
```

### Bug Fixes

- Handle empty frames case gracefully with local vars ([#2807](https://github.com/getsentry/sentry-ruby/pull/2807))
Expand Down
16 changes: 16 additions & 0 deletions sentry-ruby/lib/sentry/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,15 @@ class Configuration
# @return [Proc, nil]
attr_reader :before_send_metric

# Optional Proc, called to filter log messages before sending to Sentry
# @example
# config.std_lib_logger_filter = lambda do |logger, message, level|
# # Only send error and fatal logs to Sentry
# [:error, :fatal].include?(level)
# end
# @return [Proc, nil]
attr_reader :std_lib_logger_filter

# these are not config options
# @!visibility private
attr_reader :errors, :gem_specs
Expand Down Expand Up @@ -518,6 +527,7 @@ def initialize
self.before_send_check_in = nil
self.before_send_log = nil
self.before_send_metric = nil
self.std_lib_logger_filter = nil
self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
self.traces_sampler = nil
self.enable_logs = false
Expand Down Expand Up @@ -614,6 +624,12 @@ def before_breadcrumb=(value)
@before_breadcrumb = value
end

def std_lib_logger_filter=(value)
check_callable!("std_lib_logger_filter", value)

@std_lib_logger_filter = value
end

def environment=(environment)
@environment = environment.to_s
end
Expand Down
4 changes: 4 additions & 0 deletions sentry-ruby/lib/sentry/std_lib_logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ def add(severity, message = nil, progname = nil, &block)
message = message.to_s.strip

if !message.nil? && message != Sentry::Logger::PROGNAME && method = SEVERITY_MAP[severity]
if (filter = Sentry.configuration.std_lib_logger_filter) && !filter.call(self, message, method)
return result
end

Sentry.logger.send(method, message, origin: ORIGIN)
end
end
Expand Down
110 changes: 110 additions & 0 deletions sentry-ruby/spec/isolated/std_lib_logger_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,115 @@
expect(log_event[:body]).to eql("Fatal message")
end
end

context "with std_lib_logger_filter" do
let(:null_logger) { ::Logger.new(IO::NULL) }

context "when no filter is configured" do
it "sends all log messages to Sentry" do
logger.info("Test message")

expect(sentry_logs).to_not be_empty
expect(sentry_logs.last[:body]).to eq("Test message")
end
end

context "when filter always returns true" do
before do
Sentry.configuration.std_lib_logger_filter = ->(logger, message, level) { true }
end

it "sends log messages to Sentry" do
logger.info("Test message")

expect(sentry_logs).to_not be_empty
expect(sentry_logs.last[:body]).to eq("Test message")
end
end

context "when filter always returns false" do
before do
Sentry.configuration.std_lib_logger_filter = ->(logger, message, level) { false }
end

it "blocks messages from Sentry but still logs locally" do
expect {
logger.info("Test message")
}.to output(/Test message/).to_stdout

expect(sentry_logs).to be_empty
end
end

context "when filter uses logger instance for decisions" do
before do
Sentry.configuration.std_lib_logger_filter = ->(logger, message, level) do
!logger.instance_variable_get(:@logdev).nil?
end
end

it "allows logs from regular loggers" do
logger.info("Regular log message")

expect(sentry_logs).to_not be_empty
expect(sentry_logs.last[:body]).to eq("Regular log message")
end

it "blocks logs from IO::NULL loggers" do
null_logger.error("Null log message")

expect(sentry_logs).to be_empty
end
end

context "when filter uses message content for decisions" do
before do
Sentry.configuration.std_lib_logger_filter = ->(logger, message, level) do
!message.to_s.include?("SKIP")
end
end

it "allows messages without SKIP keyword" do
logger.info("Regular info message")

expect(sentry_logs).to_not be_empty
expect(sentry_logs.last[:body]).to eq("Regular info message")
end

it "blocks messages containing SKIP keyword" do
expect {
logger.info("SKIP: this should be filtered")
}.to output(/SKIP: this should be filtered/).to_stdout

expect(sentry_logs).to be_empty
end
end

context "when filter uses log level for decisions" do
before do
Sentry.configuration.std_lib_logger_filter = ->(logger, message, level) do
[:error, :fatal].include?(level)
end
end

it "allows error and fatal logs" do
logger.error("Error message")
logger.fatal("Fatal message")

expect(sentry_logs.size).to eq(2)
expect(sentry_logs[0][:body]).to eq("Error message")
expect(sentry_logs[1][:body]).to eq("Fatal message")
end

it "blocks info and warn logs" do
expect {
logger.info("Info message")
logger.warn("Warn message")
}.to output(/Info message.*Warn message/m).to_stdout

expect(sentry_logs).to be_empty
end
end
end
end
end
44 changes: 44 additions & 0 deletions sentry-ruby/spec/sentry/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -805,4 +805,48 @@ class SentryConfigurationSample < Sentry::Configuration
expect { subject.before_send_metric = true }.to raise_error(ArgumentError, "before_send_metric must be callable (or nil to disable)")
end
end

describe "#std_lib_logger_filter" do
it "defaults to nil" do
expect(subject.std_lib_logger_filter).to be_nil
end

it "accepts nil" do
expect {
subject.std_lib_logger_filter = nil
}.not_to raise_error

expect(subject.std_lib_logger_filter).to be_nil
end

it "accepts a proc" do
filter_proc = ->(logger, message, level) { true }

expect {
subject.std_lib_logger_filter = filter_proc
}.not_to raise_error

expect(subject.std_lib_logger_filter).to eq(filter_proc)
end

it "accepts a callable object" do
callable_object = Class.new do
def call(logger, message, level)
false
end
end.new

expect {
subject.std_lib_logger_filter = callable_object
}.not_to raise_error

expect(subject.std_lib_logger_filter).to eq(callable_object)
end

it "does not accept non-callable objects" do
expect {
subject.std_lib_logger_filter = "not a callable"
}.to raise_error(ArgumentError, "std_lib_logger_filter must be callable (or nil to disable)")
end
end
end
Loading