Overview¶
Installation¶
pip install loguru
Features¶
- Ready to use out of the box without boilerplate
- No Handler, no Formatter, no Filter: one function to rule them all
- Easier file logging with rotation / retention / compression
- Modern string formatting using braces style
- Exceptions catching within threads or main
- Pretty logging with colors
- Asynchronous, Thread-safe, Multiprocess-safe
- Fully descriptive exceptions
- Structured logging as needed
- Lazy evaluation of expensive functions
- Customizable levels
- Better datetime handling
- Suitable for scripts and libraries
- Entirely compatible with standard logging
- Personalizable defaults through environment variables
- Convenient parser
- Exhaustive notifier
10x faster than built-in logging
Take the tour¶
Ready to use out of the box without boilerplate¶
The main concept of Loguru is that there is one and only one logger
.
For convenience, it is pre-configured and outputs to stderr
to begin with (but that’s entirely configurable).
from loguru import logger
logger.debug("That's it, beautiful and simple logging!")
The logger
is just an interface which dispatches log messages to configured handlers. Simple, right?
No Handler, no Formatter, no Filter: one function to rule them all¶
How to add an handler? How to setup logs formatting? How to filter messages? How to set level?
One answer: the start()
function.
logger.start(sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO")
This function should be used to register sinks which are responsible of managing log messages contextualized with a record dict. A sink can take many forms: a simple function, a string path, a file-like object, a built-in Handler or a custom class.
Easier file logging with rotation / retention / compression¶
If you want to send logged messages to a file, you just have to use a string path as the sink. It can be automatically timed too for convenience:
logger.start("file_{time}.log")
It is also easily configurable if you need rotating logger, if you want to remove older logs, or if you wish to compress your files at closure.
logger.start("file_1.log", rotation="500 MB") # Automatically rotate too big file
logger.start("file_2.log", rotation="12:00") # New file is created each day at noon
logger.start("file_3.log", rotation="1 week") # Once the file is too old, it's rotated
logger.start("file_X.log", retention="10 days") # Cleanup after some time
logger.start("file_Y.log", compression="zip") # Save some loved space
Modern string formatting using braces style¶
Loguru favors the much more elegant and powerful {}
formatting over %
, logging functions are actually equivalent to str.format()
.
logger.info("If you're using Python {}, prefer {feature} of course!", 3.6, feature="f-strings")
Exceptions catching within threads or main¶
Have you ever seen your program crashing unexpectedly without seeing anything in the logfile? Did you ever noticed that exceptions occuring in threads were not logged? This can be solved using the catch()
decorator / context manager which ensures that any error is correctly propagated to the logger
.
@logger.catch
def my_function(x, y, z):
# An error? It's catched anyway!
return 1 / (x + y + z)
Pretty logging with colors¶
Loguru automatically adds colors to your logs if your terminal is compatible. You can define your favorite style by using markup tags in the sink format.
logger.start(sys.stdout, colorize=True, format="<green>{time}</green> <level>{message}</level>")
Asynchronous, Thread-safe, Multiprocess-safe¶
All sinks added to the logger
are thread-safe by default. If you want async logging or need to use the same sink through different multiprocesses, you just have to enqueue
the messages.
logger.start("somefile.log", enqueue=True)
Fully descriptive exceptions¶
Logging exceptions that occur in your code is important to track bugs, but it’s quite useless if you don’t know why it failed. Loguru help you identify problems by allowing the entire stack trace to be displayed, including variables values.
The code:
logger.start("output.log", backtrace=True) # Set 'False' to avoid leaking sensible data in prod
def func(a, b):
return a / b
def nested(c):
try:
func(5, c)
except ZeroDivisionError:
logger.exception("What?!")
nested(0)
Would result in:
2018-07-17 01:38:43.975 | ERROR | __main__:nested:10 - What?!
Traceback (most recent call last, catch point marked):
File "test.py", line 12, in <module>
nested(0)
└ <function nested at 0x7f5c755322f0>
> File "test.py", line 8, in nested
func(5, c)
│ └ 0
└ <function func at 0x7f5c79fc2e18>
File "test.py", line 4, in func
return a / b
│ └ 0
└ 5
ZeroDivisionError: division by zero
Structured logging as needed¶
Want your logs to be serialized for easier parsing or to pass them around? Using the serialize
argument, each log message will be converted to a JSON string before being sent to the configured sink.
logger.start(custom_sink_function, serialize=True)
Using bind()
you can contextualize your logger messages by modifying the extra record attribute.
logger.start("file.log", format="{extra[ip]} {extra[user]} {message}")
logger_ctx = logger.bind(ip="192.168.0.1", user="someone")
logger_ctx.info("Contextualize your logger easily")
logger_ctx.bind(user="someoneelse").info("Inline binding of extra attribute")
Lazy evaluation of expensive functions¶
Sometime you would like to log verbose information without performance penalty in production, you can use the opt()
method to achieve this.
logger.opt(lazy=True).debug("If sink level <= DEBUG: {x}", x=lambda: expensive_function(2**64))
# By the way, "opt()" serves many usages
logger.opt(exception=True).info("Exception with an "INFO" level")
logger.opt(ansi=True).info("Per message <blue>colors</blue>")
logger.opt(record=True).info("Log record attributes (eg. {record[thread].id})")
logger.opt(raw=True).info("Bypass sink formatting\n")
logger.opt(depth=1).info("Use parent stack context (useful within wrapped functions)")
Customizable levels¶
Loguru comes with all standard logging levels to which trace()
and success()
are added. Do you need more? Then, just create it by using the level()
function.
new_level = logger.level("SNAKY", no=8, color="<yellow>", icon="🐍")
logger.log("SNAKY", "Here we go!")
Better datetime handling¶
The standard logging is bloated with arguments like datefmt
or msecs
, %(asctime)s
and %(created)s
, naive datetimes without timezone information, not intuitive formatting, etc. Loguru fixes it:
logger.start("file.log", format="{time:YYYY-MM-DD at HH:mm:ss} | {level} | {message}")
Suitable for scripts and libraries¶
Using the logger in your scripts is easy, and you can configure()
it at start. To use Loguru from inside a libary, remember to never call start()
but use disable()
instead so logging functions become no-op. If an user want to see your library’s logs, he can enable()
it again.
# For scripts
my_logging_config = dict(
handlers=[{'sink': sys.stdout, 'colorize': False, format="{time} - {message}"}],
extra={"user": "someone"}
)
logger.configure(**my_logging_config)
# For libraries
logger.disable("my_library")
logger.info("No matter started sinks, this message is not displayed")
logger.enable("my_library")
logger.info("This message however is propagated to the sinks")
Entirely compatible with standard logging¶
Wish to use built-in logging Handler
as a Loguru sink?
handler = logging.handlers.SysLogHandler(address=('localhost', 514))
logger.start(handler)
Need to propagate Loguru messages to standard logging?
class PropagateHandler(logging.Handler):
def emit(self, record):
logging.getLogger(record.name).handle(record)
logger.start(PropagateHandler())
Want to intercept standard logging messages toward your Loguru sinks?
class InterceptHandler(logging.Handler):
def emit(self, record):
logger_opt = logger.opt(depth=6, exception=record.exc_info)
logger_opt.log(record.levelno, record.getMessage())
logging.getLogger(None).addHandler(InterceptHandler())
Personalizable defaults through environment variables¶
Don’t like the default logger formatting? Would prefer another DEBUG
color? No problem:
# Linux / OSX
export LOGURU_FORMAT="{time} | <lvl>{message}</lvl>"
# Windows
setx LOGURU_DEBUG_COLOR="<green>"
Convenient parser¶
It is often useful to extract specific information from generated logs, this is why Loguru provides a parser
which helps dealing with logs and regexes.
from loguru import parser
pattern = r"(?P<time>.*) - (?P<level>[0-9]+) - (?P<message>.*)"
for groups in parser.parse("file.log", pattern):
groups = parser.cast(groups, time=time.strptime, level=int)
print("Parsed message at {} with severity {}".format(groups["time"], groups["level"]))
Exhaustive notifier¶
Receive an e-mail when your program fail unexpectedly or send many other kind of notifications using Loguru notifier
.
from loguru import notifier
gmail_notifier = notifier.gmail(to="dest@gmail.com", username="you@gmail.com", password="abc123")
# Send a notification
gmail_notifier.send("The application is running!")
# Be alerted on each error messages
logger.start(gmail_notifier.send, level="ERROR")
10x faster than built-in logging¶
Although logging impact on performances is in most cases negligeable, a zero-cost logger would allow to use it anywhere without much concern. In an upcoming release, Loguru’s critical functions will be implemented in C for maximum speed.