HushLog
Zero-config PII redaction for Python logging.
Features
- Zero-config -- one call to
hushlog.patch()and you're done - Non-invasive -- wraps existing formatters, no logger rewrites needed
- Performant -- pre-compiled regex with heuristic early-exit checks
- Type-safe -- fully typed with PEP 561
py.typedmarker - Python 3.10+ -- supports Python 3.10 through 3.13
Installation
pip install hushlog
Or with uv:
uv add hushlog
Quick Start
import logging
import hushlog
# Configure logging FIRST, then patch
logging.basicConfig(level=logging.INFO)
hushlog.patch()
logger = logging.getLogger(__name__)
logger.info("User email: john@example.com")
# Output: User email: [EMAIL REDACTED]
logger.info("Card: 4111-1111-1111-1111")
# Output: Card: [CREDIT_CARD REDACTED]
logger.info("SSN: 123-45-6789")
# Output: SSN: [SSN REDACTED]
How It Works
HushLog wraps your existing logging formatters with a RedactingFormatter that scans the final formatted string for PII patterns. It never replaces loggers or handlers -- your existing logger.info() calls remain unchanged. All regex patterns are pre-compiled at import time with lightweight heuristic pre-checks to minimize overhead on the hot logging path.
What Gets Redacted
| Pattern | Example | Output | Notes |
|---|---|---|---|
john@example.com |
[EMAIL REDACTED] |
RFC 5322 subset, @ heuristic pre-check |
|
| Credit Card | 4111-1111-1111-1111 |
[CREDIT_CARD REDACTED] |
Luhn validated, supports spaces/dashes |
| SSN | 123-45-6789 |
[SSN REDACTED] |
Dashed format only, invalid ranges excluded |
| Phone | (555) 123-4567 |
[PHONE REDACTED] |
US NANP, multiple formats |
| JWT | eyJhbGci... |
[JWT REDACTED] |
3-5 segment base64url tokens |
| AWS Access Key | AKIAIOSFODNN7EXAMPLE |
[AWS_ACCESS_KEY REDACTED] |
AKIA/ASIA prefixed |
| AWS Secret Key | aws_secret_access_key=... |
[AWS_SECRET_KEY REDACTED] |
Context-dependent (requires label) |
| Stripe Key | sk_live_abc123... |
[STRIPE_KEY REDACTED] |
sk/pk/rk live/test keys |
| GitHub Token | ghp_xxxx... |
[GITHUB_TOKEN REDACTED] |
Classic + fine-grained (github_pat_) |
| GCP API Key | AIzaSyA... |
[GCP_KEY REDACTED] |
AIza-prefixed keys |
| Generic Secret | password=MyS3cret |
[SECRET REDACTED] |
Label-based (password, secret, api_key, etc.) |
| IPv4 | 192.168.1.1 |
[IPV4 REDACTED] |
Octet-validated, rejects version strings |
| IPv6 | 2001:db8::8a2e:370:7334 |
[IPV6 REDACTED] |
Full, compressed, and mixed forms |
| CPF | 529.982.247-25 |
[CPF REDACTED] |
Brazilian individual taxpayer ID, check digit validated |
| CNPJ | 11.222.333/0001-81 |
[CNPJ REDACTED] |
Brazilian company taxpayer ID, check digit validated |
| BR Phone | (11) 91234-5678 |
[BR_PHONE REDACTED] |
Brazilian mobile/landline, optional +55 prefix |
| IBAN | GB29 NWBK 6016 1331 9268 19 |
[IBAN REDACTED] |
International bank account number, mod-97 validated |
| EU VAT | DE123456789 |
[EU_VAT REDACTED] |
EU VAT numbers with country prefix |
| Aadhaar | 2345 6789 0124 |
[AADHAAR REDACTED] |
Indian 12-digit ID, Verhoeff checksum validated |
| PAN | BNZPM2501F |
[PAN REDACTED] |
Indian Permanent Account Number, entity type validated |
| IN Phone | +91 98765 43210 |
[IN_PHONE REDACTED] |
Indian mobile numbers, optional +91/0 prefix |
| SIN | 130-692-544 |
[SIN REDACTED] |
Canadian Social Insurance Number, Luhn validated |
| E.164 Phone | +44 7911 123456 |
[E164_PHONE REDACTED] |
International phone numbers, 8-15 digits |
| SWIFT/BIC | DEUTDEFF |
[SWIFT REDACTED] |
Bank identifier codes, 8 or 11 characters |
Configuration
Disable specific built-in patterns or add custom ones:
from hushlog import Config
hushlog.patch(Config(
disable_patterns=frozenset({"phone"}),
custom_patterns={"internal_id": r"ID-[A-Z]{3}-[0-9]{6}"},
))
Partial Masking
Show partial values instead of full redaction:
hushlog.patch(Config(mask_style="partial"))
# john@example.com → j***@e***.com
# 4111111111111111 → ****-****-****-1111
# 078-05-1120 → ***-**-1120
# (555) 234-5678 → (***) ***-5678
Use a custom mask character:
hushlog.patch(Config(mask_style="partial", mask_character="#"))
# john@example.com → j###@e###.com
Note: Partial masking reveals partial information (first/last characters). In small organizations, this may be identifying. Use
mask_style="full"(default) for maximum privacy.
JSON / Structured Logging
HushLog supports JSON log output with automatic PII redaction in all string values, including nested structures.
RedactingJsonFormatter
Use RedactingJsonFormatter as a drop-in JSON formatter for any handler:
import logging
from hushlog import RedactingJsonFormatter
formatter = RedactingJsonFormatter.from_config()
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logging.getLogger().addHandler(handler)
logger = logging.getLogger(__name__)
logger.info("Contact user@example.com", extra={"ssn": "078-05-1120"})
# Output: {"message": "Contact [EMAIL REDACTED]", "ssn": "[SSN REDACTED]", ...}
Works with or without python-json-logger installed. Install the optional dependency for enhanced JSON serialization:
pip install hushlog[json]
redact_dict()
For manual redaction of dict/list/string structures:
import hushlog
data = {"user": {"email": "alice@corp.io", "name": "Alice", "age": 30}}
clean = hushlog.redact_dict(data)
# {"user": {"email": "[EMAIL REDACTED]", "name": "Alice", "age": 30}}
Note:
redact_dict()creates a newPatternRegistryon every call. For repeated use, create a registry once viaPatternRegistry.from_config()and callregistry.redact_dict()directly.
structlog
Use structlog_processor() as a processor in your structlog pipeline:
import structlog
from hushlog import structlog_processor
structlog.configure(
processors=[
structlog.stdlib.add_log_level,
structlog_processor(),
structlog.dev.ConsoleRenderer(),
],
)
logger = structlog.get_logger()
logger.info("login", email="alice@corp.com")
# Output: email=[EMAIL REDACTED]
Install the optional dependency: pip install hushlog[structlog]
loguru
Wrap any loguru sink with PII redaction:
from loguru import logger
from hushlog import loguru_sink
logger.remove() # Remove default sink
logger.add(loguru_sink(print), format="{message}")
logger.info("User alice@corp.com logged in")
# Output: User [EMAIL REDACTED] logged in
Install the optional dependency: pip install hushlog[loguru]
Teardown
Call unpatch() to remove HushLog's formatter wrappers and restore the original formatters. This is useful for testing or runtime toggling:
hushlog.unpatch()
Calling unpatch() without a prior patch() is safe (no-op). Calling patch() multiple times is also safe (idempotent).
Limitations
- Only handlers present on the root logger at
patch()time are wrapped. Handlers added later will not be redacted. - Named loggers with
propagate=Falseand their own handlers bypass root-level redaction. - For structlog/loguru, use the dedicated integrations (
structlog_processor,loguru_sink) instead ofpatch(). - Phone detection covers US NANP, Brazilian, Indian, and E.164 international formats.
Security Model
HushLog uses regex-based pattern matching for PII detection. This is a best-effort approach with known trade-offs:
- Heuristic pre-checks (e.g., checking for
@before running email regex) are performance optimizations, not security boundaries. They reduce regex invocations but do not guarantee detection. - Regex patterns are pre-compiled and tested against common formats, but they cannot detect PII in obfuscated, encrypted, or encoded forms.
- Partial masking (
mask_style="partial") reveals partial information by design. In small organizations, first/last characters may be identifying. Usemask_style="full"for maximum privacy. - Custom patterns are compiled at Config construction and validated, but HushLog does not check for ReDoS vulnerability in user-supplied regex. Use tested patterns from trusted sources.
- Unicode normalization (NFC) is applied before redaction to handle decomposed character forms, but does not protect against confusable characters (homoglyphs like Cyrillic "a" vs Latin "a").
For high-security environments, combine HushLog with additional controls (log access restrictions, encryption at rest, audit logging).
Planned
Production hardening, docs site, and more. See the roadmap for details.
Contributing
Contributions are welcome! See CONTRIBUTING.md for guidelines.
License
MIT -- see LICENSE for details.
Metadata
Release files for hushlog 1.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hushlog-1.5.0.tar.gz | 127.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hushlog-1.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 150.6 kB
Release files / hushlog-1.5.0.tar.gz
| Download URL | hushlog-1.5.0.tar.gz |
|---|---|
| Size | 127.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
809facd6450f7009ad0367b86321a79927ddfafa20977a17fcbf220e0e843876
|
|
BLAKE2b-256 checksum How to use checksums |
eff960622b6dd1ae3d1e937ba43c327739f354561c68f958108589b3f795444d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 20, 2026.
Transparency logRelease files / hushlog-1.5.0-py3-none-any.whl
| Download URL | hushlog-1.5.0-py3-none-any.whl |
|---|---|
| Size | 23.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
83a29cb05a2c53cb104045cffa0904218ff64950460155360022f977e1b485c6
|
|
BLAKE2b-256 checksum How to use checksums |
6ff09351aad431cd19b4f3f5c07ad857db42b5d3361a48769d7d0522ecc7b002
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 20, 2026.
Transparency log