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