Hone logo
Hone
Problems

Custom Python Logging Handler

Logging is a crucial part of software development, providing insights into application behavior and aiding in debugging. Python's built-in logging module offers a flexible framework, but sometimes you need to send logs to custom destinations or format them in specific ways. This challenge asks you to create a custom logging handler that writes log messages to a file, with a specific formatting requirement.

Problem Description

Your task is to implement a custom logging handler in Python. This handler should capture log messages from Python's logging module and write them to a specified file. The log messages should be formatted in a particular way, including a timestamp, the log level, the logger name, and the message itself.

Key Requirements:

  1. Create a Custom Handler Class:
    • Inherit from logging.Handler.
    • Implement the emit(self, record) method. This method will be responsible for processing and outputting the log record.
  2. File Output:
    • The handler should accept a file path during initialization.
    • Log messages should be appended to this file. If the file does not exist, it should be created.
  3. Custom Formatting:
    • Each log message in the file must follow this format: YYYY-MM-DD HH:MM:SS,ms - LEVEL - LOGGER_NAME - MESSAGE
    • YYYY-MM-DD HH:MM:SS,ms represents the timestamp with milliseconds.
    • LEVEL is the uppercase name of the log level (e.g., INFO, WARNING, ERROR).
    • LOGGER_NAME is the name of the logger that generated the message.
    • MESSAGE is the actual log message.
  4. Error Handling:
    • Ensure that the file operations are handled gracefully. If there's an error writing to the file, it should not crash the application.

Expected Behavior:

When log messages are generated and directed to your custom handler, they should appear in the specified file with the correct format.

Edge Cases:

  • Log messages with special characters: Ensure your formatting handles these correctly.
  • Concurrent writes (optional but good to consider): While not strictly required for this challenge, consider how multiple threads might attempt to write to the same file. The standard logging module has mechanisms to handle this.
  • Large log files: The handler should not become prohibitively slow with many log messages.

Examples

Example 1:

Let's assume your custom handler is named CustomFileHandler.

import logging
import os
from datetime import datetime

# Assume CustomFileHandler is defined elsewhere and works as expected

log_file = "app.log"
if os.path.exists(log_file):
    os.remove(log_file) # Clean up for reproducible example

logger = logging.getLogger("MyApplication")
logger.setLevel(logging.DEBUG)

# Create and add your custom handler
custom_handler = CustomFileHandler(log_file)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
custom_handler.setFormatter(formatter) # Although we will override formatting in emit for this specific challenge's requirement
logger.addHandler(custom_handler)

logger.info("Application started.")
logger.warning("Configuration file not found.")
logger.debug("Processing user request.")

Expected app.log content (timestamps will vary):

2023-10-27 10:30:00,123 - INFO - MyApplication - Application started.
2023-10-27 10:30:01,456 - WARNING - MyApplication - Configuration file not found.
2023-10-27 10:30:02,789 - DEBUG - MyApplication - Processing user request.

Note: The exact millisecond values will differ based on execution time.

Example 2: Different Logger Names and Levels

import logging
import os
from datetime import datetime

# Assume CustomFileHandler is defined

log_file = "system.log"
if os.path.exists(log_file):
    os.remove(log_file)

# First logger
logger1 = logging.getLogger("DatabaseManager")
logger1.setLevel(logging.INFO)
handler1 = CustomFileHandler(log_file)
logger1.addHandler(handler1)

# Second logger
logger2 = logging.getLogger("NetworkService")
logger2.setLevel(logging.ERROR)
handler2 = CustomFileHandler(log_file)
logger2.addHandler(handler2)

logger1.info("Database connection established.")
logger1.error("Failed to execute query.")
logger2.warning("Low network bandwidth detected.") # This won't be logged by logger2 as its level is ERROR
logger2.error("Connection timed out.")

Expected system.log content (timestamps will vary):

2023-10-27 10:35:00,111 - INFO - DatabaseManager - Database connection established.
2023-10-27 10:35:01,222 - ERROR - DatabaseManager - Failed to execute query.
2023-10-27 10:35:03,444 - ERROR - NetworkService - Connection timed out.

Constraints

  • Your CustomFileHandler class must inherit from logging.Handler.
  • The output file name can be any valid string representing a file path.
  • The emit method must correctly format the log message according to the specified format.
  • The code should be runnable with standard Python libraries.

Notes

  • The logging.Handler base class provides several useful methods and attributes. You'll primarily need to focus on implementing emit.
  • You can access the log record's attributes (like levelname, name, getMessage(), created for timestamp) within the emit method.
  • To get the formatted message string, you can use a logging.Formatter and call its format(record) method, or you can manually construct the string as per the format requirement. The challenge implies you should construct it to meet the exact millisecond format.
  • The created attribute of a LogRecord is a float representing seconds since the epoch. You'll need to convert this to a human-readable string with milliseconds. The datetime module will be your friend here.
  • Consider how to handle file opening and closing. A with statement is a good practice for file operations. However, since handlers are typically long-lived, you might want to open the file in the __init__ and close it in a close method that the logging module may call. For simplicity in this challenge, opening and closing within emit for each message is acceptable, but be aware of potential performance implications in real-world scenarios. A more robust solution would open the file once.
Loading editor...
python