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