How to Redact Secrets from Log Messages in Python
Build a lightweight RedactingFormatter class that replaces sensitive tokens like passwords and API keys with [REDACTED] before log messages are printed.
Python code
26 linesclass RedactingFormatter:
def __init__(self, secrets):
self.secrets = secrets
def redact(self, message):
for secret in self.secrets:
message = message.replace(secret, "[REDACTED]")
return message
def format(self, record):
message = record["message"]
return self.redact(message)
if __name__ == "__main__":
secrets = ["my_password", "token123"]
formatter = RedactingFormatter(secrets)
log_records = [
{"level": "INFO", "message": "User logged in with my_password"},
{"level": "DEBUG", "message": "API key token123 used for request"},
{"level": "ERROR", "message": "Connection failed (token123 vs my_password)"}
]
for record in log_records:
print(f"{record['level']}: {formatter.format(record)}")
Output
INFO: User logged in with [REDACTED]
DEBUG: API key [REDACTED] used for request
ERROR: Connection failed ([REDACTED] vs [REDACTED])
How it works
The constructor stores the list of secret strings on the formatter instance. The redact method loops over each secret and calls str.replace to swap every occurrence with [REDACTED] — a simple, dependency-free substitution. The format method extracts the log message from the record dict and passes it through the redaction pipeline, returning a clean string. The if __name__ block simulates a small logging flow, showing how the same formatter can sanitize different severity levels consistently.
Common mistakes
- Redacting substrings that appear inside legitimate text, like 'token1234', because replace matches partial strings
- Using mutable state for secrets and changing the list while messages are being processed in a concurrent logger
- Forgetting to preserve metadata or context (level, timestamp) when returning a raw message string
Variations
- Use `re.sub` with word boundaries (`\b`) to avoid partial secret matches
- Integrate with Python's `logging.Formatter` by overriding `format` in a subclass that also calls `super().format`
Real-world use cases
- Stripping database credentials from SQL query logs before they hit central log aggregation.
- Masking customer API keys in microservice access logs to prevent secrets leaking into SIEM dashboards.
- Sanitizing CI build output so cloud provider tokens are never printed to the terminal or archived builds.
Sponsored
More from Observability & SRE
- Adding a Correlation ID to Log Context in Python medium
- Calculate Error Rate from Log Stream in Python easy
- Check if a Timestamp Falls in a Daily Maintenance Window in Python easy
- Export Metrics with OTLP Mock in Python medium
- Generate Mock CPU and Memory Metrics in Python easy
- Generate Prometheus Text Exposition Format in Python easy
Keep learning
Related tutorials and quizzes for this topic.