Files
sunhpc-go/internal/log/logger.go
2026-02-14 05:36:00 +08:00

345 lines
7.1 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 log
import (
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"sync"
"time"
"github.com/fatih/color"
)
// 日志级别
type Level int
const (
DebugLevel Level = iota
InfoLevel
WarnLevel
ErrorLevel
FatalLevel
)
// 级别名称
var levelNames = map[Level]string{
DebugLevel: "DEBUG",
InfoLevel: "INFO",
WarnLevel: "WARN",
ErrorLevel: "ERROR",
FatalLevel: "FATAL",
}
// 级别简写
var levelShort = map[Level]string{
DebugLevel: "[d]",
InfoLevel: "[i]",
WarnLevel: "[w]",
ErrorLevel: "[e]",
FatalLevel: "[f]",
}
// 级别颜色
var levelColor = map[Level]func(format string, a ...interface{}) string{
DebugLevel: color.CyanString, // 青色
InfoLevel: color.GreenString, // 绿色
WarnLevel: color.YellowString, // 黄色
ErrorLevel: color.RedString, // 红色
FatalLevel: color.MagentaString, // 品红
}
// Logger 日志器结构体
type Logger struct {
mu sync.Mutex
consoleOut io.Writer // 控制台输出
fileOut io.Writer // 文件输出
minLevel Level // 最小输出级别
showColor bool // 是否显示颜色
showCaller bool // 是否显示调用者信息
callerSkip int // 调用者跳过的层级
timeFormat string // 时间格式
}
// 默认日志器实例
var defaultLogger *Logger
const (
defaultTimeFormat = "2006-01-02 15:04:05"
logFile = "/var/log/sunhpc/sunhpc.log"
)
// Init 初始化日志系统
func Init(verbose bool) {
// 确保日志目录存在
if err := os.MkdirAll(filepath.Dir(logFile), 0755); err != nil {
fmt.Fprintf(os.Stderr, "创建日志目录失败: %v\n", err)
os.Exit(1)
}
// 打开日志文件
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "打开日志文件失败: %v\n", err)
os.Exit(1)
}
// 控制台输出
consoleOut := os.Stdout
// 创建日志器
defaultLogger = &Logger{
consoleOut: consoleOut,
fileOut: file,
minLevel: InfoLevel,
showColor: true,
showCaller: false,
callerSkip: 2,
timeFormat: defaultTimeFormat,
}
// 详细模式下显示调试信息
if verbose {
defaultLogger.minLevel = DebugLevel
defaultLogger.showCaller = true
}
// 初始化颜色支持
if runtime.GOOS == "windows" {
color.NoColor = false
}
}
// log 核心日志输出方法
func (l *Logger) log(level Level, format string, args ...interface{}) {
if level < l.minLevel {
return
}
l.mu.Lock()
defer l.mu.Unlock()
// 生成时间戳
timestamp := time.Now().Format(l.timeFormat)
// 获取调用者信息
caller := ""
if l.showCaller {
_, file, line, ok := runtime.Caller(l.callerSkip)
if ok {
// 只保留文件名和行号
file = filepath.Base(file)
caller = fmt.Sprintf(" %s:%d", file, line)
}
}
// 格式化消息
var message string
if format == "" {
message = fmt.Sprint(args...)
} else {
message = fmt.Sprintf(format, args...)
}
// ---- 控制台输出(带颜色和简写)----
if l.consoleOut != nil {
// 获取级别简写
shortPrefix := levelShort[level]
// 构建控制台行
var consoleLine string
if l.showColor {
// 带颜色输出 - 简写有颜色,时间戳灰色
colorFunc := levelColor[level]
consoleLine = fmt.Sprintf("%s %s %s",
color.HiBlackString(timestamp), // 时间戳灰色
colorFunc(shortPrefix), // 级别简写彩色
message) // 消息普通颜色
} else {
// 不带颜色输出
consoleLine = fmt.Sprintf("%s %s %s",
timestamp,
shortPrefix,
message)
}
// 添加调用者信息(灰色)
if caller != "" {
if l.showColor {
consoleLine += fmt.Sprintf(" %s", color.HiBlackString(caller))
} else {
consoleLine += fmt.Sprintf(" %s", caller)
}
}
fmt.Fprintln(l.consoleOut, consoleLine)
}
// ---- 文件输出(完整格式)----
if l.fileOut != nil {
// 获取级别全名
levelName := levelNames[level]
// 文件使用完整格式:时间 [级别] 消息 调用者
fileLine := fmt.Sprintf("%s [%s] %s%s\n",
timestamp,
levelName,
message,
caller)
fmt.Fprint(l.fileOut, fileLine)
}
// 致命错误退出程序
if level == FatalLevel {
os.Exit(1)
}
}
// 全局日志函数
// Debug 调试日志
func Debug(args ...interface{}) {
if defaultLogger != nil {
defaultLogger.log(DebugLevel, "", args...)
}
}
// Debugf 格式化调试日志
func Debugf(format string, args ...interface{}) {
if defaultLogger != nil {
defaultLogger.log(DebugLevel, format, args...)
}
}
// Info 信息日志
func Info(args ...interface{}) {
if defaultLogger != nil {
defaultLogger.log(InfoLevel, "", args...)
}
}
// Infof 格式化信息日志
func Infof(format string, args ...interface{}) {
if defaultLogger != nil {
defaultLogger.log(InfoLevel, format, args...)
}
}
// Warn 警告日志
func Warn(args ...interface{}) {
if defaultLogger != nil {
defaultLogger.log(WarnLevel, "", args...)
}
}
// Warnf 格式化警告日志
func Warnf(format string, args ...interface{}) {
if defaultLogger != nil {
defaultLogger.log(WarnLevel, format, args...)
}
}
// Error 错误日志
func Error(args ...interface{}) {
if defaultLogger != nil {
defaultLogger.log(ErrorLevel, "", args...)
}
}
// Errorf 格式化错误日志
func Errorf(format string, args ...interface{}) {
if defaultLogger != nil {
defaultLogger.log(ErrorLevel, format, args...)
}
}
// Fatal 致命错误日志,输出后退出程序
func Fatal(args ...interface{}) {
if defaultLogger != nil {
defaultLogger.log(FatalLevel, "", args...)
}
}
// Fatalf 格式化致命错误日志,输出后退出程序
func Fatalf(format string, args ...interface{}) {
if defaultLogger != nil {
defaultLogger.log(FatalLevel, format, args...)
}
}
// Writer 返回一个 io.Writer可将子命令的输出写入日志Debug级别
func Writer() *io.PipeWriter {
r, w := io.Pipe()
go func() {
buf := make([]byte, 1024)
for {
n, err := r.Read(buf)
if n > 0 {
Debug(string(buf[:n]))
}
if err != nil {
break
}
}
}()
return w
}
// SetLevel 设置日志级别
func SetLevel(level Level) {
if defaultLogger != nil {
defaultLogger.mu.Lock()
defer defaultLogger.mu.Unlock()
defaultLogger.minLevel = level
}
}
// EnableColor 启用/禁用颜色输出
func EnableColor(enable bool) {
if defaultLogger != nil {
defaultLogger.mu.Lock()
defer defaultLogger.mu.Unlock()
defaultLogger.showColor = enable
}
}
// EnableCaller 启用/禁用调用者信息
func EnableCaller(enable bool) {
if defaultLogger != nil {
defaultLogger.mu.Lock()
defer defaultLogger.mu.Unlock()
defaultLogger.showCaller = enable
}
}
// SetTimeFormat 设置时间格式
func SetTimeFormat(format string) {
if defaultLogger != nil {
defaultLogger.mu.Lock()
defer defaultLogger.mu.Unlock()
defaultLogger.timeFormat = format
}
}
// Sync 同步日志文件
func Sync() {
if defaultLogger != nil && defaultLogger.fileOut != nil {
if f, ok := defaultLogger.fileOut.(*os.File); ok {
f.Sync()
}
}
}
// Close 关闭日志文件
func Close() error {
if defaultLogger != nil && defaultLogger.fileOut != nil {
if f, ok := defaultLogger.fileOut.(*os.File); ok {
return f.Close()
}
}
return nil
}