Log em Python: use milissegundos em formato de hora

163

Por padrão, logging.Formatter('%(asctime)s')imprime com o seguinte formato:

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

onde 638 é o milissegundo. Preciso alterar a vírgula para um ponto:

2011-06-09 10:54:40.638

Para formatar a hora que posso usar:

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

no entanto, a documentação não especifica como formatar milissegundos. Eu encontrei esta pergunta SO que fala sobre microssegundos, mas a) eu preferiria milissegundos eb) o seguinte não funciona no Python 2.6 (no qual estou trabalhando) devido ao %f:

logging.Formatter(fmt='%(asctime)s',datefmt='%Y-%m-%d,%H:%M:%S.%f')
Jonathan
fonte
1
Talvez mudar o código do idioma possa ajudar?
pajton
1
@ pajton - no link a seguir diz "As informações de localidade não são usadas pelo asctime ()" - docs.python.org/library/time.html#time.asctime
Jonathan
%fnão funciona em python 2.7.9 ou 3.5.1, quer
Antony Hatchkins
4
Boa conversa aqui. Eu vim aqui porque loggingafirma que seu formato de hora padrão segue a ISO 8601. Não é. Ele usa espaço, não "T" para separar o tempo e a vírgula por segundos fracionários, não o ponto decimal. Como eles poderiam estar tão errados?
LS

Respostas:

76

Observe que a solução de Craig McDaniel é claramente melhor.


O formatTimemétodo do log de formatação é assim:

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 a vírgula em "%s,%03d". Isso não pode ser corrigido especificando a datefmtporque cté a time.struct_timee esses objetos não registram milissegundos.

Se mudarmos a definição de ctpara torná-lo um datetimeobjeto em vez de a struct_time, então (pelo menos nas versões modernas do Python) podemos chamar ct.strftimee então podemos usar %fpara formatar microssegundos:

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.

Ou, para obter milissegundos, altere a vírgula para um ponto decimal e omita o 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
fonte
1
então% f daria microssegundos, não milissegundos, certo?
26611 Jonathan
@ Jonathan: oops, você está certo, %fdá microssegundos. Suponho que a maneira mais fácil de obter milissegundos é alterar a vírgula para um ponto decimal (veja a edição acima).
unutbu
3
Na verdade, acho que essa é a melhor resposta devido ao fato de que você consegue voltar a usar as opções de formatação PADRÃO. Na verdade, eu queria microssegundos, e essa era a única opção que poderia fazer isso!
trumpetlicks
Obrigado. Esta resposta fornece uma solução fácil para obter microssegundos.
Yongwei Wu
337

Isso deve funcionar também:

logging.Formatter(fmt='%(asctime)s.%(msecs)03d',datefmt='%Y-%m-%d,%H:%M:%S')
Craig McDaniel
fonte
12
Obrigado: Aqui estão os documentos para: docs.python.org/2/library/logging.html#logrecord-attributes docs.python.org/3/library/logging.html#logrecord-attributes .. Existe uma maneira de ainda inclui o fuso horário (% z)? ... Os tempos de formato ISO8601 nos logs do Python (, ->.) Seriam ótimos.
Wes Turner
19
Esta solução é deficiente, porque se você tem %zou %Zem sua datefmtque você quer que apareça após o msecs, não antes.
Wim
1
E também se você estiver usando um relógio de 12 horas com AMouPM
DollarAkshay 5/18 /
1
@wim como acompanhamento do meu comentário anterior (não foi possível editar mais ...), eis o que eu fiz: 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 Não é possível incorporar o fuso horário em default_msec_format(a partir do Python 3,7) porque apenas o tempo e milissegundos são substituídos em A partir da. loggingFonte:self.default_msec_format % (t, record.msecs)
M. Dudley
27

Adicionar msegs foi a melhor opção, obrigado. Aqui está a minha alteração usando isso com o Python 3.5.3 no 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")
Mestre James
fonte
1
De longe, a opção mais simples e limpa. Não sei por que você está recebendo o logger quando você pode chamar logging.info (msg) etc, mas o formato é exatamente o que eu estava procurando. Qualquer pessoa que esteja procurando todos os atributos utilizáveis pode olhar aqui: docs.python.org/3.6/library/logging.html#logrecord-attributes
naphier
Hmmm ponto interessante, obrigado pelo comentário, é um alimento para reflexão, com certeza. Sim, eu provavelmente o adicionei como uma lição do que está acontecendo lá também, e para ter certeza de que está lá e porque pedi várias coisas para que não precise de várias chamadas para o pai (via '.') Para buscá-la. Se você chamou .info ou .debug novamente, talvez eu os salve diretamente novamente, como você sugere para salvar um ciclo de consulta de referência. [let info = logging.info]
Mestre James
Obrigado por dizer Jason. Às vezes, existe uma maneira mais simples de ver o mundo, não tenha medo de tentar descobrir essa verdade em muitas situações, se não em todas / em todas as situações.
Master James
15

A maneira mais simples que encontrei foi substituir default_msec_format:

formatter = logging.Formatter('%(asctime)s')
formatter.default_msec_format = '%s.%03d'
Mickey B
fonte
1
Interessante, obrigado. Mas isso não funcionou para mim no Python 2.7. Pode funcionar apenas no Python 3.x para algum valor de x.
nealmcb
1
@nealmcb isso não está disponível até o Python 3.3 de acordo com os documentos
Mark
3

Depois de instanciar um Formattereu costumo definir formatter.converter = gmtime. Portanto, para que a resposta do @ unutbu funcione nesse caso, você precisará:

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
fonte
2

Uma expansão simples que não requer o datetimemódulo e não é prejudicada como algumas outras soluções é usar a substituição simples de cadeias da seguinte maneira:

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

Dessa forma, um formato de data pode ser gravado da maneira que você desejar, mesmo permitindo diferenças de região, usando %Fpor milissegundos. Por exemplo:

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
fonte
1

Se você estiver usando flecha ou se não se importa em usar flecha. Você pode substituir a formatação da hora do python pela seta.

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)

Agora você pode usar toda a formatação da hora da seta no datefmtatributo

Slapy
fonte
-1

tl; dr para pessoas que procuram aqui uma data no formato ISO:

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

Jeff Bryner
fonte
-3

A partir de agora, o seguinte funciona perfeitamente com o 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')

dá a seguinte saída

2020/01/11 18: 51: 19.011 INFO

Pasindu Madushan
fonte
1
Isso não funciona. % d está imprimindo a data. No seu exemplo, a data é impressa com um 0 preenchido na frente.
Klik 31/01