feat: add logger finally...
All checks were successful
Go Tests / Run Tests (1.24.2) (push) Successful in 9m48s
All checks were successful
Go Tests / Run Tests (1.24.2) (push) Successful in 9m48s
This commit is contained in:
parent
139c33533e
commit
a8c72fccfa
267
pkg/common/log/logger.go
Normal file
267
pkg/common/log/logger.go
Normal file
@ -0,0 +1,267 @@
|
||||
// Package log provides a common logging interface for Kevo components.
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Level represents the logging level
|
||||
type Level int
|
||||
|
||||
const (
|
||||
// LevelDebug level for detailed troubleshooting information
|
||||
LevelDebug Level = iota
|
||||
// LevelInfo level for general operational information
|
||||
LevelInfo
|
||||
// LevelWarn level for potentially harmful situations
|
||||
LevelWarn
|
||||
// LevelError level for error events that might still allow the application to continue
|
||||
LevelError
|
||||
// LevelFatal level for severe error events that will lead the application to abort
|
||||
LevelFatal
|
||||
)
|
||||
|
||||
// String returns the string representation of the log level
|
||||
func (l Level) String() string {
|
||||
switch l {
|
||||
case LevelDebug:
|
||||
return "DEBUG"
|
||||
case LevelInfo:
|
||||
return "INFO"
|
||||
case LevelWarn:
|
||||
return "WARN"
|
||||
case LevelError:
|
||||
return "ERROR"
|
||||
case LevelFatal:
|
||||
return "FATAL"
|
||||
default:
|
||||
return fmt.Sprintf("LEVEL(%d)", l)
|
||||
}
|
||||
}
|
||||
|
||||
// Logger interface defines the methods for logging at different levels
|
||||
type Logger interface {
|
||||
// Debug logs a debug-level message
|
||||
Debug(msg string, args ...interface{})
|
||||
// Info logs an info-level message
|
||||
Info(msg string, args ...interface{})
|
||||
// Warn logs a warning-level message
|
||||
Warn(msg string, args ...interface{})
|
||||
// Error logs an error-level message
|
||||
Error(msg string, args ...interface{})
|
||||
// Fatal logs a fatal-level message and then calls os.Exit(1)
|
||||
Fatal(msg string, args ...interface{})
|
||||
// WithFields returns a new logger with the given fields added to the context
|
||||
WithFields(fields map[string]interface{}) Logger
|
||||
// WithField returns a new logger with the given field added to the context
|
||||
WithField(key string, value interface{}) Logger
|
||||
// GetLevel returns the current logging level
|
||||
GetLevel() Level
|
||||
// SetLevel sets the logging level
|
||||
SetLevel(level Level)
|
||||
}
|
||||
|
||||
// StandardLogger implements the Logger interface with a standard output format
|
||||
type StandardLogger struct {
|
||||
mu sync.Mutex
|
||||
level Level
|
||||
out io.Writer
|
||||
fields map[string]interface{}
|
||||
}
|
||||
|
||||
// NewStandardLogger creates a new StandardLogger with the given options
|
||||
func NewStandardLogger(options ...LoggerOption) *StandardLogger {
|
||||
logger := &StandardLogger{
|
||||
level: LevelInfo, // Default level
|
||||
out: os.Stdout,
|
||||
fields: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
// Apply options
|
||||
for _, option := range options {
|
||||
option(logger)
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
// LoggerOption is a function that configures a StandardLogger
|
||||
type LoggerOption func(*StandardLogger)
|
||||
|
||||
// WithLevel sets the logging level
|
||||
func WithLevel(level Level) LoggerOption {
|
||||
return func(l *StandardLogger) {
|
||||
l.level = level
|
||||
}
|
||||
}
|
||||
|
||||
// WithOutput sets the output writer
|
||||
func WithOutput(out io.Writer) LoggerOption {
|
||||
return func(l *StandardLogger) {
|
||||
l.out = out
|
||||
}
|
||||
}
|
||||
|
||||
// WithInitialFields sets initial fields for the logger
|
||||
func WithInitialFields(fields map[string]interface{}) LoggerOption {
|
||||
return func(l *StandardLogger) {
|
||||
for k, v := range fields {
|
||||
l.fields[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log logs a message at the specified level
|
||||
func (l *StandardLogger) log(level Level, msg string, args ...interface{}) {
|
||||
if level < l.level {
|
||||
return
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
// Format the message
|
||||
formattedMsg := msg
|
||||
if len(args) > 0 {
|
||||
formattedMsg = fmt.Sprintf(msg, args...)
|
||||
}
|
||||
|
||||
// Format timestamp
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05.000")
|
||||
|
||||
// Format fields
|
||||
fieldsStr := ""
|
||||
if len(l.fields) > 0 {
|
||||
for k, v := range l.fields {
|
||||
fieldsStr += fmt.Sprintf(" %s=%v", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Write the log entry
|
||||
fmt.Fprintf(l.out, "[%s] [%s]%s %s\n", timestamp, level.String(), fieldsStr, formattedMsg)
|
||||
|
||||
// Exit if fatal
|
||||
if level == LevelFatal {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Debug logs a debug-level message
|
||||
func (l *StandardLogger) Debug(msg string, args ...interface{}) {
|
||||
l.log(LevelDebug, msg, args...)
|
||||
}
|
||||
|
||||
// Info logs an info-level message
|
||||
func (l *StandardLogger) Info(msg string, args ...interface{}) {
|
||||
l.log(LevelInfo, msg, args...)
|
||||
}
|
||||
|
||||
// Warn logs a warning-level message
|
||||
func (l *StandardLogger) Warn(msg string, args ...interface{}) {
|
||||
l.log(LevelWarn, msg, args...)
|
||||
}
|
||||
|
||||
// Error logs an error-level message
|
||||
func (l *StandardLogger) Error(msg string, args ...interface{}) {
|
||||
l.log(LevelError, msg, args...)
|
||||
}
|
||||
|
||||
// Fatal logs a fatal-level message and then calls os.Exit(1)
|
||||
func (l *StandardLogger) Fatal(msg string, args ...interface{}) {
|
||||
l.log(LevelFatal, msg, args...)
|
||||
}
|
||||
|
||||
// WithFields returns a new logger with the given fields added to the context
|
||||
func (l *StandardLogger) WithFields(fields map[string]interface{}) Logger {
|
||||
newLogger := &StandardLogger{
|
||||
level: l.level,
|
||||
out: l.out,
|
||||
fields: make(map[string]interface{}, len(l.fields)+len(fields)),
|
||||
}
|
||||
|
||||
// Copy existing fields
|
||||
for k, v := range l.fields {
|
||||
newLogger.fields[k] = v
|
||||
}
|
||||
|
||||
// Add new fields
|
||||
for k, v := range fields {
|
||||
newLogger.fields[k] = v
|
||||
}
|
||||
|
||||
return newLogger
|
||||
}
|
||||
|
||||
// WithField returns a new logger with the given field added to the context
|
||||
func (l *StandardLogger) WithField(key string, value interface{}) Logger {
|
||||
return l.WithFields(map[string]interface{}{key: value})
|
||||
}
|
||||
|
||||
// GetLevel returns the current logging level
|
||||
func (l *StandardLogger) GetLevel() Level {
|
||||
return l.level
|
||||
}
|
||||
|
||||
// SetLevel sets the logging level
|
||||
func (l *StandardLogger) SetLevel(level Level) {
|
||||
l.level = level
|
||||
}
|
||||
|
||||
// Default logger instance
|
||||
var defaultLogger = NewStandardLogger()
|
||||
|
||||
// SetDefaultLogger sets the default logger instance
|
||||
func SetDefaultLogger(logger *StandardLogger) {
|
||||
defaultLogger = logger
|
||||
}
|
||||
|
||||
// GetDefaultLogger returns the default logger instance
|
||||
func GetDefaultLogger() *StandardLogger {
|
||||
return defaultLogger
|
||||
}
|
||||
|
||||
// These functions use the default logger
|
||||
|
||||
// Debug logs a debug-level message to the default logger
|
||||
func Debug(msg string, args ...interface{}) {
|
||||
defaultLogger.Debug(msg, args...)
|
||||
}
|
||||
|
||||
// Info logs an info-level message to the default logger
|
||||
func Info(msg string, args ...interface{}) {
|
||||
defaultLogger.Info(msg, args...)
|
||||
}
|
||||
|
||||
// Warn logs a warning-level message to the default logger
|
||||
func Warn(msg string, args ...interface{}) {
|
||||
defaultLogger.Warn(msg, args...)
|
||||
}
|
||||
|
||||
// Error logs an error-level message to the default logger
|
||||
func Error(msg string, args ...interface{}) {
|
||||
defaultLogger.Error(msg, args...)
|
||||
}
|
||||
|
||||
// Fatal logs a fatal-level message to the default logger and then calls os.Exit(1)
|
||||
func Fatal(msg string, args ...interface{}) {
|
||||
defaultLogger.Fatal(msg, args...)
|
||||
}
|
||||
|
||||
// WithFields returns a new logger with the given fields added to the context
|
||||
func WithFields(fields map[string]interface{}) Logger {
|
||||
return defaultLogger.WithFields(fields)
|
||||
}
|
||||
|
||||
// WithField returns a new logger with the given field added to the context
|
||||
func WithField(key string, value interface{}) Logger {
|
||||
return defaultLogger.WithField(key, value)
|
||||
}
|
||||
|
||||
// SetLevel sets the logging level of the default logger
|
||||
func SetLevel(level Level) {
|
||||
defaultLogger.SetLevel(level)
|
||||
}
|
132
pkg/common/log/logger_test.go
Normal file
132
pkg/common/log/logger_test.go
Normal file
@ -0,0 +1,132 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStandardLogger(t *testing.T) {
|
||||
// Create a buffer to capture output
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Create a logger with the buffer as output
|
||||
logger := NewStandardLogger(
|
||||
WithOutput(&buf),
|
||||
WithLevel(LevelDebug),
|
||||
)
|
||||
|
||||
// Test debug level
|
||||
logger.Debug("This is a debug message")
|
||||
if !strings.Contains(buf.String(), "[DEBUG]") || !strings.Contains(buf.String(), "This is a debug message") {
|
||||
t.Errorf("Debug logging failed, got: %s", buf.String())
|
||||
}
|
||||
buf.Reset()
|
||||
|
||||
// Test info level
|
||||
logger.Info("This is an info message")
|
||||
if !strings.Contains(buf.String(), "[INFO]") || !strings.Contains(buf.String(), "This is an info message") {
|
||||
t.Errorf("Info logging failed, got: %s", buf.String())
|
||||
}
|
||||
buf.Reset()
|
||||
|
||||
// Test warn level
|
||||
logger.Warn("This is a warning message")
|
||||
if !strings.Contains(buf.String(), "[WARN]") || !strings.Contains(buf.String(), "This is a warning message") {
|
||||
t.Errorf("Warn logging failed, got: %s", buf.String())
|
||||
}
|
||||
buf.Reset()
|
||||
|
||||
// Test error level
|
||||
logger.Error("This is an error message")
|
||||
if !strings.Contains(buf.String(), "[ERROR]") || !strings.Contains(buf.String(), "This is an error message") {
|
||||
t.Errorf("Error logging failed, got: %s", buf.String())
|
||||
}
|
||||
buf.Reset()
|
||||
|
||||
// Test with fields
|
||||
loggerWithFields := logger.WithFields(map[string]interface{}{
|
||||
"component": "test",
|
||||
"count": 123,
|
||||
})
|
||||
loggerWithFields.Info("Message with fields")
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "[INFO]") ||
|
||||
!strings.Contains(output, "Message with fields") ||
|
||||
!strings.Contains(output, "component=test") ||
|
||||
!strings.Contains(output, "count=123") {
|
||||
t.Errorf("Logging with fields failed, got: %s", output)
|
||||
}
|
||||
buf.Reset()
|
||||
|
||||
// Test with a single field
|
||||
loggerWithField := logger.WithField("module", "logger")
|
||||
loggerWithField.Info("Message with a field")
|
||||
output = buf.String()
|
||||
if !strings.Contains(output, "[INFO]") ||
|
||||
!strings.Contains(output, "Message with a field") ||
|
||||
!strings.Contains(output, "module=logger") {
|
||||
t.Errorf("Logging with a field failed, got: %s", output)
|
||||
}
|
||||
buf.Reset()
|
||||
|
||||
// Test level filtering
|
||||
logger.SetLevel(LevelError)
|
||||
logger.Debug("This debug message should not appear")
|
||||
logger.Info("This info message should not appear")
|
||||
logger.Warn("This warning message should not appear")
|
||||
logger.Error("This error message should appear")
|
||||
output = buf.String()
|
||||
if strings.Contains(output, "should not appear") ||
|
||||
!strings.Contains(output, "This error message should appear") {
|
||||
t.Errorf("Level filtering failed, got: %s", output)
|
||||
}
|
||||
buf.Reset()
|
||||
|
||||
// Test formatted messages
|
||||
logger.SetLevel(LevelInfo)
|
||||
logger.Info("Formatted %s with %d params", "message", 2)
|
||||
if !strings.Contains(buf.String(), "Formatted message with 2 params") {
|
||||
t.Errorf("Formatted message failed, got: %s", buf.String())
|
||||
}
|
||||
buf.Reset()
|
||||
|
||||
// Test GetLevel
|
||||
if logger.GetLevel() != LevelInfo {
|
||||
t.Errorf("GetLevel failed, expected LevelInfo, got: %v", logger.GetLevel())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultLogger(t *testing.T) {
|
||||
// Save original default logger
|
||||
originalLogger := defaultLogger
|
||||
defer func() {
|
||||
defaultLogger = originalLogger
|
||||
}()
|
||||
|
||||
// Create a buffer to capture output
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Set a new default logger
|
||||
SetDefaultLogger(NewStandardLogger(
|
||||
WithOutput(&buf),
|
||||
WithLevel(LevelInfo),
|
||||
))
|
||||
|
||||
// Test global functions
|
||||
Info("Global info message")
|
||||
if !strings.Contains(buf.String(), "[INFO]") || !strings.Contains(buf.String(), "Global info message") {
|
||||
t.Errorf("Global info logging failed, got: %s", buf.String())
|
||||
}
|
||||
buf.Reset()
|
||||
|
||||
// Test global with fields
|
||||
WithField("global", true).Info("Global with field")
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "[INFO]") ||
|
||||
!strings.Contains(output, "Global with field") ||
|
||||
!strings.Contains(output, "global=true") {
|
||||
t.Errorf("Global logging with field failed, got: %s", output)
|
||||
}
|
||||
buf.Reset()
|
||||
}
|
Loading…
Reference in New Issue
Block a user