| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- package common
- import (
- "fmt"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- "time"
- "github.com/sirupsen/logrus"
- "gopkg.in/natefinch/lumberjack.v2"
- )
- var (
- Logger *logrus.Logger
-
- // 不同级别的日志文件writer
- infoLogger *lumberjack.Logger
- warnLogger *lumberjack.Logger
- errorLogger *lumberjack.Logger
- mainLogger *lumberjack.Logger
- )
- // 日志级别常量
- const (
- LogLevelTrace = "trace"
- LogLevelDebug = "debug"
- LogLevelInfo = "info"
- LogLevelWarn = "warn"
- LogLevelError = "error"
- LogLevelFatal = "fatal"
- LogLevelPanic = "panic"
- )
- // LogConfig 日志配置结构体
- type LogConfig struct {
- LogDir string // 日志目录
- LogLevel string // 日志级别
- MaxSize int // 每个日志文件最大大小(MB)
- MaxBackups int // 保留的旧日志文件数量
- MaxAge int // 保留日志文件的最大天数
- Compress bool // 是否压缩旧日志文件
- LogModules []string // 需要记录日志的模块
- EnableCaller bool // 是否显示调用者信息
- }
- // InitLogger 初始化日志系统
- func InitLogger(config *LogConfig) error {
- if config == nil {
- // 使用默认配置
- config = getDefaultLogConfig()
- }
- // 确保日志目录存在
- if err := os.MkdirAll(config.LogDir, 0755); err != nil {
- return fmt.Errorf("创建日志目录失败: %v", err)
- }
- // 创建logrus实例
- Logger = logrus.New()
- // 设置日志级别
- level, err := logrus.ParseLevel(config.LogLevel)
- if err != nil {
- level = logrus.InfoLevel
- }
- Logger.SetLevel(level)
- // 设置日志格式
- Logger.SetFormatter(&logrus.JSONFormatter{
- TimestampFormat: "2006-01-02 15:04:05",
- })
- // 设置调用者信息
- Logger.SetReportCaller(config.EnableCaller)
- // 设置日志输出
- if err := setLogOutput(config); err != nil {
- return err
- }
- // 记录系统启动日志
- Logger.WithFields(logrus.Fields{
- "event": "system_startup",
- "pid": os.Getpid(),
- }).Info("SCADA培训系统启动")
- return nil
- }
- // setLogOutput 设置日志输出
- func setLogOutput(config *LogConfig) error {
- // 创建不同级别的日志文件
- infoLogPath := filepath.Join(config.LogDir, "info.log")
- warnLogPath := filepath.Join(config.LogDir, "warn.log")
- errorLogPath := filepath.Join(config.LogDir, "error.log")
- // 创建主日志文件
- mainLogPath := filepath.Join(config.LogDir, "app.log")
-
- // 为主日志创建writer
- mainWriter := &lumberjack.Logger{
- Filename: mainLogPath,
- MaxSize: config.MaxSize,
- MaxBackups: config.MaxBackups,
- MaxAge: config.MaxAge,
- Compress: config.Compress,
- }
- // 同时输出到控制台和文件
- // 使用MultiWriter同时写入控制台和文件
- Logger.SetOutput(mainWriter)
-
- // 保存writer引用,用于后续使用
- mainLogger = mainWriter
- infoLogger = &lumberjack.Logger{
- Filename: infoLogPath,
- MaxSize: config.MaxSize,
- MaxBackups: config.MaxBackups,
- MaxAge: config.MaxAge,
- Compress: config.Compress,
- }
- warnLogger = &lumberjack.Logger{
- Filename: warnLogPath,
- MaxSize: config.MaxSize,
- MaxBackups: config.MaxBackups,
- MaxAge: config.MaxAge,
- Compress: config.Compress,
- }
- errorLogger = &lumberjack.Logger{
- Filename: errorLogPath,
- MaxSize: config.MaxSize,
- MaxBackups: config.MaxBackups,
- MaxAge: config.MaxAge,
- Compress: config.Compress,
- }
- // 启动日志清理协程
- go startLogCleaner(config)
- return nil
- }
- // startLogCleaner 启动日志清理协程
- func startLogCleaner(config *LogConfig) {
- // 每天凌晨2点执行日志清理
- ticker := time.NewTicker(24 * time.Hour)
- defer ticker.Stop()
- // 计算到下一个凌晨2点的时间
- now := time.Now()
- next := time.Date(now.Year(), now.Month(), now.Day(), 2, 0, 0, 0, now.Location())
- if next.Before(now) {
- next = next.Add(24 * time.Hour)
- }
- initialDelay := next.Sub(now)
-
- // 先等待到下一个凌晨2点
- time.Sleep(initialDelay)
- // 执行日志清理
- cleanOldLogs(config)
- for range ticker.C {
- cleanOldLogs(config)
- }
- }
- // cleanOldLogs 清理旧的日志文件
- func cleanOldLogs(config *LogConfig) {
- if config.MaxAge <= 0 {
- return // 不启用自动清理
- }
- // 清理info级别的日志
- cleanOldLogsByPattern(config.LogDir, "info-*.log.gz", config.MaxAge)
- cleanOldLogsByPattern(config.LogDir, "info-*.log", config.MaxAge)
-
- // 清理warn级别的日志
- cleanOldLogsByPattern(config.LogDir, "warn-*.log.gz", config.MaxAge)
- cleanOldLogsByPattern(config.LogDir, "warn-*.log", config.MaxAge)
-
- // 清理error级别的日志
- cleanOldLogsByPattern(config.LogDir, "error-*.log.gz", config.MaxAge)
- cleanOldLogsByPattern(config.LogDir, "error-*.log", config.MaxAge)
- // 记录日志清理操作
- Logger.WithFields(logrus.Fields{
- "event": "log_cleanup",
- "max_age_days": config.MaxAge,
- }).Info("执行日志清理操作")
- }
- // cleanOldLogsByPattern 根据模式清理旧的日志文件
- func cleanOldLogsByPattern(logDir, pattern string, maxAge int) {
- matches, err := filepath.Glob(filepath.Join(logDir, pattern))
- if err != nil {
- Logger.WithFields(logrus.Fields{
- "event": "log_cleanup_error",
- "error": err.Error(),
- "pattern": pattern,
- }).Error("获取日志文件列表失败")
- return
- }
- cutoffTime := time.Now().AddDate(0, 0, -maxAge)
-
- for _, filePath := range matches {
- fileInfo, err := os.Stat(filePath)
- if err != nil {
- continue
- }
- if fileInfo.ModTime().Before(cutoffTime) {
- if err := os.Remove(filePath); err != nil {
- Logger.WithFields(logrus.Fields{
- "event": "log_file_delete_error",
- "error": err.Error(),
- "file": filePath,
- }).Error("删除旧日志文件失败")
- } else {
- Logger.WithFields(logrus.Fields{
- "event": "log_file_deleted",
- "file": filePath,
- "mod_time": fileInfo.ModTime().Format("2006-01-02 15:04:05"),
- }).Debug("已删除旧日志文件")
- }
- }
- }
- }
- // SetLogLevel 动态设置日志级别
- func SetLogLevel(level string) error {
- if Logger == nil {
- return fmt.Errorf("日志系统未初始化")
- }
-
- logLevel, err := logrus.ParseLevel(level)
- if err != nil {
- return fmt.Errorf("无效的日志级别: %s", level)
- }
-
- Logger.SetLevel(logLevel)
- Logger.WithFields(logrus.Fields{
- "event": "log_level_changed",
- "new_level": level,
- }).Info("日志级别已更新")
-
- return nil
- }
- // GetLogFilesInfo 获取日志文件信息
- func GetLogFilesInfo() ([]map[string]interface{}, error) {
- if Logger == nil {
- return nil, fmt.Errorf("日志系统未初始化")
- }
- // 获取log目录
- logDir := filepath.Dir(getDefaultLogConfig().LogDir)
-
- // 查找所有日志文件
- patterns := []string{"info*.log", "warn*.log", "error*.log"}
- var result []map[string]interface{}
-
- for _, pattern := range patterns {
- matches, err := filepath.Glob(filepath.Join(logDir, pattern))
- if err != nil {
- continue
- }
-
- for _, filePath := range matches {
- fileInfo, err := os.Stat(filePath)
- if err != nil {
- continue
- }
-
- fileInfoMap := map[string]interface{}{
- "name": filepath.Base(filePath),
- "path": filePath,
- "size": fileInfo.Size(),
- "mod_time": fileInfo.ModTime().Format("2006-01-02 15:04:05"),
- "is_compressed": strings.HasSuffix(filePath, ".gz"),
- }
-
- result = append(result, fileInfoMap)
- }
- }
-
- return result, nil
- }
- // getDefaultLogConfig 获取默认日志配置
- func getDefaultLogConfig() *LogConfig {
- exePath, err := os.Executable()
- if err != nil {
- exePath = "."
- }
- exeDir := filepath.Dir(exePath)
- return &LogConfig{
- LogDir: filepath.Join(exeDir, "log"),
- LogLevel: "info",
- MaxSize: 100, // 100MB
- MaxBackups: 30, // 保留30个旧文件
- MaxAge: 90, // 保留90天
- Compress: true,
- EnableCaller: true,
- }
- }
- // GetLogger 获取日志实例
- func GetLogger() *logrus.Logger {
- if Logger == nil {
- // 如果未初始化,使用默认配置初始化
- InitLogger(nil)
- }
- return Logger
- }
- // LogWithFields 带字段的日志记录
- func LogWithFields(fields logrus.Fields) *logrus.Entry {
- return GetLogger().WithFields(fields)
- }
- // LogInfo 记录信息级别日志
- func LogInfo(msg string, fields ...logrus.Fields) {
- logger := GetLogger()
-
- // 写入主日志文件
- if len(fields) > 0 {
- entry := logger.WithFields(fields[0])
- entry.Info(msg)
- // 同时写入info级别的日志文件
- writeToLevelFile("info", msg, fields[0])
- } else {
- logger.Info(msg)
- // 同时写入info级别的日志文件
- writeToLevelFile("info", msg, nil)
- }
- }
- // LogWarn 记录警告级别日志
- func LogWarn(msg string, fields ...logrus.Fields) {
- logger := GetLogger()
-
- // 写入主日志文件
- if len(fields) > 0 {
- entry := logger.WithFields(fields[0])
- entry.Warn(msg)
- // 同时写入warn级别的日志文件
- writeToLevelFile("warn", msg, fields[0])
- } else {
- logger.Warn(msg)
- // 同时写入warn级别的日志文件
- writeToLevelFile("warn", msg, nil)
- }
- }
- // LogError 记录错误级别日志
- func LogError(msg string, err error, fields ...logrus.Fields) {
- logger := GetLogger()
- logFields := logrus.Fields{}
- if err != nil {
- logFields["error"] = err.Error()
- }
- if len(fields) > 0 {
- for k, v := range fields[0] {
- logFields[k] = v
- }
- }
-
- // 写入主日志文件
- entry := logger.WithFields(logFields)
- entry.Error(msg)
-
- // 同时写入error级别的日志文件
- writeToLevelFile("error", msg, logFields)
- }
- // LogDebug 记录调试级别日志
- func LogDebug(msg string, fields ...logrus.Fields) {
- logger := GetLogger()
-
- // 写入主日志文件(debug级别不单独记录到debug.log)
- if len(fields) > 0 {
- entry := logger.WithFields(fields[0])
- entry.Debug(msg)
- } else {
- logger.Debug(msg)
- }
- }
- // LogTrace 记录跟踪级别日志
- func LogTrace(msg string, fields ...logrus.Fields) {
- logger := GetLogger()
-
- // 写入主日志文件(trace级别不单独记录到trace.log)
- if len(fields) > 0 {
- entry := logger.WithFields(fields[0])
- entry.Trace(msg)
- } else {
- logger.Trace(msg)
- }
- }
- // writeToLevelFile 将日志写入对应的级别日志文件
- func writeToLevelFile(level, msg string, fields logrus.Fields) {
- var writer *lumberjack.Logger
- switch level {
- case "info":
- writer = infoLogger
- case "warn":
- writer = warnLogger
- case "error":
- writer = errorLogger
- default:
- return
- }
-
- if writer == nil {
- return
- }
-
- // 创建文件专用的logger
- fileLogger := logrus.New()
- fileLogger.SetFormatter(&logrus.JSONFormatter{
- TimestampFormat: "2006-01-02 15:04:05",
- })
- fileLogger.SetOutput(writer)
-
- // 根据级别设置日志级别
- switch level {
- case "info":
- fileLogger.SetLevel(logrus.InfoLevel)
- if fields != nil {
- fileLogger.WithFields(fields).Info(msg)
- } else {
- fileLogger.Info(msg)
- }
- case "warn":
- fileLogger.SetLevel(logrus.WarnLevel)
- if fields != nil {
- fileLogger.WithFields(fields).Warn(msg)
- } else {
- fileLogger.Warn(msg)
- }
- case "error":
- fileLogger.SetLevel(logrus.ErrorLevel)
- if fields != nil {
- fileLogger.WithFields(fields).Error(msg)
- } else {
- fileLogger.Error(msg)
- }
- }
- }
- // LogRequest 记录HTTP请求日志
- func LogRequest(method, path, ip, userAgent string, statusCode int, duration time.Duration) {
- GetLogger().WithFields(logrus.Fields{
- "event": "http_request",
- "method": method,
- "path": path,
- "ip": ip,
- "user_agent": userAgent,
- "status": statusCode,
- "duration": duration.Milliseconds(),
- }).Info("HTTP请求")
- }
- // LogResponse 记录HTTP响应日志
- func LogResponse(data interface{}, fields ...logrus.Fields) {
- logFields := logrus.Fields{
- "event": "http_response",
- "data": data,
- }
- if len(fields) > 0 {
- for k, v := range fields[0] {
- logFields[k] = v
- }
- }
- GetLogger().WithFields(logFields).Info("HTTP响应")
- }
- // LogDBOperation 记录数据库操作日志
- func LogDBOperation(operation, table string, duration time.Duration, fields ...logrus.Fields) {
- logFields := logrus.Fields{
- "event": "db_operation",
- "operation": operation,
- "table": table,
- "duration": duration.Milliseconds(),
- }
- if len(fields) > 0 {
- for k, v := range fields[0] {
- logFields[k] = v
- }
- }
- GetLogger().WithFields(logFields).Info("数据库操作")
- }
- // LogUserAction 记录用户操作日志
- func LogUserAction(userID, userName, action, module string, fields ...logrus.Fields) {
- logFields := logrus.Fields{
- "event": "user_action",
- "user_id": userID,
- "user_name": userName,
- "action": action,
- "module": module,
- }
- if len(fields) > 0 {
- for k, v := range fields[0] {
- logFields[k] = v
- }
- }
- GetLogger().WithFields(logFields).Info("用户操作")
- }
- // LogSystemError 记录系统错误日志
- func LogSystemError(component, operation string, err error, fields ...logrus.Fields) {
- logFields := logrus.Fields{
- "event": "system_error",
- "component": component,
- "operation": operation,
- "error": err.Error(),
- }
-
- // 添加调用者信息
- if pc, file, line, ok := runtime.Caller(1); ok {
- funcName := runtime.FuncForPC(pc).Name()
- logFields["caller"] = fmt.Sprintf("%s:%d:%s", filepath.Base(file), line, funcName)
- }
-
- if len(fields) > 0 {
- for k, v := range fields[0] {
- logFields[k] = v
- }
- }
- GetLogger().WithFields(logFields).Error("系统错误")
- }
- // LogBusinessEvent 记录业务事件日志
- func LogBusinessEvent(eventType, description string, fields ...logrus.Fields) {
- logFields := logrus.Fields{
- "event": "business_event",
- "event_type": eventType,
- "description": description,
- }
- if len(fields) > 0 {
- for k, v := range fields[0] {
- logFields[k] = v
- }
- }
- GetLogger().WithFields(logFields).Info("业务事件")
- }
- // CloseLogger 关闭日志系统
- func CloseLogger() {
- if Logger != nil {
- Logger.Info("SCADA培训系统关闭")
- }
- }
|