Контекстная регистрация с Log4r - PullRequest
0 голосов
/ 16 июля 2011

Вот как работает мой существующий код регистрации с Log4r.Как вы можете видеть в WorkerX :: a_method, каждый раз, когда я регистрирую сообщение, я хочу, чтобы имя класса и вызывающий метод были включены (я не хочу, чтобы вся история вызывающих программ или какой-либо другой шум, который был моей цельюLgrHelper).

class WorkerX

  include LgrHelper

  def initialize(args = {})
    @logger = Lgr.new({:debug => args[:debug], :logger_type => 'WorkerX'})
  end

  def a_method
    error_msg("some error went down here")
    # This prints out: "WorkerX::a_method - some error went down here"
  end

end


class Lgr
  require 'log4r'
  include Log4r

  def initialize(args = {}) # args: debug boolean, logger type
    @debug = args[:debug] 
    @logger_type = args[:logger_type]

    @logger = Log4r::Logger.new(@logger_type)
    format = Log4r::PatternFormatter.new(:pattern => "%l:\t%d - %m")
    outputter = Log4r::StdoutOutputter.new('console', :formatter => format)
    @logger.outputters = outputter

    if @debug then
      @logger.level = DEBUG
    else
      @logger.level = INFO
    end
  end

  def debug(msg)
    @logger.debug(msg)
  end

  def info(msg)
    @logger.info(msg)
  end

  def warn(msg)
    @logger.warn(msg)
  end

  def error(msg)
    @logger.error(msg)
  end

  def level
    @logger.level
  end

end


module LgrHelper

  # This module should only be included in a class that has a @logger instance variable, obviously.

  protected

  def info_msg(msg)
    @logger.info(log_intro_msg(self.method_caller_name) + msg)
  end

  def debug_msg(msg)
    @logger.debug(log_intro_msg(self.method_caller_name) + msg)
  end

  def warn_msg(msg)
    @logger.warn(log_intro_msg(self.method_caller_name) + msg)
  end

  def error_msg(msg)
    @logger.error(log_intro_msg(self.method_caller_name) + msg)
  end

  def log_intro_msg(method)
    msg = class_name
    msg += '::'
    msg += method
    msg += ' - '

    msg
  end

  def class_name
    self.class.name
  end

  def method_caller_name
    if  /`(.*)'/.match(caller[1]) then # caller.first
      $1
    else
      nil
    end
  end

end

Мне действительно не нравится этот подход.Я предпочел бы просто использовать существующую переменную экземпляра @logger, чтобы напечатать сообщение и быть достаточно умным, чтобы знать контекст.Как можно сделать этот или аналогичный более простой подход?

Моя среда - Rails 2.3.11 (пока!).

1 Ответ

1 голос
/ 25 июля 2011

После публикации моего ответа с использованием extend (см. " EDIT " ниже), я подумал, что попробую использовать set_trace_func, чтобы сохранить вид трассировки стека, как в обсуждении, которое я опубликовалк.Вот мое окончательное решение;вызов set_trace_proc будет сделан в инициализаторе или аналогичном.

#!/usr/bin/env ruby

# Keep track of the classes that invoke each "call" event
# and the method they called as an array of arrays.
# The array is in the format: [calling_class, called_method]
set_trace_func proc { |event, file, line, id, bind, klass|
  if event == "call"
    Thread.current[:callstack] ||= []
    Thread.current[:callstack].push [klass, id]
  elsif event == "return"
    Thread.current[:callstack].pop
  end
}

class Lgr
  require 'log4r'
  include Log4r

  def initialize(args = {}) # args: debug boolean, logger type
    @debug = args[:debug]
    @logger_type = args[:logger_type]

    @logger = Log4r::Logger.new(@logger_type)
    format = Log4r::PatternFormatter.new(:pattern => "%l:\t%d - %m")
    outputter = Log4r::StdoutOutputter.new('console', :formatter => format)
    @logger.outputters = outputter

    if @debug then
      @logger.level = DEBUG
    else
      @logger.level = INFO
    end
  end

  def debug(msg)
    @logger.debug(msg)
  end

  def info(msg)
    @logger.info(msg)
  end

  def warn(msg)
    @logger.warn(msg)
  end

  def error(msg)
    @logger.error(msg)
  end

  def level
    @logger.level
  end

  def invoker
    Thread.current[:callstack] ||= []
    ( Thread.current[:callstack][-2] || ['Kernel', 'main'] )
  end
end

class CallingMethodLogger < Lgr
  [:info, :debug, :warn, :error].each do |meth|
    define_method(meth) { |msg| super("#{invoker[0]}::#{invoker[1]} - #{msg}") }
  end
end

class WorkerX
  def initialize(args = {})
    @logger = CallingMethodLogger.new({:debug => args[:debug], :logger_type => 'WorkerX'})
  end

  def a_method
    @logger.error("some error went down here")
    # This prints out: "WorkerX::a_method - some error went down here"
  end
end

w = WorkerX.new
w.a_method

Я не знаю, насколько сильно вызовы proc будут влиять на производительность приложения, если таковые имеются;если это в конечном итоге вызывает озабоченность, возможно, что-то не столь умное в отношении вызывающего класса (как мой старый ответ ниже) будет работать лучше.

[ РЕДАКТИРОВАТЬ : Ниже следует мой старый ответсм. выше.]

Как насчет использования extend?Вот быстрый и грязный скрипт, который я собрал из вашего кода, чтобы проверить его;Мне пришлось изменить порядок вещей, чтобы избежать ошибок, но код такой же, за исключением LgrHelper (который я переименовал CallingMethodLogger) и второй строки инициализатора WorkerX:

#!/usr/bin/env ruby

module CallingMethodLogger
  def info(msg)
    super("#{@logger_type}::#{method_caller_name} - " + msg)
  end

  def debug(msg)
    super("#{@logger_type}::#{method_caller_name} - " + msg)
  end

  def warn(msg)
    super("#{@logger_type}::#{method_caller_name} - " + msg)
  end

  def error(msg)
    super("#{@logger_type}::#{method_caller_name} - " + msg)
  end

  def method_caller_name
    if  /`(.*)'/.match(caller[1]) then # caller.first
      $1
    else
      nil
    end
  end
