Python
logging
time format
milliseconds
programming tips

Python logging use milliseconds in time format

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Python's standard logging module can include milliseconds in log timestamps, but the common mistake is trying to do it entirely through datefmt. The reliable built-in approach is to keep the date and time in %(asctime)s and append milliseconds with %(msecs)03d.

The Standard Millisecond Pattern

This is the most common working setup:

python
1import logging
2
3logging.basicConfig(
4    level=logging.INFO,
5    format="%(asctime)s.%(msecs)03d %(levelname)s %(message)s",
6    datefmt="%Y-%m-%d %H:%M:%S",
7)
8
9logging.info("Application started")

That produces output shaped like:

text
2026-03-11 14:32:18.047 INFO Application started

%(asctime)s provides the base timestamp, and %(msecs)03d adds a three-digit millisecond suffix.

Why %f Usually Does Not Work

Many developers expect this to be enough:

python
datefmt="%Y-%m-%d %H:%M:%S.%f"

In standard logging formatters, that often does not behave like datetime.strftime. The logging module uses time.strftime semantics by default, so %f is not reliably interpreted as microseconds in the normal formatter path.

That is why %(msecs)03d is the safe built-in choice for millisecond precision.

Use the Same Pattern With Explicit Handlers

If you manage handlers yourself instead of using basicConfig, the same formatting idea applies.

python
1import logging
2
3logger = logging.getLogger("demo")
4logger.setLevel(logging.INFO)
5
6handler = logging.StreamHandler()
7formatter = logging.Formatter(
8    fmt="%(asctime)s.%(msecs)03d %(name)s %(levelname)s %(message)s",
9    datefmt="%H:%M:%S",
10)
11
12handler.setFormatter(formatter)
13logger.addHandler(handler)
14
15logger.info("Processing request")

This is useful when file logging and console logging need different formats.

Custom Formatter for More Control

If you want true microseconds, custom timezone logic, or a different timestamp implementation, override formatTime.

python
1import logging
2from datetime import datetime
3
4class MicrosecondFormatter(logging.Formatter):
5    def formatTime(self, record, datefmt=None):
6        dt = datetime.fromtimestamp(record.created)
7        if datefmt:
8            return dt.strftime(datefmt)
9        return dt.isoformat()
10
11handler = logging.StreamHandler()
12handler.setFormatter(
13    MicrosecondFormatter(
14        fmt="%(asctime)s %(levelname)s %(message)s",
15        datefmt="%Y-%m-%d %H:%M:%S.%f",
16    )
17)
18
19logger = logging.getLogger("micro")
20logger.setLevel(logging.INFO)
21logger.addHandler(handler)
22
23logger.info("Detailed timestamp")

This is beyond the basic milliseconds case, but it shows where the built-in formatter stops and customization begins.

Milliseconds Are Most Useful in Dense Logs

Millisecond timestamps become valuable when many events share the same second. For example, API request handling, retries, and batch steps often generate logs too dense for second-level timestamps to be useful.

python
1import logging
2
3logging.basicConfig(
4    filename="app.log",
5    level=logging.DEBUG,
6    format="%(asctime)s.%(msecs)03d %(levelname)s %(message)s",
7    datefmt="%Y-%m-%d %H:%M:%S",
8)
9
10logging.debug("Step one complete")
11logging.debug("Step two complete")

Without milliseconds, those two lines may appear identical in time even when they represent distinct steps in a short workflow.

Think About Time Zone Too

Precision alone is not enough in distributed systems. If logs are collected from multiple machines or containers, decide whether timestamps should be local time or UTC. That is a separate concern from milliseconds, but it becomes important as soon as you start treating logs as serious operational data.

If needed, a custom formatter can change the converter or build UTC timestamps explicitly.

Common Pitfalls

The most common mistake is relying on %f inside datefmt and expecting standard logging to emit milliseconds automatically. In most normal setups, that is not how the formatter works.

Another issue is calling basicConfig after logging has already been configured elsewhere. At that point the new format may appear to do nothing.

Developers also sometimes mix handlers with different timestamp formats, which makes logs hard to compare when debugging across outputs.

Finally, millisecond precision does not imply clock synchronization. If several machines log events, precision and time consistency are separate problems.

Summary

  • The standard logging pattern for milliseconds is %(asctime)s.%(msecs)03d.
  • Use datefmt for the main date and time portion, not as the sole millisecond mechanism.
  • '%f usually requires a custom formatter if you want datetime-style behavior.'
  • The same approach works with both basicConfig and explicit handlers.
  • Millisecond timestamps help most when many log events happen within the same second.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.