Add logger package with performance benchmarks

This commit is contained in:
loveuer
2026-01-17 17:27:33 +08:00
parent f7160ce416
commit 507a67e455
7 changed files with 1105 additions and 0 deletions

278
logger/logger.go Normal file
View File

@@ -0,0 +1,278 @@
package logger
import (
"context"
"fmt"
"io"
"os"
"runtime"
"sync"
"time"
)
type Level int8
const (
DEBUG Level = iota
INFO
WARN
ERROR
FATAL
PANIC
)
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"
case PANIC:
return "PANIC"
default:
return "UNKNOWN"
}
}
type Format int8
const (
TEXT Format = iota
JSON
)
type Output int8
const (
Stdout Output = iota
Stderr
)
type Entry struct {
Time time.Time
Level Level
Message string
Fields map[string]interface{}
Caller string
}
type Logger struct {
opts options
mu sync.Mutex
writer io.Writer
formatter Formatter
}
func New(opts ...Option) *Logger {
o := defaultOptions()
for _, opt := range opts {
opt(&o)
}
var writer io.Writer
switch o.output {
case Stdout:
writer = os.Stdout
case Stderr:
writer = os.Stderr
default:
writer = os.Stdout
}
if o.outputFile != "" {
f, err := os.OpenFile(o.outputFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err == nil {
writer = f
}
}
return &Logger{
opts: o,
writer: writer,
formatter: newFormatter(o.format),
}
}
func (l *Logger) log(level Level, msg string, args ...interface{}) {
if level < l.opts.level {
return
}
entry := &Entry{
Time: time.Now(),
Level: level,
Message: fmt.Sprintf(msg, args...),
Fields: make(map[string]interface{}),
}
if l.opts.caller {
entry.Caller = getCaller(3)
}
l.mu.Lock()
defer l.mu.Unlock()
l.formatter.Format(l.writer, entry)
}
func (l *Logger) logCtx(level Level, ctx context.Context, msg string, args ...interface{}) {
if level < l.opts.level {
return
}
entry := &Entry{
Time: time.Now(),
Level: level,
Message: fmt.Sprintf(msg, args...),
Fields: make(map[string]interface{}),
}
if ctx != nil {
if traceID := ctx.Value(l.opts.fieldKey); traceID != nil {
entry.Fields[l.opts.fieldKey] = traceID
}
}
if l.opts.caller {
entry.Caller = getCaller(3)
}
l.mu.Lock()
defer l.mu.Unlock()
l.formatter.Format(l.writer, entry)
}
func (l *Logger) logField(level Level, message string, keyValues ...interface{}) {
if level < l.opts.level {
return
}
entry := &Entry{
Time: time.Now(),
Level: level,
Message: message,
Fields: parseFields(keyValues),
}
if l.opts.caller {
entry.Caller = getCaller(3)
}
l.mu.Lock()
defer l.mu.Unlock()
l.formatter.Format(l.writer, entry)
}
func (l *Logger) Debug(format string, args ...interface{}) {
l.log(DEBUG, format, args...)
}
func (l *Logger) Info(format string, args ...interface{}) {
l.log(INFO, format, args...)
}
func (l *Logger) Warn(format string, args ...interface{}) {
l.log(WARN, format, args...)
}
func (l *Logger) Error(format string, args ...interface{}) {
l.log(ERROR, format, args...)
}
func (l *Logger) Fatal(format string, args ...interface{}) {
l.log(FATAL, format, args...)
os.Exit(1)
}
func (l *Logger) Panic(format string, args ...interface{}) {
l.log(PANIC, format, args...)
l.mu.Lock()
defer l.mu.Unlock()
panic(fmt.Sprintf(format, args...))
}
func (l *Logger) DebugCtx(ctx context.Context, format string, args ...interface{}) {
l.logCtx(DEBUG, ctx, format, args...)
}
func (l *Logger) InfoCtx(ctx context.Context, format string, args ...interface{}) {
l.logCtx(INFO, ctx, format, args...)
}
func (l *Logger) WarnCtx(ctx context.Context, format string, args ...interface{}) {
l.logCtx(WARN, ctx, format, args...)
}
func (l *Logger) ErrorCtx(ctx context.Context, format string, args ...interface{}) {
l.logCtx(ERROR, ctx, format, args...)
}
func (l *Logger) FatalCtx(ctx context.Context, format string, args ...interface{}) {
l.logCtx(FATAL, ctx, format, args...)
os.Exit(1)
}
func (l *Logger) PanicCtx(ctx context.Context, format string, args ...interface{}) {
l.logCtx(PANIC, ctx, format, args...)
l.mu.Lock()
defer l.mu.Unlock()
panic(fmt.Sprintf(format, args...))
}
func (l *Logger) DebugField(message string, keyValues ...interface{}) {
l.logField(DEBUG, message, keyValues...)
}
func (l *Logger) InfoField(message string, keyValues ...interface{}) {
l.logField(INFO, message, keyValues...)
}
func (l *Logger) WarnField(message string, keyValues ...interface{}) {
l.logField(WARN, message, keyValues...)
}
func (l *Logger) ErrorField(message string, keyValues ...interface{}) {
l.logField(ERROR, message, keyValues...)
}
func (l *Logger) FatalField(message string, keyValues ...interface{}) {
l.logField(FATAL, message, keyValues...)
os.Exit(1)
}
func (l *Logger) PanicField(message string, keyValues ...interface{}) {
l.logField(PANIC, message, keyValues...)
panic(message)
}
func (l *Logger) Close() error {
if f, ok := l.writer.(*os.File); ok {
return f.Close()
}
return nil
}
func getCaller(skip int) string {
_, file, line, ok := runtime.Caller(skip)
if !ok {
return "?:?"
}
return fmt.Sprintf("%s:%d", file, line)
}
func parseFields(keyValues []interface{}) map[string]interface{} {
fields := make(map[string]interface{})
for i := 0; i+1 < len(keyValues); i += 2 {
key, ok := keyValues[i].(string)
if !ok {
continue
}
fields[key] = keyValues[i+1]
}
return fields
}