Logging is an essential part of any application, providing insights into its runtime behavior. Python’s built-in logging module is powerful but can be cumbersome to configure. Enter Loguru, a third-party logging library that simplifies logging with a more intuitive API and rich features.
Why Loguru?
Loguru offers several advantages over the standard logging module:
Simpler API: No need for handlers, formatters, or loggers. Just import and log.
Rich Formatting: Easily customize log formats with colors, timestamps, and more.
Exception Handling: Automatically captures and logs exceptions with tracebacks.
Asynchronous Logging: Supports asynchronous logging out of the box.
Customizing Loguru
Loguru allows you to customize the log format, level, and output destinations. Here’s how to configure it:
Custom Log Format
You can define a custom format for your logs using Loguru’s formatting syntax. For example, to include the log level, timestamp, and message:
2025-08-05T15:17:56.125554+0200 | INFO | Custom format logging!
Colorful Logs: Easier to Read
Colorful logs are not just visually appealing; they make logs easier to read and debug. Loguru supports colorized logs out of the box, allowing you to differentiate log levels at a glance. For example:
Code
logger.remove() # Remove default handlerlogger.add(sys.stdout, colorize=True, format="<green>{time}</green> | <level>{level}</level> | <cyan>{message}</cyan>")logger.info("This is a colorful info log!")
2025-08-05T15:17:56.132757+0200 | INFO | This is a colorful info log!
Log Levels
Loguru supports the standard log levels: TRACE, DEBUG, INFO, WARNING, ERROR, and CRITICAL. You can set the log level globally or per handler:
Code
logger.remove() # Remove default handlerlogger.add(sys.stdout, level="DEBUG")logger.warning("This is a warning message.")
2025-08-05 15:17:56.139 | WARNING | __main__:<module>:3 - This is a warning message.
Exception Handling in Loguru
It’s important to understand that ExceptionFormatter is a private internal class used by Loguru to format exceptions, but it’s not an exception itself.
If you want to capture exceptions raised within your logging handlers, you need to use catch=False when adding a sink:
Code
from loguru import loggerdef buggy_sink(message):raiseValueError("This is a buggy sink!")# Will print the error.logger.remove()logger.add(buggy_sink, catch=True)logger.info("Message")# Will raise on error.logger.remove()logger.add(buggy_sink, catch=False)try: logger.info("Message")exceptValueErroras e:print(f"Caught exception: {e}")print(f"Exception type: {type(e)}")
Caught exception: This is a buggy sink!
Exception type: <class 'ValueError'>
--- Logging error in Loguru Handler #52 ---
Record was: {'elapsed': datetime.timedelta(seconds=849, microseconds=772788), 'exception': None, 'extra': {}, 'file': (name='3110224809.py', path='/tmp/ipykernel_1196518/3110224809.py'), 'function': '<module>', 'level': (name='INFO', no=20, icon='ℹ️'), 'line': 9, 'message': 'Message', 'module': '3110224809', 'name': '__main__', 'process': (id=1196518, name='MainProcess'), 'thread': (id=140358453032768, name='MainThread'), 'time': datetime(2025, 8, 5, 15, 17, 56, 147511, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST'))}
Traceback (most recent call last):
File "/home/aemonge/usr/docs/.venv/lib/python3.12/site-packages/loguru/_handler.py", line 206, in emit
self._sink.write(str_record)
File "/home/aemonge/usr/docs/.venv/lib/python3.12/site-packages/loguru/_simple_sinks.py", line 123, in write
self._function(message)
File "/tmp/ipykernel_1196518/3110224809.py", line 4, in buggy_sink
raise ValueError("This is a buggy sink!")
ValueError: This is a buggy sink!
--- End of logging error ---
When capturing **kwargs as contextual data (rather than for formatting), prefer using logger.opt(depth=1).bind(**kwargs).trace(msg) to avoid common pitfalls.
Structured Logging with JSON
Loguru can handle JSON messages elegantly. Here’s how to implement structured logging with special handling for messages that contain “code” and “message” fields:
Code
import jsonimport sysfrom loguru import logger as loguru_loggerdef format_record(record: dict) ->str:""" Format a log record with special handling for JSON messages. """ msg =f"<level>{record['level'].name:<9}</level> "# Time formatting for specific levelsif record["level"].name in ["TRACE", "INFO", "ERROR"]: msg +=f"<italic>{record['time']:DD/MMM/YY HH:mm:ss.SSS}</italic> - "# Name/line formatting for specific levelsif record["level"].name in ["TRACE", "DEBUG", "WARNING", "ERROR", "CRITICAL"]: msg +=f"<underline>{record['name']}:{record['line']}</underline> - "# Color mapping color_tags = {"TRACE": "cyan","DEBUG": "blue","INFO": "green","WARNING": "magenta","ERROR": "yellow","CRITICAL": "red", } color_tag = color_tags.get(record["level"].name, "")# JSON message handlingtry: data = json.loads(record["message"])ifisinstance(data, dict) and"code"in data and"message"in data:# Special handling for structured error messages msg +=f"<red>[{data['code']}]</red> <{color_tag}>{data['message']}</{color_tag}>{LINE_BREAK}"else:# Generic JSON handling msg +=f"<{color_tag}>{record['message']}</{color_tag}>{LINE_BREAK}"except json.JSONDecodeError:# Handle non-JSON messages normally msg +=f"<{color_tag}>{record['message']}</{color_tag}>{LINE_BREAK}"return msgloguru_logger.remove()loguru_logger.add( sys.stdout,format=format_record, level="DEBUG", colorize=True,)# Examples of different message typesloguru_logger.info("This is a regular message")loguru_logger.info('{"key": "value", "number": 42}')loguru_logger.error('{"code": "E001", "message": "Database connection failed"}')
INFO 05/Aug/25 15:17:56.160 - This is a regular messageERROR 05/Aug/25 15:17:56.161 - __main__:56 - [E001]Database connection failed
When tracing operations that might involve JSON parsing, special care must be taken to avoid infinite loops. The raw=True option in trace logging prevents this issue, and using .bind() for contextual data is the recommended approach:
Code
import jsonimport sysfrom loguru import logger as loguru_loggerloguru_logger.remove()loguru_logger.add( sys.stdout,format="{time} | {level} | {message}", level="TRACE", colorize=True,)class SafeLogger:@staticmethoddef trace(*args: object, **kwargs: object) ->None:""" Log a trace message with raw=True to avoid JSON parsing loops. """# Using raw=True prevents potential loops when tracing JSON operations msg =" ".join(map(str, args)) loguru_logger.opt(depth=1, raw=True).trace(msg)@staticmethoddef info(*args: object, **kwargs: object) ->None:""" Log an info message with proper context handling using bind(). """ msg =" ".join(map(str, args))# For contextual data, use bind() to avoid pitfallsif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).info(msg)else: loguru_logger.opt(depth=1).info(msg)# Example usage showing proper context handlingsafe_logger = SafeLogger()safe_logger.trace("Tracing a JSON parsing operation")safe_logger.info("User login successful", user_id=1234, action="login", result="success")
Tracing a JSON parsing operation2025-08-05T15:17:56.170057+0200 | INFO | User login successful
Adding Handlers
You can add multiple handlers to log to different destinations, such as files, with different formats or levels:
Code
logger.remove() # Remove default handlerlogger.add("file.log", level="INFO", rotation="10 MB")logger.info("This log will go to both the console and a file.")
Advanced Features
Loguru supports asynchronous logging, which can improve performance in I/O-bound applications:
Code
logger.remove() # Remove default handlerlogger.add("async_log.log", enqueue=True)logger.info("This log is written asynchronously.")
Sending Logs to a Remote Server
You can use Loguru to send logs to a remote server via an API. Here’s a quick example using the requests library:
Code
import requestsfrom loguru import loggerdef send_log_to_server(message): url ="https://example.com/api/logs" payload = {"message": message} response = requests.post(url, json=payload)return response.status_codelogger.remove() # Remove default handlerlogger.add(send_log_to_server, level="INFO")logger.info("This log will be sent to a remote server.")
Saving Logs to a File
Loguru makes it easy to save logs to a file. You can specify the file path, rotation, and retention policies. Here’s an example:
Code
logger.remove() # Remove default handlerlogger.add("logs/app.log", rotation="10 MB", retention="30 days")logger.info("This log will be saved to a file.")
Final Code
Here’s a complete example of a custom logger using Loguru, with advanced features like custom formatting, exception handling, and asynchronous logging. This version properly uses bind() for contextual data:
Code
import osimport sysfrom typing import Any, NoReturnfrom loguru import logger as loguru_loggerLOG_LEVEL = os.getenv("LOG_LEVEL", "DEBUG")def format_record(record: Any) ->str:""" Format the log record. Parameters ---------- record : Any The log record. Returns ------- str """# Precompute the level name with the colon level_with_colon =f"{record['level'].name}"# Format the message msg =f"<level>{level_with_colon:<9}</level> "if record["level"].name in ["TRACE", "INFO", "ERROR"]: msg +=f"<italic>{record['time']:DD/MMM/YY HH:mm:ss.SSS}</italic> - "if record["level"].name in ["TRACE", "DEBUG", "WARNING"]: msg +=f"<underline>{record['name']}:{record['line']}</underline> - "# Use the color tags specified in your level definitions color_tags = {"TRACE": "cyan","DEBUG": "blue","INFO": "green","WARNING": "magenta","ERROR": "yellow","CRITICAL": "red", } color_tag = color_tags.get(record["level"].name, "") msg +=f"<{color_tag}>{record['message']}</{color_tag}>{LINE_BREAK}"return msgloguru_logger.remove()loguru_logger.add( sys.stdout,format=format_record, level=LOG_LEVEL, colorize=True,)loguru_logger.level("TRACE", color="<cyan>")loguru_logger.level("DEBUG", color="<blue>")loguru_logger.level("INFO", color="<green>")loguru_logger.level("WARNING", color="<magenta>")loguru_logger.level("ERROR", color="<yellow>")loguru_logger.level("CRITICAL", color="<red>")class FancyLogError(Exception):"""Exception from FancyLogger."""class FancyLogger:""" A example of a Fancy. Attributes ---------- log_level : str The current logging level. """ log_level: str= LOG_LEVELdef includes(self, level: str) ->bool:""" Check if the current logging level is less or equal than the specified level. Parameters ---------- level : str The level to compare against. Returns ------- bool True if the current level is less severe, False otherwise. """ level = level.upper() current_level = loguru_logger.level(self.log_level).no specified_level: int= loguru_logger.level(level).noreturn current_level <= specified_level@staticmethoddef trace(*args: object, **kwargs: object) ->None:""" Log a trace message, with file, line and date-time. Parameters ---------- *args: object The message(s) to log. **kwargs: object The kwargs sent to loguru. Returns ------- None """ msg =" ".join(map(str, args))# Using bind for contextual data, not for formattingif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).trace(msg)else: loguru_logger.opt(depth=1).trace(msg)@staticmethoddef debug(*args: object, **kwargs: object) ->None:""" Log a debug message, with file and line. Parameters ---------- *args: object The message(s) to log. **kwargs: object The kwargs sent to loguru. Returns ------- None """ msg =" ".join(map(str, args))# Using bind for contextual data, not for formattingif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).debug(msg)else: loguru_logger.opt(depth=1).debug(msg)@staticmethoddef info(*args: object, **kwargs: object) ->None:""" Log a info message, with date-time. Parameters ---------- *args: object The message(s) to log. **kwargs: object The kwargs sent to loguru. Returns ------- None """ msg =" ".join(map(str, args))# Using bind for contextual data, not for formattingif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).info(msg)else: loguru_logger.opt(depth=1).info(msg)@staticmethoddef warning(*args: object, **kwargs: object) ->None:""" Log a warning message, with line and file. Parameters ---------- *args: object The message(s) to log. **kwargs: object The kwargs sent to loguru. Returns ------- None """ msg =" ".join(map(str, args))# Using bind for contextual data, not for formattingif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).warning(msg)else: loguru_logger.opt(depth=1).warning(msg)@staticmethoddef error(*args: object, **kwargs: object) ->None:""" Log an error message and raises an exception, with date-time. Parameters ---------- *args: object The message(s) to log. **kwargs: object The kwargs sent to loguru. Returns ------- None Raises ------ FancyLogError """import traceback msg =" ".join(map(str, args))# Using bind for contextual data, not for formattingif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).error(msg)else: loguru_logger.opt(depth=1).error(msg) traceback.print_exc()raise FancyLogError@staticmethoddef critical(*args: object, **kwargs: object) -> NoReturn:""" Log a critical message and exits with code 1. Parameters ---------- *args: object The message(s) to log. **kwargs: object The kwargs sent to loguru. Returns ------- NoReturn """ msg =" ".join(map(str, args))# Using bind for contextual data, not for formattingif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).critical(msg)else: loguru_logger.opt(depth=1).critical(msg) sys.exit(1)logging = FancyLogger()logging.info("Example with contextual data using bind", user_id=12345, action="login", status="success")logging.info("This is a info log!")logging.debug("This is a debug log!")logging.trace("This is a trace log!")logging.warning("This is a warning log!")with contextlib.suppress(FancyLogError): logging.error("This is an error log!")with contextlib.suppress(SystemExit): logging.critical("This is a critical log!")
INFO 05/Aug/25 15:17:56.829 - Example with contextual data using bindINFO 05/Aug/25 15:17:56.830 - This is a info log!DEBUG __main__:253 - This is a debug log!WARNING __main__:255 - This is a warning log!ERROR 05/Aug/25 15:17:56.831 - This is an error log!CRITICAL This is a critical log!
NoneType: None
Conclusion
Loguru simplifies logging in Python with its intuitive API and powerful features. Whether you need basic logging or advanced customization, Loguru has you covered. The examples provided demonstrate how to create a custom logger with Loguru, including custom formatting, exception handling, structured logging with JSON, and avoiding common pitfalls with contextual data. The final code properly uses bind() for contextual data rather than passing kwargs directly to logging methods, which is the recommended approach to avoid common pitfalls.
Source Code
---title: "Logging in Python with Loguru"author: "Andres Monge <aemonge>"date: "2024-12-18"format: html: smooth-scroll: true code-fold: true code-tools: true code-copy: true code-annotations: true code-ansi: true ansi-colors: enable: true include-in-header: - text: | <script type="module"> import { AnsiUp } from "/assets/ansi_up.esm.js"; document.addEventListener("DOMContentLoaded", function () { const ansiUp = new AnsiUp({ escape_for_html: false }); const selector = '[data-class-output="code-rendered"] p'; const codeOutputs = document.querySelectorAll(selector); const LINE_BREAKS = ["[BR", "<br />"] codeOutputs.forEach((output) => { const lines = output.innerText.split(LINE_BREAKS[0]); let processedLines = lines.map((line) => ansiUp.ansi_to_html(line)); processedLines = processedLines.map(line => line.replace(/:::$/g, '')); output.innerHTML = processedLines.join(LINE_BREAKS[1]); }); }); </script>execute: freeze: auto---Logging is an essential part of any application, providing insights into its runtimebehavior. Python's built-in `logging` module is powerful but can be cumbersome toconfigure. Enter **Loguru**, a third-party logging library that simplifies logging witha more intuitive API and rich features.### Why Loguru?Loguru offers several advantages over the standard `logging` module:- **Simpler API**: No need for handlers, formatters, or loggers. Just import and log.- **Rich Formatting**: Easily customize log formats with colors, timestamps, and more.- **Exception Handling**: Automatically captures and logs exceptions with tracebacks.- **Asynchronous Logging**: Supports asynchronous logging out of the box.### Customizing LoguruLoguru allows you to customize the log format, level, and output destinations. Here'show to configure it:#### Custom Log FormatYou can define a custom format for your logs using Loguru's formatting syntax. Forexample, to include the log level, timestamp, and message:```{python}#| class-output: code-renderedLINE_BREAK =''from loguru import loggerimport sysimport contextliblogger.remove() # Remove default handlerlogger.add(sys.stdout, format="{time} | {level} | {message}"+ LINE_BREAK)logger.info("Custom format logging!")```### Colorful Logs: Easier to ReadColorful logs are not just visually appealing; they make logs easier to read and debug.Loguru supports colorized logs out of the box, allowing you to differentiate log levelsat a glance. For example:```{python}#| class-output: code-renderedlogger.remove() # Remove default handlerlogger.add(sys.stdout, colorize=True, format="<green>{time}</green> | <level>{level}</level> | <cyan>{message}</cyan>")logger.info("This is a colorful info log!")```### Log LevelsLoguru supports the standard log levels: `TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR`,and `CRITICAL`. You can set the log level globally or per handler:```{python}#| class-output: code-renderedlogger.remove() # Remove default handlerlogger.add(sys.stdout, level="DEBUG")logger.warning("This is a warning message.")```### Exception Handling in LoguruIt's important to understand that `ExceptionFormatter` is a private internal class usedby Loguru to format exceptions, but it's not an exception itself.If you want to capture exceptions raised within your logging handlers, you need to use`catch=False` when adding a sink:```{python}#| class-output: code-renderedfrom loguru import loggerdef buggy_sink(message):raiseValueError("This is a buggy sink!")# Will print the error.logger.remove()logger.add(buggy_sink, catch=True)logger.info("Message")# Will raise on error.logger.remove()logger.add(buggy_sink, catch=False)try: logger.info("Message")exceptValueErroras e:print(f"Caught exception: {e}")print(f"Exception type: {type(e)}")```When capturing `**kwargs` as contextual data (rather than for formatting), prefer using`logger.opt(depth=1).bind(**kwargs).trace(msg)` to avoid common pitfalls.### Structured Logging with JSONLoguru can handle JSON messages elegantly. Here's how to implement structured loggingwith special handling for messages that contain "code" and "message" fields:```{python}#| class-output: code-renderedimport jsonimport sysfrom loguru import logger as loguru_loggerdef format_record(record: dict) ->str:""" Format a log record with special handling for JSON messages. """ msg =f"<level>{record['level'].name:<9}</level> "# Time formatting for specific levelsif record["level"].name in ["TRACE", "INFO", "ERROR"]: msg +=f"<italic>{record['time']:DD/MMM/YY HH:mm:ss.SSS}</italic> - "# Name/line formatting for specific levelsif record["level"].name in ["TRACE", "DEBUG", "WARNING", "ERROR", "CRITICAL"]: msg +=f"<underline>{record['name']}:{record['line']}</underline> - "# Color mapping color_tags = {"TRACE": "cyan","DEBUG": "blue","INFO": "green","WARNING": "magenta","ERROR": "yellow","CRITICAL": "red", } color_tag = color_tags.get(record["level"].name, "")# JSON message handlingtry: data = json.loads(record["message"])ifisinstance(data, dict) and"code"in data and"message"in data:# Special handling for structured error messages msg +=f"<red>[{data['code']}]</red> <{color_tag}>{data['message']}</{color_tag}>{LINE_BREAK}"else:# Generic JSON handling msg +=f"<{color_tag}>{record['message']}</{color_tag}>{LINE_BREAK}"except json.JSONDecodeError:# Handle non-JSON messages normally msg +=f"<{color_tag}>{record['message']}</{color_tag}>{LINE_BREAK}"return msgloguru_logger.remove()loguru_logger.add( sys.stdout,format=format_record, level="DEBUG", colorize=True,)# Examples of different message typesloguru_logger.info("This is a regular message")loguru_logger.info('{"key": "value", "number": 42}')loguru_logger.error('{"code": "E001", "message": "Database connection failed"}')```### Avoiding JSON Parsing LoopsWhen tracing operations that might involve JSON parsing, special care must be taken toavoid infinite loops. The `raw=True` option in trace logging prevents this issue, andusing `.bind()` for contextual data is the recommended approach:```{python}#| class-output: code-renderedimport jsonimport sysfrom loguru import logger as loguru_loggerloguru_logger.remove()loguru_logger.add( sys.stdout,format="{time} | {level} | {message}", level="TRACE", colorize=True,)class SafeLogger:@staticmethoddef trace(*args: object, **kwargs: object) ->None:""" Log a trace message with raw=True to avoid JSON parsing loops. """# Using raw=True prevents potential loops when tracing JSON operations msg =" ".join(map(str, args)) loguru_logger.opt(depth=1, raw=True).trace(msg)@staticmethoddef info(*args: object, **kwargs: object) ->None:""" Log an info message with proper context handling using bind(). """ msg =" ".join(map(str, args))# For contextual data, use bind() to avoid pitfallsif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).info(msg)else: loguru_logger.opt(depth=1).info(msg)# Example usage showing proper context handlingsafe_logger = SafeLogger()safe_logger.trace("Tracing a JSON parsing operation")safe_logger.info("User login successful", user_id=1234, action="login", result="success")```### Adding HandlersYou can add multiple handlers to log to different destinations, such as files, withdifferent formats or levels:```{python}#| class-output: code-renderedlogger.remove() # Remove default handlerlogger.add("file.log", level="INFO", rotation="10 MB")logger.info("This log will go to both the console and a file.")```### Advanced FeaturesLoguru supports asynchronous logging, which can improve performance in I/O-boundapplications:```{python}#| class-output: code-renderedlogger.remove() # Remove default handlerlogger.add("async_log.log", enqueue=True)logger.info("This log is written asynchronously.")```### Sending Logs to a Remote ServerYou can use Loguru to send logs to a remote server via an API. Here's a quick exampleusing the `requests` library:```{python}#| class-output: code-renderedimport requestsfrom loguru import loggerdef send_log_to_server(message): url ="https://example.com/api/logs" payload = {"message": message} response = requests.post(url, json=payload)return response.status_codelogger.remove() # Remove default handlerlogger.add(send_log_to_server, level="INFO")logger.info("This log will be sent to a remote server.")```### Saving Logs to a FileLoguru makes it easy to save logs to a file. You can specify the file path, rotation,and retention policies. Here's an example:```{python}#| class-output: code-renderedlogger.remove() # Remove default handlerlogger.add("logs/app.log", rotation="10 MB", retention="30 days")logger.info("This log will be saved to a file.")```### Final CodeHere's a complete example of a custom logger using Loguru, with advanced features likecustom formatting, exception handling, and asynchronous logging. This version properlyuses `bind()` for contextual data:```{python}#| class-output: code-renderedimport osimport sysfrom typing import Any, NoReturnfrom loguru import logger as loguru_loggerLOG_LEVEL = os.getenv("LOG_LEVEL", "DEBUG")def format_record(record: Any) ->str:""" Format the log record. Parameters ---------- record : Any The log record. Returns ------- str """# Precompute the level name with the colon level_with_colon =f"{record['level'].name}"# Format the message msg =f"<level>{level_with_colon:<9}</level> "if record["level"].name in ["TRACE", "INFO", "ERROR"]: msg +=f"<italic>{record['time']:DD/MMM/YY HH:mm:ss.SSS}</italic> - "if record["level"].name in ["TRACE", "DEBUG", "WARNING"]: msg +=f"<underline>{record['name']}:{record['line']}</underline> - "# Use the color tags specified in your level definitions color_tags = {"TRACE": "cyan","DEBUG": "blue","INFO": "green","WARNING": "magenta","ERROR": "yellow","CRITICAL": "red", } color_tag = color_tags.get(record["level"].name, "") msg +=f"<{color_tag}>{record['message']}</{color_tag}>{LINE_BREAK}"return msgloguru_logger.remove()loguru_logger.add( sys.stdout,format=format_record, level=LOG_LEVEL, colorize=True,)loguru_logger.level("TRACE", color="<cyan>")loguru_logger.level("DEBUG", color="<blue>")loguru_logger.level("INFO", color="<green>")loguru_logger.level("WARNING", color="<magenta>")loguru_logger.level("ERROR", color="<yellow>")loguru_logger.level("CRITICAL", color="<red>")class FancyLogError(Exception):"""Exception from FancyLogger."""class FancyLogger:""" A example of a Fancy. Attributes ---------- log_level : str The current logging level. """ log_level: str= LOG_LEVELdef includes(self, level: str) ->bool:""" Check if the current logging level is less or equal than the specified level. Parameters ---------- level : str The level to compare against. Returns ------- bool True if the current level is less severe, False otherwise. """ level = level.upper() current_level = loguru_logger.level(self.log_level).no specified_level: int= loguru_logger.level(level).noreturn current_level <= specified_level@staticmethoddef trace(*args: object, **kwargs: object) ->None:""" Log a trace message, with file, line and date-time. Parameters ---------- *args: object The message(s) to log. **kwargs: object The kwargs sent to loguru. Returns ------- None """ msg =" ".join(map(str, args))# Using bind for contextual data, not for formattingif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).trace(msg)else: loguru_logger.opt(depth=1).trace(msg)@staticmethoddef debug(*args: object, **kwargs: object) ->None:""" Log a debug message, with file and line. Parameters ---------- *args: object The message(s) to log. **kwargs: object The kwargs sent to loguru. Returns ------- None """ msg =" ".join(map(str, args))# Using bind for contextual data, not for formattingif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).debug(msg)else: loguru_logger.opt(depth=1).debug(msg)@staticmethoddef info(*args: object, **kwargs: object) ->None:""" Log a info message, with date-time. Parameters ---------- *args: object The message(s) to log. **kwargs: object The kwargs sent to loguru. Returns ------- None """ msg =" ".join(map(str, args))# Using bind for contextual data, not for formattingif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).info(msg)else: loguru_logger.opt(depth=1).info(msg)@staticmethoddef warning(*args: object, **kwargs: object) ->None:""" Log a warning message, with line and file. Parameters ---------- *args: object The message(s) to log. **kwargs: object The kwargs sent to loguru. Returns ------- None """ msg =" ".join(map(str, args))# Using bind for contextual data, not for formattingif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).warning(msg)else: loguru_logger.opt(depth=1).warning(msg)@staticmethoddef error(*args: object, **kwargs: object) ->None:""" Log an error message and raises an exception, with date-time. Parameters ---------- *args: object The message(s) to log. **kwargs: object The kwargs sent to loguru. Returns ------- None Raises ------ FancyLogError """import traceback msg =" ".join(map(str, args))# Using bind for contextual data, not for formattingif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).error(msg)else: loguru_logger.opt(depth=1).error(msg) traceback.print_exc()raise FancyLogError@staticmethoddef critical(*args: object, **kwargs: object) -> NoReturn:""" Log a critical message and exits with code 1. Parameters ---------- *args: object The message(s) to log. **kwargs: object The kwargs sent to loguru. Returns ------- NoReturn """ msg =" ".join(map(str, args))# Using bind for contextual data, not for formattingif kwargs: loguru_logger.opt(depth=1).bind(**kwargs).critical(msg)else: loguru_logger.opt(depth=1).critical(msg) sys.exit(1)logging = FancyLogger()logging.info("Example with contextual data using bind", user_id=12345, action="login", status="success")logging.info("This is a info log!")logging.debug("This is a debug log!")logging.trace("This is a trace log!")logging.warning("This is a warning log!")with contextlib.suppress(FancyLogError): logging.error("This is an error log!")with contextlib.suppress(SystemExit): logging.critical("This is a critical log!")```### ConclusionLoguru simplifies logging in Python with its intuitive API and powerful features.Whether you need basic logging or advanced customization, Loguru has you covered. Theexamples provided demonstrate how to create a custom logger with Loguru, includingcustom formatting, exception handling, structured logging with JSON, and avoiding commonpitfalls with contextual data. The final code properly uses `bind()` for contextual datarather than passing kwargs directly to logging methods, which is the recommendedapproach to avoid common pitfalls.