rotator.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. package log
  2. import (
  3. "os"
  4. "path/filepath"
  5. "sync"
  6. "time"
  7. )
  8. // DateRotator 日期轮转日志
  9. type DateRotator struct {
  10. // 基本配置
  11. Filename string // 日志文件路径模板,如 "./logs/app-%s.log"
  12. MaxSize int // 单个文件最大大小,单位MB
  13. MaxBackups int // 最多保留多少个备份
  14. MaxAge int // 文件最多保存多少天
  15. Compress bool // 是否压缩
  16. LogFormat string // 日期格式,如 "2006-01-02"
  17. // 内部状态
  18. mu sync.Mutex // 保护以下字段
  19. file *os.File // 当前日志文件
  20. currentDate string // 当前日期
  21. size int64 // 当前文件大小
  22. }
  23. // NewDateRotator 创建一个基于日期轮转的日志器
  24. func NewDateRotator(filename string, maxSize, maxBackups, maxAge int, compress bool, logFormat string) *DateRotator {
  25. if logFormat == "" {
  26. logFormat = "2006-01-02" // 默认按天轮转
  27. }
  28. r := &DateRotator{
  29. Filename: filename,
  30. MaxSize: maxSize,
  31. MaxBackups: maxBackups,
  32. MaxAge: maxAge,
  33. Compress: compress,
  34. LogFormat: logFormat,
  35. currentDate: time.Now().Format(logFormat),
  36. }
  37. return r
  38. }
  39. // Write 写入日志,同时检查是否需要轮转
  40. func (r *DateRotator) Write(p []byte) (n int, err error) {
  41. r.mu.Lock()
  42. defer r.mu.Unlock()
  43. // 检查日期是否变化
  44. currentDate := time.Now().Format(r.LogFormat)
  45. if r.file == nil || currentDate != r.currentDate {
  46. if err := r.rotate(currentDate); err != nil {
  47. return 0, err
  48. }
  49. }
  50. // 检查文件大小是否超限
  51. if r.size+int64(len(p)) >= int64(r.MaxSize*1024*1024) {
  52. if err := r.rotate(currentDate); err != nil {
  53. return 0, err
  54. }
  55. }
  56. // 写入日志
  57. n, err = r.file.Write(p)
  58. r.size += int64(n)
  59. return n, err
  60. }
  61. // Close 关闭当前日志文件
  62. func (r *DateRotator) Close() error {
  63. r.mu.Lock()
  64. defer r.mu.Unlock()
  65. if r.file != nil {
  66. err := r.file.Close()
  67. r.file = nil
  68. return err
  69. }
  70. return nil
  71. }
  72. // rotate 轮转日志文件
  73. func (r *DateRotator) rotate(newDate string) error {
  74. // 关闭旧文件
  75. if r.file != nil {
  76. r.file.Close()
  77. r.file = nil
  78. }
  79. // 更新日期
  80. r.currentDate = newDate
  81. // 直接使用传入的文件名,不再对文件名进行附加日期的处理
  82. // 因为在log.go中已经对文件名进行了日期格式化
  83. filename := r.Filename
  84. // 确保目录存在
  85. dir := filepath.Dir(filename)
  86. if err := os.MkdirAll(dir, 0755); err != nil {
  87. return err
  88. }
  89. // 打开新文件
  90. f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
  91. if err != nil {
  92. return err
  93. }
  94. // 获取文件大小
  95. info, err := f.Stat()
  96. if err != nil {
  97. f.Close()
  98. return err
  99. }
  100. // 更新状态
  101. r.file = f
  102. r.size = info.Size()
  103. // 清理旧文件(异步)
  104. go r.cleanup()
  105. return nil
  106. }
  107. // cleanup 清理旧日志文件
  108. func (r *DateRotator) cleanup() {
  109. // 实现基于日期和数量的旧文件清理
  110. if r.MaxAge <= 0 && r.MaxBackups <= 0 {
  111. return
  112. }
  113. pattern := ""
  114. dir := filepath.Dir(r.Filename)
  115. base := filepath.Base(r.Filename)
  116. ext := filepath.Ext(base)
  117. prefix := base[:len(base)-len(ext)]
  118. // 构造通配符模式
  119. pattern = filepath.Join(dir, prefix+"*"+ext)
  120. // 查找匹配的文件
  121. matches, err := filepath.Glob(pattern)
  122. if err != nil {
  123. return
  124. }
  125. // 按修改时间排序
  126. type fileInfo struct {
  127. path string
  128. modTime time.Time
  129. }
  130. var files []fileInfo
  131. for _, match := range matches {
  132. info, err := os.Stat(match)
  133. if err != nil {
  134. continue
  135. }
  136. files = append(files, fileInfo{match, info.ModTime()})
  137. }
  138. // 按修改时间从新到旧排序
  139. sort := func(i, j int) bool {
  140. return files[i].modTime.After(files[j].modTime)
  141. }
  142. for i := 0; i < len(files)-1; i++ {
  143. for j := i + 1; j < len(files); j++ {
  144. if !sort(i, j) {
  145. files[i], files[j] = files[j], files[i]
  146. }
  147. }
  148. }
  149. // 根据保留数量删除旧文件
  150. if r.MaxBackups > 0 && len(files) > r.MaxBackups {
  151. for i := r.MaxBackups; i < len(files); i++ {
  152. os.Remove(files[i].path)
  153. }
  154. files = files[:r.MaxBackups]
  155. }
  156. // 根据保留天数删除旧文件
  157. if r.MaxAge > 0 {
  158. cutoff := time.Now().Add(-time.Duration(r.MaxAge) * 24 * time.Hour)
  159. for _, f := range files {
  160. if f.modTime.Before(cutoff) {
  161. os.Remove(f.path)
  162. }
  163. }
  164. }
  165. }
  166. // Sync 将缓存数据同步到磁盘
  167. func (r *DateRotator) Sync() error {
  168. r.mu.Lock()
  169. defer r.mu.Unlock()
  170. if r.file != nil {
  171. return r.file.Sync()
  172. }
  173. return nil
  174. }