Custom Logger Implementation in Go
Logging is a crucial aspect of software development, providing valuable insights into application behavior, debugging information, and performance metrics. This challenge asks you to implement a custom logger in Go, allowing for flexible log levels, formatting, and output destinations. Building a custom logger provides a deeper understanding of logging principles and allows for tailored solutions to specific application needs.
Problem Description
You are tasked with creating a Logger struct in Go that provides basic logging functionality. The logger should support different log levels (Debug, Info, Warning, Error, Fatal) and allow for custom formatting of log messages. The logger should be able to write logs to a specified io.Writer (e.g., standard output, a file, or a network connection).
Key Requirements:
- Log Levels: Implement five log levels:
Debug,Info,Warning,Error, andFatal. - Formatting: Provide a mechanism to format log messages using a template string (similar to
fmt.Sprintf). - Output Destination: The logger should accept an
io.Writerinterface, allowing it to write logs to various destinations. - Log Entry Structure: Each log entry should include the log level, timestamp, and the formatted message.
- Fatal Logging: The
Fatallog level should print the message and then immediately exit the program.
Expected Behavior:
The Logger struct should have the following methods:
NewLogger(output io.Writer, format string): Constructor that initializes a new logger with the specified output writer and format string.Debug(message string): Logs a message with theDebuglevel.Info(message string): Logs a message with theInfolevel.Warning(message string): Logs a message with theWarninglevel.Error(message string): Logs a message with theErrorlevel.Fatal(message string): Logs a message with theFatallevel and exits the program.
Edge Cases to Consider:
- Handle nil
io.Writergracefully (e.g., by logging to standard output as a default). - Handle empty or invalid format strings.
- Ensure thread safety if the logger is used concurrently. (While not strictly required for this challenge, it's a good consideration).
- Consider how to handle errors during writing to the output destination.
Examples
Example 1:
Input:
Logger := NewLogger(os.Stdout, "%s - %s - %s\n")
Logger.Info("Application started")
Logger.Debug("Debug message")
Logger.Error("An error occurred")
Output:
[Timestamp] - Info - Application started
[Timestamp] - Debug - Debug message
[Timestamp] - Error - An error occurred
Explanation: The logger writes messages to standard output, formatted with the timestamp, log level, and the provided message.
Example 2:
Input:
file, err := os.Create("app.log")
if err != nil {
panic(err)
}
defer file.Close()
Logger := NewLogger(file, "[%d] %s: %s\n")
Logger.Warning("Potential issue detected")
Output:
(app.log file contains)
[Timestamp] - Warning - Potential issue detected
Explanation: The logger writes messages to a file named "app.log", formatted with the timestamp, log level, and the provided message.
Example 3: (Edge Case - Fatal)
Input:
Logger := NewLogger(os.Stdout, "Fatal: %s\n")
Logger.Fatal("Critical error - exiting")
Output:
Fatal: Critical error - exiting
(Program exits immediately)
Explanation: The Fatal log level prints the message and terminates the program.
Constraints
- The format string should support at least placeholders for the timestamp, log level, and message.
- The timestamp should be in a readable format (e.g., "2023-10-27 10:00:00").
- The logger should be reasonably efficient; avoid unnecessary allocations or operations.
- The
Fatalmethod must exit the program. - The log levels should be represented as strings (e.g., "Debug", "Info").
Notes
- You can use the
timepackage to generate timestamps. - The
fmtpackage can be helpful for formatting log messages. - Consider using a
switchstatement or a map to handle different log levels. - Think about how to make your logger extensible for future features (e.g., adding support for different output formats or log rotation).
- While thread safety isn't explicitly required, consider the implications if your logger might be used concurrently. A simple mutex could be used to protect the output writer.