2016-06-21 34 views
8

Ho un'applicazione che fa uso di multi-threading e viene eseguita in background su un server. Per monitorare l'applicazione senza dover accedere al server, ho deciso di includere Bottle per rispondere ad alcuni endpoint HTTP e lo stato dei report, eseguire shutdown remoto, ecc.Registro rotante di Python a una variabile

Volevo anche aggiungere un modo consultare il file di log. Potrei accedere utilizzando lo FileHandler e inviare il file di destinazione quando viene richiesto l'URL (ad esempio /log).

Tuttavia, mi chiedevo se sarebbe stato possibile implementare qualcosa come un RotatingFileHandler, ma invece di accedere a un file, accedere a una variabile (ad esempio BytesIO). In questo modo, potrei limitare il registro alle informazioni più recenti e allo stesso tempo poterlo restituire al browser come testo anziché come download di file separato.

Il RotatingFileHandler richiede un nome file, quindi non è un'opzione per passare un flusso BytesIO. La registrazione a una variabile stessa è perfettamente fattibile (ad esempio Capturing Python Log Output In A Variable), ma sono un po 'perplesso su come eseguire la parte a rotazione.

Qualsiasi pensiero, suggerimento, suggerimento sarebbe molto apprezzato.

+1

Hai bisogno di un oggetto 'collections.deque' per catturare l'output di registro? Si imposta una lunghezza massima per la deque e, una volta raggiunta questa lunghezza, gli elementi del registro vecchi vengono disattivati ​​all'avvio quando si aggiungono nuovi elementi del registro. –

risposta

1

Proseguendo su suggerimento di Andrew Guy, ho sottoclasse logging.Handler e implementato un gestore che utilizza collections.deque con una lunghezza fissa di tenere un registro dei messaggi di log.

import logging 
import collections 


class TailLogHandler(logging.Handler): 

    def __init__(self, log_queue): 
     logging.Handler.__init__(self) 
     self.log_queue = log_queue 

    def emit(self, record): 
     self.log_queue.append(self.format(record)) 


class TailLogger(object): 

    def __init__(self, maxlen): 
     self._log_queue = collections.deque(maxlen=maxlen) 
     self._log_handler = TailLogHandler(self._log_queue) 

    def contents(self): 
     return '\n'.join(self._log_queue) 

    @property 
    def log_handler(self): 
     return self._log_handler 

Esempio utilizzo:

import random 

logger = logging.getLogger(__name__) 

tail = TailLogger(10) 

formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 

log_handler = tail.log_handler 
log_handler.setFormatter(formatter) 
logger.addHandler(log_handler) 

levels = [logging.INFO, logging.ERROR, logging.WARN, logging.DEBUG, logging.CRITICAL] 
logger.setLevel(logging.ERROR) 

for i in range(500): 
    logger.log(random.choice(levels), 'Message {}'.format(i)) 

print(tail.contents()) 

uscita:

2016-06-22 13:58:25,975 - __main__ - CRITICAL - Message 471 
2016-06-22 13:58:25,975 - __main__ - ERROR - Message 472 
2016-06-22 13:58:25,975 - __main__ - ERROR - Message 473 
2016-06-22 13:58:25,975 - __main__ - ERROR - Message 474 
2016-06-22 13:58:25,975 - __main__ - ERROR - Message 477 
2016-06-22 13:58:25,975 - __main__ - CRITICAL - Message 481 
2016-06-22 13:58:25,975 - __main__ - CRITICAL - Message 483 
2016-06-22 13:58:25,975 - __main__ - ERROR - Message 484 
2016-06-22 13:58:25,975 - __main__ - CRITICAL - Message 485 
2016-06-22 13:58:25,976 - __main__ - CRITICAL - Message 490 
6

Utilizzare la tecnica descritta in Capturing Python Log Output In A Variable, ma catturarlo in un flusso personalizzato che getta via i vecchi dati.

Come così:

# Adapted from http://alanwsmith.com/capturing-python-log-output-in-a-variable 

import logging 
import io 
import collections 

class FIFOIO(io.TextIOBase): 
    def __init__(self, size, *args): 
     self.maxsize = size 
     io.TextIOBase.__init__(self, *args) 
     self.deque = collections.deque() 
    def getvalue(self): 
     return ''.join(self.deque) 
    def write(self, x): 
     self.deque.append(x) 
     self.shrink() 
    def shrink(self): 
     if self.maxsize is None: 
      return 
     size = sum(len(x) for x in self.deque) 
     while size > self.maxsize: 
      x = self.deque.popleft() 
      size -= len(x) 

### Create the logger 
logger = logging.getLogger('basic_logger') 
logger.setLevel(logging.DEBUG) 

### Setup the console handler with a FIFOIO object 
log_capture_string = FIFOIO(256) 
ch = logging.StreamHandler(log_capture_string) 
ch.setLevel(logging.DEBUG) 

### Optionally add a formatter 
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 
ch.setFormatter(formatter) 

### Add the console handler to the logger 
logger.addHandler(ch) 


### Send log messages. 
logger.debug('debug message') 
logger.info('info message') 
logger.warn('warn message') 
logger.error('error message') 
logger.critical('critical message') 


### Pull the contents back into a string and close the stream 
log_contents = log_capture_string.getvalue() 
log_capture_string.close() 

### Output as lower case to prove it worked. 
print(log_contents.lower()) 
+0

Grazie per l'input. L'unica preoccupazione che ho con questa implementazione (questo non era specificato nel post originale), è che lo stato [docs] (https://docs.python.org/3.5/library/io.html#multi-threading) che "gli oggetti TextIOWrapper non sono thread-safe", ma non dicono nulla sugli oggetti "TextIOBase'. Inoltre, lo stesso [docs] (https://docs.python.org/3.5/library/io.html#id3) dice che 'StringIO' dovrebbe essere significativamente più veloce di' TextIO' quando si ha a che fare con file di grandi dimensioni. Potresti scambiare 'TextIOBase' con' StringIO' come classe base 'FIFOIO'? –

Problemi correlati