first commit
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
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
This commit is contained in:
522
internal/logging/entry.go
Normal file
522
internal/logging/entry.go
Normal file
@@ -0,0 +1,522 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LogLevel 定义日志级别
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
DEBUG LogLevel = iota
|
||||
INFO
|
||||
WARN
|
||||
ERROR
|
||||
FATAL
|
||||
)
|
||||
|
||||
// LogEntry 表示日志条目
|
||||
// 该结构体包含结构化日志的所有字段
|
||||
type LogEntry struct {
|
||||
// 基本字段
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Level LogLevel `json:"level"`
|
||||
Message string `json:"message"`
|
||||
LevelString string `json:"level_string"`
|
||||
|
||||
// 位置信息
|
||||
File string `json:"file,omitempty"`
|
||||
Line int `json:"line,omitempty"`
|
||||
Function string `json:"function,omitempty"`
|
||||
Stacktrace string `json:"stacktrace,omitempty"`
|
||||
|
||||
// 上下文信息
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
SessionID string `json:"session_id,omitempty"`
|
||||
IPAddress string `json:"ip_address,omitempty"`
|
||||
UserAgent string `json:"user_agent,omitempty"`
|
||||
|
||||
// 分类信息
|
||||
Category string `json:"category,omitempty"`
|
||||
Subsystem string `json:"subsystem,omitempty"`
|
||||
Component string `json:"component,omitempty"`
|
||||
Operation string `json:"operation,omitempty"`
|
||||
|
||||
// 数据字段
|
||||
Fields map[string]interface{} `json:"fields,omitempty"`
|
||||
Metrics map[string]interface{} `json:"metrics,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
|
||||
// 错误信息
|
||||
Error error `json:"error,omitempty"`
|
||||
ErrorType string `json:"error_type,omitempty"`
|
||||
ErrorDetail string `json:"error_detail,omitempty"`
|
||||
|
||||
// 性能信息
|
||||
Duration time.Duration `json:"duration,omitempty"`
|
||||
MemoryUsage int64 `json:"memory_usage,omitempty"`
|
||||
CPUUsage float64 `json:"cpu_usage,omitempty"`
|
||||
|
||||
// 请求信息
|
||||
Method string `json:"method,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
StatusCode int `json:"status_code,omitempty"`
|
||||
ResponseSize int64 `json:"response_size,omitempty"`
|
||||
|
||||
// 自定义属性
|
||||
Custom map[string]interface{} `json:"custom,omitempty"`
|
||||
}
|
||||
|
||||
// NewLogEntry 创建新的日志条目
|
||||
// 返回包含基本信息的LogEntry结构体
|
||||
func NewLogEntry(level LogLevel, message string) *LogEntry {
|
||||
return &LogEntry{
|
||||
Timestamp: time.Now(),
|
||||
Level: level,
|
||||
Message: message,
|
||||
LevelString: levelToString(level),
|
||||
Fields: make(map[string]interface{}),
|
||||
Metrics: make(map[string]interface{}),
|
||||
Custom: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// NewLogEntryWithLocation 创建包含位置信息的日志条目
|
||||
// 返回包含调用位置信息的LogEntry结构体
|
||||
func NewLogEntryWithLocation(level LogLevel, message string) *LogEntry {
|
||||
entry := NewLogEntry(level, message)
|
||||
entry.captureLocation()
|
||||
return entry
|
||||
}
|
||||
|
||||
// captureLocation 捕获调用位置信息
|
||||
func (e *LogEntry) captureLocation() {
|
||||
_, file, line, ok := runtime.Caller(2) // 跳过2层调用栈
|
||||
if ok {
|
||||
e.File = file
|
||||
e.Line = line
|
||||
e.Function = getFunctionName(file, line)
|
||||
}
|
||||
}
|
||||
|
||||
// getFunctionName 获取函数名称
|
||||
func getFunctionName(file string, line int) string {
|
||||
// 简化的函数名获取,实际可以使用更复杂的方法
|
||||
return fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
|
||||
// WithField 添加字段到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithField(key string, value interface{}) *LogEntry {
|
||||
if e.Fields == nil {
|
||||
e.Fields = make(map[string]interface{})
|
||||
}
|
||||
e.Fields[key] = value
|
||||
return e
|
||||
}
|
||||
|
||||
// WithFields 添加多个字段到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithFields(fields map[string]interface{}) *LogEntry {
|
||||
if e.Fields == nil {
|
||||
e.Fields = make(map[string]interface{})
|
||||
}
|
||||
for k, v := range fields {
|
||||
e.Fields[k] = v
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// WithMetric 添加指标到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithMetric(key string, value interface{}) *LogEntry {
|
||||
if e.Metrics == nil {
|
||||
e.Metrics = make(map[string]interface{})
|
||||
}
|
||||
e.Metrics[key] = value
|
||||
return e
|
||||
}
|
||||
|
||||
// WithTag 添加标签到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithTag(tag string) *LogEntry {
|
||||
if e.Tags == nil {
|
||||
e.Tags = make([]string, 0)
|
||||
}
|
||||
e.Tags = append(e.Tags, tag)
|
||||
return e
|
||||
}
|
||||
|
||||
// WithTags 添加多个标签到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithTags(tags []string) *LogEntry {
|
||||
if e.Tags == nil {
|
||||
e.Tags = make([]string, 0)
|
||||
}
|
||||
e.Tags = append(e.Tags, tags...)
|
||||
return e
|
||||
}
|
||||
|
||||
// WithError 添加错误信息到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithError(err error) *LogEntry {
|
||||
if err != nil {
|
||||
e.Error = err
|
||||
e.ErrorType = fmt.Sprintf("%T", err)
|
||||
e.ErrorDetail = err.Error()
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// WithDuration 添加持续时间到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithDuration(duration time.Duration) *LogEntry {
|
||||
e.Duration = duration
|
||||
return e
|
||||
}
|
||||
|
||||
// WithRequest 添加请求信息到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithRequest(method, path string, statusCode int) *LogEntry {
|
||||
e.Method = method
|
||||
e.Path = path
|
||||
e.StatusCode = statusCode
|
||||
return e
|
||||
}
|
||||
|
||||
// WithContext 添加上下文信息到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithContext(requestID, userID, sessionID string) *LogEntry {
|
||||
e.RequestID = requestID
|
||||
e.UserID = userID
|
||||
e.SessionID = sessionID
|
||||
return e
|
||||
}
|
||||
|
||||
// WithNetwork 添加网络信息到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithNetwork(ip, userAgent string) *LogEntry {
|
||||
e.IPAddress = ip
|
||||
e.UserAgent = userAgent
|
||||
return e
|
||||
}
|
||||
|
||||
// WithCategory 添加分类信息到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithCategory(category, subsystem, component string) *LogEntry {
|
||||
e.Category = category
|
||||
e.Subsystem = subsystem
|
||||
e.Component = component
|
||||
return e
|
||||
}
|
||||
|
||||
// WithOperation 添加操作信息到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithOperation(operation string) *LogEntry {
|
||||
e.Operation = operation
|
||||
return e
|
||||
}
|
||||
|
||||
// WithPerformance 添加性能信息到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithPerformance(memory int64, cpu float64) *LogEntry {
|
||||
e.MemoryUsage = memory
|
||||
e.CPUUsage = cpu
|
||||
return e
|
||||
}
|
||||
|
||||
// WithCustom 添加自定义属性到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithCustom(key string, value interface{}) *LogEntry {
|
||||
if e.Custom == nil {
|
||||
e.Custom = make(map[string]interface{})
|
||||
}
|
||||
e.Custom[key] = value
|
||||
return e
|
||||
}
|
||||
|
||||
// WithResponseSize 添加响应大小到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithResponseSize(size int64) *LogEntry {
|
||||
e.ResponseSize = size
|
||||
return e
|
||||
}
|
||||
|
||||
// WithStacktrace 添加堆栈跟踪到日志条目
|
||||
// 返回更新后的LogEntry实例
|
||||
func (e *LogEntry) WithStacktrace(stacktrace string) *LogEntry {
|
||||
e.Stacktrace = stacktrace
|
||||
return e
|
||||
}
|
||||
|
||||
// String 返回日志条目的字符串表示
|
||||
// 实现Stringer接口
|
||||
func (e *LogEntry) String() string {
|
||||
if e.Level == DEBUG || e.Level == INFO {
|
||||
return fmt.Sprintf("[%s] %s", e.LevelString, e.Message)
|
||||
}
|
||||
return fmt.Sprintf("[%s] %s - %s:%d", e.LevelString, e.Message, e.File, e.Line)
|
||||
}
|
||||
|
||||
// ToJSON 将日志条目转换为JSON格式
|
||||
// 返回JSON格式的日志字符串
|
||||
func (e *LogEntry) ToJSON() (string, error) {
|
||||
data, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal log entry: %v", err)
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// ToJSONBytes 将日志条目转换为JSON字节数组
|
||||
// 返回JSON格式的日志字节数组
|
||||
func (e *LogEntry) ToJSONBytes() ([]byte, error) {
|
||||
return json.Marshal(e)
|
||||
}
|
||||
|
||||
// FromJSON 从JSON字符串解析日志条目
|
||||
// 解析JSON字符串到LogEntry
|
||||
func (e *LogEntry) FromJSON(jsonStr string) error {
|
||||
return json.Unmarshal([]byte(jsonStr), e)
|
||||
}
|
||||
|
||||
// ToText 将日志条目转换为文本格式
|
||||
// 返回文本格式的日志字符串
|
||||
func (e *LogEntry) ToText() string {
|
||||
var fields []string
|
||||
for k, v := range e.Fields {
|
||||
fields = append(fields, fmt.Sprintf("%s=%v", k, v))
|
||||
}
|
||||
|
||||
var metrics []string
|
||||
for k, v := range e.Metrics {
|
||||
metrics = append(metrics, fmt.Sprintf("%s=%v", k, v))
|
||||
}
|
||||
|
||||
var tags []string
|
||||
for _, tag := range e.Tags {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
result := fmt.Sprintf("[%s] %s", e.LevelString, e.Message)
|
||||
|
||||
if len(fields) > 0 {
|
||||
result += fmt.Sprintf(" fields=[%s]", joinStrings(fields, ", "))
|
||||
}
|
||||
|
||||
if len(metrics) > 0 {
|
||||
result += fmt.Sprintf(" metrics=[%s]", joinStrings(metrics, ", "))
|
||||
}
|
||||
|
||||
if len(tags) > 0 {
|
||||
result += fmt.Sprintf(" tags=[%s]", joinStrings(tags, ", "))
|
||||
}
|
||||
|
||||
if e.Duration > 0 {
|
||||
result += fmt.Sprintf(" duration=%v", e.Duration)
|
||||
}
|
||||
|
||||
if e.Error != nil {
|
||||
result += fmt.Sprintf(" error=%v", e.Error)
|
||||
}
|
||||
|
||||
if e.File != "" {
|
||||
result += fmt.Sprintf(" location=%s:%d", e.File, e.Line)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Validate 验证日志条目的有效性
|
||||
// 返回error如果日志条目无效,nil表示有效
|
||||
func (e *LogEntry) Validate() error {
|
||||
if e.Timestamp.IsZero() {
|
||||
return fmt.Errorf("timestamp cannot be zero")
|
||||
}
|
||||
|
||||
if e.Message == "" {
|
||||
return fmt.Errorf("message cannot be empty")
|
||||
}
|
||||
|
||||
if e.Level < DEBUG || e.Level > FATAL {
|
||||
return fmt.Errorf("invalid log level: %d", e.Level)
|
||||
}
|
||||
|
||||
if e.LevelString != levelToString(e.Level) {
|
||||
return fmt.Errorf("level string mismatch: expected %s, got %s",
|
||||
levelToString(e.Level), e.LevelString)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone 创建日志条目的副本
|
||||
// 返回新的LogEntry实例
|
||||
func (e *LogEntry) Clone() *LogEntry {
|
||||
clone := &LogEntry{
|
||||
Timestamp: e.Timestamp,
|
||||
Level: e.Level,
|
||||
Message: e.Message,
|
||||
LevelString: e.LevelString,
|
||||
File: e.File,
|
||||
Line: e.Line,
|
||||
Function: e.Function,
|
||||
Stacktrace: e.Stacktrace,
|
||||
RequestID: e.RequestID,
|
||||
UserID: e.UserID,
|
||||
SessionID: e.SessionID,
|
||||
IPAddress: e.IPAddress,
|
||||
UserAgent: e.UserAgent,
|
||||
Category: e.Category,
|
||||
Subsystem: e.Subsystem,
|
||||
Component: e.Component,
|
||||
Operation: e.Operation,
|
||||
Error: e.Error,
|
||||
ErrorType: e.ErrorType,
|
||||
ErrorDetail: e.ErrorDetail,
|
||||
Duration: e.Duration,
|
||||
MemoryUsage: e.MemoryUsage,
|
||||
CPUUsage: e.CPUUsage,
|
||||
Method: e.Method,
|
||||
Path: e.Path,
|
||||
StatusCode: e.StatusCode,
|
||||
ResponseSize: e.ResponseSize,
|
||||
}
|
||||
|
||||
// 深拷贝切片
|
||||
if e.Tags != nil {
|
||||
clone.Tags = make([]string, len(e.Tags))
|
||||
copy(clone.Tags, e.Tags)
|
||||
}
|
||||
|
||||
// 深拷贝映射
|
||||
if e.Fields != nil {
|
||||
clone.Fields = make(map[string]interface{})
|
||||
for k, v := range e.Fields {
|
||||
clone.Fields[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if e.Metrics != nil {
|
||||
clone.Metrics = make(map[string]interface{})
|
||||
for k, v := range e.Metrics {
|
||||
clone.Metrics[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if e.Custom != nil {
|
||||
clone.Custom = make(map[string]interface{})
|
||||
for k, v := range e.Custom {
|
||||
clone.Custom[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// GetField 获取字段值
|
||||
// 返回指定字段的值和是否存在
|
||||
func (e *LogEntry) GetField(key string) (interface{}, bool) {
|
||||
value, exists := e.Fields[key]
|
||||
return value, exists
|
||||
}
|
||||
|
||||
// GetMetric 获取指标值
|
||||
// 返回指定指标的值和是否存在
|
||||
func (e *LogEntry) GetMetric(key string) (interface{}, bool) {
|
||||
value, exists := e.Metrics[key]
|
||||
return value, exists
|
||||
}
|
||||
|
||||
// GetCustom 获取自定义属性值
|
||||
// 返回指定自定义属性的值和是否存在
|
||||
func (e *LogEntry) GetCustom(key string) (interface{}, bool) {
|
||||
value, exists := e.Custom[key]
|
||||
return value, exists
|
||||
}
|
||||
|
||||
// HasTag 检查是否包含指定标签
|
||||
// 返回true如果包含指定标签
|
||||
func (e *LogEntry) HasTag(tag string) bool {
|
||||
for _, t := range e.Tags {
|
||||
if t == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsErrorLevel 检查是否为错误级别
|
||||
// 返回true如果是ERROR或FATAL级别
|
||||
func (e *LogEntry) IsErrorLevel() bool {
|
||||
return e.Level >= ERROR
|
||||
}
|
||||
|
||||
// IsDebugLevel 检查是否为调试级别
|
||||
// 返回true如果是DEBUG级别
|
||||
func (e *LogEntry) IsDebugLevel() bool {
|
||||
return e.Level == DEBUG
|
||||
}
|
||||
|
||||
// GetLevelString 获取级别字符串
|
||||
// 返回日志级别的字符串表示
|
||||
func (e *LogEntry) GetLevelString() string {
|
||||
return e.LevelString
|
||||
}
|
||||
|
||||
// GetTimestamp 获取时间戳
|
||||
// 返回日志记录的时间戳
|
||||
func (e *LogEntry) GetTimestamp() time.Time {
|
||||
return e.Timestamp
|
||||
}
|
||||
|
||||
// GetMessage 获取消息
|
||||
// 返回日志消息
|
||||
func (e *LogEntry) GetMessage() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// GetError 获取错误信息
|
||||
// 返回错误信息
|
||||
func (e *LogEntry) GetError() error {
|
||||
return e.Error
|
||||
}
|
||||
|
||||
// GetDuration 获取持续时间
|
||||
// 返回操作持续时间
|
||||
func (e *LogEntry) GetDuration() time.Duration {
|
||||
return e.Duration
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
func levelToString(level LogLevel) string {
|
||||
switch level {
|
||||
case DEBUG:
|
||||
return "DEBUG"
|
||||
case INFO:
|
||||
return "INFO"
|
||||
case WARN:
|
||||
return "WARN"
|
||||
case ERROR:
|
||||
return "ERROR"
|
||||
case FATAL:
|
||||
return "FATAL"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
func joinStrings(strings []string, separator string) string {
|
||||
result := ""
|
||||
for i, s := range strings {
|
||||
if i > 0 {
|
||||
result += separator
|
||||
}
|
||||
result += s
|
||||
}
|
||||
return result
|
||||
}
|
||||
635
internal/logging/logger.go
Normal file
635
internal/logging/logger.go
Normal file
@@ -0,0 +1,635 @@
|
||||
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...)
|
||||
}
|
||||
Reference in New Issue
Block a user