Open-Nursing-Validator / core /safe_logging.py
NurseCitizenDeveloper's picture
Deploy Open Nursing Validator (Docker)
6d12932 verified
"""Safe logging utilities to prevent sensitive data leakage.
This module provides utilities for safely logging exceptions without exposing
sensitive information like passwords, tokens, or PII in application logs.
"""
import logging
from typing import Optional
def log_exception_safe(
logger: logging.Logger,
message: str,
exception: Exception,
level: str = "error",
include_type: bool = True
) -> None:
"""
Safely log an exception without exposing sensitive data.
Instead of logging the full exception message (which may contain sensitive data),
this function logs only the exception type or a generic message.
Args:
logger: Logger instance to use
message: Context message describing what failed
exception: Exception object to log safely
level: Log level (error, warning, info, debug)
include_type: Whether to include exception type name in the log
Example:
>>> try:
>>> create_user("admin", "SecretPassword123!")
>>> except Exception as e:
>>> log_exception_safe(logger, "User creation failed", e)
>>> # Logs: "User creation failed: IntegrityError"
>>> # Does NOT log: "User creation failed: UNIQUE constraint failed: password=SecretPassword123!"
"""
if include_type:
safe_message = f"{message}: {type(exception).__name__}"
else:
safe_message = message
log_func = getattr(logger, level.lower())
log_func(safe_message)
def get_safe_error_message(exception: Exception) -> str:
"""
Get a safe error message from an exception.
Returns only the exception type name, not the potentially sensitive message.
Args:
exception: Exception object
Returns:
String containing only the exception type name
Example:
>>> try:
>>> raise ValueError("password=secret123")
>>> except Exception as e:
>>> safe_msg = get_safe_error_message(e)
>>> # safe_msg = "ValueError"
"""
return type(exception).__name__
def mask_identifier(identifier: str, prefix: str = "id") -> str:
"""
Mask an identifier for safe logging.
Completely masks the identifier to prevent any sensitive data leakage.
This security measure ensures no part of the identifier (which may contain
or be derived from sensitive information) is exposed in logs.
The prefix parameter is used to identify the type of identifier in logs.
Completely masks the identifier value to prevent any sensitive data exposure.
Returns only the prefix with masked content, without revealing any characters
from the actual identifier.
Args:
identifier: The identifier to mask (patient_id, username, etc.)
prefix: Prefix to use for the masked value (e.g., 'pat', 'user', 'id')
Returns:
Masked identifier string in the format: prefix_****
Masked identifier string in format: "{prefix}_****"
Example:
>>> mask_identifier("patient12345", "pat")
'pat_****'
>>> mask_identifier("admin", "user")
'user_****'
>>> mask_identifier("", "id") # Even empty strings are masked consistently
'id_****'
"""
return f"{prefix}_****"
def safe_log_info(
logger: logging.Logger,
message: str,
**identifiers
) -> None:
"""
Safely log an info message with masked identifiers.
Args:
logger: Logger instance to use
message: Message template with {placeholders}
**identifiers: Key-value pairs of identifiers to mask
Example:
>>> safe_log_info(logger, "Processing patient {patient_id}",
... patient_id=("patient12345", "pat"))
# Logs: "Processing patient pat_****"
"""
masked_values = {}
for key, value in identifiers.items():
if isinstance(value, tuple):
identifier, prefix = value
masked_values[key] = mask_identifier(identifier, prefix)
else:
masked_values[key] = mask_identifier(value, key)
logger.info(message.format(**masked_values))
def safe_log_warning(
logger: logging.Logger,
message: str,
**identifiers
) -> None:
"""
Safely log a warning message with masked identifiers.
Args:
logger: Logger instance to use
message: Message template with {placeholders}
**identifiers: Key-value pairs of identifiers to mask
Example:
>>> safe_log_warning(logger, "No data for patient {patient_id}",
... patient_id=("67890", "pat"))
# Logs: "No data for patient pat_****"
"""
masked_values = {}
for key, value in identifiers.items():
if isinstance(value, tuple):
identifier, prefix = value
masked_values[key] = mask_identifier(identifier, prefix)
else:
masked_values[key] = mask_identifier(value, key)
logger.warning(message.format(**masked_values))