rotator.go 4.9 KB

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