Implementing Robust Audit Logging in Python
Audit logging is a critical component of any application that requires tracking user actions, system events, and changes to sensitive data. It provides a historical record for security analysis, debugging, and compliance. This challenge asks you to build a flexible and configurable audit logging system in Python.
Problem Description
Your task is to create a Python module that facilitates the recording of audit logs. This system should be able to log various events with different levels of severity and store them in a structured format. The system needs to be easily integrated into existing or new Python applications.
Key Requirements:
- Log Event Recording: Implement a function or class method to record audit events.
- Event Information: Each log entry should capture essential information such as:
- Timestamp of the event.
- User or system identifier performing the action.
- Type of event (e.g., "CREATE", "READ", "UPDATE", "DELETE", "LOGIN", "LOGOUT", "ERROR").
- A descriptive message or details about the action taken.
- Severity level (e.g., INFO, WARNING, ERROR, CRITICAL).
- Configuration: The logging system should be configurable. This includes:
- Log Destination: Ability to specify where logs are written (e.g., console, file).
- Log Format: Flexibility in defining the structure of each log message.
- Severity Filtering: Option to set a minimum severity level for logs to be recorded.
- Structured Output: Logs should be output in a consistent and easily parseable format. A common format is JSON, but other structured formats are acceptable.
- Error Handling: The logging system itself should handle potential errors gracefully (e.g., if a log file cannot be written to).
Expected Behavior:
When an event is logged, it should be processed according to the configured settings. If the event's severity meets or exceeds the configured minimum, it should be formatted and written to the specified destination.
Edge Cases:
- What happens if the user attempts to log an event with an invalid severity level?
- How should the system behave if it fails to write to a log file (e.g., due to permissions or disk full)?
- Consider concurrent logging requests from multiple threads/processes (though a simple implementation for single-threaded scenarios is acceptable initially).
Examples
Example 1: Basic Logging to Console
# Assume 'AuditLogger' is your implemented class/module
logger = AuditLogger(log_destination='console', log_level='INFO')
# Simulate a user login event
logger.log_event(
user_id="user123",
event_type="LOGIN",
message="User logged in successfully.",
severity="INFO"
)
# Simulate an attempt to access restricted data
logger.log_event(
user_id="user123",
event_type="ACCESS_DENIED",
message="Attempted to access restricted resource /admin.",
severity="WARNING"
)
Expected Console Output (JSON format):
{"timestamp": "2023-10-27T10:30:00.123456", "user": "user123", "event": "LOGIN", "details": "User logged in successfully.", "level": "INFO"}
{"timestamp": "2023-10-27T10:30:05.678901", "user": "user123", "event": "ACCESS_DENIED", "details": "Attempted to access restricted resource /admin.", "level": "WARNING"}
(Note: Timestamps will vary)
Explanation: Two events are logged. Both are at or above the 'INFO' level, so they are printed to the console in JSON format.
Example 2: Logging to a File with Custom Format and Higher Severity Filter
# Assume 'AuditLogger' is your implemented class/module
logger = AuditLogger(
log_destination='file',
log_file_path='application.audit.log',
log_level='WARNING',
log_format='{timestamp} [{level}] - {user}: {event} - {details}'
)
# Simulate a successful data update
logger.log_event(
user_id="admin",
event_type="UPDATE",
message="User profile updated for user456.",
severity="INFO"
)
# Simulate a critical system error
logger.log_event(
user_id="system",
event_type="SYSTEM_ERROR",
message="Database connection lost.",
severity="CRITICAL"
)
Expected application.audit.log File Content:
2023-10-27T10:31:00.111222 [CRITICAL] - system: SYSTEM_ERROR - Database connection lost.
(Note: Timestamps will vary)
Explanation: The 'UPDATE' event with severity 'INFO' is below the configured 'WARNING' level and is therefore not logged. The 'SYSTEM_ERROR' event with severity 'CRITICAL' meets the threshold and is logged to the file using the custom format.
Example 3: Handling Invalid Severity and File Write Error
# Assume 'AuditLogger' is your implemented class/module
logger = AuditLogger(log_destination='file', log_file_path='invalid_permissions.log', log_level='INFO')
# Attempt to log with an invalid severity
try:
logger.log_event(
user_id="test_user",
event_type="INVALID_ACTION",
message="This is an invalid event.",
severity="UNKNOWN" # Invalid severity
)
except ValueError as e:
print(f"Caught expected error: {e}")
# Simulate a file write failure (e.g., if the directory doesn't exist or is read-only)
# For demonstration, let's assume a path that won't be writable.
# In a real scenario, this might require specific OS setup or mock objects.
try:
logger_no_perm = AuditLogger(log_destination='file', log_file_path='/root/no_access.log', log_level='INFO')
logger_no_perm.log_event(user_id="test", event_type="TEST", message="Will fail", severity="INFO")
except IOError as e: # Or a more specific exception based on your implementation
print(f"Caught expected error during file write: {e}")
Expected Output (Console):
Caught expected error: Invalid severity level: UNKNOWN. Accepted levels are: INFO, WARNING, ERROR, CRITICAL.
Caught expected error during file write: [Errno 13] Permission denied: '/root/no_access.log'
(Note: Specific error messages might vary based on OS and Python version)
Explanation:
The first log_event raises a ValueError because "UNKNOWN" is not a valid severity. The second attempt to log to /root/no_access.log (assuming no root privileges) would raise an IOError (or similar) which the logging system should ideally catch and report, perhaps by logging the failure itself to another destination or by printing to stderr, rather than crashing the application.
Constraints
- Severity Levels: Accepted severity levels are: "INFO", "WARNING", "ERROR", "CRITICAL".
- Timestamp Format: Timestamps should be in ISO 8601 format (e.g.,
YYYY-MM-DDTHH:MM:SS.ffffff). - Log File Size: For this challenge, assume no specific file size rollover mechanism is required, but the system should not crash if a very large log file is generated.
- Concurrency: A basic implementation that works correctly for a single thread is sufficient. Advanced thread-safety is a bonus but not mandatory for a passing solution.
- Python Version: Solution should be compatible with Python 3.7+.
Notes
- Consider using Python's built-in
loggingmodule as inspiration or a foundation, but the goal is to build your own implementation for learning purposes. You can implement it from scratch or extend/wrap parts of theloggingmodule if you feel it aids in achieving the requirements. - The
log_formatstring can use placeholders like{timestamp},{user},{event},{details},{level}. - Think about how you will map severity strings ("INFO", "WARNING", etc.) to numerical levels if you decide to use a numerical system internally.
- When dealing with file writing errors, logging the failure to log is a good practice.