Loguru Python Logging
raw JSON → 0.7.3 verified Tue May 12 auth: no python install: verified quickstart: stale
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.
pip install loguru Common errors
error ModuleNotFoundError: No module named 'loguru' ↓
cause The Loguru library has not been installed in the active Python environment.
fix
Run
pip install loguru in your terminal. error Sensitive data leakage in production logs due to diagnose=True ↓
cause The default `diagnose=True` parameter for sinks in Loguru includes local variable values in exception tracebacks, which can expose sensitive information in production environments.
fix
Explicitly set
diagnose=False when adding sinks in production: logger.add('file.log', diagnose=False). error Logs are duplicated ↓
cause The logger is being configured multiple times by repeatedly calling `logger.add()` without first calling `logger.remove()`, or by running configuration code in a multiprocessing environment without proper `if __name__ == '__main__':` guards.
fix
Call
logger.remove() (optionally with the handler ID, e.g., logger.remove(0) for the default handler) before logger.add() to prevent duplicate handlers. For multiprocessing, wrap logger configuration in if __name__ == '__main__':. error KeyError: 'some_key' or ValueError: Single '{' encountered in format string when logging f-strings ↓
cause Loguru's message formatting uses `str.format()` semantics for positional and keyword arguments, which can conflict with f-strings that contain unescaped curly braces not intended as format placeholders.
fix
Escape literal curly braces in f-strings with double braces (
{{ and }}) or pass the data as separate arguments to Loguru's logging methods if they are intended for structured logging. error ValueError: I/O operation error on closed file ↓
cause Another library, IDE, or environment tool has replaced or closed `sys.stderr` or `sys.stdout` while Loguru was still attempting to write to it.
fix
Configure Loguru to use a lambda function as the sink to dynamically retrieve
sys.stderr or sys.stdout (e.g., logger.add(lambda m: sys.stderr.write(m))), or ensure the logger is re-initialized if the standard streams might be replaced. Warnings
breaking diagnose=True is the DEFAULT for all sinks. It displays variable values in exception tracebacks — leaks passwords, tokens, PII in production logs. ↓
fix Always set diagnose=False in production: logger.add('prod.log', diagnose=False)
gotcha Default sink outputs to stderr, not stdout. Many developers expect stdout. Adding a new sink without calling logger.remove() causes duplicate log output. ↓
fix Call logger.remove() before logger.add() to remove the default stderr handler.
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. ↓
fix In library code: logger.disable(__name__). In app code: logger.enable('library_name') to see library logs.
gotcha enqueue=True (multiprocess/async safe) requires logger.complete() on shutdown. Without it, buffered messages in the queue are lost when the process exits. ↓
fix await logger.complete() in async code, or asyncio.run(logger.complete()) at shutdown.
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. ↓
fix Use string names: logger.log('INFO', 'message') not logger.log(20, 'message')
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. ↓
fix handler_id = logger.add(...); logger.remove(handler_id) to remove specific sink.
Install compatibility verified last tested: 2026-05-12
python os / libc status wheel install import disk
3.10 alpine (musl) - - 0.00s 18.2M
3.10 slim (glibc) - - 0.00s 19M
3.11 alpine (musl) - - 0.00s 20.1M
3.11 slim (glibc) - - 0.00s 21M
3.12 alpine (musl) - - 0.00s 12.0M
3.12 slim (glibc) - - 0.00s 13M
3.13 alpine (musl) - - 0.00s 11.6M
3.13 slim (glibc) - - 0.00s 12M
3.9 alpine (musl) - - 0.00s 17.7M
3.9 slim (glibc) - - 0.00s 18M
Imports
- basic usage + reconfigure wrong
from loguru import logger # Wrong: adding new sink without removing default # Now logs go to BOTH stderr AND your new sink logger.add('app.log') # default stderr sink still active # Wrong: diagnose=True (default) in production leaks variable values logger.add('prod.log', diagnose=True) # exposes sensitive data in tracebackscorrectimport 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 wrong
# Wrong in a library — forces logging config on app developer from loguru import logger logger.add('library.log') # pollutes app's loggingcorrect# 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 stale last tested: 2026-04-23
# 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