Registro de Python: use milisegundos en formato de hora

163

Por defecto logging.Formatter('%(asctime)s')imprime con el siguiente formato:

2011-06-09 10:54:40,638

donde 638 es el milisegundo. Necesito cambiar la coma a un punto:

2011-06-09 10:54:40.638

Para formatear el tiempo que puedo usar:

logging.Formatter(fmt='%(asctime)s',datestr=date_format_str)

sin embargo, la documentación no especifica cómo formatear milisegundos. He encontrado esta pregunta SO que habla de microsegundos, pero a) preferiría milisegundos yb) lo siguiente no funciona en Python 2.6 (en el que estoy trabajando) debido a %f:

logging.Formatter(fmt='%(asctime)s',datefmt='%Y-%m-%d,%H:%M:%S.%f')
Jonathan
fuente
1
¿Quizás cambiar la configuración regional podría ayudar?
pajton
1
@ pajton - en el siguiente enlace dice "asctime () no usa la información de la configuración regional" - docs.python.org/library/time.html#time.asctime
Jonathan
%ftampoco funciona en Python 2.7.9 o 3.5.1
Antony Hatchkins
44
Buena conversación aquí. Vine aquí porque loggingafirma que su formato de hora predeterminado sigue ISO 8601. No lo hace. Utiliza el espacio, no "T" para separar el tiempo y la coma durante segundos fraccionarios, no el punto decimal. ¿Cómo podrían estar tan equivocados?
LS

Respuestas:

76

Tenga en cuenta que la solución de Craig McDaniel es claramente mejor.


El formatTimemétodo de Formatter se ve así:

def formatTime(self, record, datefmt=None):
    ct = self.converter(record.created)
    if datefmt:
        s = time.strftime(datefmt, ct)
    else:
        t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
        s = "%s,%03d" % (t, record.msecs)
    return s

Observe la coma en "%s,%03d". Esto no se puede solucionar especificando un datefmtporque ctes a time.struct_timey estos objetos no registran milisegundos.

Si cambiamos la definición de ctpara que sea un datetimeobjeto en lugar de un struct_time, entonces (al menos con las versiones modernas de Python) podemos llamar ct.strftimey luego podemos usar %fpara formatear microsegundos:

import logging
import datetime as dt

class MyFormatter(logging.Formatter):
    converter=dt.datetime.fromtimestamp
    def formatTime(self, record, datefmt=None):
        ct = self.converter(record.created)
        if datefmt:
            s = ct.strftime(datefmt)
        else:
            t = ct.strftime("%Y-%m-%d %H:%M:%S")
            s = "%s,%03d" % (t, record.msecs)
        return s

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

console = logging.StreamHandler()
logger.addHandler(console)

formatter = MyFormatter(fmt='%(asctime)s %(message)s',datefmt='%Y-%m-%d,%H:%M:%S.%f')
console.setFormatter(formatter)

logger.debug('Jackdaws love my big sphinx of quartz.')
# 2011-06-09,07:12:36.553554 Jackdaws love my big sphinx of quartz.

O, para obtener milisegundos, cambie la coma a un punto decimal y omita el datefmtargumento:

class MyFormatter(logging.Formatter):
    converter=dt.datetime.fromtimestamp
    def formatTime(self, record, datefmt=None):
        ct = self.converter(record.created)
        if datefmt:
            s = ct.strftime(datefmt)
        else:
            t = ct.strftime("%Y-%m-%d %H:%M:%S")
            s = "%s.%03d" % (t, record.msecs)
        return s

...
formatter = MyFormatter(fmt='%(asctime)s %(message)s')
...
logger.debug('Jackdaws love my big sphinx of quartz.')
# 2011-06-09 08:14:38.343 Jackdaws love my big sphinx of quartz.
unutbu
fuente
1
entonces% f realmente daría microsegundos, no milisegundos, ¿verdad?
Jonathan
@ Jonathan: Vaya, tienes razón, %fda microsegundos. Supongo que la forma más fácil de obtener milisegundos es cambiar la coma a un punto decimal (consulte la edición anterior).
unutbu
3
De hecho, creo que esta es la mejor respuesta debido al hecho de que le permite volver a usar las opciones de formato ESTÁNDAR. En realidad quería microsegundos, ¡y esta era la única opción que podía hacerlo!
trumpetlicks
Gracias. Esta respuesta ofrece una solución fácil para obtener microsegundos.
Yongwei Wu
337

Esto también debería funcionar:

logging.Formatter(fmt='%(asctime)s.%(msecs)03d',datefmt='%Y-%m-%d,%H:%M:%S')
Craig McDaniel
fuente
12
Gracias: Aquí están los documentos para estos: docs.python.org/2/library/logging.html#logrecord-attributes docs.python.org/3/library/logging.html#logrecord-attributes .. ¿Hay alguna manera de ¿todavía incluye la zona horaria (% z)? ... los tiempos de formato ISO8601 en los registros de Python (, ->.) Serían geniales.
Wes Turner
19
Esta solución está en desventaja, porque si usted tiene %zo %Zen tu datefmtquieres que para que aparezca después de los milisegundos, no antes.
wim
1
Y también si está utilizando un reloj de 12 horas que tiene AMoPM
DollarAkshay
1
@wim como seguimiento de mi comentario anterior (no podía editar más ...), esto es lo que he hecho: from time import gmtime- # Use UTC rather than local date/time- logging.Formatter.converter = gmtime-logging.basicConfig(datefmt='%Y-%m-%dT%H:%M:%S', format='%(name)s | %(asctime)s.%(msecs)03dZ | %(message)s', level=log_level)
Mark
1
@Mark No puede incrustar la zona horaria en default_msec_format(a partir de Python 3.7) porque solo se sustituyen la hora y los milisegundos. De la loggingfuente:self.default_msec_format % (t, record.msecs)
M. Dudley
27

Agregar msecs fue la mejor opción, gracias. Aquí está mi enmienda usando esto con Python 3.5.3 en Blender

import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s.%(msecs)03d %(levelname)s:\t%(message)s', datefmt='%Y-%m-%d %H:%M:%S')
log = logging.getLogger(__name__)
log.info("Logging Info")
log.debug("Logging Debug")
El maestro james
fuente
1
Con mucho, la opción más simple y limpia. No estoy seguro de por qué está obteniendo el registrador cuando solo puede llamar a logging.info (msg), etc., pero el formato es exactamente lo que estaba buscando. Cualquier otra persona que busque todos los atributos utilizables puede buscar aquí: docs.python.org/3.6/library/logging.html#logrecord-attributes
naphier
Hmmm, punto interesante, gracias por comentar que es seguro pensarlo. Sí, probablemente también lo agregué como una lección de lo que está sucediendo allí, y para asegurarme de que esté allí y porque he preguntado varias cosas para que no necesite múltiples llamadas a los padres (a través de '.') Para buscarlo. Si llamara a .info o .debug nuevamente, tal vez los guardaría directamente nuevamente, ya que sugiere guardar un ciclo de búsqueda de referencia. [let info = logging.info]
Master James el
Gracias por decir Jason. A veces hay una manera más simple de ver el mundo, no tengas miedo de tratar de descubrir esa verdad en muchas situaciones, si no en todas.
Maestro James
15

La forma más simple que encontré fue anular default_msec_format:

formatter = logging.Formatter('%(asctime)s')
formatter.default_msec_format = '%s.%03d'
Mickey B
fuente
1
Interesante, gracias. Pero esto no funcionó para mí en Python 2.7. Es posible que solo funcione en Python 3.x para algún valor de x.
nealmcb
1
@nealmcb esto no está disponible hasta Python 3.3 según los documentos
Mark
3

Después de crear una instancia Formatter, generalmente configuro formatter.converter = gmtime. Entonces, para que la respuesta de @ unutbu funcione en este caso, necesitará:

class MyFormatter(logging.Formatter):
    def formatTime(self, record, datefmt=None):
        ct = self.converter(record.created)
        if datefmt:
            s = time.strftime(datefmt, ct)
        else:
            t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
            s = "%s.%03d" % (t, record.msecs)
        return s
Jonathan
fuente
2

Una expansión simple que no requiere el datetimemódulo y que no se ve obstaculizada como otras soluciones es usar un reemplazo de cadena simple de esta manera:

import logging
import time

class MyFormatter(logging.Formatter):
    def formatTime(self, record, datefmt=None):
    ct = self.converter(record.created)
    if datefmt:
        if "%F" in datefmt:
            msec = "%03d" % record.msecs
            datefmt = datefmt.replace("%F", msec)
        s = time.strftime(datefmt, ct)
    else:
        t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
        s = "%s,%03d" % (t, record.msecs)
    return s

De esta manera, se puede escribir un formato de fecha como desee, incluso teniendo en cuenta las diferencias de región, utilizando %Fmilisegundos. Por ejemplo:

log = logging.getLogger(__name__)
log.setLevel(logging.INFO)

sh = logging.StreamHandler()
log.addHandler(sh)

fm = MyFormatter(fmt='%(asctime)s-%(levelname)s-%(message)s',datefmt='%H:%M:%S.%F')
sh.setFormatter(fm)

log.info("Foo, Bar, Baz")
# 03:26:33.757-INFO-Foo, Bar, Baz
torrentails
fuente
1

Si está usando flecha o si no le importa usar flecha. Puede sustituir el formato de hora de Python por el de flecha.

import logging

from arrow.arrow import Arrow


class ArrowTimeFormatter(logging.Formatter):

    def formatTime(self, record, datefmt=None):
        arrow_time = Arrow.fromtimestamp(record.created)

        if datefmt:
            arrow_time = arrow_time.format(datefmt)

        return str(arrow_time)


logger = logging.getLogger(__name__)

default_handler = logging.StreamHandler()
default_handler.setFormatter(ArrowTimeFormatter(
    fmt='%(asctime)s',
    datefmt='YYYY-MM-DD HH:mm:ss.SSS'
))

logger.setLevel(logging.DEBUG)
logger.addHandler(default_handler)

Ahora puede usar todo el formato de tiempo de la flecha en el datefmtatributo.

Slapy
fuente
-1

tl; dr para personas que buscan aquí una fecha con formato ISO:

datefmt: '% Y-% m-% d% H:% M:% S.% 03d% z'

Jeff Bryner
fuente
-3

A partir de ahora, lo siguiente funciona perfectamente con Python 3.

         logging.basicConfig(level=logging.DEBUG,
                     format='%(asctime)s %(levelname)-8s %(message)s',
                     datefmt='%Y/%m/%d %H:%M:%S.%03d',
                     filename=self.log_filepath,
                     filemode='w')

da el siguiente resultado

2020/01/11 18: 51: 19.011 INFO

Pasindu Madushan
fuente
1
Esto no funciona. % d está imprimiendo la fecha. En su ejemplo, la fecha se imprime con un 0 rellenado delante de ella.
Klik