266

By default logging.Formatter('%(asctime)s') prints with the following format:

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

where 638 is the millisecond. I need to change the comma to a dot:

2011-06-09 10:54:40.638

To format the time I can use:

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

however the documentation doesn't specify how to format milliseconds. I've found this SO question which talks about microseconds, but a) I would prefer milliseconds and b) the following doesn't work on Python 2.6 (which I'm working on) due to the %f:

logging.Formatter(fmt='%(asctime)s',datefmt='%Y-%m-%d,%H:%M:%S.%f')
vvvvv
  • 25,404
  • 19
  • 49
  • 81
Jonathan Livni
  • 101,334
  • 104
  • 266
  • 359

16 Answers16

498

This should work too:

logging.Formatter(
    fmt='%(asctime)s.%(msecs)03d',
    datefmt='%Y-%m-%d,%H:%M:%S'
)
Gringo Suave
  • 29,931
  • 6
  • 88
  • 75
Craig McDaniel
  • 5,210
  • 2
  • 16
  • 6
  • 20
    Thanks: Here are the docs for these: https://docs.python.org/2/library/logging.html#logrecord-attributes https://docs.python.org/3/library/logging.html#logrecord-attributes .. Is there a way to still include the timezone (%z)? ... ISO8601 format times in Python logs (, -> .) would be great. – Wes Turner Jun 22 '15 at 04:50
  • 50
    This solution is handicapped, because if you have `%z` or `%Z` in your `datefmt` you want that to appear AFTER the msecs, not before. – wim Apr 27 '17 at 21:01
  • 1
    And also if you are using a 12 hour clock which has `AM` or `PM` – DollarAkshay Feb 05 '18 at 14:14
  • 5
    @wim it's a workaround, but use UTC rather than local time by doing `logging.Formatter.converter = time.gmtime` then you don't need to use `%z` or `%Z`. Alternatively you could probably change the `default_msec_format` attribute of your `logging.Formatter` object to %s,%03d%z or %s,%03d%Z – Mark Mar 14 '19 at 00:09
  • 3
    @wim as a follow up to my previous comment (couldn't edit anymore...), here is what I've done: `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 Mar 14 '19 at 00:38
  • 2
    @Mark You can't embed the time zone in `default_msec_format` (as of Python 3.7) because only the time and milliseconds are substituted in. From the `logging` source: `self.default_msec_format % (t, record.msecs)` – sourcenouveau Jul 17 '19 at 14:54
  • Is there a way to get make the , -> . ? With the given format: – asylumax Apr 07 '20 at 13:56
  • With the exact given format, I still get a 2020-04-07 13:55:57,902, instead of ...57.902.' Have I missed something? This is with Python 3.7.3 on a RPi, logging.__version__ returns 0.5.12 – asylumax Apr 07 '20 at 15:40
  • 1
    With Python 3.7.6 and logging 0.5.1.2 I had to use the following (largely the same but a positional argument for the format, not `fmt=`): `formatter = logging.Formatter('%(asctime)s.%(msecs)03d, %(message)s', datefmt='%Y-%m-%dT%H:%M:%S')` – Aleksander Lidtke Apr 17 '21 at 15:06
95

Please note Craig McDaniel's solution is preferable, unless you need support for all ISO 8601 format codes.


logging.Formatter's formatTime method looks like this:

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

Notice the comma in "%s,%03d". This can not be fixed by specifying a datefmt because ct is a time.struct_time and these objects do not record milliseconds.

If we change the definition of ct to make it a datetime object instead of a struct_time, then (at least with modern versions of Python) we can call ct.strftime and then we can use %f to format microseconds:

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.

Or, to get milliseconds, change the comma to a decimal point, and omit the datefmt argument:

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.
Arne
  • 17,706
  • 5
  • 83
  • 99
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • 2
    so %f would actually give microseconds, not milliseconds, right? – Jonathan Livni Jun 26 '11 at 15:43
  • @Jonathan: oops, you are right, `%f` gives microseconds. I suppose the easiest way to get milliseconds is to change the comma to a decimal point (see edit above). – unutbu Jun 26 '11 at 17:11
  • 3
    I actually think this is the best answer due to the fact that it gets you right back to being able to use STANDARD formatting options. I actually wanted microseconds, and this was the only option that could do it! – trumpetlicks Sep 25 '18 at 14:08
68

Adding msecs was the better option, Thanks. Here is my amendment using this with Python 3.5.3 in 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")
Gringo Suave
  • 29,931
  • 6
  • 88
  • 75
Master James
  • 1,691
  • 15
  • 19
  • 4
    By far the simplest and cleanest option. Not sure why you're getting the logger when you can just call logging.info(msg) etc, but the format is exactly what I was looking for. Anyone else looking for all the usable attributes can look here: https://docs.python.org/3.6/library/logging.html#logrecord-attributes – naphier Apr 17 '19 at 03:49
  • 1
    Hmmm interesting point, thanks for comment it is food for thought for sure. Ya I probably just added it as a lesson in what's going on there too, and to make sure it's there and because I've asked multiple things so it doesn't need multiple calls to parent (via '.') to fetch it. If you called .info or .debug again I would maybe save those directly again as you suggest to save a reference look up cycle. [let info = logging.info] – Master James Nov 01 '19 at 06:48
  • 1
    @naphier, you are getting the logger instead of 'just' calling `logging.info(msg)` because you usually want to exploit Python's logger name hierarchy. (such as setting different verbosity levels for certain packages or relying on `%(levelname)` yielding useful values). See also e.g. `python3 -c 'import logging; logging.basicConfig(); logging.warning("foo"); logging.getLogger(__name__).warning("bar")'`. – maxschlepzig Mar 28 '21 at 10:00
31

The simplest way I found was to override default_msec_format:

formatter = logging.Formatter('%(asctime)s')
formatter.default_msec_format = '%s.%03d'
Mickey B
  • 411
  • 4
  • 2
  • 1
    Interesting, thanks. But this didn't work for me in Python 2.7. It may only work in Python 3.x for some value of x. – nealmcb Oct 16 '17 at 05:28
  • 2
    @nealmcb this isn't available until Python 3.3 per the [docs](https://docs.python.org/3.7/library/logging.html#logging.Formatter.formatTime) – Mark Mar 13 '19 at 23:58
  • 1
    Very simple indeed! Unfortunately it only works when using the default time format (i.e. `datefmt=None`). When using a different date format (e.g. when you want to print time with milliseconds but without the data) this solution is not applicable. – wovano Oct 28 '21 at 12:10
  • if handler is not used, `logging.Formatter.default_msec_format='%s.%03d'` – Smart Manoj Apr 02 '22 at 04:59
11

I figured out a two-liner to get the Python logging module to output timestamps in RFC 3339 (ISO 1801 compliant) format, with both properly formatted milliseconds and timezone and without external dependencies:

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())

Example:

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

Alternatively, that last line could be written out as follows:

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

logging.Formatter.formatTime = formatTime_RFC3339

That method could also be used on specific formatter instances, rather than overriding at the class level, in which case you will need to remove self from the method signature.

jrc
  • 20,354
  • 10
  • 69
  • 64
8

Many outdated, over-complicated and weird answers here. The reason is that the documentation is inadequate and the simple solution is to just use basicConfig() and set it as follows:

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

The trick here was that you have to also set the datefmt argument, as the default messes it up and is not what is (currently) shown in the how-to python docs. So rather look here.


An alternative and possibly cleaner way, would have been to override the default_msec_format variable with:

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

However, that did not work for unknown reasons.

PS. I am using Python 3.8.

not2qubit
  • 14,531
  • 8
  • 95
  • 135
5

A simple expansion that doesn't require the datetime module and isn't handicapped like some other solutions is to use simple string replacement like so:

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

This way a date format can be written however you want, even allowing for region differences, by using %F for milliseconds. For example:

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
Wolfgang Kuehn
  • 12,206
  • 2
  • 33
  • 46
torrentails
  • 109
  • 1
  • 8
4

After instantiating a Formatter I usually set formatter.converter = gmtime. So in order for @unutbu's answer to work in this case you'll need:

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 Livni
  • 101,334
  • 104
  • 266
  • 359
3

If you are using arrow or if you don't mind using arrow. You can substitute python's time formatting for arrow's one.

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)

Now you can use all of arrow's time formatting in datefmt attribute.

Slapy
  • 317
  • 3
  • 9
3

If you prefer to use style='{', fmt="{asctime}.{msecs:0<3.0f}" will 0-pad your microseconds to three places for consistency.

chrisharrel
  • 337
  • 2
  • 10
  • This just tacks on the `,nnn` formatting (to `%S` part) when used in `logger.Formatter()`. So it need to be used in the `basicConfig(datefmt=..., format=..., style='{')`. – not2qubit Jan 03 '21 at 10:04
3

After burning some of my precious time the below hack worked for me. I just updated my formatter in settings.py and added datefmt as %y/%b/%Y %H:%M:%S and appended the milliseconds to the asctime like this {asctime}.{msecs:0<3.0f}

E.G:

    'formatters': {
        'verbose': {
            'format': '[{asctime}.{msecs:0<3.0f}] {levelname} [{threadName:s}] {module} → {message}',
            'datefmt': "%y/%b/%Y %H:%M:%S",
            'style': '{',
        },
    }
Shahid Tariq
  • 886
  • 6
  • 19
  • It's not clear where you put this snippet. Please provide a more complete answer. – not2qubit May 04 '23 at 09:23
  • This is the only answer supporting the `{` formatting! What does the `0<3` part do? I see it for the first time and it's hard to google it. – t3chb0t Jul 20 '23 at 19:41
1

Using this smart answer for the timezone and the chosen answer, you can construct the millisecond and timezone with your desired format:

import logging
import time

if __name__ == "__main__":
    tz = time.strftime('%z')
    logging.basicConfig(
        format=(
            "%(asctime)s.%(msecs)03d" + tz + " %(levelname)s "
            "%(pathname)s:%(lineno)d[%(threadName)s]: %(message)s"
        ),
        level=logging.DEBUG,
        datefmt="%Y-%m-%dT%H:%M:%S",
    )
    logging.info("log example")

Personally, I like to keep all the logs in UTC but also have this explicitly in the log as a datetime without a timezone is meaningless in a multizone application:

    logging.Formatter.converter = time.gmtime
    logging.basicConfig(
        format=(
            "%(asctime)s.%(msecs)03d+0000 %(levelname)s "
            "%(pathname)s:%(lineno)d[%(threadName)s]: %(message)s"
        ),
        level=logging.DEBUG,
        datefmt="%Y-%m-%dT%H:%M:%S",
    )
anishtain4
  • 2,342
  • 2
  • 17
  • 21
1

My take on this is the following:

class MicrosecondsFormatter(logging.Formatter):
    def formatTime(self, record, datefmt=None):
        return super(MicroSecondsFormatter, self).formatTime(record, self.datefmt.replace('%f', str(datetime.fromtimestamp(record.created).microsecond).ljust(6, '0')))

logging.Formatter = MicrosecondsFormatter

The above adds support for "%f" in the date format, and so one can continue using the logging module as usual, for example:

log_format = f'%(asctime)s %(levelname)-8s %(message)s'
log_date_format = '%d/%m/%Y %H:%M:%S.%f %z'

logging.basicConfig(stream=sys.stdout,
                    format=log_format,
                    datefmt=log_date_format,
                    level=logging.INFO)
jonathans
  • 320
  • 3
  • 9
0

All I needed to do was set default_msec_format attribute before creating the formatter.

logging.Formatter.default_msec_format = '%s.%03d'                        # Just add this line
formatter = logging.Formatter('%(asctime)s %(levelname)s - %(message)s')
Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
-2

tl;dr for folks looking here for an ISO formatted date:

instead of using something like '%Y-%m-%d %H:%M:%S.%03d%z', create your own class as @unutbu indicated. Here's one for iso date format:

import logging
from time import gmtime, strftime

class ISOFormatter(logging.Formatter):
    def formatTime(self, record, datefmt=None):
        t = strftime("%Y-%m-%dT%H:%M:%S", gmtime(record.created))
        z = strftime("%z",gmtime(record.created))
        s = "%s.%03d%s" % (t, record.msecs,z)        
        return s

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

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

formatter = ISOFormatter(fmt='%(asctime)s - %(module)s - %(levelname)s - %(message)s')
console.setFormatter(formatter)

logger.debug('Jackdaws love my big sphinx of quartz.')
#2020-10-23T17:25:48.310-0800 - <stdin> - DEBUG - Jackdaws love my big sphinx of quartz.

Jeff Bryner
  • 109
  • 1
  • 4
  • `%03d` prints the date, not milliseconds. – Josh Correia Oct 21 '20 at 21:26
  • 1
    %03d is (was, see below) a string format modifier in this context for the milliseconds rather than a dateformat component as noted here: https://docs.python.org/3/library/logging.html#logging.Formatter.formatTime However this is mute, because this modifier apparently no longer works after 3.7 according to https://stackoverflow.com/questions/6290739/python-logging-use-milliseconds-in-time-format/62477924?noredirect=1#comment100680659_7517430 – Jeff Bryner Oct 23 '20 at 15:11
  • updated the answer to be currently accurate using @unutbu 's starting point – Jeff Bryner Oct 23 '20 at 17:28
-4

As of now the following works perfectly with 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')

gives the following output

2020/01/11 18:51:19.011 INFO

  • 2
    This does not work. %d is printing the date. In your example the date is printed with a 0 padded in front of it. – Klik Jan 31 '20 at 04:26