2026-04-13 00:38:50 +08:00
|
|
|
|
package log
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"bufio"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"io"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"runtime"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"sync"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ErrorRecord 错误历史记录条目
|
|
|
|
|
|
type ErrorRecord struct {
|
|
|
|
|
|
Err error
|
|
|
|
|
|
Msg string
|
|
|
|
|
|
Time time.Time
|
|
|
|
|
|
Caller string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Logger 日志核心结构体,封装 zerolog
|
|
|
|
|
|
type Logger struct {
|
|
|
|
|
|
zl zerolog.Logger
|
|
|
|
|
|
logLevel int
|
|
|
|
|
|
errors []ErrorRecord
|
|
|
|
|
|
errIdx int // 环形缓冲写入位置
|
|
|
|
|
|
errCount int // 实际写入总数(用于判断缓冲是否满)
|
|
|
|
|
|
errorsMu sync.RWMutex
|
|
|
|
|
|
maxErrors int
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewLogger 创建日志实例
|
|
|
|
|
|
// logLevel: 0=仅 error,>=1=全部
|
|
|
|
|
|
// logFile: 文件路径模板(如 "logs/20060102.txt"),空则不写文件
|
|
|
|
|
|
// maxErrors: 错误历史最大条数,0 则不记录
|
|
|
|
|
|
func NewLogger(logLevel int, logFile string, maxErrors int) *Logger {
|
|
|
|
|
|
zerolog.CallerMarshalFunc = callerMarshalFunc
|
|
|
|
|
|
zerolog.TimeFieldFormat = "2006-01-02 15:04:05"
|
|
|
|
|
|
|
|
|
|
|
|
var level zerolog.Level
|
|
|
|
|
|
if logLevel == 0 {
|
|
|
|
|
|
level = zerolog.ErrorLevel
|
|
|
|
|
|
} else {
|
|
|
|
|
|
level = zerolog.DebugLevel
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
consoleWriter := zerolog.ConsoleWriter{
|
2026-04-14 14:07:59 +08:00
|
|
|
|
Out: os.Stderr,
|
|
|
|
|
|
TimeFormat: "2006-01-02 15:04:05",
|
|
|
|
|
|
NoColor: false,
|
2026-04-13 00:38:50 +08:00
|
|
|
|
FormatLevel: formatLevelColor,
|
|
|
|
|
|
FormatCaller: func(i interface{}) string {
|
|
|
|
|
|
if i == nil {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
return fmt.Sprintf("[%s]", i)
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var writers []io.Writer
|
|
|
|
|
|
writers = append(writers, consoleWriter)
|
|
|
|
|
|
|
|
|
|
|
|
if logFile != "" {
|
|
|
|
|
|
fw := &TemplateFileWriter{pathTemplate: logFile}
|
|
|
|
|
|
writers = append(writers, fw)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
multi := zerolog.MultiLevelWriter(writers...)
|
|
|
|
|
|
zl := zerolog.New(multi).
|
|
|
|
|
|
Level(level).
|
|
|
|
|
|
With().
|
|
|
|
|
|
Timestamp().
|
|
|
|
|
|
Caller().
|
|
|
|
|
|
Logger()
|
|
|
|
|
|
|
|
|
|
|
|
l := &Logger{
|
|
|
|
|
|
zl: zl,
|
|
|
|
|
|
logLevel: logLevel,
|
|
|
|
|
|
maxErrors: maxErrors,
|
|
|
|
|
|
}
|
|
|
|
|
|
if maxErrors > 0 {
|
|
|
|
|
|
l.errors = make([]ErrorRecord, maxErrors)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return l
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-14 14:07:59 +08:00
|
|
|
|
// NewLoggerNoCaller 创建不带自动 Caller 字段的日志实例(用于访问日志等 caller 无意义的场景)
|
|
|
|
|
|
func NewLoggerNoCaller(logLevel int, logFile string, maxErrors int) *Logger {
|
|
|
|
|
|
zerolog.CallerMarshalFunc = callerMarshalFunc
|
|
|
|
|
|
zerolog.TimeFieldFormat = "2006-01-02 15:04:05"
|
|
|
|
|
|
|
|
|
|
|
|
var level zerolog.Level
|
|
|
|
|
|
if logLevel == 0 {
|
|
|
|
|
|
level = zerolog.ErrorLevel
|
|
|
|
|
|
} else {
|
|
|
|
|
|
level = zerolog.DebugLevel
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
consoleWriter := zerolog.ConsoleWriter{
|
|
|
|
|
|
Out: os.Stderr,
|
|
|
|
|
|
TimeFormat: "2006-01-02 15:04:05",
|
|
|
|
|
|
NoColor: false,
|
|
|
|
|
|
FormatLevel: formatLevelColor,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var writers []io.Writer
|
|
|
|
|
|
writers = append(writers, consoleWriter)
|
|
|
|
|
|
|
|
|
|
|
|
if logFile != "" {
|
|
|
|
|
|
fw := &TemplateFileWriter{pathTemplate: logFile}
|
|
|
|
|
|
writers = append(writers, fw)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
multi := zerolog.MultiLevelWriter(writers...)
|
|
|
|
|
|
zl := zerolog.New(multi).
|
|
|
|
|
|
Level(level).
|
|
|
|
|
|
With().
|
|
|
|
|
|
Timestamp().
|
|
|
|
|
|
Logger()
|
|
|
|
|
|
|
|
|
|
|
|
l := &Logger{
|
|
|
|
|
|
zl: zl,
|
|
|
|
|
|
logLevel: logLevel,
|
|
|
|
|
|
maxErrors: maxErrors,
|
|
|
|
|
|
}
|
|
|
|
|
|
if maxErrors > 0 {
|
|
|
|
|
|
l.errors = make([]ErrorRecord, maxErrors)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return l
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 00:38:50 +08:00
|
|
|
|
// NewTestLogger 创建用于测试的静默 Logger(输出到 io.Discard)
|
|
|
|
|
|
func NewTestLogger() *Logger {
|
|
|
|
|
|
zl := zerolog.New(io.Discard).Level(zerolog.Disabled)
|
|
|
|
|
|
return &Logger{zl: zl, logLevel: 0}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SetOutput 设置日志输出(用于测试等场景)
|
|
|
|
|
|
func (l *Logger) SetOutput(w io.Writer) {
|
|
|
|
|
|
if w == io.Discard {
|
|
|
|
|
|
l.zl = zerolog.New(w).Level(zerolog.Disabled)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
consoleWriter := zerolog.ConsoleWriter{
|
2026-04-14 14:07:59 +08:00
|
|
|
|
Out: w,
|
|
|
|
|
|
TimeFormat: "2006-01-02 15:04:05",
|
|
|
|
|
|
NoColor: true,
|
2026-04-13 00:38:50 +08:00
|
|
|
|
FormatLevel: formatLevelPlain,
|
|
|
|
|
|
FormatCaller: func(i interface{}) string {
|
|
|
|
|
|
if i == nil {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
return fmt.Sprintf("[%s]", i)
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
l.zl = zerolog.New(consoleWriter).
|
|
|
|
|
|
With().
|
|
|
|
|
|
Timestamp().
|
|
|
|
|
|
Caller().
|
|
|
|
|
|
Logger()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetLevel 获取当前日志等级
|
|
|
|
|
|
func (l *Logger) GetLevel() int {
|
|
|
|
|
|
return l.logLevel
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- 链式调用 API ---
|
|
|
|
|
|
// 以下方法兼容两种调用风格:
|
|
|
|
|
|
// 无参数:返回 *zerolog.Event 用于链式调用,如 l.Error().Str("k","v").Msg("...")
|
|
|
|
|
|
// 有参数:直接拼接并打印日志(logrus 兼容),返回 no-op Event
|
|
|
|
|
|
|
|
|
|
|
|
func (l *Logger) Debug(args ...interface{}) *zerolog.Event {
|
|
|
|
|
|
if len(args) > 0 {
|
|
|
|
|
|
l.zl.Debug().Msg(fmt.Sprint(args...))
|
|
|
|
|
|
nop := zerolog.Nop()
|
|
|
|
|
|
return nop.Debug()
|
|
|
|
|
|
}
|
|
|
|
|
|
return l.zl.Debug()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (l *Logger) Info(args ...interface{}) *zerolog.Event {
|
|
|
|
|
|
if len(args) > 0 {
|
|
|
|
|
|
l.zl.Info().Msg(fmt.Sprint(args...))
|
|
|
|
|
|
nop := zerolog.Nop()
|
|
|
|
|
|
return nop.Info()
|
|
|
|
|
|
}
|
|
|
|
|
|
return l.zl.Info()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (l *Logger) Warn(args ...interface{}) *zerolog.Event {
|
|
|
|
|
|
if len(args) > 0 {
|
|
|
|
|
|
l.zl.Warn().Msg(fmt.Sprint(args...))
|
|
|
|
|
|
nop := zerolog.Nop()
|
|
|
|
|
|
return nop.Warn()
|
|
|
|
|
|
}
|
|
|
|
|
|
return l.zl.Warn()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (l *Logger) Error(args ...interface{}) *zerolog.Event {
|
|
|
|
|
|
if len(args) > 0 {
|
|
|
|
|
|
msg := fmt.Sprint(args...)
|
|
|
|
|
|
if l.maxErrors > 0 {
|
|
|
|
|
|
l.recordError(msg, nil)
|
|
|
|
|
|
}
|
|
|
|
|
|
l.zl.Error().Msg(msg)
|
|
|
|
|
|
nop := zerolog.Nop()
|
|
|
|
|
|
return nop.Error()
|
|
|
|
|
|
}
|
|
|
|
|
|
if l.maxErrors > 0 {
|
|
|
|
|
|
l.recordError("", nil)
|
|
|
|
|
|
}
|
|
|
|
|
|
return l.zl.Error()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RecordError 手动记录一条错误到历史(用于需要指定详情的场景)
|
|
|
|
|
|
func (l *Logger) RecordError(err error, msg string, caller string) {
|
|
|
|
|
|
if l.maxErrors <= 0 {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
l.errorsMu.Lock()
|
|
|
|
|
|
defer l.errorsMu.Unlock()
|
|
|
|
|
|
l.errors[l.errIdx] = ErrorRecord{
|
|
|
|
|
|
Err: err,
|
|
|
|
|
|
Msg: msg,
|
|
|
|
|
|
Time: time.Now(),
|
|
|
|
|
|
Caller: caller,
|
|
|
|
|
|
}
|
|
|
|
|
|
l.errIdx = (l.errIdx + 1) % l.maxErrors
|
|
|
|
|
|
l.errCount++
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (l *Logger) recordError(msg string, err error) {
|
|
|
|
|
|
_, file, line, ok := runtime.Caller(2)
|
|
|
|
|
|
caller := ""
|
|
|
|
|
|
if ok {
|
|
|
|
|
|
caller = formatCaller(file, line)
|
|
|
|
|
|
}
|
|
|
|
|
|
l.RecordError(err, msg, caller)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetRecentErrors 获取最近 N 条错误(不传则返回全部已存储的)
|
|
|
|
|
|
func (l *Logger) GetRecentErrors(n ...int) []ErrorRecord {
|
|
|
|
|
|
if l.maxErrors <= 0 {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
l.errorsMu.RLock()
|
|
|
|
|
|
defer l.errorsMu.RUnlock()
|
|
|
|
|
|
|
|
|
|
|
|
total := l.errCount
|
|
|
|
|
|
if total > l.maxErrors {
|
|
|
|
|
|
total = l.maxErrors
|
|
|
|
|
|
}
|
|
|
|
|
|
if total == 0 {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
want := total
|
|
|
|
|
|
if len(n) > 0 && n[0] > 0 && n[0] < want {
|
|
|
|
|
|
want = n[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result := make([]ErrorRecord, 0, want)
|
|
|
|
|
|
// 从最新的往前读
|
|
|
|
|
|
for i := 0; i < want; i++ {
|
|
|
|
|
|
idx := (l.errIdx - 1 - i + l.maxErrors) % l.maxErrors
|
|
|
|
|
|
if l.errors[idx].Time.IsZero() {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
result = append(result, l.errors[idx])
|
|
|
|
|
|
}
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- 格式化快捷方法 ---
|
|
|
|
|
|
|
|
|
|
|
|
func (l *Logger) Debugf(format string, v ...interface{}) {
|
|
|
|
|
|
l.zl.Debug().Msgf(format, v...)
|
|
|
|
|
|
}
|
|
|
|
|
|
func (l *Logger) Infof(format string, v ...interface{}) {
|
|
|
|
|
|
l.zl.Info().Msgf(format, v...)
|
|
|
|
|
|
}
|
|
|
|
|
|
func (l *Logger) Warnf(format string, v ...interface{}) {
|
|
|
|
|
|
l.zl.Warn().Msgf(format, v...)
|
|
|
|
|
|
}
|
|
|
|
|
|
func (l *Logger) Errorf(format string, v ...interface{}) {
|
|
|
|
|
|
if l.maxErrors > 0 {
|
|
|
|
|
|
l.recordError(fmt.Sprintf(format, v...), nil)
|
|
|
|
|
|
}
|
|
|
|
|
|
l.zl.Error().Msgf(format, v...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- 日志级别颜色 ---
|
|
|
|
|
|
|
|
|
|
|
|
func formatLevelColor(i interface{}) string {
|
|
|
|
|
|
level := strings.ToUpper(fmt.Sprintf("%s", i))
|
|
|
|
|
|
switch level {
|
|
|
|
|
|
case "DEBUG":
|
|
|
|
|
|
return fmt.Sprintf("\x1b[36m|%s|\x1b[0m", level) // cyan
|
|
|
|
|
|
case "INFO":
|
|
|
|
|
|
return fmt.Sprintf("\x1b[32m|%s|\x1b[0m", level) // green
|
|
|
|
|
|
case "WARN":
|
|
|
|
|
|
return fmt.Sprintf("\x1b[33m|%s|\x1b[0m", level) // yellow
|
|
|
|
|
|
case "ERROR":
|
|
|
|
|
|
return fmt.Sprintf("\x1b[31m|%s|\x1b[0m", level) // red
|
|
|
|
|
|
case "FATAL":
|
|
|
|
|
|
return fmt.Sprintf("\x1b[35m|%s|\x1b[0m", level) // magenta
|
|
|
|
|
|
default:
|
|
|
|
|
|
return fmt.Sprintf("|%s|", level)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func formatLevelPlain(i interface{}) string {
|
|
|
|
|
|
return fmt.Sprintf("|%s|", strings.ToUpper(fmt.Sprintf("%s", i)))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- TemplateFileWriter ---
|
|
|
|
|
|
|
|
|
|
|
|
// TemplateFileWriter 按时间模板切换文件路径的 Writer
|
|
|
|
|
|
type TemplateFileWriter struct {
|
|
|
|
|
|
pathTemplate string
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
|
currentPath string
|
|
|
|
|
|
file *os.File
|
|
|
|
|
|
writer *bufio.Writer
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (w *TemplateFileWriter) Write(p []byte) (n int, err error) {
|
|
|
|
|
|
w.mu.Lock()
|
|
|
|
|
|
defer w.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
path := time.Now().Format(w.pathTemplate)
|
|
|
|
|
|
if path != w.currentPath {
|
|
|
|
|
|
if w.writer != nil {
|
|
|
|
|
|
_ = w.writer.Flush()
|
|
|
|
|
|
}
|
|
|
|
|
|
if w.file != nil {
|
|
|
|
|
|
_ = w.file.Close()
|
|
|
|
|
|
}
|
|
|
|
|
|
_ = os.MkdirAll(filepath.Dir(path), 0755)
|
|
|
|
|
|
w.file, err = os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return 0, err
|
|
|
|
|
|
}
|
|
|
|
|
|
w.writer = bufio.NewWriterSize(w.file, 4096)
|
|
|
|
|
|
w.currentPath = path
|
|
|
|
|
|
}
|
|
|
|
|
|
n, err = w.writer.Write(p)
|
|
|
|
|
|
_ = w.writer.Flush()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- 调用者智能过滤 ---
|
|
|
|
|
|
|
|
|
|
|
|
func callerMarshalFunc(_ uintptr, file string, line int) string {
|
|
|
|
|
|
return findCaller(file, line)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func findCaller(origFile string, origLine int) string {
|
2026-04-13 06:45:16 +08:00
|
|
|
|
var lastInfraFile string
|
|
|
|
|
|
var lastInfraLine int
|
2026-04-14 14:07:59 +08:00
|
|
|
|
var primaryCaller string
|
|
|
|
|
|
var appLayerCaller string
|
2026-04-13 00:38:50 +08:00
|
|
|
|
|
|
|
|
|
|
for i := 1; i < 20; i++ {
|
|
|
|
|
|
_, file, line, ok := runtime.Caller(i)
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
shortFile := shortenPath(file)
|
|
|
|
|
|
|
2026-04-13 06:45:16 +08:00
|
|
|
|
if isInfrastructureFile(shortFile) {
|
|
|
|
|
|
lastInfraFile = shortFile
|
|
|
|
|
|
lastInfraLine = line
|
2026-04-13 00:38:50 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-14 14:07:59 +08:00
|
|
|
|
if primaryCaller == "" {
|
|
|
|
|
|
primaryCaller = fmt.Sprintf("%s:%d", shortFile, line)
|
|
|
|
|
|
}
|
|
|
|
|
|
if appLayerCaller == "" && !strings.Contains(strings.ToLower(file), "hotime") {
|
|
|
|
|
|
appLayerCaller = fmt.Sprintf("%s:%d", shortFile, line)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result := ""
|
|
|
|
|
|
if primaryCaller != "" {
|
|
|
|
|
|
result = primaryCaller
|
|
|
|
|
|
} else if lastInfraFile != "" {
|
|
|
|
|
|
result = fmt.Sprintf("%s:%d", lastInfraFile, lastInfraLine)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
result = fmt.Sprintf("%s:%d", shortenPath(origFile), origLine)
|
2026-04-13 00:38:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-14 14:07:59 +08:00
|
|
|
|
if appLayerCaller != "" && result != appLayerCaller {
|
|
|
|
|
|
result += " <- " + appLayerCaller
|
2026-04-13 00:38:50 +08:00
|
|
|
|
}
|
2026-04-14 14:07:59 +08:00
|
|
|
|
|
|
|
|
|
|
return result
|
2026-04-13 00:38:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func formatCaller(file string, line int) string {
|
|
|
|
|
|
return fmt.Sprintf("%s:%d", shortenPath(file), line)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func shortenPath(file string) string {
|
|
|
|
|
|
n := 0
|
|
|
|
|
|
for i := len(file) - 1; i > 0; i-- {
|
|
|
|
|
|
if file[i] == '/' || file[i] == '\\' {
|
|
|
|
|
|
n++
|
|
|
|
|
|
if n >= 2 {
|
|
|
|
|
|
return file[i+1:]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return file
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 06:45:16 +08:00
|
|
|
|
// isInfrastructureFile 判断文件是否为基础设施(日志/DB/缓存/通用工具)管线,
|
|
|
|
|
|
// 这些文件在调用栈中会被跳过,以显示真正的业务调用者。
|
2026-04-14 14:07:59 +08:00
|
|
|
|
// context.go 是 Display/View 的纯中间层,也作为基础设施跳过。
|
|
|
|
|
|
// application.go、code/makecode.go 等保留,会作为有意义的调用者返回。
|
2026-04-13 06:45:16 +08:00
|
|
|
|
func isInfrastructureFile(file string) bool {
|
2026-04-13 00:38:50 +08:00
|
|
|
|
if strings.HasPrefix(file, "zerolog/") || strings.HasPrefix(file, "zerolog@") {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
if strings.HasPrefix(file, "logrus/") || strings.HasPrefix(file, "logrus@") {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
if strings.HasPrefix(file, "runtime/") {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
2026-04-13 06:45:16 +08:00
|
|
|
|
if strings.HasPrefix(file, "log/") {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
2026-04-13 00:38:50 +08:00
|
|
|
|
|
2026-04-13 06:45:16 +08:00
|
|
|
|
infraPrefixes := []string{"db/", "cache/", "common/", "dri/"}
|
|
|
|
|
|
for _, prefix := range infraPrefixes {
|
|
|
|
|
|
if strings.HasPrefix(file, prefix) {
|
2026-04-13 00:38:50 +08:00
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 06:45:16 +08:00
|
|
|
|
lowerFile := strings.ToLower(file)
|
|
|
|
|
|
if strings.Contains(lowerFile, "hotime") {
|
|
|
|
|
|
infraDirs := []string{"db/", "cache/", "common/", "log/", "dri/"}
|
|
|
|
|
|
for _, dir := range infraDirs {
|
|
|
|
|
|
if strings.Contains(file, dir) {
|
|
|
|
|
|
return true
|
2026-04-13 00:38:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-14 14:07:59 +08:00
|
|
|
|
if strings.HasSuffix(file, "/context.go") || strings.HasSuffix(file, "\\context.go") {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
2026-04-13 00:38:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|