gunicorn access log format: --access-logformat examples (tested)

gunicorn writes access lines only with --access-logfile, in the format set by --access-logformat. Below: the default line and three custom formats (milliseconds, a request header, JSON-style), each run against a real request, plus what a typo in a key does.

1. Four access-log formats, measured

b1.sh starts gunicorn on a minimal WSGI app with --access-logfile - and different --access-logformat strings, sends one request with an X-Request-ID header, and prints the log line:

# start gunicorn with an access-log format, send one request, print what it logged
run() {
  gunicorn app:app -b 127.0.0.1:8965 --access-logfile - --error-logfile err.log "$@" > access.log 2>&1 &
  pid=$!; until curl -s -o /dev/null -H 'X-Request-ID: abc-123' 'http://127.0.0.1:8965/items?id=7'; do sleep 0.2; done
  kill $pid; wait $pid 2>/dev/null; cat access.log
}
echo '== default format'; run
echo '== custom: time in ms, request id header, status'
run --access-logformat '%(h)s "%(r)s" %(s)s %(M)sms id=%({x-request-id}i)s'
echo '== JSON-style'
run --access-logformat '{"ip":"%(h)s","path":"%(U)s","query":"%(q)s","status":%(s)s,"secs":%(L)s}'
echo '== unknown key %(foo)s'
run --access-logformat '%(h)s %(foo)s %(s)s'
echo '== error log line format'; head -1 err.log

Output (gunicorn 26.2.0, Python 3.13):

== default format
127.0.0.1 - - [23/Oct/2026:23:09:53 +0100] "GET /items?id=7 HTTP/1.1" 200 2 "-" "curl/8.14.1"
== custom: time in ms, request id header, status
127.0.0.1 "GET /items?id=7 HTTP/1.1" 200 0ms id=abc-123
== JSON-style
{"ip":"127.0.0.1","path":"/items","query":"id=7","status":200,"secs":0.000271}
== unknown key %(foo)s
127.0.0.1 - 200
== error log line format
[2026-10-23 23:09:53 +0100] [3562812] [INFO] Starting gunicorn 26.2.0

2. What the output shows

3. In gunicorn.conf.py

The same settings as a config file: accesslog = "-" and access_log_format = '%(h)s "%(r)s" %(s)s %(M)sms'. Without accesslog (or --access-logfile) there are no access lines at all, whatever the format.

Related: where gunicorn logs go, gunicorn unrecognized arguments, gunicorn WORKER TIMEOUT.

Frequently asked questions

How do I log request time in gunicorn?

Use %(M)s for milliseconds or %(L)s for decimal seconds in --access-logformat.

How do I log a request header in gunicorn?

Use %({header-name}i)s with the header name in lower case, e.g. %({x-request-id}i)s.

What happens with an unknown key in gunicorn access_log_format?

It is printed as "-"; there is no error, so check the output after changing the format.

Written with AI assistance. Measured on 2026-10-23 with gunicorn 26.2.0 and Python 3.13 on Linux. The script ran verbatim and the output was pasted unedited. PlainTables is our extension; gunicorn is free.