2011-05-29 15 views
14

Peleo un poco con el registro. Me gustaría pasar los registros después de cierto período de tiempo y también después de alcanzar cierto tamaño.The logging.handlers: ¿Cómo reiniciar después del tiempo o maxBytes?

Rollover después de un período de tiempo se hace por TimedRotatingFileHandler, y vuelco después de alcanzar cierto tamaño de registro se hace por RotatingFileHandler.

Pero el TimedRotatingFileHandler no tiene el atributo maxBytes y la RotatingFileHandler no puede girar después de un cierto período de tiempo. También traté de agregar ambos manejadores al registrador, pero el resultado fue el doble de registro.

¿Extraño algo?

También busqué en el código fuente de logging.handlers. Traté de subclase TimedRotatingFileHandler y reemplazar el método shouldRollover() para crear una clase con capacidades de ambos:

class EnhancedRotatingFileHandler(logging.handlers.TimedRotatingFileHandler): 
    def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=0, utc=0, maxBytes=0): 
     """ This is just a combination of TimedRotatingFileHandler and RotatingFileHandler (adds maxBytes to TimedRotatingFileHandler) """ 
     # super(self). #It's old style class, so super doesn't work. 
     logging.handlers.TimedRotatingFileHandler.__init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=0, utc=0) 
     self.maxBytes=maxBytes 

    def shouldRollover(self, record): 
     """ 
     Determine if rollover should occur. 

     Basically, see if the supplied record would cause the file to exceed 
     the size limit we have. 

     we are also comparing times   
     """ 
     if self.stream is None:     # delay was set... 
      self.stream = self._open() 
     if self.maxBytes > 0:     # are we rolling over? 
      msg = "%s\n" % self.format(record) 
      self.stream.seek(0, 2) #due to non-posix-compliant Windows feature 
      if self.stream.tell() + len(msg) >= self.maxBytes: 
       return 1 
     t = int(time.time()) 
     if t >= self.rolloverAt: 
      return 1 
     #print "No need to rollover: %d, %d" % (t, self.rolloverAt) 
     return 0   

Pero como esto crea el registro de una copia de seguridad y el se sobreescribe. Parece que también tengo que anular el método doRollover() que no es tan fácil.

¿Alguna otra idea de cómo crear un registrador que transfiere el archivo después de cierto tiempo y también después de alcanzar cierto tamaño?

+0

Obviamente, no hay una respuesta directa, excepto sumergirse en el código de la biblioteca y descifrarlo.Afortunadamente, la biblioteca estándar generalmente está bien escrita e idiomática, por lo que si debe hacer esto, aprenderá. Sin embargo, le preguntaría esto a cualquiera que se acerque a mí con esta pregunta: ¿por qué quiere la función de tiempo o tamaño? ¿Qué estás intentando lograr? ¿Cuál es el caso de uso? Como el 'registro' es bastante completo, su deseo puede considerarse "atípico" o la instalación estaría allí. – msw

Respuesta

10

Así que hice un pequeño hack a TimedRotatingFileHandler para poder hacer rollover después del tiempo y el tamaño. Tuve que modificar __init__, shouldRollover, doRollover y getFilesToDelete (ver a continuación). Este es el resultado, al configurar cuando = 'M', intervalo = 2, backupCount = 20, MaxBytes = 1048576:

-rw-r--r-- 1 user group 185164 Jun 10 00:54 sumid.log 
-rw-r--r-- 1 user group 1048462 Jun 10 00:48 sumid.log.2011-06-10_00-48.001  
-rw-r--r-- 1 user group 1048464 Jun 10 00:48 sumid.log.2011-06-10_00-48.002  
-rw-r--r-- 1 user group 1048533 Jun 10 00:49 sumid.log.2011-06-10_00-48.003  
-rw-r--r-- 1 user group 1048544 Jun 10 00:50 sumid.log.2011-06-10_00-49.001  
-rw-r--r-- 1 user group 574362 Jun 10 00:52 sumid.log.2011-06-10_00-50.001 

Se puede ver que los cuatro primeros registros se rodaron sobre después de alcanzar el tamaño de 1 MB, mientras que el último vuelco ocurrió después de dos minutos. Hasta ahora no he probado la eliminación de archivos de registro antiguos, por lo que probablemente no funcione. El código ciertamente no funcionará para backupCount> = 1000. Añado solo tres dígitos al final del nombre del archivo.

Este es el código modificado:

class EnhancedRotatingFileHandler(logging.handlers.TimedRotatingFileHandler): 
    def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=0, utc=0, maxBytes=0): 
     """ This is just a combination of TimedRotatingFileHandler and RotatingFileHandler (adds maxBytes to TimedRotatingFileHandler) """ 
     logging.handlers.TimedRotatingFileHandler.__init__(self, filename, when, interval, backupCount, encoding, delay, utc) 
     self.maxBytes=maxBytes 

    def shouldRollover(self, record): 
     """ 
     Determine if rollover should occur. 

     Basically, see if the supplied record would cause the file to exceed 
     the size limit we have. 

     we are also comparing times   
     """ 
     if self.stream is None:     # delay was set... 
      self.stream = self._open() 
     if self.maxBytes > 0:     # are we rolling over? 
      msg = "%s\n" % self.format(record) 
      self.stream.seek(0, 2) #due to non-posix-compliant Windows feature 
      if self.stream.tell() + len(msg) >= self.maxBytes: 
       return 1 
     t = int(time.time()) 
     if t >= self.rolloverAt: 
      return 1 
     #print "No need to rollover: %d, %d" % (t, self.rolloverAt) 
     return 0   

    def doRollover(self): 
     """ 
     do a rollover; in this case, a date/time stamp is appended to the filename 
     when the rollover happens. However, you want the file to be named for the 
     start of the interval, not the current time. If there is a backup count, 
     then we have to get a list of matching filenames, sort them and remove 
     the one with the oldest suffix. 
     """ 
     if self.stream: 
      self.stream.close() 
     # get the time that this sequence started at and make it a TimeTuple 
     currentTime = int(time.time()) 
     dstNow = time.localtime(currentTime)[-1] 
     t = self.rolloverAt - self.interval 
     if self.utc: 
      timeTuple = time.gmtime(t) 
     else: 
      timeTuple = time.localtime(t) 
      dstThen = timeTuple[-1] 
      if dstNow != dstThen: 
       if dstNow: 
        addend = 3600 
       else: 
        addend = -3600 
       timeTuple = time.localtime(t + addend) 
     dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple) 
     if self.backupCount > 0: 
      cnt=1 
      dfn2="%s.%03d"%(dfn,cnt) 
      while os.path.exists(dfn2): 
       dfn2="%s.%03d"%(dfn,cnt) 
       cnt+=1     
      os.rename(self.baseFilename, dfn2) 
      for s in self.getFilesToDelete(): 
       os.remove(s) 
     else: 
      if os.path.exists(dfn): 
       os.remove(dfn) 
      os.rename(self.baseFilename, dfn) 
     #print "%s -> %s" % (self.baseFilename, dfn) 
     self.mode = 'w' 
     self.stream = self._open() 
     newRolloverAt = self.computeRollover(currentTime) 
     while newRolloverAt <= currentTime: 
      newRolloverAt = newRolloverAt + self.interval 
     #If DST changes and midnight or weekly rollover, adjust for this. 
     if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc: 
      dstAtRollover = time.localtime(newRolloverAt)[-1] 
      if dstNow != dstAtRollover: 
       if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour 
        addend = -3600 
       else:   # DST bows out before next rollover, so we need to add an hour 
        addend = 3600 
       newRolloverAt += addend 
     self.rolloverAt = newRolloverAt 

    def getFilesToDelete(self): 
     """ 
     Determine the files to delete when rolling over. 

     More specific than the earlier method, which just used glob.glob(). 
     """ 
     dirName, baseName = os.path.split(self.baseFilename) 
     fileNames = os.listdir(dirName) 
     result = [] 
     prefix = baseName + "." 
     plen = len(prefix) 
     for fileName in fileNames: 
      if fileName[:plen] == prefix: 
       suffix = fileName[plen:-4] 
       if self.extMatch.match(suffix): 
        result.append(os.path.join(dirName, fileName)) 
     result.sort() 
     if len(result) < self.backupCount: 
      result = [] 
     else: 
      result = result[:len(result) - self.backupCount] 
     return result    
+0

Gracias por compartir. –

+4

Acabo de corregir un error en TimedRotatingFileHandler en Python, en relación con el horario de verano y el sufijo de tiempo del nombre del archivo. También puede considerar aplicar la corrección aquí: http://hg.python.org/cpython/rev/a5c4b8ccca8b –

+1

dstNow y currentTime no están definidos ... –

5

Si realmente necesita esta funcionalidad, escriba su propio controlador basado en TimedRotatingFileHandler para usar sobre todo el tiempo para renovar, pero incorpore rollover basado en tamaño en la lógica existente. Ha intentado esto, pero necesita (como mínimo) anular los métodos shouldRollover() y doRollover(). El primer método determina cuándo reiniciarse, el segundo cierra el archivo de registro actual, renombra los archivos existentes y elimina los archivos obsoletos, y luego abre el nuevo archivo.

La lógica doRollover() puede ser un poco complicada, pero ciertamente factible.

0

Esto es lo que yo uso:

import logging 

