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  }