Registro de Python: use milisegundos en formato de tiempo

9 minutos de lectura

avatar de usuario
jonathan livni

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 la hora puedo usar:

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

sin embargo, el documentación no especifica cómo formatear milisegundos. Encontré 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 la %f:

logging.Formatter(fmt="%(asctime)s",datefmt="%Y-%m-%d,%H:%M:%S.%f")

  • ¿Quizás cambiar la configuración regional podría ayudar?

    – pajtón

    9 de junio de 2011 a las 9:33

  • @ pajton – en el siguiente enlace dice “La información local no es utilizada por asctime ()” – docs.python.org/library/time.html#time.asctime

    – Jonathan Livni

    9 de junio de 2011 a las 9:35

  • %f tampoco funciona en python 2.7.9 o 3.5.1

    –Antony Hatchkins

    23 de agosto de 2016 a las 16:49

  • Buena conversación aquí. Vine aquí porque logging afirma que su formato de hora predeterminado sigue la norma ISO 8601. No es así. Utiliza espacio, no “T” para separar el tiempo y la coma para las fracciones de segundo, no el punto decimal. ¿Cómo pueden estar tan equivocados?

    – Sr. Lance E Sloan

    10 de septiembre de 2016 a las 14:58

avatar de usuario
craig mc daniel

Esto debería funcionar también:

logging.Formatter(
    fmt="%(asctime)s.%(msecs)03d",
    datefmt="%Y-%m-%d,%H:%M:%S"
)

  • Gracias: Aquí están los documentos para estos: docs.python.org/2/library/logging.html#logrecord-atributos docs.python.org/3/library/logging.html#logrecord-atributos .. ¿Hay alguna forma de incluir la zona horaria (%z)? … Los tiempos de formato ISO8601 en los registros de Python (, -> .) serían geniales.

    –Wes Turner

    22 de junio de 2015 a las 4:50


  • Esta solución tiene desventajas, porque si tiene %z o %Z en tus datefmt quieres que aparezca DESPUÉS de los mseg, no antes.

    – Wim

    27/04/2017 a las 21:01

  • Y también si está usando un reloj de 12 horas que tiene AM o PM

    – Dólar Akshay

    5 de febrero de 2018 a las 14:14

  • @wim es una solución alternativa, pero use UTC en lugar de la hora local haciendo logging.Formatter.converter = time.gmtime entonces no necesitas usar %z o %Z. Alternativamente, probablemente podría cambiar el default_msec_format atributo de tu logging.Formatter objetar a %s,%03d%z o %s,%03d%Z

    – Marca

    14 de marzo de 2019 a las 0:09


  • @wim como continuación de mi comentario anterior (no pude editar más…), esto es lo que hice: from time import gmtime# Use UTC rather than local date/timelogging.Formatter.converter = gmtimelogging.basicConfig(datefmt='%Y-%m-%dT%H:%M:%S', format='%(name)s | %(asctime)s.%(msecs)03dZ | %(message)s', level=log_level)

    – Marca

    14 de marzo de 2019 a las 0:38


avatar de usuario
unutbu

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


logging.Formatter’s formatTime el método 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 arreglar especificando un datefmt porque ct es un time.struct_time y estos objetos no registran milisegundos.

Si cambiamos la definición de ct para que sea un datetime objeto en lugar de un struct_timeentonces (al menos con las versiones modernas de Python) podemos llamar ct.strftime y entonces podemos usar %f para 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 datefmt argumento:

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.

  • entonces %f en realidad daría microsegundos, no milisegundos, ¿verdad?

    – Jonathan Livni

    26 de junio de 2011 a las 15:43

  • @Jonathan: Ups, tienes razón, %f da microsegundos. Supongo que la forma más fácil de obtener milisegundos es cambiar la coma a un punto decimal (ver la edición anterior).

    – unutbu

    26 de junio de 2011 a las 17:11

  • De hecho, creo que esta es la mejor respuesta debido al hecho de que te permite volver a poder usar las opciones de formato ESTÁNDAR. De hecho, quería microsegundos, ¡y esta era la única opción que podía hacerlo!

    – toques de trompeta

    25 de septiembre de 2018 a las 14:08

avatar de usuario
maestro james

