diff --git a/CHANGELOG.md b/CHANGELOG.md index 3637dfc1c..6b79d5c43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ ## Unreleased +### Feature + +- Support for `:logger` patch which enables sending logs to Sentry when `enabled_logs` is set to true ([#2657](https://github.com/getsentry/sentry-ruby/pull/2657)) + + Here's a sample config: + + ```ruby + Sentry.init do |config| + # ... your setup ... + config.enable_logs = true + config.enabled_patches = [:logger] + end + ``` + +### Bug Fixes - Skip creating `LogEventBuffer` if logging is not enabled ([#2652](https://github.com/getsentry/sentry-ruby/pull/2652)) ## 5.25.0 diff --git a/sentry-ruby/lib/sentry-ruby.rb b/sentry-ruby/lib/sentry-ruby.rb index 8d3f0578e..a2a43b4e3 100644 --- a/sentry-ruby/lib/sentry-ruby.rb +++ b/sentry-ruby/lib/sentry-ruby.rb @@ -690,3 +690,4 @@ def dependency_installed?(name) require "sentry/graphql" require "sentry/faraday" require "sentry/excon" +require "sentry/std_lib_logger" diff --git a/sentry-ruby/lib/sentry/std_lib_logger.rb b/sentry-ruby/lib/sentry/std_lib_logger.rb new file mode 100644 index 000000000..b851e9763 --- /dev/null +++ b/sentry-ruby/lib/sentry/std_lib_logger.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Sentry + # Ruby Logger support Add commentMore actions + # intercepts any logger instance and send the log to Sentry too. + module StdLibLogger + SEVERITY_MAP = { + 0 => :debug, + 1 => :info, + 2 => :warn, + 3 => :error, + 4 => :fatal + }.freeze + + def add(severity, message = nil, progname = nil, &block) + result = super + + return unless Sentry.initialized? && Sentry.get_current_hub + + # exclude sentry SDK logs -- to prevent recursive log action, + # do not process internal logs again + if message.nil? && progname != Sentry::Logger::PROGNAME + + # handle different nature of Ruby Logger class: + # inspo from Sentry::Breadcrumb::SentryLogger + if block_given? + message = yield + else + message = progname + end + + message = message.to_s.strip + + if !message.nil? && message != Sentry::Logger::PROGNAME && method = SEVERITY_MAP[severity] + Sentry.logger.send(method, message) + end + end + + result + end + end +end + +Sentry.register_patch(:logger) do |config| + if config.enable_logs + ::Logger.prepend(Sentry::StdLibLogger) + else + config.sdk_logger.warn(":logger patch enabled but `enable_logs` is turned off - skipping applying patch") + end +end diff --git a/sentry-ruby/spec/sentry/breadcrumb/sentry_logger_spec.rb b/sentry-ruby/spec/isolated/sentry_logger_spec.rb similarity index 81% rename from sentry-ruby/spec/sentry/breadcrumb/sentry_logger_spec.rb rename to sentry-ruby/spec/isolated/sentry_logger_spec.rb index 4fe034954..164d451c9 100644 --- a/sentry-ruby/spec/sentry/breadcrumb/sentry_logger_spec.rb +++ b/sentry-ruby/spec/isolated/sentry_logger_spec.rb @@ -1,9 +1,14 @@ # frozen_string_literal: true +SimpleCov.command_name "SentryLogger" + RSpec.describe "Sentry::Breadcrumbs::SentryLogger" do before do perform_basic_setup do |config| config.breadcrumbs_logger = [:sentry_logger] + config.enable_logs = true + config.max_log_events = 1 + config.enabled_patches = [:logger] end end @@ -100,4 +105,22 @@ end end end + + it "does not conflict with :logger patch" do + logger = ::Logger.new(nil) + + logger.info("Hello World") + + expect(sentry_logs).to_not be_empty + + log_event = sentry_logs.last + + expect(log_event[:level]).to eql("info") + expect(log_event[:body]).to eql("Hello World") + + breadcrumb = breadcrumbs.peek + + expect(breadcrumb.level).to eq("info") + expect(breadcrumb.message).to eq("Hello World") + end end diff --git a/sentry-ruby/spec/isolated/std_lib_logger_spec.rb b/sentry-ruby/spec/isolated/std_lib_logger_spec.rb new file mode 100644 index 000000000..9d07d60d7 --- /dev/null +++ b/sentry-ruby/spec/isolated/std_lib_logger_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +SimpleCov.command_name "StdLibLogger" + +RSpec.describe Sentry::StdLibLogger do + let(:logger) { ::Logger.new($stdout) } + + context "when logger patch is enabled but enable_logs is turned off" do + it "logs a warning message" do + string_io = StringIO.new + + perform_basic_setup do |config| + config.enable_logs = false + config.enabled_patches = [:logger] + config.sdk_logger = ::Logger.new(string_io) + end + + expect(string_io.string).to include("WARN -- : :logger patch enabled but `enable_logs` is turned off - skipping applying patch") + end + end + + context "when enable_logs is set to true but logger patch is not enabled" do + before do + perform_basic_setup do |config| + config.enable_logs = true + end + end + + it "does not send log using stdlib logger" do + expect { + logger.send(:info, "Hello World") + }.to output(/Hello World/).to_stdout + + expect(sentry_logs).to be_empty + end + end + + context "when enable_logs is set to true and logger patch is set" do + before do + perform_basic_setup do |config| + config.max_log_events = 1 + config.enable_logs = true + config.enabled_patches = [:redis, :puma, :http, :logger] + end + end + + ["info", "warn", "error", "fatal"].each do |level| + describe "##{level}" do + it "send logs using stdlib logger" do + expect { + logger.send(level, "Hello World") + }.to output(/Hello World/).to_stdout + + expect(sentry_logs).to_not be_empty + + log_event = sentry_logs.last + + expect(log_event[:level]).to eql(level) + expect(log_event[:body]).to eql("Hello World") + end + end + end + end +end