1
0
Fork 0
forked from goffee/core

Added Level type - A custom int type with constants DEBUG, INFO, WARNING, ERROR in increasing severity order.

Added `minLevel` field to Logger struct to track the minimum level threshold.
Added `SetLevel()` and `GetLevel()` methods for dynamically changing/reading the log level at runtime.
This commit is contained in:
Zeni Kim 2026-05-06 16:06:25 -05:00
parent 5e389115fc
commit 0da7ea8ab1
3 changed files with 331 additions and 24 deletions

View file

@ -1,4 +1,4 @@
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
// Copyright (c) 2026 Zeni Kim <zenik@smarteching.com>
// Use of this source code is governed by MIT-style
// license that can be found in the LICENSE file.
@ -8,16 +8,68 @@ import (
"io"
"log"
"os"
"strings"
)
// logs file
var logsFile *os.File
// ANSI color codes for terminal output
const (
colorReset = "\033[0m"
colorRed = "\033[31m"
colorYellow = "\033[33m"
colorBlue = "\033[34m"
colorCyan = "\033[36m"
)
// Level represents a log severity level.
type Level int
const (
DEBUG Level = iota
INFO
WARNING
ERROR
)
// String returns the string representation of a log level.
func (l Level) String() string {
switch l {
case DEBUG:
return "debug"
case INFO:
return "info"
case WARNING:
return "warning"
case ERROR:
return "error"
default:
return "unknown"
}
}
// coloredWriter wraps an io.Writer and replaces plain log prefixes with ANSI-colored ones
// in the output stream. The original bytes pass through unmodified to any additional writers.
type coloredWriter struct {
writer io.Writer
plain string
colored string
}
func (w *coloredWriter) Write(p []byte) (n int, err error) {
// Replace the plain prefix with the colored version in the output
colored := strings.Replace(string(p), w.plain, w.colored, 1)
return w.writer.Write([]byte(colored))
}
type Logger struct {
minLevel Level
infoLogger *log.Logger
warningLogger *log.Logger
errorLogger *log.Logger
debugLogger *log.Logger
file *os.File
}
var l *Logger
@ -40,9 +92,36 @@ func (f LogFileDriver) GetTarget() interface{} {
return f.FilePath
}
// levelPrefixes returns the plain and colored (with ANSI escapes) prefix strings for a given level.
func levelPrefixes(level Level) (plain, colored string) {
switch level {
case INFO:
return "info: ", colorBlue + "INFO" + colorReset + ": "
case DEBUG:
return "debug: ", colorCyan + "DEBUG" + colorReset + ": "
case WARNING:
return "warning: ", colorYellow + "WARNING" + colorReset + ": "
case ERROR:
return "error: ", colorRed + "ERROR" + colorReset + ": "
default:
return "info: ", "INFO: "
}
}
// NewLogger creates a new Logger with the given driver and a minimum level of DEBUG (all levels logged).
func NewLogger(driver LogsDriver) *Logger {
stdoutEnabled := os.Getenv("LOG_STDOUT_ENABLE") == "true"
return NewLoggerWithStdout(driver, DEBUG, stdoutEnabled)
}
// NewLoggerWithStdout creates a new Logger with the given driver, minimum log level,
// and optionally writes to stdout in addition to the target file.
// When stdoutEnabled is true, log messages are written to both the target and stdout.
// ANSI color codes are applied to log prefixes in terminal output only.
func NewLoggerWithStdout(driver LogsDriver, minLevel Level, stdoutEnabled bool) *Logger {
if driver.GetTarget() == nil {
l = &Logger{
minLevel: minLevel,
infoLogger: log.New(io.Discard, "info: ", log.LstdFlags),
warningLogger: log.New(io.Discard, "warning: ", log.LstdFlags),
errorLogger: log.New(io.Discard, "error: ", log.LstdFlags),
@ -54,41 +133,103 @@ func NewLogger(driver LogsDriver) *Logger {
if !ok {
panic("something wrong with the file path")
}
logsFile, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
var err error
logsFile, err = os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
if err != nil {
panic(err)
}
l = &Logger{
infoLogger: log.New(logsFile, "info: ", log.LstdFlags),
warningLogger: log.New(logsFile, "warning: ", log.LstdFlags),
errorLogger: log.New(logsFile, "error: ", log.LstdFlags),
debugLogger: log.New(logsFile, "debug: ", log.LstdFlags),
if stdoutEnabled {
// Build each logger with a MultiWriter: the file gets the original plain output,
// stdout gets the output with ANSI-colored prefixes.
infoPlain, infoColored := levelPrefixes(INFO)
debugPlain, debugColored := levelPrefixes(DEBUG)
warnPlain, warnColored := levelPrefixes(WARNING)
errPlain, errColored := levelPrefixes(ERROR)
l = &Logger{
minLevel: minLevel,
infoLogger: log.New(
io.MultiWriter(logsFile, &coloredWriter{writer: os.Stdout, plain: infoPlain, colored: infoColored}),
infoPlain, log.LstdFlags,
),
warningLogger: log.New(
io.MultiWriter(logsFile, &coloredWriter{writer: os.Stdout, plain: warnPlain, colored: warnColored}),
warnPlain, log.LstdFlags,
),
errorLogger: log.New(
io.MultiWriter(logsFile, &coloredWriter{writer: os.Stdout, plain: errPlain, colored: errColored}),
errPlain, log.LstdFlags,
),
debugLogger: log.New(
io.MultiWriter(logsFile, &coloredWriter{writer: os.Stdout, plain: debugPlain, colored: debugColored}),
debugPlain, log.LstdFlags,
),
file: logsFile,
}
} else {
// No stdout: all output goes to file with plain prefixes
l = &Logger{
minLevel: minLevel,
infoLogger: log.New(logsFile, "info: ", log.LstdFlags),
warningLogger: log.New(logsFile, "warning: ", log.LstdFlags),
errorLogger: log.New(logsFile, "error: ", log.LstdFlags),
debugLogger: log.New(logsFile, "debug: ", log.LstdFlags),
file: logsFile,
}
}
return l
}
// NewLoggerWithLevel creates a new Logger with the given driver and minimum log level.
// Messages below the minimum level will be discarded. Stdout output is disabled by default.
func NewLoggerWithLevel(driver LogsDriver, minLevel Level) *Logger {
stdoutEnabled := os.Getenv("LOG_STDOUT_ENABLE") == "true"
return NewLoggerWithStdout(driver, minLevel, stdoutEnabled)
}
// SetLevel sets the minimum log level for the logger.
// Messages below this level will be discarded.
func (l *Logger) SetLevel(level Level) {
l.minLevel = level
}
// GetLevel returns the current minimum log level.
func (l *Logger) GetLevel() Level {
return l.minLevel
}
func ResolveLogger() *Logger {
return l
}
func (l *Logger) Info(msg interface{}) {
l.infoLogger.Println(msg)
if l.minLevel <= INFO {
l.infoLogger.Println(msg)
}
}
func (l *Logger) Debug(msg interface{}) {
l.debugLogger.Println(msg)
if l.minLevel <= DEBUG {
l.debugLogger.Println(msg)
}
}
func (l *Logger) Warning(msg interface{}) {
l.warningLogger.Println(msg)
if l.minLevel <= WARNING {
l.warningLogger.Println(msg)
}
}
func (l *Logger) Error(msg interface{}) {
l.errorLogger.Println(msg)
}
func CloseLogsFile() {
if logsFile != nil {
defer logsFile.Close()
if l.minLevel <= ERROR {
l.errorLogger.Println(msg)
}
}
func (l *Logger) Close() error {
if l.file != nil {
return l.file.Close()
}
return nil
}