Agregar msegs 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")

  • Con mucho, la opción más simple y limpia. No estoy seguro de por qué está obteniendo el registrador cuando simplemente 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-atributos

    – naphier

    17 de abril de 2019 a las 3:49

  • Hmmm punto interesante, gracias por comentar, es motivo de reflexión seguro. Sí, probablemente solo lo agregué como una lección sobre lo que está sucediendo allí también, y para asegurarme de que esté allí y porque he preguntado varias cosas, por lo que no necesita varias llamadas a los padres (a través de ‘.’) para buscarlo. Si llamara a .info o .debug nuevamente, tal vez los guardaría directamente nuevamente como sugiere para guardar un ciclo de búsqueda de referencia. [let info = logging.info]

    – Maestro Jaime

    1 de noviembre de 2019 a las 6:48

  • @naphier, está obteniendo el registrador en lugar de ‘solo’ llamar logging.info(msg) porque generalmente desea explotar la jerarquía de nombres de registradores de Python. (como establecer diferentes niveles de verbosidad para ciertos paquetes o confiar en %(levelname) dando valores útiles). Véase también, por ejemplo python3 -c 'import logging; logging.basicConfig(); logging.warning("foo"); logging.getLogger(__name__).warning("bar")'.

    – maxschlepzig

    28/03/2021 a las 10:00

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

formatter = logging.Formatter('%(asctime)s')
formatter.default_msec_format="%s.%03d"

avatar de usuario
no2qubit

Muchas respuestas obsoletas, demasiado complicadas y extrañas aquí. La razón es que la documentación es inadecuada y la solución simple es simplemente usar basicConfig() y configurarlo de la siguiente manera:

logging.basicConfig(datefmt="%Y-%m-%d %H:%M:%S", format="{asctime}.{msecs:0<3.0f} {name} {threadName} {levelname}: {message}", style="{")

El truco aquí era que tú también hay que configurar el datefmt argumento, como el defecto lo estropea y es no lo que se muestra (actualmente) en el cómo hacer documentos de python. Así que más bien mira aquí.


Una forma alternativa y posiblemente más limpia, habría sido anular el default_msec_format variables con:

formatter = logging.Formatter('%(asctime)s')
formatter.default_msec_format="%s.%03d"

Sin embargo, eso no funcionó por razones desconocidas.

PD. Estoy usando Phyton 3.8.

  • No estoy seguro de que ninguna de sus soluciones funcione.

    – mike roedor

    26 de marzo de 2021 a las 8:55

  • @mikerodent Como dije, el primero que usé y funcionó para mí, el segundo se encontró en la documentación y no funcionó. Entonces, si “no está seguro”, ¿qué está diciendo en realidad?

    – no2qubit

    27 de marzo de 2021 a las 3:18

  • No imprime la zona horaria, por lo que me temo que no es ISO8601, pero funciona si solo necesita los milisegundos

    –Bosco Domingo

    16 de junio de 2021 a las 13:45

  • @BoscoDomingo Puedes ajustar el datefmt cadena a lo que quieras/necesites para incluir también TZ en formato de tiempo ISO-8601.

    – no2qubit

    11 de marzo a las 13:12

Descubrí un dos líneas para hacer que el módulo de registro de Python genere marcas de tiempo en formato RFC 3339 (compatible con ISO 1801), con milisegundos y zona horaria formateados correctamente y sin dependencias externas:

import datetime
import logging

# Output timestamp, as the default format string does not include it
logging.basicConfig(format="%(asctime)s: level=%(levelname)s module=%(module)s msg=%(message)s")

# Produce RFC 3339 timestamps
logging.Formatter.formatTime = (lambda self, record, datefmt=None: datetime.datetime.fromtimestamp(record.created, datetime.timezone.utc).astimezone().isoformat())

Ejemplo:

>>> logging.getLogger().error("Hello, world!")
2021-06-03T13:20:49.417084+02:00: level=ERROR module=<stdin> msg=Hello, world!

Alternativamente, esa última línea podría escribirse de la siguiente manera:

def formatTime_RFC3339(self, record, datefmt=None):
    return (
        datetime.datetime.fromtimestamp(record.created, datetime.timezone.utc)
        .astimezone()
        .isoformat()
    )

logging.Formatter.formatTime = formatTime_RFC3339

Ese método también podría usarse en instancias específicas del formateador, en lugar de anularlo en el nivel de clase, en cuyo caso deberá eliminar self de la firma del método.

  • No estoy seguro de que ninguna de sus soluciones funcione.

    – mike roedor

    26 de marzo de 2021 a las 8:55

  • @mikerodent Como dije, el primero que usé y funcionó para mí, el segundo se encontró en la documentación y no funcionó. Entonces, si “no está seguro”, ¿qué está diciendo en realidad?

    – no2qubit

    27 de marzo de 2021 a las 3:18

  • No imprime la zona horaria, por lo que me temo que no es ISO8601, pero funciona si solo necesita los milisegundos

    –Bosco Domingo

    16 de junio de 2021 a las 13:45

  • @BoscoDomingo Puedes ajustar el datefmt cadena a lo que quieras/necesites para incluir también TZ en formato de tiempo ISO-8601.

    – no2qubit

    11 de marzo a las 13:12

avatar de usuario
Wolfgang Kuehn

Una expansión simple que no requiere la datetime El módulo y no tiene desventajas como otras soluciones es usar un reemplazo de cadena simple como este:

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 regionales, utilizando %F por milisegundos. 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

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad