reevoo/sapience-rb

View on GitHub
lib/sapience/logger.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# frozen_string_literal: true
require "concurrent"

# rubocop:disable ClassVars, Style/SafeNavigation
module Sapience
  # Logger stores the class name to be used for all log messages so that every
  # log message written by this instance will include the class name
  class Logger < Base
    include Sapience::Concerns::Compatibility

    # Flush all queued log entries disk, database, etc.
    #  All queued log messages are written and then each appender is flushed in turn
    def self.flush # rubocop:disable AbcSize
      return unless appender_thread
      appender_thread << lambda do
        Sapience.appenders.each do |appender|
          next unless appender.valid?
          begin
            logger.trace "Appender thread: Flushing appender: #{appender.class.name}"
            appender.flush
          rescue StandardError => exc
            $stderr.write("Appender thread: Failed to flush to appender: #{appender.inspect}\n #{exc.inspect}")
          end
        end

        logger.trace "Appender thread: All appenders flushed"
      end
    end

    # Close all appenders and flush any outstanding messages
    def self.close
      return unless appender_thread
      appender_thread << lambda do
        Sapience.appenders.each do |appender|
          next unless appender.valid?
          begin
            close_appender(appender)
          rescue StandardError => exc
            logger.error "Appender thread: Failed to close appender: #{appender.inspect}", exc
          end
        end
        logger.trace "Appender thread: All appenders flushed"
      end
    end

    def self.close_appender(appender)
      logger.trace "Appender thread: Closing appender: #{appender.name}"
      appender.flush
      appender.close
      Sapience.remove_appender(appender)
    end

    @@appender_thread = nil
    @@logger = nil

    # Internal logger for Sapience
    #   For example when an appender is not working etc..
    #   By default logs to STDERR
    def self.logger
      @@logger ||= Sapience[Sapience]
    end

    # Start the appender thread
    def self.start_appender_thread
      return false if appender_thread_active?

      @@appender_thread = Sapience.log_executor_class.new
      fail "Failed to start Appender Thread" unless @@appender_thread
      true
    end

    def self.start_invalid_appenders_task
      @@invalid_appenders_task = Concurrent::TimerTask.new(execution_interval: 120, timeout_interval: 5) do
        Sapience.appenders.each do |appender|
          next if appender.valid?
          logger.warn { "#{appender.class} is not valid. #{appender::VALIDATION_MESSAGE}" }
        end
      end
      invalid_appenders_task.execute
    end

    # Returns true if the appender_thread is active
    def self.appender_thread_active?
      @@appender_thread && @@appender_thread.running?
    end

    # Separate appender thread responsible for reading log messages and
    # calling the appenders in it's thread
    def self.appender_thread
      @@appender_thread
    end

    def self.invalid_appenders_task
      @@invalid_appenders_task
    end

    # Allow the internal logger to be overridden from its default to STDERR
    #   Can be replaced with another Ruby logger or Rails logger, but never to
    #   Sapience::Logger itself since it is for reporting problems
    #   while trying to log to the various appenders
    def self.logger=(logger)
      @@logger = logger
    end

    # Returns a Logger instance
    #
    # Return the logger for a specific class, supports class specific log levels
    #   logger = Sapience::Logger.new(self)
    # OR
    #   logger = Sapience::Logger.new('MyClass')
    #
    # Parameters:
    #  application
    #    A class, module or a string with the application/class name
    #    to be used in the logger
    #
    #  level
    #    The initial log level to start with for this logger instance
    #    Default: Sapience.config.default_level
    #
    #  filter [Regexp|Proc]
    #    RegExp: Only include log messages where the class name matches the supplied
    #    regular expression. All other messages will be ignored
    #    Proc: Only include log messages where the supplied Proc returns true
    #          The Proc must return true or false
    def initialize(klass, level = nil, filter = nil)
      super
    end

    # Place log request on the queue for the Appender thread to write to each
    # appender in the order that they were registered
    def log(log, message = nil, progname = nil, &block)
      # Compatibility with ::Logger
      return add(log, message, progname, &block) unless log.is_a?(Sapience::Log)
      if @@appender_thread
        @@appender_thread << lambda do
          Sapience.appenders.each do |appender|
            next unless appender.valid?
            begin
              appender.log(log)
            rescue StandardError => exc
              $stderr.write("Appender thread: Failed to log to appender: #{appender.inspect}\n #{exc.inspect}")
            end
          end
          Sapience.clear_tags!
        end
      end
    end

    def flush
      self.class.flush
    end
  end
end
# rubocop:enable ClassVars, Style/SafeNavigation