logs.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. package global
  2. import (
  3. "fmt"
  4. "io"
  5. "log"
  6. "os"
  7. "time"
  8. "github.com/sirupsen/logrus"
  9. )
  10. var Logger *logrus.Logger
  11. // Hook 结构体定义了一个日志钩子,用于将日志条目输出到指定的 Writer,并使用指定的 Formatter 格式化日志条目。
  12. // Level 字段是一个包含日志级别的切片,用于指定该钩子应处理的日志级别。
  13. type Hook struct {
  14. Writer io.Writer
  15. Formatter logrus.Formatter
  16. Level []logrus.Level
  17. }
  18. // Fire 方法是 logrus.Hook 接口的实现。当日志条目符合钩子的日志级别时,
  19. // 这个方法会被调用。它首先使用 Formatter 将日志条目格式化为字节切片,
  20. // 然后将其写入 Writer。此版本增强了日志条目,添加了错误发生的文件名和行号。
  21. func (h *Hook) Fire(entry *logrus.Entry) error {
  22. // 获取错误发生的文件名和行号
  23. if entry.HasCaller() {
  24. entry.Data["file"] = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
  25. }
  26. line, err := h.Formatter.Format(entry)
  27. if err != nil {
  28. return err
  29. }
  30. h.Writer.Write(line)
  31. return nil
  32. }
  33. // Levels 方法返回该钩子支持的日志级别切片。
  34. // logrus 在写入日志时会调用这个方法来决定是否使用这个钩子。
  35. func (h *Hook) Levels() []logrus.Level {
  36. return h.Level
  37. }
  38. // newHook 函数用于创建一个新的 Hook 实例。它接收一个 Writer,
  39. // 一个 Formatter 和一个最低的日志级别 level 作为参数。
  40. // 函数会为低于或等于指定级别的所有日志级别创建一个 Hook,
  41. // 并返回该 Hook 实例。
  42. func newHook(writer io.Writer, formatter logrus.Formatter, level logrus.Level) *Hook {
  43. var levels []logrus.Level
  44. for _, l := range logrus.AllLevels {
  45. if l <= level {
  46. levels = append(levels, l)
  47. }
  48. }
  49. return &Hook{
  50. Writer: writer,
  51. Formatter: formatter,
  52. Level: levels,
  53. }
  54. }
  55. // InitLogs 函数负责初始化 logrus.Logger 对象。它首先检查是否存在指定的日志目录(默认为 ./logs/)。
  56. // 如果目录不存在,它会创建该目录。
  57. func InitLogs(logFilePath string, logLevelStr string) *logrus.Logger {
  58. //dir := "./logs/"
  59. // 检查目录是否存在
  60. if _, err := os.Stat(logFilePath); os.IsNotExist(err) {
  61. // 目录不存在,创建目录
  62. err := os.MkdirAll(logFilePath, os.ModePerm)
  63. if err != nil {
  64. fmt.Println("Error creating directory:", err)
  65. return nil
  66. }
  67. fmt.Println("Directory created:", logFilePath)
  68. }
  69. // 尝试将 logLevelStr 转换为 logrus.Level。如果转换失败,则默认使用 InfoLevel 级别。
  70. logLevel, err := logrus.ParseLevel(logLevelStr)
  71. if err != nil {
  72. logLevel = logrus.InfoLevel
  73. log.Printf("Invalid log level: %s. Defaulting to info", logLevelStr)
  74. }
  75. // 初始化 Logger 实例,并将其输出设置为 io.Discard,即默认情况下不输出任何日志。
  76. // 这是为了强制所有日志输出都通过钩子来处理。
  77. Logger = logrus.New()
  78. Logger.SetOutput(io.Discard)
  79. // 启用记录调用者信息,这样可以记录错误发生的位置(文件名和行号)。
  80. Logger.SetReportCaller(true)
  81. // 使用当前日期生成日志文件名,格式为 `log-YYYY-MM-DD.log`
  82. currentDate := time.Now().Format("2006-01-02")
  83. logFileName := fmt.Sprintf("%s/log-%s.log", logFilePath, currentDate)
  84. // 打开或创建日志文件。日志文件的路径由 logFilePath 参数指定。
  85. logFile, err := os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
  86. if err != nil {
  87. log.Printf("Failed to open log file: %s", logFileName)
  88. panic(err)
  89. }
  90. // 添加一个新的 Hook,将日志输出到日志文件中,并使用 JSONFormatter 进行格式化。
  91. Logger.AddHook(newHook(
  92. logFile,
  93. &logrus.JSONFormatter{},
  94. logLevel,
  95. ))
  96. // 添加另一个 Hook,将日志输出到标准错误输出(通常是控制台),
  97. // 并使用 TextFormatter 格式化日志,显示完整时间戳并强制使用颜色。
  98. Logger.AddHook(newHook(
  99. os.Stderr,
  100. &logrus.TextFormatter{
  101. FullTimestamp: true,
  102. ForceColors: true,
  103. },
  104. logLevel,
  105. ))
  106. return Logger
  107. }