glog_logger_rotate.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
  2. //
  3. // This Source Code Form is subject to the terms of the MIT License.
  4. // If a copy of the MIT was not distributed with this file,
  5. // You can obtain one at https://github.com/gogf/gf.
  6. package glog
  7. import (
  8. "fmt"
  9. "github.com/gogf/gf/container/garray"
  10. "github.com/gogf/gf/encoding/gcompress"
  11. "github.com/gogf/gf/internal/intlog"
  12. "github.com/gogf/gf/os/gfile"
  13. "github.com/gogf/gf/os/gmlock"
  14. "github.com/gogf/gf/os/gtime"
  15. "github.com/gogf/gf/os/gtimer"
  16. "github.com/gogf/gf/text/gregex"
  17. "time"
  18. )
  19. const (
  20. memoryLockPrefixForRotating = "glog.rotateChecksTimely:"
  21. )
  22. // rotateFileBySize rotates the current logging file according to the
  23. // configured rotation size.
  24. func (l *Logger) rotateFileBySize(now time.Time) {
  25. if l.config.RotateSize <= 0 {
  26. return
  27. }
  28. if err := l.doRotateFile(l.getFilePath(now)); err != nil {
  29. // panic(err)
  30. intlog.Error(l.ctx, err)
  31. }
  32. }
  33. // doRotateFile rotates the given logging file.
  34. func (l *Logger) doRotateFile(filePath string) error {
  35. memoryLockKey := "glog.doRotateFile:" + filePath
  36. if !gmlock.TryLock(memoryLockKey) {
  37. return nil
  38. }
  39. defer gmlock.Unlock(memoryLockKey)
  40. intlog.PrintFunc(l.ctx, func() string {
  41. return fmt.Sprintf(`start rotating file by size: %s, file: %s`, gfile.SizeFormat(filePath), filePath)
  42. })
  43. defer intlog.PrintFunc(l.ctx, func() string {
  44. return fmt.Sprintf(`done rotating file by size: %s, size: %s`, gfile.SizeFormat(filePath), filePath)
  45. })
  46. // No backups, it then just removes the current logging file.
  47. if l.config.RotateBackupLimit == 0 {
  48. if err := gfile.Remove(filePath); err != nil {
  49. return err
  50. }
  51. intlog.Printf(
  52. l.ctx,
  53. `%d size exceeds, no backups set, remove original logging file: %s`,
  54. l.config.RotateSize, filePath,
  55. )
  56. return nil
  57. }
  58. // Else it creates new backup files.
  59. var (
  60. dirPath = gfile.Dir(filePath)
  61. fileName = gfile.Name(filePath)
  62. fileExtName = gfile.ExtName(filePath)
  63. newFilePath = ""
  64. )
  65. // Rename the logging file by adding extra datetime information to microseconds, like:
  66. // access.log -> access.20200326101301899002.log
  67. // access.20200326.log -> access.20200326.20200326101301899002.log
  68. for {
  69. var (
  70. now = gtime.Now()
  71. micro = now.Microsecond() % 1000
  72. )
  73. if micro == 0 {
  74. micro = 101
  75. } else {
  76. for micro < 100 {
  77. micro *= 10
  78. }
  79. }
  80. newFilePath = gfile.Join(
  81. dirPath,
  82. fmt.Sprintf(
  83. `%s.%s%d.%s`,
  84. fileName, now.Format("YmdHisu"), micro, fileExtName,
  85. ),
  86. )
  87. if !gfile.Exists(newFilePath) {
  88. break
  89. } else {
  90. intlog.Printf(l.ctx, `rotation file exists, continue: %s`, newFilePath)
  91. }
  92. }
  93. intlog.Printf(l.ctx, "rotating file by size from %s to %s", filePath, newFilePath)
  94. if err := gfile.Rename(filePath, newFilePath); err != nil {
  95. return err
  96. }
  97. return nil
  98. }
  99. // rotateChecksTimely timely checks the backups expiration and the compression.
  100. func (l *Logger) rotateChecksTimely() {
  101. defer gtimer.AddOnce(l.config.RotateCheckInterval, l.rotateChecksTimely)
  102. // Checks whether file rotation not enabled.
  103. if l.config.RotateSize <= 0 && l.config.RotateExpire == 0 {
  104. intlog.Printf(
  105. l.ctx,
  106. "logging rotation ignore checks: RotateSize: %d, RotateExpire: %s",
  107. l.config.RotateSize, l.config.RotateExpire.String(),
  108. )
  109. return
  110. }
  111. // It here uses memory lock to guarantee the concurrent safety.
  112. memoryLockKey := memoryLockPrefixForRotating + l.config.Path
  113. if !gmlock.TryLock(memoryLockKey) {
  114. return
  115. }
  116. defer gmlock.Unlock(memoryLockKey)
  117. var (
  118. now = time.Now()
  119. pattern = "*.log, *.gz"
  120. files, err = gfile.ScanDirFile(l.config.Path, pattern, true)
  121. )
  122. if err != nil {
  123. intlog.Error(l.ctx, err)
  124. }
  125. intlog.Printf(l.ctx, "logging rotation start checks: %+v", files)
  126. // =============================================================
  127. // Rotation of expired file checks.
  128. // =============================================================
  129. if l.config.RotateExpire > 0 {
  130. var (
  131. mtime time.Time
  132. subDuration time.Duration
  133. expireRotated bool
  134. )
  135. for _, file := range files {
  136. if gfile.ExtName(file) == "gz" {
  137. continue
  138. }
  139. mtime = gfile.MTime(file)
  140. subDuration = now.Sub(mtime)
  141. if subDuration > l.config.RotateExpire {
  142. expireRotated = true
  143. intlog.Printf(
  144. l.ctx,
  145. `%v - %v = %v > %v, rotation expire logging file: %s`,
  146. now, mtime, subDuration, l.config.RotateExpire, file,
  147. )
  148. if err := l.doRotateFile(file); err != nil {
  149. intlog.Error(l.ctx, err)
  150. }
  151. }
  152. }
  153. if expireRotated {
  154. // Update the files array.
  155. files, err = gfile.ScanDirFile(l.config.Path, pattern, true)
  156. if err != nil {
  157. intlog.Error(l.ctx, err)
  158. }
  159. }
  160. }
  161. // =============================================================
  162. // Rotated file compression.
  163. // =============================================================
  164. needCompressFileArray := garray.NewStrArray()
  165. if l.config.RotateBackupCompress > 0 {
  166. for _, file := range files {
  167. // Eg: access.20200326101301899002.log.gz
  168. if gfile.ExtName(file) == "gz" {
  169. continue
  170. }
  171. // Eg:
  172. // access.20200326101301899002.log
  173. if gregex.IsMatchString(`.+\.\d{20}\.log`, gfile.Basename(file)) {
  174. needCompressFileArray.Append(file)
  175. }
  176. }
  177. if needCompressFileArray.Len() > 0 {
  178. needCompressFileArray.Iterator(func(_ int, path string) bool {
  179. err := gcompress.GzipFile(path, path+".gz")
  180. if err == nil {
  181. intlog.Printf(l.ctx, `compressed done, remove original logging file: %s`, path)
  182. if err = gfile.Remove(path); err != nil {
  183. intlog.Print(l.ctx, err)
  184. }
  185. } else {
  186. intlog.Print(l.ctx, err)
  187. }
  188. return true
  189. })
  190. // Update the files array.
  191. files, err = gfile.ScanDirFile(l.config.Path, pattern, true)
  192. if err != nil {
  193. intlog.Error(l.ctx, err)
  194. }
  195. }
  196. }
  197. // =============================================================
  198. // Backups count limitation and expiration checks.
  199. // =============================================================
  200. var (
  201. backupFilesMap = make(map[string]*garray.SortedArray)
  202. originalLoggingFilePath = ""
  203. )
  204. if l.config.RotateBackupLimit > 0 || l.config.RotateBackupExpire > 0 {
  205. for _, file := range files {
  206. originalLoggingFilePath, _ = gregex.ReplaceString(`\.\d{20}`, "", file)
  207. if backupFilesMap[originalLoggingFilePath] == nil {
  208. backupFilesMap[originalLoggingFilePath] = garray.NewSortedArray(func(a, b interface{}) int {
  209. // Sorted by rotated/backup file mtime.
  210. // The older rotated/backup file is put in the head of array.
  211. var (
  212. file1 = a.(string)
  213. file2 = b.(string)
  214. result = gfile.MTimestampMilli(file1) - gfile.MTimestampMilli(file2)
  215. )
  216. if result <= 0 {
  217. return -1
  218. }
  219. return 1
  220. })
  221. }
  222. // Check if this file a rotated/backup file.
  223. if gregex.IsMatchString(`.+\.\d{20}\.log`, gfile.Basename(file)) {
  224. backupFilesMap[originalLoggingFilePath].Add(file)
  225. }
  226. }
  227. intlog.Printf(l.ctx, `calculated backup files map: %+v`, backupFilesMap)
  228. for _, array := range backupFilesMap {
  229. diff := array.Len() - l.config.RotateBackupLimit
  230. for i := 0; i < diff; i++ {
  231. path, _ := array.PopLeft()
  232. intlog.Printf(l.ctx, `remove exceeded backup limit file: %s`, path)
  233. if err := gfile.Remove(path.(string)); err != nil {
  234. intlog.Error(l.ctx, err)
  235. }
  236. }
  237. }
  238. // Backups expiration checking.
  239. if l.config.RotateBackupExpire > 0 {
  240. var (
  241. mtime time.Time
  242. subDuration time.Duration
  243. )
  244. for _, array := range backupFilesMap {
  245. array.Iterator(func(_ int, v interface{}) bool {
  246. path := v.(string)
  247. mtime = gfile.MTime(path)
  248. subDuration = now.Sub(mtime)
  249. if subDuration > l.config.RotateBackupExpire {
  250. intlog.Printf(
  251. l.ctx,
  252. `%v - %v = %v > %v, remove expired backup file: %s`,
  253. now, mtime, subDuration, l.config.RotateBackupExpire, path,
  254. )
  255. if err := gfile.Remove(path); err != nil {
  256. intlog.Error(l.ctx, err)
  257. }
  258. return true
  259. } else {
  260. return false
  261. }
  262. })
  263. }
  264. }
  265. }
  266. }