aio-libs/aiohttp · error · ValueError
Gunicorn's style options in form of `%(name)s` are not suppo
Error message
Gunicorn's style options in form of `%(name)s` are not supported for the log formatting. Please use aiohttp's format specification to configure access log formatting: http://docs.aiohttp.org/en/stable/logging.html#format-specification
What it means
Raised by GunicornWebWorker._get_valid_log_format() as a ValueError when the access_log_format string contains Gunicorn-style named placeholders like `%(name)s`. aiohttp's AccessLogger uses its own format specification (positional `%s` and `{name}` style), not Gunicorn's `%(name)s` dictionary interpolation, so such formats are rejected at worker startup.
Source
Thrown at aiohttp/worker.py:238
See ssl.SSLSocket.__init__ for more details.
"""
if ssl is None: # pragma: no cover
raise RuntimeError("SSL is not supported.")
ctx = ssl.SSLContext(cfg.ssl_version)
ctx.load_cert_chain(cfg.certfile, cfg.keyfile)
ctx.verify_mode = cfg.cert_reqs
if cfg.ca_certs:
ctx.load_verify_locations(cfg.ca_certs)
if cfg.ciphers:
ctx.set_ciphers(cfg.ciphers)
return ctx
def _get_valid_log_format(self, source_format: str) -> str:
if source_format == self.DEFAULT_GUNICORN_LOG_FORMAT:
return self.DEFAULT_AIOHTTP_LOG_FORMAT
elif re.search(r"%\([^\)]+\)", source_format):
raise ValueError(
"Gunicorn's style options in form of `%(name)s` are not "
"supported for the log formatting. Please use aiohttp's "
"format specification to configure access log formatting: "
"http://docs.aiohttp.org/en/stable/logging.html"
"#format-specification"
)
else:
return source_format
class GunicornUVLoopWebWorker(GunicornWebWorker):
def init_process(self) -> None:
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
super().init_process()
View on GitHub (pinned to c0ef574e29)
Solutions
- Use aiohttp's format spec: positional `%s` for fixed fields and `{header_name}` / `{client_ip}` etc. See the logging docs link in the error.
- If you want gunicorn's default behavior, leave access_log_format unset so it matches DEFAULT_GUNICORN_LOG_FORMAT and is auto-converted to aiohttp's DEFAULT_AIOHTTP_LOG_FORMAT.
- Convert any `%(h)s` style tokens to the equivalent aiohttp `{client_ip}` / `%s` fields.
Example fix
# before (gunicorn.cfg)
access_log_format = '%(h)s %(l)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
# after (aiohttp format spec)
access_log_format = '%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"' Defensive patterns
Strategy: validation
Validate before calling
import re
def validate_log_format(fmt: str) -> str:
if re.search(r"%\([^\)]+\)", fmt):
raise ValueError("use aiohttp format spec, not gunicorn %(name)s")
return fmt Type guard
import re
def is_aiohttp_log_format(fmt: str) -> bool:
return not re.search(r"%\([^\)]+\)", fmt) Prevention
- Use aiohttp's access log format spec (%s positional, {name} fields), not gunicorn's %(name)s.
- Leave access_log_format unset to get the auto-converted default.
- Test the worker startup with the configured format in a staging env.
When it happens
Trigger: Setting gunicorn's `access_log_format` to a string containing a `%(...)s` pattern (e.g. gunicorn's default `%(h)s %(l)s ...`) while using the aiohttp GunicornWebWorker. The regex search at aiohttp/worker.py:237 detects the pattern and raises.
Common situations: Copying a Gunicorn access log format from another project into the aiohttp worker config; relying on gunicorn's default format which the worker remaps automatically only when it exactly equals DEFAULT_GUNICORN_LOG_FORMAT; mixing gunicorn and aiohttp logging docs.
Related errors
- wsgi app should be either Application or async function retu
- SSL is not supported.
- keepalive_timeout cannot be set if force_close is True
- ssl should be SSLContext, Fingerprint, or bool, got {ssl!r}
- No .netrc file found
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/98132f6e50dd6201.json.
Report an issue: GitHub.