Concurrent Log Handler
A robust drop-in replacement for Python's `RotatingFileHandler` and `TimedRotatingFileHandler` that supports safe concurrent writes from multiple processes and threads. It also includes features like gzip compression and better Windows compatibility. The library is actively maintained, with frequent minor releases addressing bugs and adding features, typically every few months.
Warnings
- breaking Python 2.x support was dropped. Versions 0.9.23 and higher require Python 3.6 or higher.
- gotcha When using `fork()` to create new processes, log handlers inherited by child processes could experience race conditions, leading to corrupted logs or deadlocks.
- deprecated The `queue.py` `setup_logging_queues()` function has been deprecated and should no longer be used.
- gotcha Before 0.9.28, if an application shut down during a scheduled rollover time (e.g., for `ConcurrentTimedRotatingFileHandler`), the handler might miss the rollover entirely and skip to the next scheduled time without rotating accumulated logs.
- gotcha Since 0.9.26, the handler keeps file handles open (`keep_file_open=True` by default) for performance. However, on Windows, the log file must still be closed after each write for rollovers to occur correctly due to filesystem API behavior.
Install
-
pip install concurrent-log-handler
Imports
- ConcurrentRotatingFileHandler
from concurrent_log_handler import ConcurrentRotatingFileHandler
- ConcurrentTimedRotatingFileHandler
from concurrent_log_handler import ConcurrentTimedRotatingFileHandler
Quickstart
import logging
import os
from concurrent_log_handler import ConcurrentRotatingFileHandler
log_file_path = os.path.join(os.getcwd(), 'app.log')
# Configure logging
log = logging.getLogger('my_app')
log.setLevel(logging.INFO)
# Use ConcurrentRotatingFileHandler
# Rotate log after reaching 10MB, keep 5 old copies.
# Set encoding to 'utf-8' to avoid UnicodeEncodeError on some systems.
handler = ConcurrentRotatingFileHandler(
log_file_path, 'a', 10 * 1024 * 1024, 5, encoding='utf-8'
)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
log.addHandler(handler)
log.info('This is an informational message.')
log.warning('This is a warning message.')
log.error('This is an error message.')
print(f"Log messages written to: {log_file_path}")