308

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') 
5
  • 1
    Maybe changing the locale could help? Commented Jun 9, 2011 at 9:33
  • 2
    @ pajton - in the following link it says "Locale information is not used by asctime()" - docs.python.org/library/time.html#time.asctime Commented Jun 9, 2011 at 9:35
  • 3
    %f doesn't work on python 2.7.9 or 3.5.1 either Commented Aug 23, 2016 at 16:49
  • 21
    Good conversation here. I came here because logging claims its default time format follows ISO 8601. It doesn't. It uses space, not "T" to separate time and comma for fractional seconds, not decimal point. How could they be so wrong? Commented Sep 10, 2016 at 14:58
  • @Mr.LanceESloan github.com/python/cpython/issues/74141 Commented Apr 21 at 21:16

18 Answers 18

563

This should work too:

logging.Formatter( fmt='%(asctime)s.%(msecs)03d', datefmt='%Y-%m-%d,%H:%M:%S' ) 
Sign up to request clarification or add additional context in comments.

9 Comments

Thanks: Here are the docs for these: docs.python.org/2/library/logging.html#logrecord-attributes 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.
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 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
@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 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)
|
96

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. 

3 Comments

so %f would actually give microseconds, not milliseconds, right?
@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).
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!
74

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

3 Comments

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: docs.python.org/3.6/library/logging.html#logrecord-attributes
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]
@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")'.
36

The simplest way I found was to override default_msec_format:

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

4 Comments

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 this isn't available until Python 3.3 per the docs
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.
if handler is not used, logging.Formatter.default_msec_format='%s.%03d'
16

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.

1 Comment

What worked for me with Python 3.11: def formatTime_RFC3339(record, datefmt=None): return ( datetime.fromtimestamp(record.created) .astimezone() .isoformat(timespec="milliseconds") )
15

TLDR: Although it's not what the OP asked for, if you want to print all log timestamps as ISO8601 with milliseconds and UTC offset you can overwrite the default formatter formatTime like this:

import logging from datetime import datetime def formatTime(self, record, datefmt=None): return datetime.fromtimestamp(record.created).astimezone().isoformat(timespec='milliseconds') logging.Formatter.formatTime = formatTime 

produces logs like this

2024-01-15T19:23:01.371+01:00 INFO __main__ test 1 

The %(asctime)s is formatted through logging.Formatter.formatTime(self, record), the documentation states that formatTime will use time.strftime() which is not the same as datetime.strftime(). time.strftime has no way of printing milliseconds , datetime.strftime() on the other hand has %f. So you can't use %f on the datefmt.

If you only need to change the , (comma) to a . (dot) you have the simple options from other answers combining %(asctime)s, and %(msecs)03d like so (using logging.config.dictConfig() and a YAML file)

# log_config.yml version: 1 formatters: default: format: "%(asctime)s.%(msecs)03d %(levelname)-8s %(name)-15s %(message)s" datefmt: "%Y-%m-%d %H:%M:%S" 

But that will not work if you need to add the UTC offset /timezone after the milliseconds, for example.

In order to have a more sensible timestamp formatting you can implement your own formatter that subclasses logging.Formatter and just override formatTime. For example, the following formatter will produce logs with timestamps in ISO8601 including milliseconds and UTC offset by using datetime.isoformat():

# myformatter.py import logging from datetime import datetime class CustomFormatter(logging.Formatter): def formatTime(self, record, datefmt=None): return datetime.fromtimestamp(record.created).astimezone().isoformat(timespec='milliseconds') 

In the code above

# log_config.yml version: 1 formatters: default: class: myformatter.CustomFormatter format: "%(asctime)s %(levelname)-8s %(name)-15s %(message)s" handlers: console: class: logging.StreamHandler stream: ext://sys.stdout formatter: default loggers: root: level: INFO handlers: [console] 

The above configuration results in :

