Files
ngzz-mc/internal/logx/log.go
2026-09-19 14:21:08 +08:00

150 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package logx 分级日志debug/info/warn/error输出 stderr 与可选文件。
package logx
import (
"fmt"
"io"
"os"
"sync"
"time"
)
// Level 日志级别。
type Level int
const (
// LevelDebug 调试。
LevelDebug Level = iota
// LevelInfo 常规信息。
LevelInfo
// LevelWarn 警告。
LevelWarn
// LevelError 错误。
LevelError
)
const defaultRing = 8
// Logger 线程安全分级日志器。
type Logger struct {
mu sync.Mutex
level Level
w io.Writer
closer io.Closer
ring []string
ringCap int
}
// New 创建日志器file 为空仅输出 stderr否则 stderr + 文件。
func New(file string, level Level) (*Logger, error) {
return newLogger(file, level, true, defaultRing)
}
// NewFile 仅写文件GUI 客户端:无 CMD构建与发布.md §1。file 为空则丢弃。
func NewFile(file string, level Level) (*Logger, error) {
return newLogger(file, level, false, defaultRing)
}
func newLogger(file string, level Level, stderr bool, ringCap int) (*Logger, error) {
var writers []io.Writer
if stderr {
writers = append(writers, os.Stderr)
}
if file != "" {
if err := os.MkdirAll(pathDir(file), 0o755); err != nil {
return nil, fmt.Errorf("logx.New mkdir: %w", err)
}
f, err := os.OpenFile(file, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
if err != nil {
return nil, fmt.Errorf("logx.New %s: %w", file, err)
}
writers = append(writers, f)
}
w := io.Writer(io.Discard)
switch len(writers) {
case 1:
w = writers[0]
case 2:
w = io.MultiWriter(writers[0], writers[1])
}
if ringCap < 1 {
ringCap = defaultRing
}
var closer io.Closer
if file != "" {
if f, ok := writers[len(writers)-1].(*os.File); ok {
closer = f
}
}
return &Logger{level: level, w: w, closer: closer, ringCap: ringCap}, nil
}
// Close 关闭日志文件(若有)。
func (l *Logger) Close() error {
if l == nil || l.closer == nil {
return nil
}
return l.closer.Close()
}
// Recent 返回最近若干行(旧→新,供 Loading 左下角)。
func (l *Logger) Recent() []string {
if l == nil {
return nil
}
l.mu.Lock()
defer l.mu.Unlock()
out := make([]string, len(l.ring))
copy(out, l.ring)
return out
}
// pathDir 返回路径的目录部分("a.log" → ".")。
func pathDir(p string) string {
for i := len(p) - 1; i >= 0; i-- {
if p[i] == '/' || p[i] == '\\' {
if i == 0 {
return p[:1]
}
return p[:i]
}
}
return "."
}
func (l *Logger) logf(lv Level, tag, format string, args ...any) {
if l == nil || lv < l.level {
return
}
msg := fmt.Sprintf(format, args...)
line := fmt.Sprintf("[%s] [%s] %s", time.Now().Format("15:04:05.000"), tag, msg)
l.mu.Lock()
defer l.mu.Unlock()
fmt.Fprintln(l.w, line)
l.pushRingLocked(msg)
}
func (l *Logger) pushRingLocked(s string) {
if l.ringCap <= 0 {
return
}
if len(l.ring) < l.ringCap {
l.ring = append(l.ring, s)
return
}
copy(l.ring, l.ring[1:])
l.ring[l.ringCap-1] = s
}
// Debugf 调试输出。
func (l *Logger) Debugf(format string, args ...any) { l.logf(LevelDebug, "DEBUG", format, args...) }
// Infof 常规输出。
func (l *Logger) Infof(format string, args ...any) { l.logf(LevelInfo, "INFO", format, args...) }
// Warnf 警告输出。
func (l *Logger) Warnf(format string, args ...any) { l.logf(LevelWarn, "WARN", format, args...) }
// Errorf 错误输出。
func (l *Logger) Errorf(format string, args ...any) { l.logf(LevelError, "ERROR", format, args...) }