Go Log Package Development
This challenge asks you to build a robust and flexible logging package for Go applications. A well-designed logging system is crucial for debugging, monitoring, and understanding the behavior of any software. You will implement a custom logger that allows for different logging levels and output destinations.
Problem Description
Your task is to create a Go package named logger that provides a simple yet powerful logging utility. This package should allow developers to log messages at various severity levels and direct these logs to different output targets.
Key Requirements:
- Logging Levels: Support at least the following logging levels:
DEBUGINFOWARNERRORFATAL(should also terminate the program)
- Output Destinations: Allow logs to be written to:
- Standard Output (
os.Stdout) - Standard Error (
os.Stderr) - A file
- Standard Output (
- Configurability: The package should be configurable to set:
- The minimum logging level to be displayed.
- The output destination(s).
- Formatting: Each log entry should include:
- Timestamp
- Logging Level
- The actual log message
- Error Handling: Gracefully handle potential errors during file operations (e.g., opening/writing to files).
FatalLevel: Implement aFatallogging function that logs the message and then callsos.Exit(1).
Examples
Example 1: Basic Info Logging to Stdout
package main
import (
"github.com/your_username/logger" // Assuming your package is published here
)
func main() {
logger.SetLevel(logger.INFO)
logger.SetOutput(logger.Stdout)
logger.Info("Application started")
logger.Debug("This debug message will not be shown")
logger.Warn("Configuration file not found, using defaults")
}
Expected Output (approximately):
2023-10-27T10:00:00Z INFO Application started
2023-10-27T10:00:01Z WARN Configuration file not found, using defaults
(Timestamps will vary)
Explanation:
The logger is configured to show INFO level and above. The Info and Warn messages are displayed with their timestamps and levels because they meet the minimum level. The Debug message is suppressed.
Example 2: Logging Errors to Stderr and a File
package main
import (
"github.com/your_username/logger"
"os"
)
func main() {
logFile, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
// Handle error opening file - for demonstration, we'll just log to stderr
logger.Errorf("Failed to open log file: %v", err)
logger.SetOutput(logger.Stderr)
logger.SetLevel(logger.DEBUG)
} else {
logger.SetOutputs([]logger.OutputTarget{logger.Stderr, logger.FileOutput(logFile)})
logger.SetLevel(logger.DEBUG)
}
logger.Debug("User logged in")
logger.Error("Database connection failed")
}
Expected Output to app.log and os.Stderr (approximately):
2023-10-27T10:05:00Z DEBUG User logged in
2023-10-27T10:05:01Z ERROR Database connection failed
(Timestamps will vary)
Explanation:
The logger is configured to output to both Stderr and a file named app.log. All messages from DEBUG upwards are enabled.
Example 3: Fatal Error
package main
import (
"github.com/your_username/logger"
)
func init() {
logger.SetLevel(logger.INFO)
logger.SetOutput(logger.Stdout)
}
func main() {
// ... some application logic ...
if !isValidConfiguration() {
logger.Fatal("Invalid configuration detected. Exiting.")
}
}
func isValidConfiguration() bool {
// Simulate an invalid configuration
return false
}
Expected Output:
2023-10-27T10:10:00Z FATAL Invalid configuration detected. Exiting.
(Timestamp will vary. The program will then exit with status code 1.)
Explanation:
When logger.Fatal is called, it logs the message and then terminates the program.
Constraints
- The logger should be thread-safe. Multiple goroutines should be able to log concurrently without corrupting output or causing panics.
- The
loggerpackage should not depend on any external logging libraries. - File operations should be handled efficiently. Avoid unnecessary file reopening.
- The
Fatalfunction must trigger program termination usingos.Exit(1).
Notes
- Consider using
sync.Mutexfor thread safety. - For timestamps, use
time.Now().UTC().Format("2006-01-02T15:04:05Z")for a standardized format. - Think about how you will manage multiple output targets. A slice of
io.Writermight be a good starting point. - The
FileOutputfunction should accept an*os.Filehandle. You may need to consider how to manage the lifecycle of this file handle if the logger is responsible for closing it. For this challenge, assume the caller is responsible for closing*os.Fileif they pass it to the logger. - You might want to define an
OutputTargettype or an interface to represent different output destinations more cleanly.