2012-03-09 13 views
8

Ho un esempio Logger:Redirect stderr per esempio Logger

require 'logger' 
logger = Logger.new('foo.log', 'weekly') 

Voglio riorientare gli errori di runtime (output stderr) nel registro pure. Ho trovato this forum thread che ha il consiglio:

new_fd = logger.get_logger_file_descriptor 
$stderr.reopen new_fd 

Tuttavia, Logger non ha un metodo di istanza get_logger_file_descriptor, né posso trovare nessuna metodi esposti in tutto l'accesso al dispositivo di registro o il file.

Come posso causare l'output di $ stderr nel log?

risposta

15

Se si sta creando il logger da soli, è possibile creare l'oggetto File prima, quindi utilizzarlo per creare il data logger e assegnarlo a $stderr:

log_file = File.new('foo.log', 'a') 
logger = Logger.new(log_file, 'weekly') 
$stderr = log_file #usually no need to use reopen 

Si noti che questo si tradurrà in l'output del registro essere mescolato con l'output di $stderr, che potrebbe causare problemi se si sta analizzando il file di registro in attesa che si trovi in ​​un determinato formato (questo accadrà anche con la soluzione).

Se non si dispone del file sottostante ma si riceve lo logger da qualche altra parte, è un po 'più complicato. Ciò che è necessario è un oggetto simile a IO che può essere assegnato a $stderr e passa qualsiasi cosa scritta al registratore. La classe IO in Ruby è, purtroppo, abbastanza strettamente legata al sistema i/o sottostante (descrittori di file e simili), e non esiste un'interfaccia generale che possa essere utilizzata per creare flussi di input e output. (StringIO è l'eccezione notevole).

Tuttavia la maggior parte, se non tutti, i metodi di uscita sul IO infine passare attraverso #write, quindi eseguendo l'override questo metodo è possibile avvicinarsi a quello che stai dopo:

class IOToLog < IO 

    def initialize(logger) 
    @logger = logger 
    end 

    def write(string) 
    #assume anything written to stderr is an error 
    @logger.error(message) 
    end 

end 

logger = get_logger_from_somewhere 

$stderr = IOToLog.new(logger) 

Ora qualsiasi cosa scritta $stderr finirà per andare al file di registro. La formattazione tuttavia sarà un po 'strana. Ogni volta che uno qualsiasi dei metodi di scrittura chiama #write, nel file di registro verrà creata una nuova voce. Ad esempio, #puts chiamato con un array chiamerà #write per ogni voce dell'array e nuovamente con un carattere di nuova riga tra ciascuna voce, risultando in 2n - 1 voci di registro, n - 1 di cui sarà vuoto.

È possibile rendere più complesso il metodo sostituito a #write per gestire questo problema, magari utilizzando un buffer interno, e chiamare il registratore solo quando si ritiene di avere un messaggio completo. In alternativa, è possibile sovrascrivere i singoli metodi per scrivere nel registratore stesso. Se lo facevi, la classe IOToLog non dovrebbe necessariamente ereditare da IO.

La soluzione migliore dipenderà da come si desidera l'uscita standard error per apparire nel file di log, come il programma utilizza $stderr, e come si vuole fare metodi di attuazione da IO lavoro molto.

+0

Ottima risposta, grazie! Nel mio caso sto creando il logger in modo che la tua prima soluzione sia pulita ed elegante. Mi piace particolarmente che tu abbia incluso la soluzione alternativa per l'altro caso. (Anche se in tal caso potrei sostenere il mio trucco di raggiungere e prendere direttamente il file.) – Phrogz

+0

Il secondo metodo non funziona per me su jruby. Ottengo il seguente errore: (Errno :: EBADF) Descrittore di file errato –

+0

Ci dovrebbe essere '@ logger.error (stringa)' Penso – lojza

2

Il meglio che ho potuto venire in mente è questa portata-around incidere:

$stderr.reopen logger.instance_variable_get(:@logdev).dev 

Funziona, ma naturalmente rompe l'incapsulamento di Logger, che presumo fosse lì per un motivo.