2009-05-27 6 views
42

Praca nad małym skryptem w języku Ruby, który wychodzi do internetu i indeksuje różne usługi. Mam w środku moduł z kilkoma klasami:Ruby - współudział loggera między modułami/klasami

module Crawler 
    class Runner 
    class Options 
    class Engine 
end 

Chcę udostępnić jeden rejestrator wśród wszystkich tych klas. Normalnie bym po prostu umieścić to na stałe w module i odwoływać się do niej tak:

Crawler::LOGGER.info("Hello, world") 

Problem polega na tym, że nie mogę tworzyć moje wystąpienie rejestratora dopóki wiem gdzie wyjście dzieje. Uruchomieniu robota poprzez linię poleceń iw tym momencie można powiedzieć, to chcesz uruchomić w rozwoju (log wyjście idzie do STDOUT) lub produkcji (log wyjście idzie do pliku, crawler.log):

crawler --environment=production 

Mam klasę Options, która analizuje opcje przekazywane za pomocą wiersza polecenia. Tylko w tym momencie wiem, jak utworzyć rejestrator z poprawną lokalizacją wyjściową.

Moje pytanie brzmi: jak/gdzie umieścić obiekt mojego rejestratora, aby wszystkie moje klasy miały dostęp do niego?

Mogę przekazać moją instancję rejestratora do każdego wywołania new() dla każdej instancji klasy, którą utworzę, ale wiem, że musi być lepsza, sposób Rubyish to zrobić. Wyobrażam sobie jakąś dziwną zmienną klasy w module, która jest wspólna z class << self lub inną magią. :)

Trochę więcej szczegółów: Runner zaczyna wszystko przez przekazanie opcji wiersza polecenia do klasy Options i wróci obiekt z kilku zmiennych instancji:

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 

Potrzebuję kod w celu Engine mieć dostęp do obiektu rejestratora (wraz z kilkoma więcej klasami, które są inicjowane wewnątrz Engine). Wsparcie!

Wszystko to można było uniknąć, gdybyś mógł dynamicznie zmienić lokalizację wyjściową już utworzonego programu rejestrującego (podobnie jak zmieniasz poziom rejestrowania). Utworzę go STDOUT, a następnie przejdę do pliku, jeśli jestem w produkcji. Widziałem gdzieś sugestię zmiany globalnej zmiennej Ruby $ stdout, która przekierowywała wyjście gdzieś poza STDOUT, ale wydaje się to dość hackowe.

Dzięki!

Odpowiedz

21

Z zaprojektowanym układem wygląda na to, że najprościej jest dać robotowi Crawler metodę modułową, która zwraca moduł ivar.

module Crawler 
    def self.logger 
    @logger 
    end 
    def self.logger=(logger) 
    @logger = logger 
    end 
end 

Lub można użyć "class <<self magii", jeśli chcesz:

module Crawler 
    class <<self 
    attr_accessor :logger 
    end 
end 

Robi dokładnie to samo.

+0

Dzięki Chuck! Właśnie to robiłem po drobnych testach na moim końcu! –

2

Może to być jakaś dziwna magia rubinowa, która pozwoli ci jej uniknąć, ale istnieje dość proste rozwiązanie, które nie musi być dziwne. Wystarczy umieścić moduł rejestrujący w module i uzyskać do niego dostęp bezpośrednio z mechanizmem do ustawienia. Jeśli chcesz się z tym pogodzić, zdefiniuj "leniwy rejestrator", który utrzymuje flagę, jeśli ma jeszcze rejestrator, i cicho przekazuje komunikaty do czasu ustawienia rejestratora, zgłasza wyjątek, że coś jest rejestrowane przed rejestratorem ustawić lub dodać komunikat dziennika do listy, aby mógł zostać zarejestrowany po zdefiniowaniu rejestratora.

2

Mały fragment kodu pokazujący, jak to działa. Ja po prostu tworząc nowy podstawowy obiekt tak, że mogę stwierdzić, że object_id pozostaje taka sama w całej rozmowy:

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}" 

