Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add support for filtering out secrets when diagnose=True
  • Loading branch information
PerchunPak committed Mar 18, 2026
commit a0f46d606b9216198134075cf73793fef9f76d37
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ Logging exceptions that occur in your code is important to track bugs, but it's
The code:

```python
# Caution, "diagnose=True" is the default and may leak sensitive data in prod
# Caution, "diagnose=True" is the default and may leak sensitive data in prod.
# Read further for the solution
logger.add("out.log", backtrace=True, diagnose=True)

def func(a, b):
Expand Down Expand Up @@ -191,6 +192,36 @@ ZeroDivisionError: division by zero

Note that this feature won't work on default Python REPL due to unavailable frame data.

But for passwords and other credentials, you should exclude them using `diagnose_excludes` parameter:

```python
logger.add("out.log", backtrace=True, diagnose=True, diagnose_excludes=["myS3cr3tP@ss!"])

def connect_to_db(password):
# ...
raise TimeoutError("could not connect to the database")

password = "myS3cr3tP@ss!"
connect_to_db(password)
```

This will replace all occurrences of `myS3cr3tP@ss!` with `<hidden>` so the result traceback would be something like

```none
2026-03-18 17:06:44.822 | ERROR | __main__:<module>:15 - What?!
Traceback (most recent call last):

> File "test.py", line 13, in <module>
connect_to_db(password)
│ └ '<hidden>'
└ <function connect_to_db at 0x7f75b152f740>

File "test.py", line 9, in connect_to_db
raise TimeoutError("could not connect to the database")

