aio-libs/aiohttp · error · TypeError

access_log_class must be subclass of…

Error message

access_log_class must be subclass of aiohttp.abc.AbstractAccessLogger, got {}

What it means

AppRunner.__init__ validates that access_log_class is a subclass of aiohttp.abc.AbstractAccessLogger. The check runs after kwargs are assembled (so it covers both the explicit parameter and any value injected via app._handler_args). Aiohttp needs the access logger to implement the log(request, response, time) interface defined by AbstractAccessLogger; an arbitrary class would break access logging at runtime.

Solutions

  1. Make your custom logger inherit from aiohttp.abc.AbstractAccessLogger and implement the log(self, request, response, time) method.
  2. If you do not need a custom logger, omit access_log_class entirely to use the default AccessLogger.
  3. Verify the object passed is the class itself, not an instance or a function.

Example fix

// before
class MyLogger:
    def log(self, request, response, time): ...
runner = web.AppRunner(app, access_log_class=MyLogger)
// after
from aiohttp.abc import AbstractAccessLogger
class MyLogger(AbstractAccessLogger):
    def log(self, request, response, time): ...
runner = web.AppRunner(app, access_log_class=MyLogger)
Defensive patterns

Strategy: validation

Validate before calling

from aiohttp.abc import AbstractAccessLogger

def register_logger(cls):
    if not (isinstance(cls, type) and issubclass(cls, AbstractAccessLogger)):
        raise TypeError('access_log_class must subclass AbstractAccessLogger')
    return cls

Type guard

from aiohttp.abc import AbstractAccessLogger

def is_access_logger_class(obj) -> bool:
    return isinstance(obj, type) and issubclass(obj, AbstractAccessLogger)

Prevention

When it happens

Trigger: Passing access_log_class=<some_callable_or_class> where the class does not inherit from AbstractAccessLogger. Common: passing a plain function, a logging.Logger subclass, or a custom class that implements the right methods but forgot to extend the ABC. The value can also arrive indirectly through Application(handler_args={'access_log_class': ...}).

Common situations: Writing a custom access logger and forgetting `class MyLogger(AbstractAccessLogger)`; migrating from an older aiohttp version where the base class was named differently; passing a callable factory instead of the class itself.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/8d959723ff3a5004. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/web_runner.py:450

        self,
        app: Application,
        *,
        handle_signals: bool = False,
        access_log_class: type[AbstractAccessLogger] = AccessLogger,
        **kwargs: Any,
    ) -> None:
        if not isinstance(app, Application):
            raise TypeError(
                f"The first argument should be web.Application instance, got {app!r}"
            )
        kwargs["access_log_class"] = access_log_class

        if app._handler_args:
            for k, v in app._handler_args.items():
                kwargs[k] = v

        if not issubclass(kwargs["access_log_class"], AbstractAccessLogger):
            raise TypeError(
                "access_log_class must be subclass of "
                "aiohttp.abc.AbstractAccessLogger, got {}".format(
                    kwargs["access_log_class"]
                )
            )

        super().__init__(handle_signals=handle_signals, **kwargs)
        self._app = app

    @property
    def app(self) -> Application:
        return self._app

    async def shutdown(self) -> None:
        await self._app.shutdown()

    async def _make_server(self) -> Server[Request]:
        self._app.on_startup.freeze()

View on GitHub (pinned to d041d4d0fd)