logger.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. package common
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "runtime"
  7. "strings"
  8. "time"
  9. "github.com/sirupsen/logrus"
  10. "gopkg.in/natefinch/lumberjack.v2"
  11. )
  12. var (
  13. Logger *logrus.Logger
  14. // 不同级别的日志文件writer
  15. infoLogger *lumberjack.Logger
  16. warnLogger *lumberjack.Logger
  17. errorLogger *lumberjack.Logger
  18. mainLogger *lumberjack.Logger
  19. )
  20. // 日志级别常量
  21. const (
  22. LogLevelTrace = "trace"
  23. LogLevelDebug = "debug"
  24. LogLevelInfo = "info"
  25. LogLevelWarn = "warn"
  26. LogLevelError = "error"
  27. LogLevelFatal = "fatal"
  28. LogLevelPanic = "panic"
  29. )
  30. // LogConfig 日志配置结构体
  31. type LogConfig struct {
  32. LogDir string // 日志目录
  33. LogLevel string // 日志级别
  34. MaxSize int // 每个日志文件最大大小(MB)
  35. MaxBackups int // 保留的旧日志文件数量
  36. MaxAge int // 保留日志文件的最大天数
  37. Compress bool // 是否压缩旧日志文件
  38. LogModules []string // 需要记录日志的模块
  39. EnableCaller bool // 是否显示调用者信息
  40. }
  41. // InitLogger 初始化日志系统
  42. func InitLogger(config *LogConfig) error {
  43. if config == nil {
  44. // 使用默认配置
  45. config = getDefaultLogConfig()
  46. }
  47. // 确保日志目录存在
  48. if err := os.MkdirAll(config.LogDir, 0755); err != nil {
  49. return fmt.Errorf("创建日志目录失败: %v", err)
  50. }
  51. // 创建logrus实例
  52. Logger = logrus.New()
  53. // 设置日志级别
  54. level, err := logrus.ParseLevel(config.LogLevel)
  55. if err != nil {
  56. level = logrus.InfoLevel
  57. }
  58. Logger.SetLevel(level)
  59. // 设置日志格式
  60. Logger.SetFormatter(&logrus.JSONFormatter{
  61. TimestampFormat: "2006-01-02 15:04:05",
  62. })
  63. // 设置调用者信息
  64. Logger.SetReportCaller(config.EnableCaller)
  65. // 设置日志输出
  66. if err := setLogOutput(config); err != nil {
  67. return err
  68. }
  69. // 记录系统启动日志
  70. Logger.WithFields(logrus.Fields{
  71. "event": "system_startup",
  72. "pid": os.Getpid(),
  73. }).Info("SCADA培训系统启动")
  74. return nil
  75. }
  76. // setLogOutput 设置日志输出
  77. func setLogOutput(config *LogConfig) error {
  78. // 创建不同级别的日志文件
  79. infoLogPath := filepath.Join(config.LogDir, "info.log")
  80. warnLogPath := filepath.Join(config.LogDir, "warn.log")
  81. errorLogPath := filepath.Join(config.LogDir, "error.log")
  82. // 创建主日志文件
  83. mainLogPath := filepath.Join(config.LogDir, "app.log")
  84. // 为主日志创建writer
  85. mainWriter := &lumberjack.Logger{
  86. Filename: mainLogPath,
  87. MaxSize: config.MaxSize,
  88. MaxBackups: config.MaxBackups,
  89. MaxAge: config.MaxAge,
  90. Compress: config.Compress,
  91. }
  92. // 同时输出到控制台和文件
  93. // 使用MultiWriter同时写入控制台和文件
  94. Logger.SetOutput(mainWriter)
  95. // 保存writer引用,用于后续使用
  96. mainLogger = mainWriter
  97. infoLogger = &lumberjack.Logger{
  98. Filename: infoLogPath,
  99. MaxSize: config.MaxSize,
  100. MaxBackups: config.MaxBackups,
  101. MaxAge: config.MaxAge,
  102. Compress: config.Compress,
  103. }
  104. warnLogger = &lumberjack.Logger{
  105. Filename: warnLogPath,
  106. MaxSize: config.MaxSize,
  107. MaxBackups: config.MaxBackups,
  108. MaxAge: config.MaxAge,
  109. Compress: config.Compress,
  110. }
  111. errorLogger = &lumberjack.Logger{
  112. Filename: errorLogPath,
  113. MaxSize: config.MaxSize,
  114. MaxBackups: config.MaxBackups,
  115. MaxAge: config.MaxAge,
  116. Compress: config.Compress,
  117. }
  118. // 启动日志清理协程
  119. go startLogCleaner(config)
  120. return nil
  121. }
  122. // startLogCleaner 启动日志清理协程
  123. func startLogCleaner(config *LogConfig) {
  124. // 每天凌晨2点执行日志清理
  125. ticker := time.NewTicker(24 * time.Hour)
  126. defer ticker.Stop()
  127. // 计算到下一个凌晨2点的时间
  128. now := time.Now()
  129. next := time.Date(now.Year(), now.Month(), now.Day(), 2, 0, 0, 0, now.Location())
  130. if next.Before(now) {
  131. next = next.Add(24 * time.Hour)
  132. }
  133. initialDelay := next.Sub(now)
  134. // 先等待到下一个凌晨2点
  135. time.Sleep(initialDelay)
  136. // 执行日志清理
  137. cleanOldLogs(config)
  138. for range ticker.C {
  139. cleanOldLogs(config)
  140. }
  141. }
  142. // cleanOldLogs 清理旧的日志文件
  143. func cleanOldLogs(config *LogConfig) {
  144. if config.MaxAge <= 0 {
  145. return // 不启用自动清理
  146. }
  147. // 清理info级别的日志
  148. cleanOldLogsByPattern(config.LogDir, "info-*.log.gz", config.MaxAge)
  149. cleanOldLogsByPattern(config.LogDir, "info-*.log", config.MaxAge)
  150. // 清理warn级别的日志
  151. cleanOldLogsByPattern(config.LogDir, "warn-*.log.gz", config.MaxAge)
  152. cleanOldLogsByPattern(config.LogDir, "warn-*.log", config.MaxAge)
  153. // 清理error级别的日志
  154. cleanOldLogsByPattern(config.LogDir, "error-*.log.gz", config.MaxAge)
  155. cleanOldLogsByPattern(config.LogDir, "error-*.log", config.MaxAge)
  156. // 记录日志清理操作
  157. Logger.WithFields(logrus.Fields{
  158. "event": "log_cleanup",
  159. "max_age_days": config.MaxAge,
  160. }).Info("执行日志清理操作")
  161. }
  162. // cleanOldLogsByPattern 根据模式清理旧的日志文件
  163. func cleanOldLogsByPattern(logDir, pattern string, maxAge int) {
  164. matches, err := filepath.Glob(filepath.Join(logDir, pattern))
  165. if err != nil {
  166. Logger.WithFields(logrus.Fields{
  167. "event": "log_cleanup_error",
  168. "error": err.Error(),
  169. "pattern": pattern,
  170. }).Error("获取日志文件列表失败")
  171. return
  172. }
  173. cutoffTime := time.Now().AddDate(0, 0, -maxAge)
  174. for _, filePath := range matches {
  175. fileInfo, err := os.Stat(filePath)
  176. if err != nil {
  177. continue
  178. }
  179. if fileInfo.ModTime().Before(cutoffTime) {
  180. if err := os.Remove(filePath); err != nil {
  181. Logger.WithFields(logrus.Fields{
  182. "event": "log_file_delete_error",
  183. "error": err.Error(),
  184. "file": filePath,
  185. }).Error("删除旧日志文件失败")
  186. } else {
  187. Logger.WithFields(logrus.Fields{
  188. "event": "log_file_deleted",
  189. "file": filePath,
  190. "mod_time": fileInfo.ModTime().Format("2006-01-02 15:04:05"),
  191. }).Debug("已删除旧日志文件")
  192. }
  193. }
  194. }
  195. }
  196. // SetLogLevel 动态设置日志级别
  197. func SetLogLevel(level string) error {
  198. if Logger == nil {
  199. return fmt.Errorf("日志系统未初始化")
  200. }
  201. logLevel, err := logrus.ParseLevel(level)
  202. if err != nil {
  203. return fmt.Errorf("无效的日志级别: %s", level)
  204. }
  205. Logger.SetLevel(logLevel)
  206. Logger.WithFields(logrus.Fields{
  207. "event": "log_level_changed",
  208. "new_level": level,
  209. }).Info("日志级别已更新")
  210. return nil
  211. }
  212. // GetLogFilesInfo 获取日志文件信息
  213. func GetLogFilesInfo() ([]map[string]interface{}, error) {
  214. if Logger == nil {
  215. return nil, fmt.Errorf("日志系统未初始化")
  216. }
  217. // 获取log目录
  218. logDir := filepath.Dir(getDefaultLogConfig().LogDir)
  219. // 查找所有日志文件
  220. patterns := []string{"info*.log", "warn*.log", "error*.log"}
  221. var result []map[string]interface{}
  222. for _, pattern := range patterns {
  223. matches, err := filepath.Glob(filepath.Join(logDir, pattern))
  224. if err != nil {
  225. continue
  226. }
  227. for _, filePath := range matches {
  228. fileInfo, err := os.Stat(filePath)
  229. if err != nil {
  230. continue
  231. }
  232. fileInfoMap := map[string]interface{}{
  233. "name": filepath.Base(filePath),
  234. "path": filePath,
  235. "size": fileInfo.Size(),
  236. "mod_time": fileInfo.ModTime().Format("2006-01-02 15:04:05"),
  237. "is_compressed": strings.HasSuffix(filePath, ".gz"),
  238. }
  239. result = append(result, fileInfoMap)
  240. }
  241. }
  242. return result, nil
  243. }
  244. // getDefaultLogConfig 获取默认日志配置
  245. func getDefaultLogConfig() *LogConfig {
  246. exePath, err := os.Executable()
  247. if err != nil {
  248. exePath = "."
  249. }
  250. exeDir := filepath.Dir(exePath)
  251. return &LogConfig{
  252. LogDir: filepath.Join(exeDir, "log"),
  253. LogLevel: "info",
  254. MaxSize: 100, // 100MB
  255. MaxBackups: 30, // 保留30个旧文件
  256. MaxAge: 90, // 保留90天
  257. Compress: true,
  258. EnableCaller: true,
  259. }
  260. }
  261. // GetLogger 获取日志实例
  262. func GetLogger() *logrus.Logger {
  263. if Logger == nil {
  264. // 如果未初始化,使用默认配置初始化
  265. InitLogger(nil)
  266. }
  267. return Logger
  268. }
  269. // LogWithFields 带字段的日志记录
  270. func LogWithFields(fields logrus.Fields) *logrus.Entry {
  271. return GetLogger().WithFields(fields)
  272. }
  273. // LogInfo 记录信息级别日志
  274. func LogInfo(msg string, fields ...logrus.Fields) {
  275. logger := GetLogger()
  276. // 写入主日志文件
  277. if len(fields) > 0 {
  278. entry := logger.WithFields(fields[0])
  279. entry.Info(msg)
  280. // 同时写入info级别的日志文件
  281. writeToLevelFile("info", msg, fields[0])
  282. } else {
  283. logger.Info(msg)
  284. // 同时写入info级别的日志文件
  285. writeToLevelFile("info", msg, nil)
  286. }
  287. }
  288. // LogWarn 记录警告级别日志
  289. func LogWarn(msg string, fields ...logrus.Fields) {
  290. logger := GetLogger()
  291. // 写入主日志文件
  292. if len(fields) > 0 {
  293. entry := logger.WithFields(fields[0])
  294. entry.Warn(msg)
  295. // 同时写入warn级别的日志文件
  296. writeToLevelFile("warn", msg, fields[0])
  297. } else {
  298. logger.Warn(msg)
  299. // 同时写入warn级别的日志文件
  300. writeToLevelFile("warn", msg, nil)
  301. }
  302. }
  303. // LogError 记录错误级别日志
  304. func LogError(msg string, err error, fields ...logrus.Fields) {
  305. logger := GetLogger()
  306. logFields := logrus.Fields{}
  307. if err != nil {
  308. logFields["error"] = err.Error()
  309. }
  310. if len(fields) > 0 {
  311. for k, v := range fields[0] {
  312. logFields[k] = v
  313. }
  314. }
  315. // 写入主日志文件
  316. entry := logger.WithFields(logFields)
  317. entry.Error(msg)
  318. // 同时写入error级别的日志文件
  319. writeToLevelFile("error", msg, logFields)
  320. }
  321. // LogDebug 记录调试级别日志
  322. func LogDebug(msg string, fields ...logrus.Fields) {
  323. logger := GetLogger()
  324. // 写入主日志文件(debug级别不单独记录到debug.log)
  325. if len(fields) > 0 {
  326. entry := logger.WithFields(fields[0])
  327. entry.Debug(msg)
  328. } else {
  329. logger.Debug(msg)
  330. }
  331. }
  332. // LogTrace 记录跟踪级别日志
  333. func LogTrace(msg string, fields ...logrus.Fields) {
  334. logger := GetLogger()
  335. // 写入主日志文件(trace级别不单独记录到trace.log)
  336. if len(fields) > 0 {
  337. entry := logger.WithFields(fields[0])
  338. entry.Trace(msg)
  339. } else {
  340. logger.Trace(msg)
  341. }
  342. }
  343. // writeToLevelFile 将日志写入对应的级别日志文件
  344. func writeToLevelFile(level, msg string, fields logrus.Fields) {
  345. var writer *lumberjack.Logger
  346. switch level {
  347. case "info":
  348. writer = infoLogger
  349. case "warn":
  350. writer = warnLogger
  351. case "error":
  352. writer = errorLogger
  353. default:
  354. return
  355. }
  356. if writer == nil {
  357. return
  358. }
  359. // 创建文件专用的logger
  360. fileLogger := logrus.New()
  361. fileLogger.SetFormatter(&logrus.JSONFormatter{
  362. TimestampFormat: "2006-01-02 15:04:05",
  363. })
  364. fileLogger.SetOutput(writer)
  365. // 根据级别设置日志级别
  366. switch level {
  367. case "info":
  368. fileLogger.SetLevel(logrus.InfoLevel)
  369. if fields != nil {
  370. fileLogger.WithFields(fields).Info(msg)
  371. } else {
  372. fileLogger.Info(msg)
  373. }
  374. case "warn":
  375. fileLogger.SetLevel(logrus.WarnLevel)
  376. if fields != nil {
  377. fileLogger.WithFields(fields).Warn(msg)
  378. } else {
  379. fileLogger.Warn(msg)
  380. }
  381. case "error":
  382. fileLogger.SetLevel(logrus.ErrorLevel)
  383. if fields != nil {
  384. fileLogger.WithFields(fields).Error(msg)
  385. } else {
  386. fileLogger.Error(msg)
  387. }
  388. }
  389. }
  390. // LogRequest 记录HTTP请求日志
  391. func LogRequest(method, path, ip, userAgent string, statusCode int, duration time.Duration) {
  392. GetLogger().WithFields(logrus.Fields{
  393. "event": "http_request",
  394. "method": method,
  395. "path": path,
  396. "ip": ip,
  397. "user_agent": userAgent,
  398. "status": statusCode,
  399. "duration": duration.Milliseconds(),
  400. }).Info("HTTP请求")
  401. }
  402. // LogResponse 记录HTTP响应日志
  403. func LogResponse(data interface{}, fields ...logrus.Fields) {
  404. logFields := logrus.Fields{
  405. "event": "http_response",
  406. "data": data,
  407. }
  408. if len(fields) > 0 {
  409. for k, v := range fields[0] {
  410. logFields[k] = v
  411. }
  412. }
  413. GetLogger().WithFields(logFields).Info("HTTP响应")
  414. }
  415. // LogDBOperation 记录数据库操作日志
  416. func LogDBOperation(operation, table string, duration time.Duration, fields ...logrus.Fields) {
  417. logFields := logrus.Fields{
  418. "event": "db_operation",
  419. "operation": operation,
  420. "table": table,
  421. "duration": duration.Milliseconds(),
  422. }
  423. if len(fields) > 0 {
  424. for k, v := range fields[0] {
  425. logFields[k] = v
  426. }
  427. }
  428. GetLogger().WithFields(logFields).Info("数据库操作")
  429. }
  430. // LogUserAction 记录用户操作日志
  431. func LogUserAction(userID, userName, action, module string, fields ...logrus.Fields) {
  432. logFields := logrus.Fields{
  433. "event": "user_action",
  434. "user_id": userID,
  435. "user_name": userName,
  436. "action": action,
  437. "module": module,
  438. }
  439. if len(fields) > 0 {
  440. for k, v := range fields[0] {
  441. logFields[k] = v
  442. }
  443. }
  444. GetLogger().WithFields(logFields).Info("用户操作")
  445. }
  446. // LogSystemError 记录系统错误日志
  447. func LogSystemError(component, operation string, err error, fields ...logrus.Fields) {
  448. logFields := logrus.Fields{
  449. "event": "system_error",
  450. "component": component,
  451. "operation": operation,
  452. "error": err.Error(),
  453. }
  454. // 添加调用者信息
  455. if pc, file, line, ok := runtime.Caller(1); ok {
  456. funcName := runtime.FuncForPC(pc).Name()
  457. logFields["caller"] = fmt.Sprintf("%s:%d:%s", filepath.Base(file), line, funcName)
  458. }
  459. if len(fields) > 0 {
  460. for k, v := range fields[0] {
  461. logFields[k] = v
  462. }
  463. }
  464. GetLogger().WithFields(logFields).Error("系统错误")
  465. }
  466. // LogBusinessEvent 记录业务事件日志
  467. func LogBusinessEvent(eventType, description string, fields ...logrus.Fields) {
  468. logFields := logrus.Fields{
  469. "event": "business_event",
  470. "event_type": eventType,
  471. "description": description,
  472. }
  473. if len(fields) > 0 {
  474. for k, v := range fields[0] {
  475. logFields[k] = v
  476. }
  477. }
  478. GetLogger().WithFields(logFields).Info("业务事件")
  479. }
  480. // CloseLogger 关闭日志系统
  481. func CloseLogger() {
  482. if Logger != nil {
  483. Logger.Info("SCADA培训系统关闭")
  484. }
  485. }