Wyjście z tego kodu wygląda (an object_id 4 oznacza 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 

Dzięki za pomoc!

1

Jak o owijania rejestratora w Singleton następnie można do niego dostęp za pomocą MyLogger.instance

+0

Jeśli nie masz zamiaru najpierw przejść do podklasy "Logger", przeczytaj [The Chainsaw Infanticide Logger Manuever] (http://groups.google.com/group/comp.lang.ruby/browse_frm/thread/aaec68ab9088e3ef/d0f054886e0bf71c?lnk= gst & q = chainsaw # d0f054886e0bf71c) [sic] przed zrobieniem singla dla każdego, kto może ponownie użyć twojego kodu. – Phrogz

91

Lubię mieć logger metody dostępne w moich zajęciach, ale nie wiem jak zraszanie @logger = Logging.logger we wszystkich moich inicjalizatorów. Zazwyczaj robię to:

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 

Następnie w klasach:

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 

Ponieważ metoda Logging#logger może uzyskać dostęp do instancji, że moduł miesza się, to jest trywialne przedłużyć swój moduł logowania do nagrać classname z logów:

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 

Twój Widget loguje teraz wiadomości z nazwy klasy i nie trzeba zmienić jeden bit :)

+0

Dodaj to do swojego modułu rejestrowania i teraz możesz go skonfigurować. '@out = STDOUT (jak region zmienny)' i tę metodę klasy: 'def konfiguracji (config) wylogowania = konfiguracji [ 'wylogowania'] Jeśli wylogowanie = '' STDOUT następnie @ out = wylogowanie # powinno być ścieżką dziennika, np. /tmp/log.txt koniec koniec ' – sethcall

+1

To jest cudowne, z wyjątkiem tego, że nie może być używane w metodach klasowych (' self.some_method'). Jeśli użyjesz 'extend' zamiast tego naprawi problem, ale potem użyje metody logger dla instancji, będziesz musiał poprzedzić' logger' wywołania z nazwą klasy .. tj. 'Widget.logger' lub użyć' self.class.logger' . Osobiście uważam, że 'extend' jest bardziej przydatny w tej sytuacji. – zanegray

+0

Testowałem ostatnią wersję, która zmienia nazwę progu, ale Logger.new (STDOUT) zawsze dostarcza mi instancji smae .. więc to nie działa; przegapiłem coś? – Notalifeform

10

Jak zauważa Zenagray, logowanie z metod klasowych zostało pominięte w odpowiedzi Jacoba. Niewielkim dodatkiem rozwiązuje ten problem:

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 

określonego zastosowania jest przez "obejmują":

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 

Wytwarza:

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 

Uwaga id rejestratorze jest taka sama we wszystkich czterech przypadki. Jeśli chcesz inną instancję dla każdej klasy, a nie używać Logging.logger raczej używać 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 

Ten sam program produkuje obecnie:

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 

Należy pamiętać, że pierwsze dwa id są takie same ale różnią się one od dwóch drugich identyfikatorów, pokazując, że mamy dwie instancje - po jednej dla każdej klasy.

+0

Witam, myślałem, że dodatek będzie wyprowadzać rzeczywistą nazwę metody klasy jako poprawkę do tego, co robi Jacob? Z przykładów nie można wyobrazić sobie, która metoda jest używana. – Angela

+0

Nie jestem pewien na twoje pytanie ani wersję. W drugiej wersji z innym rejestratorem dla każdej klasy, logowanie z metody klasy przechodzi bezpośrednio do programu rejestrującego dla klas, podczas gdy logowanie z metody instancji przechodzi do rejestratora instancji, która następnie wywołuje rejestrator dla klasy. – pedz

0

podstawie Twojego komentarza

Wszystko to można by uniknąć, gdyby można było po prostu dynamicznie zmieniać położenie wyjściowe już-instancja Logger (podobny do tego, jak zmienić poziomu log).

Jeśli nie jesteś ograniczony do domyślnego programu rejestrującego, możesz użyć innego log-gem.

Jako przykład 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 

W trybie prod dane logowania są przechowywane w pliku (dołączony do istniejącego pliku, ale istnieją możliwości tworzenia nowych plików dziennika lub wdrażają toczenia logów).

Rezultat:

INFO main: Created instance for Crawler::Runner 

Jeśli używasz mechanizmu dziedziczenia Log4r (a), można zdefiniować rejestrator dla każdej klasy (lub w moim poniższym przykładzie dla każdej instancji) i dzielić outputter.

Przykład:

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) 

Rezultatem

INFO Runner 1: Created instance for Crawler::Runner with id 1 
INFO Runner 2: Created instance for Crawler::Runner with id 2 

(a) Nazwa rejestrator jak A::B ma nazwę B i dziecko rejestratorze z nazwą A. O ile mi wiadomo, nie jest to dziedziczenie przedmiotów.

Zaleta tego podejścia: jeśli chcesz używać pojedynczego rejestratora dla każdej klasy, wystarczy zmienić nazwę rejestratora.

1

Zainspirowany tym wątkiem stworzyłem klejnot easy_logging.

Łączy wszystkie cechy omówione takie jak:

  • Dodaje zalogowaniem funkcjonalności w dowolnym miejscu z jednym, krótki, self-opisowy komenda
  • Rejestrator działa w obu metod klasy i instancji
  • Logger jest specyficzny do klasy i zawiera nazwę klasy

Instalacja:

gem install 'easy_logging 

Zastosowanie:

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 

Wyjście

I, [2017-06-03T21:59:25.160686 #5900] INFO -- YourClass: something happened 
I, [2017-06-03T21:59:25.160686 #5900] INFO -- YourOtherClass: something happened 

Więcej szczegółów na GitHub.

0

Chociaż stare pytanie, pomyślałem, że warto udokumentować inne podejście.

Bazując na odpowiedzi Jacoba, proponuję moduł, który można dodać w razie potrzeby.

Moja wersja jest taka:

# 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 

mam to zapisane w bibliotece przydatnych modułów, i chciałbym używać go tak:

#! /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 

znajdę to dużo łatwiejsze i bardziej wszechstronny niż inne opcje, które dotąd analizowałem, więc mam nadzieję, że pomoże ci to w twoim.

+0

To proste rozwiązanie ma kilka wad wskazanych już przez innych w tym wątku, odnoszących się do odpowiedzi Jacoba. Zwróciłem się do tych w klejnocie easy_logging. Jeśli chodzi o to podejście, myślę, że jest to najbezpieczniejszy zakład. Zobacz moją odpowiedź, aby uzyskać więcej informacji. – thisismydesign

+0

Fajny punkt, ponieważ mój przypadek użycia nie uwzględniał tych przypadków skrajnych (na przykład rejestratorów specyficznych dla klasy). Dziękuję Ci. – Kitebuggy