使用一个小的Ruby脚本,该脚本可以访问Web并抓取各种服务。我有一个包含几个类的模块:
module Crawler
class Runner
class Options
class Engine
end
我想在所有这些类中共享一个记录器。通常我只是将它放在模块中的常量中并像这样引用它:
Crawler::LOGGER.info("Hello, world")
问题是我无法创建我的记录器实例,直到我知道输出的去向。您可以通过命令行启动爬网程序,此时您可以告诉它您要在开发中运行(日志输出转到STDOUT)或生产(日志输出转到文件,crawler.log):
crawler --environment=production
我有一个类Options
,用于解析通过命令行传入的选项。只有在那时我才知道如何用正确的输出位置实例化记录器。
所以,我的问题是:我如何/在哪里放置我的记录器对象以便我的所有类都可以访问它?
我可以将我的记录器实例传递给我创建的每个类实例的每个new()
调用,但我知道必须有一个更好的Rubyish方法来实现它。我正在想象一个与class << self
或其他魔法共享的模块上的一些奇怪的类变量。 :)
更多详细信息:Runner
通过将命令行选项传递给Options
类来启动所有内容,并获取包含几个实例变量的对象:
module Crawler
class Runner
def initialize(argv)
@options = Options.new(argv)
# feels like logger initialization should go here
# @options.log_output => STDOUT or string (log file name)
# @options.log_level => Logger::DEBUG or Logger::INFO
@engine = Engine.new()
end
def run
@engine.go
end
end
end
runner = Runner.new(ARGV)
runner.run
我需要Engine
中的代码才能访问记录器对象(以及在Engine
内初始化的几个类)。救命啊!
如果您可以动态更改已经实例化的Logger的输出位置(类似于更改日志级别的方式),则可以避免所有这些。我将它实例化为STDOUT,然后如果我正在制作中则转换为文件。我确实看到了一个关于改变Ruby的$ stdout全局变量的建议,它会将输出重定向到除STDOUT之外的某个地方,但这看起来很糟糕。
谢谢!
答案 0 :(得分:97)
我希望在我的课程中使用logger
方法,但我不喜欢在所有初始化程序中添加@logger = Logging.logger
。通常,我这样做:
module Logging
# This is the magical bit that gets mixed into your classes
def logger
Logging.logger
end
# Global, memoized, lazy initialized instance of a logger
def self.logger
@logger ||= Logger.new(STDOUT)
end
end
然后,在你的课程中:
class Widget
# Mix in the ability to log stuff ...
include Logging
# ... and proceed to log with impunity:
def discombobulate(whizbang)
logger.warn "About to combobulate the whizbang"
# commence discombobulation
end
end
因为Logging#logger
方法可以访问模块混合的实例,所以扩展日志记录模块以记录带有日志消息的类名是微不足道的:
module Logging
def logger
@logger ||= Logging.logger_for(self.class.name)
end
# Use a hash class-ivar to cache a unique Logger per class:
@loggers = {}
class << self
def logger_for(classname)
@loggers[classname] ||= configure_logger_for(classname)
end
def configure_logger_for(classname)
logger = Logger.new(STDOUT)
logger.progname = classname
logger
end
end
end
您的Widget
现在使用其类名记录消息,并且不需要更改一位:)
答案 1 :(得分:22)
根据您已经设计的设计,看起来最简单的解决方案是为Crawler提供一个返回模块ivar的模块方法。
module Crawler
def self.logger
@logger
end
def self.logger=(logger)
@logger = logger
end
end
如果您愿意,也可以使用“class <<self
magic”:
module Crawler
class <<self
attr_accessor :logger
end
end
完全相同。
答案 2 :(得分:11)
正如Zenagray所指出的那样,从雅各布的答案中遗漏出来的课堂方法。一个小小的补充解决了:
require 'logger'
module Logging
class << self
def logger
@logger ||= Logger.new($stdout)
end
def logger=(logger)
@logger = logger
end
end
# Addition
def self.included(base)
class << base
def logger
Logging.logger
end
end
end
def logger
Logging.logger
end
end
预期用途是通过&#34; include&#34;:
class Dog
include Logging
def self.bark
logger.debug "chirp"
puts "#{logger.__id__}"
end
def bark
logger.debug "grrr"
puts "#{logger.__id__}"
end
end
class Cat
include Logging
def self.bark
logger.debug "chirp"
puts "#{logger.__id__}"
end
def bark
logger.debug "grrr"
puts "#{logger.__id__}"
end
end
Dog.new.bark
Dog.bark
Cat.new.bark
Cat.bark
产地:
D, [2014-05-06T22:27:33.991454 #2735] DEBUG -- : grrr
70319381806200
D, [2014-05-06T22:27:33.991531 #2735] DEBUG -- : chirp
70319381806200
D, [2014-05-06T22:27:33.991562 #2735] DEBUG -- : grrr
70319381806200
D, [2014-05-06T22:27:33.991588 #2735] DEBUG -- : chirp
70319381806200
请注意,在所有四种情况下,记录器的ID都相同。如果您想为每个课程设置不同的实例,请不要使用Logging.logger
,而应使用self.class.logger
:
require 'logger'
module Logging
def self.included(base)
class << base
def logger
@logger ||= Logger.new($stdout)
end
def logger=(logger)
@logger = logger
end
end
end
def logger
self.class.logger
end
end
现在同样的程序产生:
D, [2014-05-06T22:36:07.709645 #2822] DEBUG -- : grrr
70350390296120
D, [2014-05-06T22:36:07.709723 #2822] DEBUG -- : chirp
70350390296120
D, [2014-05-06T22:36:07.709763 #2822] DEBUG -- : grrr
70350390295100
D, [2014-05-06T22:36:07.709791 #2822] DEBUG -- : chirp
70350390295100
请注意,前两个ID是相同的,但与前两个ID不同,表明我们有两个实例 - 每个类一个。
答案 3 :(得分:4)
受此线程的启发,我创建了easy_logging gem。
它结合了所讨论的所有功能,例如:
安装:
gem install 'easy_logging
用法:
require 'easy_logging'
class YourClass
include EasyLogging
def do_something
# ...
logger.info 'something happened'
end
end
class YourOtherClass
include EasyLogging
def self.do_something
# ...
logger.info 'something happened'
end
end
YourClass.new.do_something
YourOtherClass.do_something
输出
I, [2017-06-03T21:59:25.160686 #5900] INFO -- YourClass: something happened
I, [2017-06-03T21:59:25.160686 #5900] INFO -- YourOtherClass: something happened
有关GitHub的更多详情。
答案 4 :(得分:2)
可能是一些奇怪的Ruby魔法,可以让你避免它,但有一个相当简单的解决方案,不需要奇怪。只需将记录器放入模块并直接访问它,并使用一种机制进行设置。如果你想对它感到很酷,请定义一个“懒惰的记录器”,它保留一个标志,说明它是否还有一个记录器,并且静默地丢弃消息直到记录器被设置,抛出记录器之前记录的异常。设置或将日志消息添加到列表中,以便在定义记录器后记录它。
答案 5 :(得分:2)
一小段代码来演示这是如何工作的。我只是创建一个新的基本对象,以便我可以观察到object_id在整个调用过程中保持不变:
module M
class << self
attr_accessor :logger
end
@logger = nil
class C
def initialize
puts "C.initialize, before setting M.logger: #{M.logger.object_id}"
M.logger = Object.new
puts "C.initialize, after setting M.logger: #{M.logger.object_id}"
@base = D.new
end
end
class D
def initialize
puts "D.initialize M.logger: #{M.logger.object_id}"
end
end
end
puts "M.logger (before C.new): #{M.logger.object_id}"
engine = M::C.new
puts "M.logger (after C.new): #{M.logger.object_id}"
此代码的输出类似于(object_id
为4表示nil
):
M.logger (before C.new): 4
C.initialize, before setting M.logger: 4
C.initialize, after setting M.logger: 59360
D.initialize M.logger: 59360
M.logger (after C.new): 59360
感谢帮助人员!
答案 6 :(得分:1)
如何将记录器包装在单件中,然后您可以使用MyLogger.instance
访问它答案 7 :(得分:0)
根据您的评论
如果您可以动态更改已经实例化的Logger的输出位置(类似于更改日志级别的方式),则可以避免所有这些。
如果您不受限于默认记录器,则可以使用另一个log-gem。
以log4r为例:
require 'log4r'
module Crawler
LOGGER = Log4r::Logger.new('mylog')
class Runner
def initialize
LOGGER.info('Created instance for %s' % self.class)
end
end
end
ARGV << 'test' #testcode
#...
case ARGV.first
when 'test'
Crawler::LOGGER.outputters = Log4r::StdoutOutputter.new('stdout')
when 'prod'
Crawler::LOGGER.outputters = Log4r::FileOutputter.new('file', :filename => 'test.log') #append to existing log
end
#...
Crawler::Runner.new
在prod模式下,日志记录数据存储在一个文件中(附加到现有文件,但有创建新日志文件或实现滚动日志文件的选项)。
结果:
INFO main: Created instance for Crawler::Runner
如果使用log4r(a)的继承机制,您可以为每个类定义一个记录器(或者在我的下面的每个实例示例中)并共享输出器。
示例:
require 'log4r'
module Crawler
LOGGER = Log4r::Logger.new('mylog')
class Runner
def initialize(id)
@log = Log4r::Logger.new('%s::%s %s' % [LOGGER.fullname,self.class,id])
@log.info('Created instance for %s with id %s' % [self.class, id])
end
end
end
ARGV << 'test' #testcode
#...
case ARGV.first
when 'test'
Crawler::LOGGER.outputters = Log4r::StdoutOutputter.new('stdout')
when 'prod'
Crawler::LOGGER.outputters = Log4r::FileOutputter.new('file', :filename => 'test.log') #append to existing log
end
#...
Crawler::Runner.new(1)
Crawler::Runner.new(2)
结果:
INFO Runner 1: Created instance for Crawler::Runner with id 1
INFO Runner 2: Created instance for Crawler::Runner with id 2
(a)像A::B
这样的记录器名称的名称为B
,并且是名为A
的记录器的子项。据我所知,这不是对象继承。
此方法的一个优点是:如果要为每个类使用单个记录器,则只需更改记录器的名称。
答案 8 :(得分:0)
虽然是一个老问题,但我认为记录一种不同的方法是值得的。
根据Jacob的回答,我建议您在需要时添加一个模块。
我的版本是:
# saved into lib/my_log.rb
require 'logger'
module MyLog
def self.logger
if @logger.nil?
@logger = Logger.new( STDERR)
@logger.datetime_format = "%H:%M:%S "
end
@logger
end
def self.logger=( logger)
@logger = logger
end
levels = %w(debug info warn error fatal)
levels.each do |level|
define_method( "#{level.to_sym}") do |msg|
self.logger.send( level, msg)
end
end
end
include MyLog
我把它保存到一个方便的模块库中,我会这样使用它:
#! /usr/bin/env ruby
#
require_relative '../lib/my_log.rb'
MyLog.debug "hi"
# => D, [19:19:32 #31112] DEBUG -- : hi
MyLog.warn "ho"
# => W, [19:20:14 #31112] WARN -- : ho
MyLog.logger.level = Logger::INFO
MyLog.logger = Logger.new( 'logfile.log')
MyLog.debug 'huh'
# => no output, sent to logfile.log instead
我发现这比我到目前为止所看到的其他选项更容易,更通用,所以我希望它可以帮助你。