mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-21 19:06:47 +02:00
refactor: redesign logging system for CLI-friendly output (#561)
* refactor: redesign logging system for CLI-friendly output * refactor: remove ANSI color support from logger * fix: address PR review feedback
This commit is contained in:
+99
-121
@@ -3,180 +3,158 @@ package log
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
stdlog "log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/moond4rk/hackbrowserdata/log/level"
|
||||
)
|
||||
|
||||
// NewLogger creates and returns a new instance of Logger.
|
||||
// Log level is set to DebugLevel by default.
|
||||
// Default level is InfoLevel (Debug messages are suppressed unless SetVerbose is called).
|
||||
func NewLogger(base Base) *Logger {
|
||||
if base == nil {
|
||||
base = newBase(os.Stderr)
|
||||
}
|
||||
return &Logger{base: base, minLevel: level.WarnLevel}
|
||||
return &Logger{base: base, minLevel: InfoLevel}
|
||||
}
|
||||
|
||||
// Logger logs message to io.Writer at various log levels.
|
||||
// Logger logs messages to io.Writer at various log levels.
|
||||
type Logger struct {
|
||||
base Base
|
||||
|
||||
// Minimum log level for this logger.
|
||||
// Message with level lower than this level won't be outputted.
|
||||
minLevel level.Level
|
||||
// Messages with level lower than this won't be outputted.
|
||||
minLevel Level
|
||||
}
|
||||
|
||||
// canLogAt reports whether logger can log at level v.
|
||||
func (l *Logger) canLogAt(v level.Level) bool {
|
||||
return v >= level.Level(atomic.LoadInt32((*int32)(&l.minLevel)))
|
||||
func (l *Logger) canLogAt(v Level) bool {
|
||||
return v >= Level(atomic.LoadInt32((*int32)(&l.minLevel)))
|
||||
}
|
||||
|
||||
// SetLevel sets the logger level.
|
||||
// It panics if v is less than DebugLevel or greater than FatalLevel.
|
||||
func (l *Logger) SetLevel(v level.Level) {
|
||||
if v < level.DebugLevel || v > level.FatalLevel {
|
||||
func (l *Logger) SetLevel(v Level) {
|
||||
if v < DebugLevel || v > FatalLevel {
|
||||
panic("log: invalid log level")
|
||||
}
|
||||
atomic.StoreInt32((*int32)(&l.minLevel), int32(v))
|
||||
}
|
||||
|
||||
// baseCallerSkip is the number of frames to skip in runtime.Caller to reach
|
||||
// the actual call site. Both package-level functions (log.Xxx -> logMsg -> base.Log)
|
||||
// and Logger methods (Logger.Xxx -> logMsg -> base.Log) add exactly one frame
|
||||
// above logMsg, so the skip is the same: base.Log(0) -> logMsg(1) -> caller_wrapper(2) -> caller(3).
|
||||
const baseCallerSkip = 3
|
||||
|
||||
// logMsg is the internal method all public methods delegate to.
|
||||
func (l *Logger) logMsg(lvl Level, msg string) {
|
||||
if !l.canLogAt(lvl) {
|
||||
return
|
||||
}
|
||||
l.base.Log(baseCallerSkip, lvl, msg)
|
||||
}
|
||||
|
||||
func (l *Logger) Debug(args ...any) {
|
||||
if !l.canLogAt(level.DebugLevel) {
|
||||
return
|
||||
}
|
||||
l.base.Debug(args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(args ...any) {
|
||||
if !l.canLogAt(level.WarnLevel) {
|
||||
return
|
||||
}
|
||||
l.base.Warn(args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Error(args ...any) {
|
||||
if !l.canLogAt(level.ErrorLevel) {
|
||||
return
|
||||
}
|
||||
l.base.Error(args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatal(args ...any) {
|
||||
if !l.canLogAt(level.FatalLevel) {
|
||||
return
|
||||
}
|
||||
l.base.Fatal(args...)
|
||||
l.logMsg(DebugLevel, fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Debugf(format string, args ...any) {
|
||||
if !l.canLogAt(level.DebugLevel) {
|
||||
return
|
||||
}
|
||||
l.base.Debug(fmt.Sprintf(format, args...))
|
||||
l.logMsg(DebugLevel, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Info(args ...any) {
|
||||
l.logMsg(InfoLevel, fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Infof(format string, args ...any) {
|
||||
l.logMsg(InfoLevel, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(args ...any) {
|
||||
l.logMsg(WarnLevel, fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Warnf(format string, args ...any) {
|
||||
if !l.canLogAt(level.WarnLevel) {
|
||||
return
|
||||
}
|
||||
l.base.Warn(fmt.Sprintf(format, args...))
|
||||
l.logMsg(WarnLevel, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Error(args ...any) {
|
||||
l.logMsg(ErrorLevel, fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Errorf(format string, args ...any) {
|
||||
if !l.canLogAt(level.ErrorLevel) {
|
||||
return
|
||||
}
|
||||
l.base.Error(fmt.Sprintf(format, args...))
|
||||
l.logMsg(ErrorLevel, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Fatal(args ...any) {
|
||||
l.logMsg(FatalLevel, fmt.Sprint(args...))
|
||||
osExit(1)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatalf(format string, args ...any) {
|
||||
if !l.canLogAt(level.FatalLevel) {
|
||||
return
|
||||
}
|
||||
l.base.Fatal(fmt.Sprintf(format, args...))
|
||||
l.logMsg(FatalLevel, fmt.Sprintf(format, args...))
|
||||
osExit(1)
|
||||
}
|
||||
|
||||
// Base is the interface that underlies the Logger. It receives the caller
|
||||
// skip count, log level, and formatted message.
|
||||
type Base interface {
|
||||
Debug(args ...any)
|
||||
Warn(args ...any)
|
||||
Error(args ...any)
|
||||
Fatal(args ...any)
|
||||
Log(callerSkip int, lvl Level, msg string)
|
||||
}
|
||||
|
||||
// baseLogger is a wrapper object around log.Logger from the standard library.
|
||||
// It supports logging at various log levels.
|
||||
// baseLogger writes formatted log messages to an io.Writer.
|
||||
// Output format:
|
||||
//
|
||||
// [DBG] file.go:42: message
|
||||
// [INF] message
|
||||
// [WRN] message
|
||||
// [ERR] message
|
||||
// [FTL] message
|
||||
type baseLogger struct {
|
||||
*stdlog.Logger
|
||||
callDepth int
|
||||
out io.Writer
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func newBase(out io.Writer) *baseLogger {
|
||||
prefix := "[hack-browser-data] "
|
||||
base := &baseLogger{
|
||||
Logger: stdlog.New(out, prefix, stdlog.Lshortfile),
|
||||
}
|
||||
base.callDepth = base.calculateCallDepth()
|
||||
return base
|
||||
}
|
||||
|
||||
// calculateCallDepth returns the call depth for the logger.
|
||||
func (l *baseLogger) calculateCallDepth() int {
|
||||
return l.getCallDepth()
|
||||
}
|
||||
|
||||
func (l *baseLogger) prefixPrint(prefix string, args ...any) {
|
||||
args = append([]any{prefix}, args...)
|
||||
if err := l.Output(l.callDepth, fmt.Sprint(args...)); err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "log output error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *baseLogger) getCallDepth() int {
|
||||
defaultCallDepth := 2
|
||||
pcs := make([]uintptr, 10)
|
||||
n := runtime.Callers(defaultCallDepth, pcs)
|
||||
frames := runtime.CallersFrames(pcs[:n])
|
||||
for i := 0; i < n; i++ {
|
||||
frame, more := frames.Next()
|
||||
if !l.isLoggerPackage(frame.Function) {
|
||||
return i + 1
|
||||
}
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
return defaultCallDepth
|
||||
}
|
||||
|
||||
func (l *baseLogger) isLoggerPackage(funcName string) bool {
|
||||
const loggerFuncName = "hackbrowserdata/log"
|
||||
return strings.Contains(funcName, loggerFuncName)
|
||||
}
|
||||
|
||||
// Debug logs a message at Debug level.
|
||||
func (l *baseLogger) Debug(args ...any) {
|
||||
l.prefixPrint("DEBUG: ", args...)
|
||||
}
|
||||
|
||||
// Warn logs a message at Warning level.
|
||||
func (l *baseLogger) Warn(args ...any) {
|
||||
l.prefixPrint("WARN: ", args...)
|
||||
}
|
||||
|
||||
// Error logs a message at Error level.
|
||||
func (l *baseLogger) Error(args ...any) {
|
||||
l.prefixPrint("ERROR: ", args...)
|
||||
return &baseLogger{out: out}
|
||||
}
|
||||
|
||||
var osExit = os.Exit
|
||||
|
||||
// Fatal logs a message at Fatal level
|
||||
// and process will exit with status set to 1.
|
||||
func (l *baseLogger) Fatal(args ...any) {
|
||||
l.prefixPrint("FATAL: ", args...)
|
||||
osExit(1)
|
||||
// continuation is the indent for multi-line messages.
|
||||
// Width matches "[DBG] " (6 chars).
|
||||
const continuation = " "
|
||||
|
||||
func (l *baseLogger) Log(callerSkip int, lvl Level, msg string) {
|
||||
msg = strings.TrimRight(msg, "\n")
|
||||
if strings.Contains(msg, "\n") {
|
||||
msg = strings.ReplaceAll(msg, "\n", "\n"+continuation)
|
||||
}
|
||||
|
||||
label := l.formatLabel(lvl)
|
||||
var line string
|
||||
if lvl == DebugLevel {
|
||||
_, file, num, ok := runtime.Caller(callerSkip)
|
||||
if ok {
|
||||
file = filepath.Base(file)
|
||||
} else {
|
||||
file = "???"
|
||||
num = 0
|
||||
}
|
||||
line = fmt.Sprintf("%s %s:%d: %s\n", label, file, num, msg)
|
||||
} else {
|
||||
line = fmt.Sprintf("%s %s\n", label, msg)
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
_, _ = io.WriteString(l.out, line)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// formatLabel returns the bracketed level label, e.g. "[DBG]".
|
||||
func (l *baseLogger) formatLabel(lvl Level) string {
|
||||
return "[" + lvl.String() + "]"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user