Files
Rogee 7fcabe0225
Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
first commit
2025-09-28 10:05:07 +08:00

635 lines
16 KiB
Go
Raw Permalink 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 logging
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/sirupsen/logrus"
)
// Logger 封装了 logrus.Logger提供结构化日志功能
// 该结构体实现了统一的日志记录接口,支持多种输出格式和级别
type Logger struct {
*logrus.Logger
config *LoggingConfig
entryPool chan *LogEntry
closed bool
}
// LoggingConfig 日志配置
type LoggingConfig struct {
Level string `yaml:"level" json:"level"` // 日志级别: debug, info, warn, error, fatal
Format string `yaml:"format" json:"format"` // 输出格式: json, text
Output string `yaml:"output" json:"output"` // 输出目标: stdout, file, both
FilePath string `yaml:"file_path" json:"file_path"` // 日志文件路径
MaxSize int `yaml:"max_size_mb" json:"max_size_mb"` // 单个文件最大大小(MB)
MaxBackups int `yaml:"max_backups" json:"max_backups"` // 最大备份文件数
MaxAge int `yaml:"max_age_days" json:"max_age_days"` // 最大保存天数
Compress bool `yaml:"compress" json:"compress"` // 是否压缩旧日志
ReportCaller bool `yaml:"report_caller" json:"report_caller"` // 是否记录调用者信息
EnableColor bool `yaml:"enable_color" json:"enable_color"` // 是否启用颜色输出
}
// NewLogger 创建新的日志记录器
// 返回配置好的 Logger 实例
func NewLogger(config *LoggingConfig) (*Logger, error) {
if config == nil {
config = &LoggingConfig{
Level: "info",
Format: "text",
Output: "stdout",
MaxSize: 100,
MaxBackups: 3,
MaxAge: 30,
Compress: true,
ReportCaller: false,
EnableColor: true,
}
}
// 创建 logrus 实例
logger := logrus.New()
// 设置日志级别
level, err := logrus.ParseLevel(config.Level)
if err != nil {
return nil, fmt.Errorf("invalid log level %s: %v", config.Level, err)
}
logger.SetLevel(level)
// 设置格式化器
if err := setFormatter(logger, config); err != nil {
return nil, fmt.Errorf("failed to set formatter: %v", err)
}
// 设置输出目标
if err := setOutput(logger, config); err != nil {
return nil, fmt.Errorf("failed to set output: %v", err)
}
// 设置调用者报告
logger.SetReportCaller(config.ReportCaller)
// 创建 Logger 包装器
wrappedLogger := &Logger{
Logger: logger,
config: config,
entryPool: make(chan *LogEntry, 100),
closed: false,
}
return wrappedLogger, nil
}
// NewDefaultLogger 创建使用默认配置的日志记录器
// 返回使用默认配置的 Logger 实例
func NewDefaultLogger() (*Logger, error) {
config := &LoggingConfig{
Level: "info",
Format: "text",
Output: "stdout",
MaxSize: 100,
MaxBackups: 3,
MaxAge: 30,
Compress: true,
ReportCaller: false,
EnableColor: true,
}
return NewLogger(config)
}
// WithFields 创建带有字段的日志记录器
// 返回新的 logrus.Entry 实例
func (l *Logger) WithFields(fields map[string]interface{}) *logrus.Entry {
return l.Logger.WithFields(fields)
}
// WithField 创建带有单个字段的日志记录器
// 返回新的 logrus.Entry 实例
func (l *Logger) WithField(key string, value interface{}) *logrus.Entry {
return l.Logger.WithField(key, value)
}
// WithError 创建带有错误信息的日志记录器
// 返回新的 logrus.Entry 实例
func (l *Logger) WithError(err error) *logrus.Entry {
return l.Logger.WithError(err)
}
// WithTime 创建带有特定时间的日志记录器
// 返回新的 logrus.Entry 实例
func (l *Logger) WithTime(t time.Time) *logrus.Entry {
return l.Logger.WithTime(t)
}
// Log 使用 LogEntry 记录结构化日志
// 根据 LogEntry 的配置记录日志
func (l *Logger) Log(entry *LogEntry) error {
if entry == nil {
return fmt.Errorf("log entry cannot be nil")
}
// 转换 LogLevel 到 logrus.Level
level, err := l.convertLogLevel(entry.Level)
if err != nil {
return fmt.Errorf("invalid log level: %v", err)
}
// 创建 logrus.Entry
logrusEntry := l.Logger.WithFields(entry.Fields)
if entry.Error != nil {
logrusEntry = logrusEntry.WithError(entry.Error)
}
// 添加性能指标
if entry.Duration > 0 {
logrusEntry = logrusEntry.WithField("duration_ms", entry.Duration.Milliseconds())
}
if entry.MemoryUsage > 0 {
logrusEntry = logrusEntry.WithField("memory_bytes", entry.MemoryUsage)
}
if entry.CPUUsage > 0 {
logrusEntry = logrusEntry.WithField("cpu_usage_percent", entry.CPUUsage)
}
// 添加请求信息
if entry.RequestID != "" {
logrusEntry = logrusEntry.WithField("request_id", entry.RequestID)
}
if entry.UserID != "" {
logrusEntry = logrusEntry.WithField("user_id", entry.UserID)
}
if entry.SessionID != "" {
logrusEntry = logrusEntry.WithField("session_id", entry.SessionID)
}
// 添加操作信息
if entry.Operation != "" {
logrusEntry = logrusEntry.WithField("operation", entry.Operation)
}
if entry.Category != "" {
logrusEntry = logrusEntry.WithField("category", entry.Category)
}
if entry.Subsystem != "" {
logrusEntry = logrusEntry.WithField("subsystem", entry.Subsystem)
}
// 添加网络信息
if entry.IPAddress != "" {
logrusEntry = logrusEntry.WithField("ip_address", entry.IPAddress)
}
if entry.Method != "" {
logrusEntry = logrusEntry.WithField("method", entry.Method)
}
if entry.Path != "" {
logrusEntry = logrusEntry.WithField("path", entry.Path)
}
if entry.StatusCode > 0 {
logrusEntry = logrusEntry.WithField("status_code", entry.StatusCode)
}
// 添加标签
if len(entry.Tags) > 0 {
logrusEntry = logrusEntry.WithField("tags", entry.Tags)
}
// 添加自定义字段
if len(entry.Custom) > 0 {
for k, v := range entry.Custom {
logrusEntry = logrusEntry.WithField(k, v)
}
}
// 根据级别记录日志
switch level {
case logrus.PanicLevel:
logrusEntry.Panic(entry.Message)
case logrus.FatalLevel:
logrusEntry.Fatal(entry.Message)
case logrus.ErrorLevel:
logrusEntry.Error(entry.Message)
case logrus.WarnLevel:
logrusEntry.Warn(entry.Message)
case logrus.InfoLevel:
logrusEntry.Info(entry.Message)
case logrus.DebugLevel:
logrusEntry.Debug(entry.Message)
default:
logrusEntry.Info(entry.Message)
}
return nil
}
// Debug 记录调试级别日志
func (l *Logger) Debug(args ...interface{}) {
l.Logger.Debug(args...)
}
// Debugf 记录格式化调试级别日志
func (l *Logger) Debugf(format string, args ...interface{}) {
l.Logger.Debugf(format, args...)
}
// Info 记录信息级别日志
func (l *Logger) Info(args ...interface{}) {
l.Logger.Info(args...)
}
// Infof 记录格式化信息级别日志
func (l *Logger) Infof(format string, args ...interface{}) {
l.Logger.Infof(format, args...)
}
// Warn 记录警告级别日志
func (l *Logger) Warn(args ...interface{}) {
l.Logger.Warn(args...)
}
// Warnf 记录格式化警告级别日志
func (l *Logger) Warnf(format string, args ...interface{}) {
l.Logger.Warnf(format, args...)
}
// Error 记录错误级别日志
func (l *Logger) Error(args ...interface{}) {
l.Logger.Error(args...)
}
// Errorf 记录格式化错误级别日志
func (l *Logger) Errorf(format string, args ...interface{}) {
l.Logger.Errorf(format, args...)
}
// Fatal 记录致命错误级别日志并退出程序
func (l *Logger) Fatal(args ...interface{}) {
l.Logger.Fatal(args...)
}
// Fatalf 记录格式化致命错误级别日志并退出程序
func (l *Logger) Fatalf(format string, args ...interface{}) {
l.Logger.Fatalf(format, args...)
}
// Panic 记录 panic 级别日志并触发 panic
func (l *Logger) Panic(args ...interface{}) {
l.Logger.Panic(args...)
}
// Panicf 记录格式化 panic 级别日志并触发 panic
func (l *Logger) Panicf(format string, args ...interface{}) {
l.Logger.Panicf(format, args...)
}
// LogOperation 记录操作日志,包含开始和结束时间
// 返回操作结束记录函数
func (l *Logger) LogOperation(operation string, fields map[string]interface{}) func() {
start := time.Now()
entryFields := make(map[string]interface{})
for k, v := range fields {
entryFields[k] = v
}
entryFields["operation"] = operation
entryFields["status"] = "started"
l.WithFields(entryFields).Info("Operation started")
return func() {
duration := time.Since(start)
entryFields["duration_ms"] = duration.Milliseconds()
entryFields["status"] = "completed"
l.WithFields(entryFields).Infof("Operation completed in %v", duration)
}
}
// LogHTTPRequest 记录HTTP请求日志
func (l *Logger) LogHTTPRequest(method, path string, statusCode int, duration time.Duration, fields map[string]interface{}) {
logFields := map[string]interface{}{
"method": method,
"path": path,
"status_code": statusCode,
"duration_ms": duration.Milliseconds(),
}
for k, v := range fields {
logFields[k] = v
}
message := fmt.Sprintf("%s %s - %d", method, path, statusCode)
if statusCode >= 500 {
l.WithFields(logFields).Error(message)
} else if statusCode >= 400 {
l.WithFields(logFields).Warn(message)
} else {
l.WithFields(logFields).Info(message)
}
}
// LogConversion 记录转换操作日志
func (l *Logger) LogConversion(targetFormat, sourceURL string, nodeCount int, duration time.Duration, success bool) {
fields := map[string]interface{}{
"target_format": targetFormat,
"source_url": sourceURL,
"node_count": nodeCount,
"duration_ms": duration.Milliseconds(),
"success": success,
}
message := fmt.Sprintf("Conversion %s -> %s (%d nodes, %v)",
sourceURL, targetFormat, nodeCount, duration)
if success {
l.WithFields(fields).Info(message)
} else {
l.WithFields(fields).Error(message)
}
}
// LogProxyValidation 记录代理验证日志
func (l *Logger) LogProxyValidation(proxyName, proxyType string, isValid bool, pingTime int, err error) {
fields := map[string]interface{}{
"proxy_name": proxyName,
"proxy_type": proxyType,
"is_valid": isValid,
"ping_ms": pingTime,
}
message := fmt.Sprintf("Proxy validation: %s (%s)", proxyName, proxyType)
if !isValid {
if err != nil {
fields["error"] = err.Error()
l.WithFields(fields).Error(message)
} else {
l.WithFields(fields).Warn(message)
}
} else {
l.WithFields(fields).Info(message)
}
}
// GetConfig 获取当前日志配置
// 返回日志配置的副本
func (l *Logger) GetConfig() *LoggingConfig {
if l.config == nil {
return nil
}
// 返回配置的深拷贝
config := *l.config
return &config
}
// UpdateConfig 更新日志配置
// 动态更新日志记录器的配置
func (l *Logger) UpdateConfig(config *LoggingConfig) error {
if config == nil {
return fmt.Errorf("config cannot be nil")
}
// 更新日志级别
if config.Level != "" {
level, err := logrus.ParseLevel(config.Level)
if err != nil {
return fmt.Errorf("invalid log level %s: %v", config.Level, err)
}
l.SetLevel(level)
}
// 更新格式化器
if config.Format != "" {
if err := setFormatter(l.Logger, config); err != nil {
return fmt.Errorf("failed to update formatter: %v", err)
}
}
// 更新输出目标
if config.Output != "" {
if err := setOutput(l.Logger, config); err != nil {
return fmt.Errorf("failed to update output: %v", err)
}
}
// 更新其他配置
l.config = config
l.SetReportCaller(config.ReportCaller)
return nil
}
// Close 关闭日志记录器,清理资源
func (l *Logger) Close() error {
if l.closed {
return nil
}
// 关闭 entry pool
close(l.entryPool)
l.closed = true
return nil
}
// convertLogLevel 转换 LogLevel 到 logrus.Level
func (l *Logger) convertLogLevel(level LogLevel) (logrus.Level, error) {
switch level {
case DEBUG:
return logrus.DebugLevel, nil
case INFO:
return logrus.InfoLevel, nil
case WARN:
return logrus.WarnLevel, nil
case ERROR:
return logrus.ErrorLevel, nil
case FATAL:
return logrus.FatalLevel, nil
default:
return logrus.InfoLevel, fmt.Errorf("unsupported log level: %d", level)
}
}
// setFormatter 设置日志格式化器
func setFormatter(logger *logrus.Logger, config *LoggingConfig) error {
switch strings.ToLower(config.Format) {
case "json":
logger.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
FieldMap: logrus.FieldMap{
logrus.FieldKeyTime: "timestamp",
logrus.FieldKeyLevel: "level",
logrus.FieldKeyMsg: "message",
},
})
case "text":
logger.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2006-01-02 15:04:05",
DisableColors: !config.EnableColor,
ForceColors: config.EnableColor,
})
default:
return fmt.Errorf("unsupported log format: %s", config.Format)
}
return nil
}
// setOutput 设置日志输出目标
func setOutput(logger *logrus.Logger, config *LoggingConfig) error {
switch strings.ToLower(config.Output) {
case "stdout":
logger.SetOutput(os.Stdout)
case "file":
if config.FilePath == "" {
config.FilePath = "logs/subconverter-go.log"
}
if err := ensureLogDirectory(config.FilePath); err != nil {
return fmt.Errorf("failed to create log directory: %v", err)
}
file, err := os.OpenFile(config.FilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return fmt.Errorf("failed to open log file: %v", err)
}
logger.SetOutput(file)
case "both":
// 同时输出到控制台和文件
if config.FilePath == "" {
config.FilePath = "logs/subconverter-go.log"
}
if err := ensureLogDirectory(config.FilePath); err != nil {
return fmt.Errorf("failed to create log directory: %v", err)
}
file, err := os.OpenFile(config.FilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return fmt.Errorf("failed to open log file: %v", err)
}
logger.SetOutput(file) // 暂时只输出到文件,实际项目中可以使用 MultiWriter
default:
return fmt.Errorf("unsupported log output: %s", config.Output)
}
return nil
}
// ensureLogDirectory 确保日志目录存在
func ensureLogDirectory(filePath string) error {
dir := filepath.Dir(filePath)
if dir == "" {
return nil
}
return os.MkdirAll(dir, 0755)
}
// GetCallerInfo 获取调用者信息
func GetCallerInfo() (string, int, string) {
_, file, line, ok := runtime.Caller(2)
if !ok {
return "unknown", 0, "unknown"
}
// 获取函数名
fn := runtime.FuncForPC(runtimeCaller(2))
var funcName string
if fn != nil {
funcName = fn.Name()
// 简化函数名
if idx := strings.LastIndex(funcName, "/"); idx != -1 {
funcName = funcName[idx+1:]
}
if idx := strings.LastIndex(funcName, "."); idx != -1 {
funcName = funcName[idx+1:]
}
}
return filepath.Base(file), line, funcName
}
// runtimeCaller 获取调用者的程序计数器
func runtimeCaller(skip int) uintptr {
pc, _, _, ok := runtime.Caller(skip)
if !ok {
return 0
}
return pc
}
// 全局日志记录器实例
var defaultLogger *Logger
// GetDefaultLogger 获取全局默认日志记录器
// 如果未初始化则创建默认实例
func GetDefaultLogger() *Logger {
if defaultLogger == nil {
logger, err := NewDefaultLogger()
if err != nil {
// 如果创建失败,使用最简单的配置
logrusLogger := logrus.New()
logrusLogger.SetLevel(logrus.InfoLevel)
logrusLogger.SetFormatter(&logrus.TextFormatter{
DisableColors: true,
})
defaultLogger = &Logger{Logger: logrusLogger}
} else {
defaultLogger = logger
}
}
return defaultLogger
}
// SetDefaultLogger 设置全局默认日志记录器
func SetDefaultLogger(logger *Logger) {
defaultLogger = logger
}
// 便捷的全局日志函数
func Debug(args ...interface{}) {
GetDefaultLogger().Debug(args...)
}
func Debugf(format string, args ...interface{}) {
GetDefaultLogger().Debugf(format, args...)
}
func Info(args ...interface{}) {
GetDefaultLogger().Info(args...)
}
func Infof(format string, args ...interface{}) {
GetDefaultLogger().Infof(format, args...)
}
func Warn(args ...interface{}) {
GetDefaultLogger().Warn(args...)
}
func Warnf(format string, args ...interface{}) {
GetDefaultLogger().Warnf(format, args...)
}
func Error(args ...interface{}) {
GetDefaultLogger().Error(args...)
}
func Errorf(format string, args ...interface{}) {
GetDefaultLogger().Errorf(format, args...)
}
func Fatal(args ...interface{}) {
GetDefaultLogger().Fatal(args...)
}
func Fatalf(format string, args ...interface{}) {
GetDefaultLogger().Fatalf(format, args...)
}
func Panic(args ...interface{}) {
GetDefaultLogger().Panic(args...)
}
func Panicf(format string, args ...interface{}) {
GetDefaultLogger().Panicf(format, args...)
}