Need help with loguru?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

Global Rank
Topics of expertise
Not found
7.6K Stars 349 Forks MIT License 543 Commits 32 Opened issues


Python logging made (stupidly) simple

Services available


Need anything else?

Contributors list

.. raw:: html

Loguru logo

Pypi version Python versions Documentation Build status Coverage Code quality License

Loguru logo


Loguru is a library which aims to bring enjoyable logging in Python.

Did you ever feel lazy about configuring a logger and used

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.

.. end-of-readme-intro



pip install loguru


  • 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
  • |strike|
    10x faster than built-in logging
    _ |/strike|

Take the tour

.. highlight:: python3

.. |logger| replace::

.. logger:

.. |add| replace::

.. add:

.. |remove| replace::

.. remove:

.. |complete| replace::

.. complete:

.. |catch| replace::

.. catch:

.. |bind| replace::

.. bind:

.. |contextualize| replace::

.. contextualize:

.. |patch| replace::

.. patch:

.. |opt| replace::

.. opt:

.. |trace| replace::

.. trace:

.. |success| replace::

.. success:

.. |level| replace::

.. level:

.. |configure| replace::

.. configure:

.. |disable| replace::

.. disable:

.. |enable| replace::

.. enable:

.. |parse| replace::

.. parse:

.. _sinks: .. _record dict: .. _log messages: .. _easily configurable: .. _markup tags: .. _fixes it: .. _No problem: .. _logging levels:

.. |betterexceptions| replace:: ``betterexceptions`` .. betterexceptions:

.. |notifiers| replace::

.. _notifiers:

Ready to use out of the box without boilerplate ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The main concept of

is that there is one and only one |logger|_.

For convenience, it is pre-configured and outputs to

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 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 coroutine function or a built-in Handler.

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

handler: just call
to make a fresh start.

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::


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 file
logger.add("file_2.log", rotation="12:00")     # New file is created each day at noon
logger.add("file_3.log", rotation="1 week")    # Once the file is too old, it's rotated

logger.add("file_X.log", retention="10 days") # Cleanup after some time

logger.add("file_Y.log", compression="zip") # Save some loved space

Modern string formatting using braces style ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

favors the much more elegant and powerful
formatting over
, logging functions are actually equivalent to

::"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 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|_.


def my_function(x, y, z):
    # An error? It's caught anyway!
    return 1 / (x + y + z)

Pretty logging with colors ^^^^^^^^^^^^^^^^^^^^^^^^^^

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="{time} {message}")

Asynchronous, Thread-safe, Multiprocess-safe ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

All sinks added to the |logger|_ are thread-safe by default. They are not multiprocess-safe, but you can

the messages to ensure logs integrity. This same argument can also be used if you want async logging.


logger.add("somefile.log", enqueue=True)

Coroutine functions used as sinks are also supported and should be awaited with |complete|_.

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.

helps you identify problems by allowing the entire stack trace to be displayed, including values of variables (thanks |betterexceptions| for this!).

The code::

logger.add("out.log", backtrace=True, diagnose=True)  # Caution, may leak sensitive data in prod

def func(a, b): return a / b

def nested(c): try: func(5, c) except ZeroDivisionError: logger.exception("What?!")


Would result in:

.. code-block:: none

2018-07-17 01:38:43.975 | ERROR    | __main__:nested:10 - What?!
Traceback (most recent call last):

File "", line 12, in nested(0) └

> File "", line 8, in nested func(5, c) │ └ 0 └

File "", 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

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

record attribute.


logger.add("file.log", format="{extra[ip]} {extra[user]} {message}")
context_logger = logger.bind(ip="", user="someone")"Contextualize your logger easily")
context_logger.bind(user="someone_else").info("Inline binding of extra attribute")"Use kwargs to add context during formatting: {user}", user="anybody")

It is possible to modify a context-local state temporarily with |contextualize|_:


with logger.contextualize(task=task_id):
    do_something()"End of task")

You can also have more fine-grained control over your logs by combining |bind|_ and



logger.add("special.log", filter=lambda record: "special" in record["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(lambda record: record["extra"].update(utc=datetime.utcnow()))

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("Error stacktrace added to the log message (tuple accepted too)") logger.opt(colors=True).info("Per message colors") 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)") logger.opt(capture=False).info("Keyword arguments not added to {dest} dict", dest="extra")

Customizable levels ^^^^^^^^^^^^^^^^^^^

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="", icon="🐍")

logger.log("SNAKY", "Here we go!")

Better datetime handling ^^^^^^^^^^^^^^^^^^^^^^^^

The standard logging is bloated with arguments like

, naive datetimes without timezone information, not intuitive formatting, etc.
fixes it


logger.add("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

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 scripts
config = {
    "handlers": [
        {"sink": sys.stdout, "format": "{time} - {message}"},
        {"sink": "file.log", "serialize": True},
    "extra": {"user": "someone"}

For libraries

logger.disable("my_library")"No matter added sinks, this message is not displayed") logger.enable("my_library")"This message however is propagated to the sinks")

Entirely compatible with standard logging ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Wish to use built-in logging

as a


handler = logging.handlers.SysLogHandler(address=('localhost', 514))

Need to propagate

messages to standard


class PropagateHandler(logging.Handler):
    def emit(self, record):

logger.add(PropagateHandler(), format="{message}")

Want to intercept standard

messages toward your


class InterceptHandler(logging.Handler):
    def emit(self, record):
        # Get corresponding Loguru level if it exists
            level = logger.level(record.levelname).name
        except ValueError:
            level = record.levelno

    # Find caller from where originated the logged message
    frame, depth = logging.currentframe(), 2
    while frame.f_code.co_filename == logging.__file__:
        frame = frame.f_back
        depth += 1

    logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())

logging.basicConfig(handlers=[InterceptHandler()], level=0)

Personalizable defaults through environment variables ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Don't like the default logger formatting? Would prefer another

No problem
# Linux / OSX
export LOGURU_FORMAT="{time} | {message}"



Convenient parser ^^^^^^^^^^^^^^^^^

It is often useful to extract specific information from generated logs, this is why

provides a |parse|_ method which helps to deal with logs and regexes.


pattern = r"(?P

Exhaustive notifier ^^^^^^^^^^^^^^^^^^^

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.


import notifiers

params = { "username": "[email protected]", "password": "abc123", "to": "[email protected]" }

Send a single notification

notifier = notifiers.get_notifier("gmail") notifier.notify(message="The application is running!", **params)

Be alerted on each error message

from notifiers.logging import NotificationHandler

handler = NotificationHandler("gmail", defaults=params) logger.add(handler, level="ERROR")


10x faster than built-in logging ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


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.

.. |strike| raw:: html

.. |/strike| raw:: html

.. end-of-readme-usage


  • API Reference 
  • Help & Guides 
  • Type hints 
  • Contributing 
  • License 
  • Changelog 

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.