aiohttp Logging: From Basic Setup to Advanced Methods
In this tutorial, you’ll learn how to set up and customize logging in your aiohttp projects.
You’ll learn how to configure loggers, customize log formats, track HTTP requests and responses, measure performance, implement log rotation, and more.
Configuring aiohttp Logger
To configure the aiohttp logger, use the Python built-in logging module.
Here’s how to set up basic logging:
import logging import aiohttp import asyncio logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('aiohttp') async def main(): async with aiohttp.ClientSession() as session: async with session.get('https://api.github.com') as resp: print(f"Status: {resp.status}") asyncio.run(main())
Output:
DEBUG:asyncio:Using proactor: IocpProactor Status: 200
You can customize the log format to include specific information:
import logging import aiohttp import asyncio logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger('aiohttp') async def main(): async with aiohttp.ClientSession() as session: async with session.get('https://api.github.com') as resp: print(f"Status: {resp.status}") asyncio.run(main())
Output:
2024-09-09 10:01:34,136 - asyncio - DEBUG - Using proactor: IocpProactor Status: 200
This output includes timestamps, logger names, and log levels in addition to the log messages.
Logging HTTP Requests and Responses
Log Request details
You can also create a custom logger class to log request details.
import logging from aiohttp import web from datetime import datetime class RequestLogger: def __init__(self): self.logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) async def log_request(self, request): self.start_time = datetime.now() self.logger.info(f"Request started: {request.method} {request.path}") self.logger.info(f"Headers: {dict(request.headers)}") app = web.Application() request_logger = RequestLogger() async def hello(request): await request_logger.log_request(request) response = web.Response(text="Hello, World!") return response app.add_routes([web.get('/', hello)]) if __name__ == '__main__': web.run_app(app)
Output:
INFO:__main__:Request started: GET / INFO:__main__:Headers: {'Host': '127.0.0.1:8080', 'Connection': 'keep-alive', 'sec-ch-ua': '"Chromium";v="128", "Not;A=Brand";v="24", "Microsoft Edge";v="128"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-User': '?1', 'Sec-Fetch-Dest': 'document', 'Accept-Encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'en-US,en;q=0.9,ar;q=0.8'} INFO:aiohttp.access:127.0.0.1 [09/Sep/2024:09:10:39 +0200] "GET / HTTP/1.1" 200 166 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0"
This code logs the request method, URL, and headers before sending the request.
Response details (status code, body size)
To log response details, modify the custom ClientSession:
import logging from aiohttp import web from datetime import datetime logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @web.middleware async def response_logger_middleware(request, handler): response = await handler(request) logger.info(f"Response: {request.method} {request.path} - Status: {response.status}") logger.info(f"Response Headers: {dict(response.headers)}") # Log 200 characters of the body logger.info(f"Response Body: {str(response.body)[:200]}...") return response app = web.Application(middlewares=[response_logger_middleware]) async def hello(request): return web.Response(text="Hello, World!") app.add_routes([web.get('/', hello)]) if __name__ == '__main__': web.run_app(app)
Output:
INFO:__main__:Response: GET / - Status: 200 INFO:__main__:Response Headers: {'Content-Type': 'text/plain; charset=utf-8'} INFO:__main__:Response Body: b'Hello, World!'... INFO:aiohttp.access:127.0.0.1 [09/Sep/2024:09:14:09 +0200] "GET / HTTP/1.1" 200 166 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0"
This code logs response details including the response status code and body content.
Timing information
To log timing information, you can set up a timing middleware that logs the start of each request and the time taken to process it:
from aiohttp import web import logging import time import asyncio logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @web.middleware async def timing_middleware(request, handler): start_time = time.time() logger.info(f"Request started: {request.method} {request.path}") response = await handler(request) elapsed_time = time.time() - start_time logger.info(f"Response: Status {response.status}, Time {elapsed_time:.4f}s") return response async def hello(request): return web.Response(text="Hello, World!") async def slow(request): await asyncio.sleep(2) return web.Response(text="This was a slow response") app = web.Application(middlewares=[timing_middleware]) app.router.add_get('/', hello) app.router.add_get('/slow', slow) if __name__ == '__main__': web.run_app(app, host='localhost', port=8080)
Output:
INFO:__main__:Request started: GET / INFO:__main__:Response: Status 200, Time 0.0000s INFO:aiohttp.access:127.0.0.1 [09/Sep/2024:09:26:43 +0200] "GET / HTTP/1.1" 200 166 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0" INFO:__main__:Request started: GET /slow INFO:__main__:Response: Status 200, Time 2.0014s INFO:aiohttp.access:127.0.0.1 [09/Sep/2024:09:26:46 +0200] "GET /slow HTTP/1.1" 200 177 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0"
http://localhost:8080
. This will trigger the fast route.
Then navigate to http://localhost:8080/slow
. This will trigger the slow route.
Log Resource Usage (CPU, Memory)
To log resource usage, use the psutil library:
import logging import aiohttp from aiohttp import web import psutil import os logging.basicConfig(level=logging.INFO) logger = logging.getLogger('aiohttp.server') @web.middleware async def resource_usage_middleware(request, handler): process = psutil.Process(os.getpid()) start_cpu = process.cpu_percent() start_memory = process.memory_info().rss / 1024 / 1024 # MB response = await handler(request) end_cpu = process.cpu_percent() end_memory = process.memory_info().rss / 1024 / 1024 # MB logger.info(f"Request to {request.path}: CPU {end_cpu - start_cpu:.2f}%, Memory {end_memory - start_memory:.2f}MB") return response async def hello(request): return web.Response(text="Hello, World!") app = web.Application(middlewares=[resource_usage_middleware]) app.router.add_get('/', hello) if __name__ == '__main__': web.run_app(app)
To test this, run the server and go to http://localhost:8080
:
Output:
INFO:aiohttp.server:Request to /: CPU 0.00%, Memory 0.04MB INFO:aiohttp.access:127.0.0.1 [09/Sep/2024:09:36:22 +0200] "GET / HTTP/1.1" 200 166 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0"
This middleware logs the CPU and memory usage for each request so you can identify which route consumes more resources.
Rotate Log Files
Time-based rotation
To implement time-based log rotation, use the TimedRotatingFileHandler:
import logging from logging.handlers import TimedRotatingFileHandler import aiohttp from aiohttp import web import traceback import time logger = logging.getLogger('aiohttp.server') logger.setLevel(logging.DEBUG) handler = TimedRotatingFileHandler('aiohttp_server.log', when='midnight', interval=1, backupCount=7) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) handler.setLevel(logging.DEBUG) logger.addHandler(handler) logging.getLogger('aiohttp').setLevel(logging.DEBUG) async def hello(request): logger.debug("Received request to /") return web.Response(text="Hello, World!") @web.middleware async def logging_middleware(request, handler): start_time = time.time() logger.info(f"Request: {request.method} {request.path} - Headers: {dict(request.headers)}") try: response = await handler(request) return response except web.HTTPException as ex: response = ex return response except Exception as ex: logger.error(f"Unexpected error occurred: {ex}") logger.error(traceback.format_exc()) response = web.Response(status=500, text="Internal Server Error") return response finally: duration = time.time() - start_time status = getattr(response, 'status', None) logger.info(f"Response: Status {status} - Duration: {duration:.4f}s - Headers: {dict(response.headers)}") app = web.Application(middlewares=[logging_middleware]) app.router.add_get('/', hello) if __name__ == '__main__': web.run_app(app)
This code sets up a TimedRotatingFileHandler that rotates the log file daily at midnight and keeps 7 backup files.
Size-based rotation
For size-based rotation, use the RotatingFileHandler:
import logging from logging.handlers import RotatingFileHandler import aiohttp from aiohttp import web import traceback import time logger = logging.getLogger('aiohttp.server') logger.setLevel(logging.DEBUG) handler = RotatingFileHandler('aiohttp_server.log', maxBytes=10*1024*1024, backupCount=5) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) handler.setLevel(logging.DEBUG) logger.addHandler(handler) logging.getLogger('aiohttp').setLevel(logging.DEBUG) async def hello(request): logger.debug("Received request to /") return web.Response(text="Hello, World!") @web.middleware async def logging_middleware(request, handler): start_time = time.time() logger.info(f"Request: {request.method} {request.path} - Headers: {dict(request.headers)}") try: response = await handler(request) return response except web.HTTPException as ex: response = ex return response except Exception as ex: logger.error(f"Unexpected error occurred: {ex}") logger.error(traceback.format_exc()) response = web.Response(status=500, text="Internal Server Error") return response finally: duration = time.time() - start_time status = getattr(response, 'status', None) logger.info(f"Response: Status {status} - Duration: {duration:.4f}s - Headers: {dict(response.headers)}") app = web.Application(middlewares=[logging_middleware]) app.router.add_get('/', hello) if __name__ == '__main__': web.run_app(app)
This code sets up a RotatingFileHandler that rotates the log file.
it creates a main log file named aiohttp_server.log
.
When this file reaches 10 MB, it will be renamed to aiohttp_server.log.1
, a new aiohttp_server.log
file will be created for new log entries.
This process continues, creating aiohttp_server.log.2
, aiohttp_server.log.3
, etc., up to aiohttp_server.log.5
.
When all backup files are full, the oldest one (aiohttp_server.log.5
) will be deleted to make room for a new backup.
Mokhtar is the founder of LikeGeeks.com. He is a seasoned technologist and accomplished author, with expertise in Linux system administration and Python development. Since 2010, Mokhtar has built an impressive career, transitioning from system administration to Python development in 2015. His work spans large corporations to freelance clients around the globe. Alongside his technical work, Mokhtar has authored some insightful books in his field. Known for his innovative solutions, meticulous attention to detail, and high-quality work, Mokhtar continually seeks new challenges within the dynamic field of technology.