class EnhancedRotatingFileHandler(logging.handlers.TimedRotatingFileHandler, logging.handlers.RotatingFileHandler): 
    ''' 
     cf http://stackoverflow.com/questions/29602352/how-to-mix-logging-handlers-file-timed-and-compress-log-in-the-same-config-f 

     Spec: 
     Log files limited in size & date. I.E. when the size or date is overtaken, there is a file rollover 
    ''' 

    ######################################## 


    def __init__(self, filename, mode = 'a', maxBytes = 0, backupCount = 0, encoding = None, 
      delay = 0, when = 'h', interval = 1, utc = False): 

     logging.handlers.TimedRotatingFileHandler.__init__(
     self, filename, when, interval, backupCount, encoding, delay, utc) 

     logging.handlers.RotatingFileHandler.__init__(self, filename, mode, maxBytes, backupCount, encoding, delay) 

    ######################################## 

    def computeRollover(self, currentTime): 
     return logging.handlers.TimedRotatingFileHandler.computeRollover(self, currentTime) 

    ######################################## 

    def getFilesToDelete(self): 
     return logging.handlers.TimedRotatingFileHandler.getFilesToDelete(self) 

    ######################################## 

    def doRollover(self): 
     return logging.handlers.TimedRotatingFileHandler.doRollover(self) 

    ######################################## 

    def shouldRollover(self, record): 
     """ Determine if rollover should occur. """ 
     return (logging.handlers.TimedRotatingFileHandler.shouldRollover(self, record) or logging.handlers.RotatingFileHandler.shouldRollover(self, record)) 
+0

¿Esto realmente funciona para usted en producción? En una prueba rápida donde inicio sesión en el rango (1000) maxBytes = 1000, cuando = 'm', intervalo = 1 parece que le faltan varios archivos/fragmentos de líneas de registro. –

+0

Sí, creo que funciona, acabo de comprobarlo. En realidad, yo solía tener un problema similar al tuyo en mi prueba de unidad: era porque la extensión del archivo de registro no tiene milisegundos de forma predeterminada, cuando se produce la reinversión, el patrón de archivo es el mismo que el anterior y se superpone . Un workaroud sería cambiar la extensión (ver http://stackoverflow.com/questions/24649789/how-to-force-a-rotating-name-with-pythons-timedrotatingfilehandler). En la práctica, en mi configuración configuro el tamaño del archivo ~ 1Mo, y nunca tengo más de 1Mo de registro dentro de 1 segundo –

+1

Ah bien, eso tiene sentido. Acabo de terminar la edición/publicación de una actualización de la respuesta anterior que también funciona con otras combinaciones de tamaños/intervalos de tiempo (ya que también se especifica manualmente la extensión de archivo basada en el tamaño del archivo). ¡Gracias! Vaya colaboración SO! :) –

0

Adapté código de Julien para mi uso. Ahora se reinicia después de alcanzar cierto tamaño de registro o después de un período de tiempo.

class EnhancedRotatingFileHandler(logging.handlers.TimedRotatingFileHandler, logging.handlers.RotatingFileHandler): 

def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, 
      delay=0, when='h', interval=1, utc=False): 
    logging.handlers.TimedRotatingFileHandler.__init__(
     self, filename=filename, when=when, interval=interval, 
     backupCount=backupCount, encoding=encoding, delay=delay, utc=utc) 

    logging.handlers.RotatingFileHandler.__init__(self, filename=filename, mode=mode, maxBytes=maxBytes, 
                backupCount=backupCount, encoding=encoding, delay=delay) 

def computeRollover(self, current_time): 
    return logging.handlers.TimedRotatingFileHandler.computeRollover(self, current_time) 

def doRollover(self): 
    # get from logging.handlers.TimedRotatingFileHandler.doRollover() 
    current_time = int(time.time()) 
    dst_now = time.localtime(current_time)[-1] 
    new_rollover_at = self.computeRollover(current_time) 

    while new_rollover_at <= current_time: 
     new_rollover_at = new_rollover_at + self.interval 

    # If DST changes and midnight or weekly rollover, adjust for this. 
    if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc: 
     dst_at_rollover = time.localtime(new_rollover_at)[-1] 
     if dst_now != dst_at_rollover: 
      if not dst_now: # DST kicks in before next rollover, so we need to deduct an hour 
       addend = -3600 
      else: # DST bows out before next rollover, so we need to add an hour 
       addend = 3600 
      new_rollover_at += addend 
    self.rolloverAt = new_rollover_at 

    return logging.handlers.RotatingFileHandler.doRollover(self) 

def shouldRollover(self, record): 
    return logging.handlers.TimedRotatingFileHandler.shouldRollover(self, record) or logging.handlers.RotatingFileHandler.shouldRollover(self, record) 
Cuestiones relacionadas