Go Interface Satisfaction: The Polymorphic Logger
Go's interfaces are a powerful mechanism for achieving polymorphism and decoupling. This challenge will test your understanding of how to define and implement interfaces, and how concrete types can satisfy them implicitly. You'll build a system where different data sources can be logged through a unified interface.
Problem Description
Your task is to create a flexible logging system in Go. You'll define an EventLogger interface that specifies a Log(message string) method. Then, you will implement this interface with at least two different concrete types, each representing a distinct way of handling log messages (e.g., writing to the console, writing to a file, sending to a remote server). Finally, you'll demonstrate how to use these different logger implementations interchangeably through the EventLogger interface.
Key Requirements:
- Define
EventLoggerInterface: Create an interface namedEventLoggerwith a single method:Log(message string). - Implement Concrete Loggers:
- ConsoleLogger: Implement
EventLoggerto print log messages to standard output (os.Stdout). Each message should be prefixed with[CONSOLE]. - FileLogger: Implement
EventLoggerto write log messages to a file. You'll need to handle file creation and appending. Each message should be prefixed with[FILE]. - (Optional) NetworkLogger: Implement
EventLoggerto simulate sending log messages over a network (e.g., by printing to standard output with a[NETWORK]prefix, indicating a simulated network transmission).
- ConsoleLogger: Implement
- Demonstrate Polymorphism: Create a function that accepts an
EventLoggerinterface. This function should be able to receive any concrete type that satisfies theEventLoggerinterface and call itsLogmethod. - Cleanup: Ensure any opened files are properly closed.
Expected Behavior:
When the Log method is called on an EventLogger instance, the underlying concrete implementation should execute its specific logging logic. The calling code should not need to know the specific type of logger it's interacting with.
Edge Cases:
- File Operations: Handle potential errors during file opening, writing, and closing.
- Empty Messages: Ensure the system handles empty log messages gracefully.
Examples
Example 1: Console Logging
// Assume a function `processData` that takes an EventLogger
// and a ConsoleLogger is passed in.
// Calling processData with a ConsoleLogger:
// logger.Log("User logged in")
Output:
[CONSOLE] User logged in
Example 2: File Logging
// Assume a function `processData` that takes an EventLogger
// and a FileLogger (configured to write to "app.log") is passed in.
// Calling processData with a FileLogger:
// logger.Log("Data saved successfully")
// Content of "app.log" after execution:
[FILE] Data saved successfully
Example 3: Mixed Logging and Function Usage
// Assume a function `sendNotification` that takes an EventLogger
// and a message.
// Create a ConsoleLogger and a FileLogger
consoleLogger := NewConsoleLogger()
fileLogger := NewFileLogger("system.log")
// Use the sendNotification function with different loggers
sendNotification(consoleLogger, "System startup complete")
sendNotification(fileLogger, "Configuration loaded")
// Helper function (you'll need to implement this)
func sendNotification(logger EventLogger, msg string) {
logger.Log(msg)
}
// Expected Output (to console):
[CONSOLE] System startup complete
// Expected Content of "system.log":
[FILE] Configuration loaded
Constraints
- Log messages are strings and will not exceed 1024 characters.
- File paths for
FileLoggerwill be valid and not exceed 255 characters. - Error handling for I/O operations is expected.
- The solution should be efficient, though no strict performance benchmarks are enforced for this problem.
Notes
- Remember that in Go, interfaces are satisfied implicitly. You do not need to explicitly declare that a type implements an interface.
- Consider using
deferfor resource cleanup (like closing files). - Think about how you will manage the file handle for the
FileLogger. - The
NewConsoleLoggerandNewFileLoggerfunctions are examples of constructor patterns you might use. You can name them as you see fit.