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 }