Hone logo
Hone
Problems

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:
    • DEBUG
    • INFO
    • WARN
    • ERROR
    • FATAL (should also terminate the program)
  • Output Destinations: Allow logs to be written to:
    • Standard Output (os.Stdout)
    • Standard Error (os.Stderr)
    • A file
  • 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).
  • Fatal Level: Implement a Fatal logging function that logs the message and then calls os.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 logger package should not depend on any external logging libraries.
  • File operations should be handled efficiently. Avoid unnecessary file reopening.
  • The Fatal function must trigger program termination using os.Exit(1).

Notes

  • Consider using sync.Mutex for 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.Writer might be a good starting point.
  • The FileOutput function should accept an *os.File handle. 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.File if they pass it to the logger.
  • You might want to define an OutputTarget type or an interface to represent different output destinations more cleanly.
Loading editor...
go