package logger import ( "context" "fmt" "io" "os" "runtime" "strings" "time" "github.com/google/uuid" ) // Level represents a log level type Level int const ( // DEBUG level DEBUG Level = iota // INFO level INFO // WARN level WARN // ERROR level ERROR // FATAL level FATAL ) // String returns the string representation of the log level func (l Level) String() string { switch l { case DEBUG: return "DEBUG" case INFO: return "INFO" case WARN: return "WARN" case ERROR: return "ERROR" case FATAL: return "FATAL" default: return "UNKNOWN" } } // Logger represents a logger type Logger struct { level Level output io.Writer } // contextKey is a type for context keys type contextKey string // requestIDKey is the key for request ID in context const requestIDKey = contextKey("request_id") // New creates a new logger func New(level Level, output io.Writer) *Logger { if output == nil { output = os.Stdout } return &Logger{ level: level, output: output, } } // WithLevel creates a new logger with the specified level func (l *Logger) WithLevel(level Level) *Logger { return &Logger{ level: level, output: l.output, } } // WithOutput creates a new logger with the specified output func (l *Logger) WithOutput(output io.Writer) *Logger { return &Logger{ level: l.level, output: output, } } // log logs a message with the specified level func (l *Logger) log(ctx context.Context, level Level, format string, args ...interface{}) { if level < l.level { return } // Get request ID from context requestID := getRequestID(ctx) // Get caller information _, file, line, ok := runtime.Caller(2) if !ok { file = "unknown" line = 0 } // Extract just the filename if idx := strings.LastIndex(file, "/"); idx >= 0 { file = file[idx+1:] } // Format message message := fmt.Sprintf(format, args...) // Format log entry timestamp := time.Now().Format(time.RFC3339) logEntry := fmt.Sprintf("%s [%s] %s:%d [%s] %s\n", timestamp, level.String(), file, line, requestID, message) // Write log entry _, _ = l.output.Write([]byte(logEntry)) // Exit if fatal if level == FATAL { os.Exit(1) } } // Debug logs a debug message func (l *Logger) Debug(ctx context.Context, format string, args ...interface{}) { l.log(ctx, DEBUG, format, args...) } // Info logs an info message func (l *Logger) Info(ctx context.Context, format string, args ...interface{}) { l.log(ctx, INFO, format, args...) } // Warn logs a warning message func (l *Logger) Warn(ctx context.Context, format string, args ...interface{}) { l.log(ctx, WARN, format, args...) } // Error logs an error message func (l *Logger) Error(ctx context.Context, format string, args ...interface{}) { l.log(ctx, ERROR, format, args...) } // Fatal logs a fatal message and exits func (l *Logger) Fatal(ctx context.Context, format string, args ...interface{}) { l.log(ctx, FATAL, format, args...) } // WithRequestID adds a request ID to the context func WithRequestID(ctx context.Context) context.Context { requestID := uuid.New().String() return context.WithValue(ctx, requestIDKey, requestID) } // GetRequestID gets the request ID from the context func GetRequestID(ctx context.Context) string { return getRequestID(ctx) } // getRequestID gets the request ID from the context func getRequestID(ctx context.Context) string { if ctx == nil { return "-" } requestID, ok := ctx.Value(requestIDKey).(string) if !ok { return "-" } return requestID } // Default logger var defaultLogger = New(INFO, os.Stdout) // SetDefaultLogger sets the default logger func SetDefaultLogger(logger *Logger) { defaultLogger = logger } // Debug logs a debug message using the default logger func Debug(ctx context.Context, format string, args ...interface{}) { defaultLogger.Debug(ctx, format, args...) } // Info logs an info message using the default logger func Info(ctx context.Context, format string, args ...interface{}) { defaultLogger.Info(ctx, format, args...) } // Warn logs a warning message using the default logger func Warn(ctx context.Context, format string, args ...interface{}) { defaultLogger.Warn(ctx, format, args...) } // Error logs an error message using the default logger func Error(ctx context.Context, format string, args ...interface{}) { defaultLogger.Error(ctx, format, args...) } // Fatal logs a fatal message and exits using the default logger func Fatal(ctx context.Context, format string, args ...interface{}) { defaultLogger.Fatal(ctx, format, args...) }