end

class Lgr
  require 'log4r'
  include Log4r

  def initialize(args = {}) # args: debug boolean, logger type
    @debug = args[:debug]
    @logger_type = args[:logger_type]

    @logger = Log4r::Logger.new(@logger_type)
    format = Log4r::PatternFormatter.new(:pattern => "%l:\t%d - %m")
    outputter = Log4r::StdoutOutputter.new('console', :formatter => format)
    @logger.outputters = outputter

    if @debug then
      @logger.level = DEBUG
    else
      @logger.level = INFO
    end
  end

  def debug(msg)
    @logger.debug(msg)
  end

  def info(msg)
    @logger.info(msg)
  end

  def warn(msg)
    @logger.warn(msg)
  end

  def error(msg)
    @logger.error(msg)
  end

  def level
    @logger.level
  end
end

class WorkerX
  def initialize(args = {})
    @logger = Lgr.new({:debug => args[:debug], :logger_type => 'WorkerX'})
    @logger.extend CallingMethodLogger
  end

  def a_method
    @logger.error("some error went down here")
    # This prints out: "WorkerX::a_method - some error went down here"
  end
end

w = WorkerX.new
w.a_method

Вывод:

ERROR:  2011-07-24 20:01:40 - WorkerX::a_method - some error went down here

Недостатком является то, что с помощью этого метода имя класса вызывающего не определяется автоматически;это явное основание на @logger_type, переданном в Lgr экземпляр.Однако вы можете использовать другой метод для получения фактического имени класса - возможно, что-то вроде call_stack gem или с помощью Kernel#set_trace_func - см. thisнить .

...