require 'logger' module Sentry class Breadcrumb module SentryLogger LEVELS = { ::Logger::DEBUG => 'debug', ::Logger::INFO => 'info', ::Logger::WARN => 'warn', ::Logger::ERROR => 'error', ::Logger::FATAL => 'fatal' }.freeze def add(*args, &block) super add_breadcrumb(*args, &block) nil end def add_breadcrumb(severity, message = nil, progname = nil) # because the breadcrumbs now belongs to different Hub's Scope in different threads # we need to make sure the current thread's Hub has been set before adding breadcrumbs return unless Sentry.get_current_hub category = "logger" # this is because the nature of Ruby Logger class: # # when given 1 argument, the argument will become both message and progname # # ``` # logger.info("foo") # # message == progname == "foo" # ``` # # and to specify progname with a different message, # we need to pass the progname as the argument and pass the message as a proc # # ``` # logger.info("progname") { "the message" } # ``` # # so the condition below is to replicate the similar behavior if message.nil? if block_given? message = yield category = progname else message = progname end end return if ignored_logger?(progname) || message == "" # some loggers will add leading/trailing space as they (incorrectly, mind you) # think of logging as a shortcut to std{out,err} message = message.to_s.strip last_crumb = current_breadcrumbs.peek # try to avoid dupes from logger broadcasts if last_crumb.nil? || last_crumb.message != message level = Sentry::Breadcrumb::SentryLogger::LEVELS.fetch(severity, nil) crumb = Sentry::Breadcrumb.new( level: level, category: category, message: message, type: severity >= 3 ? "error" : level ) Sentry.add_breadcrumb(crumb) end end private def ignored_logger?(progname) progname == LOGGER_PROGNAME || Sentry.configuration.exclude_loggers.include?(progname) end def current_breadcrumbs Sentry.get_current_scope.breadcrumbs end end end end ::Logger.send(:prepend, Sentry::Breadcrumb::SentryLogger)