Loguru is a library which aims to bring enjoyable logging in Python.
Did you ever feel lazy about configuring a logger and used print() instead?... I did, yet logging is fundamental to every application and eases the process of debugging. Using Loguru you have no excuse not to use logging from the start, this is as simple as from loguru import logger.
Also, this library is intended to make Python logging less painful by adding a bunch of useful functionalities that solve caveats of the standard loggers. Using logs in your application should be an automatism, Loguru tries to make it both pleasant and powerful.
pipinstallloguru- 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
The main concept of Loguru is that there is one and only onelogger.
For convenience, it is pre-configured and outputs to stderr to begin with (but that's entirely configurable).
fromloguruimportloggerlogger.debug("That's it, beautiful and simple logging!")The logger is just an interface which dispatches log messages to configured handlers. Simple, right?
How to add a handler? How to set up logs formatting? How to filter messages? How to set level?
One answer: the add() function.
logger.add(sys.stderr, format="{time}{level}{message}", filter="my_module", level="INFO")This function should be used to register sinks which are responsible for 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.
Note that you may also remove() a previously added handler by using the identifier returned while adding it. This is particularly useful if you want to supersede the default stderr handler: just call logger.remove() to make a fresh start.
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.add("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.add("file_1.log", rotation="500 MB") # Automatically rotate too big filelogger.add("file_2.log", rotation="12:00") # New file is created each day at noonlogger.add("file_3.log", rotation="1 week") # Once the file is too old, it's rotatedlogger.add("file_X.log", retention="10 days") # Cleanup after some timelogger.add("file_Y.log", compression="zip") # Save some loved spaceLoguru 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")Have you ever seen your program crashing unexpectedly without seeing anything in the log file? Did you ever noticed that exceptions occurring 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.catchdefmy_function(x, y, z): # An error? It's caught anyway!return1/ (x+y+z)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.add(sys.stdout, colorize=True, format="<green>{time}</green> <level>{message}</level>")All sinks added to the logger are thread-safe by default. They are not multiprocess-safe, but you can enqueue the messages to ensure logs integrity. This same argument can also be used if you want async logging.
logger.add("somefile.log", enqueue=True)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 helps you identify problems by allowing the entire stack trace to be displayed, including values of variables (thanks better_exceptions for this!).
The code:
logger.add("output.log", backtrace=True, diagnose=True) # Set 'False' to not leak sensitive data in proddeffunc(a, b): returna/bdefnested(c): try: func(5, c) exceptZeroDivisionError: 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): 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 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.add(custom_sink_function, serialize=True)Using bind() you can contextualize your logger messages by modifying the extra record attribute.
logger.add("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")You can also have more fine-grained control over your logs by combining bind() and filter:
logger.add("special.log", filter=lambdarecord: "special"inrecord["extra"]) logger.debug("This message is not logged to the file") logger.bind(special=True).info("This message, though, is logged to the file!")Finally, the patch() method allows dynamic values to be attached to the record dict of each new message:
logger.add(sys.stderr, format="{extra[utc]}{message}") logger=logger.patch(lambdarecord: record["extra"].update(utc=datetime.utcnow()))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 usageslogger.opt(exception=True).info("Error stacktrace added to the log message") logger.opt(ansi=True).info("Per message <blue>colors</blue>") logger.opt(record=True).info("Display values from the record (eg.{record[thread]})") logger.opt(raw=True).info("Bypass sink formatting\n") logger.opt(depth=1).info("Use parent stack context (useful within wrapped functions)")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=38, color="<yellow>", icon="🐍") logger.log("SNAKY", "Here we go!")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.add("file.log", format="{time:YYYY-MM-DD at HH:mm:ss} |{level} |{message}")Using the logger in your scripts is easy, and you can configure() it at start. To use Loguru from inside a library, remember to never call add() but use disable() instead so logging functions become no-op. If a developer wishes to see your library's logs, he can enable() it again.
# For scriptsconfig={"handlers": [{"sink": sys.stdout, "format": "{time} -{message}"},{"sink": "file.log", "serialize": True}, ], "extra":{"user": "someone"} } logger.configure(**config) # For librarieslogger.disable("my_library") logger.info("No matter added sinks, this message is not displayed") logger.enable("my_library") logger.info("This message however is propagated to the sinks")Wish to use built-in logging Handler as a Loguru sink?
handler=logging.handlers.SysLogHandler(address=('localhost', 514)) logger.add(handler)Need to propagate Loguru messages to standard logging?
classPropagateHandler(logging.Handler): defemit(self, record): logging.getLogger(record.name).handle(record) logger.add(PropagateHandler(), format="{message}")Want to intercept standard logging messages toward your Loguru sinks?
classInterceptHandler(logging.Handler): defemit(self, record): # Retrieve context where the logging call occurred, this happens to be in the 6th frame upwardlogger_opt=logger.opt(depth=6, exception=record.exc_info) logger_opt.log(record.levelno, record.getMessage()) logging.basicConfig(handlers=[InterceptHandler()], level=0)Don't like the default logger formatting? Would prefer another DEBUG color? No problem:
# Linux / OSXexportLOGURU_FORMAT="{time} | <lvl>{message}</lvl>"# WindowssetxLOGURU_DEBUG_COLOR"<green>"It is often useful to extract specific information from generated logs, this is why Loguru provides a parse() method which helps to deal with logs and regexes.
pattern=r"(?P<time>.*) - (?P<level>[0-9]+) - (?P<message>.*)"# Regex with named groupscaster_dict=dict(time=dateutil.parser.parse, level=int) # Transform matching groupsforgroupsinlogger.parse("file.log", pattern, cast=caster_dict): print("Parsed:", groups) #{"level": 30, "message": "Log example", "time": datetime(2018, 12, 09, 11, 23, 55)}Loguru can easily be combined with the great notifiers library (must be installed separately) to receive an e-mail when your program fail unexpectedly or to send many other kind of notifications.
importnotifiersparams={"username": "[email protected]", "password": "abc123", "to": "[email protected]" } # Send a single notificationnotifier=notifiers.get_notifier("gmail") notifier.notify(message="The application is running!", **params) # Be alerted on each error messagefromnotifiers.loggingimportNotificationHandlerhandler=NotificationHandler("gmail", defaults=params) logger.add(handler, level="ERROR")Although logging impact on performances is in most cases negligible, 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.