TimeoutError: could not connect to the database
```

See also: [Security considerations when using Loguru](https://loguru.readthedocs.io/en/stable/resources/recipes.html#security-considerations-when-using-loguru).

### Structured logging as needed
Expand Down
5 changes: 4 additions & 1 deletion docs/resources/recipes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,14 @@ Another danger due to external input is the possibility of a log injection attac
logger.info("User " + username + " logged in.")


Note that by default, Loguru will display the value of existing variables when an ``Exception`` is logged. This is very useful for debugging but could lead to credentials appearing in log files. Make sure to turn it off in production (or set the ``LOGURU_DIAGNOSE=NO`` environment variable).
Note that by default, Loguru will display the value of existing variables when an ``Exception`` is logged. This is very useful for debugging but could lead to credentials appearing in log files. Make sure to add your sensitive data to `diagnose_excludes` or turn it off in production (or set the ``LOGURU_DIAGNOSE=NO`` environment variable).

.. code::

logger.add("out.log", diagnose=True, diagnose_excludes=["myS3cr3tP@ss!"])
# or disable diagnose using
logger.add("out.log", diagnose=False)
# or set the ``LOGURU_DIAGNOSE=NO`` environment variable


Another thing you should consider is to change the access permissions of your log file. Loguru creates files using the built-in |open| function, which means by default they might be read by a different user than the owner. If this is not desirable, be sure to modify the default access rights.
Expand Down
13 changes: 13 additions & 0 deletions loguru/_better_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ def __init__(
colorize=False,
backtrace=False,
diagnose=True,
diagnose_excludes=None,
theme=None,
style=None,
max_length=128,
Expand All @@ -154,6 +155,11 @@ def __init__(
):
self._colorize = colorize
self._diagnose = diagnose
self._diagnose_excludes = (
diagnose_excludes.split(",")
if isinstance(diagnose_excludes, str) and diagnose_excludes
else diagnose_excludes or []
)
self._theme = theme or dict(self._default_theme)
self._backtrace = backtrace
self._syntax_highlighter = SyntaxHighlighter(style)
Expand Down Expand Up @@ -345,6 +351,9 @@ def _format_value(self, v):
except Exception:
v = "<unprintable %s object>" % type(v).__name__

for exclude in self._diagnose_excludes:
v = v.replace(repr(exclude)[1:-1], "<hidden>")

max_length = self._max_length
if max_length is not None and len(v) > max_length:
v = v[: max_length - 3] + "..."
Expand Down Expand Up @@ -473,6 +482,10 @@ def _format_exception(
# Remove final new line temporarily.
error_message = exception_only[error_message_index][:-1]

for exclude in self._diagnose_excludes:
error_message = error_message.replace(repr(exclude)[1:-1], "<hidden>")
error_message = error_message.replace(exclude, "<hidden>")

if self._colorize:
if ":" in error_message:
exception_type, exception_value = error_message.split(":", 1)
Expand Down
1 change: 1 addition & 0 deletions loguru/_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def env(key, type_, default=None):
LOGURU_SERIALIZE = env("LOGURU_SERIALIZE", bool, False)
LOGURU_BACKTRACE = env("LOGURU_BACKTRACE", bool, True)
LOGURU_DIAGNOSE = env("LOGURU_DIAGNOSE", bool, True)
LOGURU_DIAGNOSE_EXCLUDES = env("LOGURU_DIAGNOSE_EXCLUDES", str, "")
LOGURU_ENQUEUE = env("LOGURU_ENQUEUE", bool, False)
LOGURU_CONTEXT = env("LOGURU_CONTEXT", str, None)
LOGURU_CATCH = env("LOGURU_CATCH", bool, True)
Expand Down
6 changes: 6 additions & 0 deletions loguru/_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ def add(
serialize=_defaults.LOGURU_SERIALIZE,
backtrace=_defaults.LOGURU_BACKTRACE,
diagnose=_defaults.LOGURU_DIAGNOSE,
diagnose_excludes=_defaults.LOGURU_DIAGNOSE_EXCLUDES,
enqueue=_defaults.LOGURU_ENQUEUE,
context=_defaults.LOGURU_CONTEXT,
catch=_defaults.LOGURU_CATCH,
Expand Down Expand Up @@ -302,6 +303,10 @@ def add(
diagnose : |bool|, optional
Whether the exception trace should display the variables values to ease the debugging.
This should be set to ``False`` in production to avoid leaking sensitive data.
diagnose_excludes : |list| of |str|, optional
List of strings to exclude from variables in exceptions with ``diagnose=True``.
Use this if you would like to keep more context in production logs,
but don't want to leak credentials.
enqueue : |bool|, optional
Whether the messages to be logged should first pass through a multiprocessing-safe queue
before reaching the sink. This is useful while logging to a file through multiple
Expand Down Expand Up @@ -1023,6 +1028,7 @@ def add(
colorize=colorize,
encoding=encoding,
diagnose=diagnose,
diagnose_excludes=diagnose_excludes,
backtrace=backtrace,
hidden_frames_filename=self.catch.__code__.co_filename,
prefix=exception_prefix,
Expand Down
13 changes: 13 additions & 0 deletions tests/exceptions/output/diagnose/excludes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

Traceback (most recent call last):

File "tests/exceptions/source/diagnose/excludes.py", line 23, in <module>
connect_to_db(password)
│ └ '<hidden>'
└ <function connect_to_db at 0xDEADBEEF>

File "tests/exceptions/source/diagnose/excludes.py", line 18, in connect_to_db
raise TimeoutError("tried to connect to " + repr(connection_string))
 └ 'foo bar <hidden> baz'

TimeoutError: tried to connect to 'foo bar <hidden> baz'
25 changes: 25 additions & 0 deletions tests/exceptions/source/diagnose/excludes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import sys

from loguru import logger

logger.remove()
logger.add(
sys.stderr,
format="",
colorize=True,
backtrace=False,
diagnose=True,
diagnose_excludes=["myS3cr\n3tP@ss!"],
)


def connect_to_db(password):
connection_string = "foo bar " + password + " baz"
raise TimeoutError("tried to connect to " + repr(connection_string))


password = "myS3cr\n3tP@ss!"
try:
connect_to_db(password)
except TimeoutError:
logger.exception("")
1 change: 1 addition & 0 deletions tests/test_exceptions_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ def test_backtrace(filename):
"attributes",
"chained_both",
"encoding",
"excludes",
"global_variable",
"indentation_error",
"keyword_argument",
Expand Down
Loading