Loguru Python Logging
Simple Python logging library. Current version: 0.7.3 (Mar 2026). One global logger — no instantiation needed, just import. Default sink is stderr (not stdout). diagnose=True is the DEFAULT — shows variable values in tracebacks — leaks sensitive data in production. Must call logger.remove() before reconfiguring. Library authors must never call logger.add() — use logger.disable() instead. enqueue=True requires logger.complete() on shutdown to flush queued messages.
Warnings
- breaking diagnose=True is the DEFAULT for all sinks. It displays variable values in exception tracebacks — leaks passwords, tokens, PII in production logs.
- gotcha Default sink outputs to stderr, not stdout. Many developers expect stdout. Adding a new sink without calling logger.remove() causes duplicate log output.
- gotcha Library authors must never call logger.add(). Call logger.disable(__name__) instead. Calling add() in a library forces logging config on every downstream application.
- gotcha enqueue=True (multiprocess/async safe) requires logger.complete() on shutdown. Without it, buffered messages in the queue are lost when the process exits.
- gotcha Passing integer log levels to logger.log() displays as anonymous level ('Level 20') instead of level name. Loguru identifies levels by name, not number.
- gotcha logger.add() returns a handler ID integer. logger.remove() requires this ID to remove a specific handler. Calling logger.remove() with no args removes ALL handlers.
Install
-
pip install loguru
Imports
- basic usage + reconfigure
import sys from loguru import logger # Remove default stderr handler before reconfiguring logger.remove() # Add stdout handler with INFO level logger.add( sys.stdout, level='INFO', format='{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{line} | {message}', diagnose=False, # IMPORTANT: disable in production — prevents variable leak backtrace=True ) # Add file sink with rotation logger.add( 'logs/app.log', level='DEBUG', rotation='10 MB', retention='7 days', compression='zip', diagnose=False, serialize=False ) logger.info('App started') logger.debug('Debug message') logger.warning('Watch out') # Exception catching try: 1 / 0 except Exception: logger.exception('Division failed') - library usage
# In a library — NEVER call logger.add() from loguru import logger # Disable by default — let app developer enable if they want logger.disable(__name__) def my_library_function(): logger.debug('Library internal log') # no-op unless enabled by app
Quickstart
# pip install loguru
import sys
from loguru import logger
# Reconfigure: remove default stderr, add stdout + file
logger.remove()
logger.add(sys.stdout, level='INFO', diagnose=False)
logger.add('app.log', level='DEBUG', rotation='50 MB', diagnose=False)
# Basic logging
logger.debug('Debug message')
logger.info('Server started on port 8000')
logger.warning('Low disk space')
logger.error('Connection failed')
logger.critical('Database unreachable')
# Exception logging with full traceback
try:
result = 1 / 0
except ZeroDivisionError:
logger.exception('Calculation failed') # logs traceback automatically
# Catch decorator
@logger.catch
def risky_function(x):
return 100 / x
risky_function(0) # caught and logged automatically
# Structured context with bind()
request_logger = logger.bind(request_id='req-123', user_id='usr-456')
request_logger.info('Processing payment')
# JSON output for log aggregators
logger.add('app.json', serialize=True, diagnose=False)
# Async / multiprocess safe
logger.add('async.log', enqueue=True, diagnose=False)
# On shutdown:
import asyncio
asyncio.run(logger.complete()) # flush queued messages