2024-01-15T18:40:42.006307+01:00 INFO __main__ test 1 

where %(asctime)s value comes from CustomFormatter.formatTime()

Ultimately you can replace the logging.Formatter.formatTime with your own implementation instead of creating a new formatter:

import logging import logging.config import yaml from datetime import datetime def formatTime(self, record, datefmt=None): return datetime.fromtimestamp(record.created).astimezone().isoformat(timespec='milliseconds') logging.Formatter.formatTime = formatTime # no need for a custom formatter if we monkeypatch the default formatter with open("log_config.yml", "rt") as f: logging.config.dictConfig(yaml.safe_load(f.read())) logger = logging.getLogger(__name__) # __name__ myvar = 1 logger.info("test %s", myvar) 

1 Comment

Nice and simple, but it doesn't allow for user override of the datetime format, which in my case is required.
9

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.

5 Comments

Not sure that either of your solutions works.
@mikerodent Like I said, the first one I used and worked for me, the seconds was found in documentation and did not work. So if you're "not sure", what are you actually saying?
Doesn't print timezone, so it's not ISO8601 I'm afraid, but it works if you only need the milliseconds
@BoscoDomingo You can adjust the datefmt string to whatever you want/need to also include TZ in ISO-8601 time format.
why adding miliseconds does not work as part of datefmt string?
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 

Comments

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 

Comments

4

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

1 Comment

This is the best approach for or python 3.3+ since it works well with logging.dictConfig(). I just add the override in my "main" (entrypoint) function and it simply works. Remember that class attribute constants are there for a reason.
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.

Comments

3

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

1 Comment

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='{').
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': '{', }, } 

2 Comments

It's not clear where you put this snippet. Please provide a more complete answer.
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.
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", ) 

1 Comment

Please provide an example usage + output.
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) 

2 Comments

Works fine. super() is enough instead of super(MicroSecondsFormatter, self).
I like this format logging.basicConfig( format="{asctime} {levelname:8} {module}:{lineno} {message}", datefmt="%Y-%m-%d %H:%M:%S.%f", style='{' ) optionally adding %z to the end of datefmt.
0

To allow users to specify a datefmt that contains %f, you could create a custom Formatter and override the formatTime() method.

Because the override is using super() to get the default formatted time, this code doesn't change the default behaviour of the formatTime() method.

Note that the code will truncate %f to milliseconds, as opposed to microseconds.

The code below does the following -

  1. Calls super() to get the formatted time.
  2. Creates the milliseconds (msecs) to use in the formatted time, using record.msecs as the base.
  3. Splits the formatted time using %f as the separator.
  4. Joins the split formatted time on msecs.
  5. Returns the formatted time (either original, or updated with milliseconds if appropriate).
import logging class CustomFormatter(logging.Formatter): def formatTime(self, record: logging.LogRecord, datefmt: str|None=None) -> str: '''Allow `datafmt` to interpret `%f` as milliseconds.''' s = super().formatTime(record, datefmt) if '%f' in s: s_split = s.split('%f') msecs = '%03d' % record.msecs s = msecs.join(s_split) return s fmt='%(asctime)s %(levelname)-8s => %(message)s' datefmt='%Y-%m-%d %H:%M:%S.%f' stdout_handler = logging.StreamHandler() stdout_handler.setFormatter(CustomFormatter(fmt, datefmt)) logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) logger.addHandler(stdout_handler) logger.info('FooBar') 

Comments

-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. 

3 Comments

%03d prints the date, not milliseconds.
%03d is (was, see below) a string format modifier in this context for the milliseconds rather than a dateformat component as noted here: docs.python.org/3/library/… However this is mute, because this modifier apparently no longer works after 3.7 according to stackoverflow.com/questions/6290739/…
updated the answer to be currently accurate using @unutbu 's starting point
-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

1 Comment

This does not work. %d is printing the date. In your example the date is printed with a 0 padded in front of it.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.