Golang日志框架lumberjack包源码分析
github地址:? https://github.com/natefinch/lumberjack
?
獲取源碼
go get gopkg.in/natefinch/lumberjack.v2
?
介紹
? ? ?? lumberjack用于記錄日志,但是它可以控制每個日志文件的大小,以及可以按規則刪除歷史的日志文件,甚至可以對歷史的日志文件進行壓縮.
? ? ?? Logger會首先打開或創建logFile文件,如果logFile文件已存在并且該文件的大小沒有超過設置的MaxSize,就會在打開該文件并進行追加日志。否則會創建新的日志文件。
? ? ? 當前日志文件超過MaxSize MB,就會關閉當前文件,并將其重命名,并使用原始名稱創建一個新的日志文件。因此,最新的日志輸出都在原始名稱的文件中。
?
保留的日志文件
存留的歷史文件名稱為:name-timestamp.ext? [name是給定的文件名,timestamp是日志輪換格式的時間(2006-01-02T15-04-05.000)]
?
清理舊的日志文件策略
?每當創建新的日志文件時,舊的日志文件都可能被刪除。刪除會根據MaxAge和MaxBackups的參數設置
?1. 時間戳早于MaxAge天的文件都會被刪除,如果MaxAge為0,則不會根據MaxAge刪除日志文件
?2. MaxBackups是要保留的最大舊日志文件數,用來控制該程序日志文件的最大大小。早于MaxBackups數之前的文件都會被刪除,如果MaxBackups為0,則不會根據MaxBackups進行刪除日志文件
?3. 如果MaxAge 和 MaxBackups都為0,則不會刪除日志文件
?
源碼分析
? ? 核心結構體Logger
type Logger struct {// Filename is the file to write logs to. Backup log files will be retained// in the same directory. It uses <processname>-lumberjack.log in// os.TempDir() if empty.//寫日志的文件名稱Filename string `json:"filename" yaml:"filename"`// MaxSize is the maximum size in megabytes of the log file before it gets// rotated. It defaults to 100 megabytes.//每個日志文件長度的最大大小,默認100M。MaxSize int `json:"maxsize" yaml:"maxsize"`// MaxAge is the maximum number of days to retain old log files based on the// timestamp encoded in their filename. Note that a day is defined as 24// hours and may not exactly correspond to calendar days due to daylight// savings, leap seconds, etc. The default is not to remove old log files// based on age.//日志保留的最大天數(只保留最近多少天的日志)MaxAge int `json:"maxage" yaml:"maxage"`// MaxBackups is the maximum number of old log files to retain. The default// is to retain all old log files (though MaxAge may still cause them to get// deleted.)//只保留最近多少個日志文件,用于控制程序總日志的大小MaxBackups int `json:"maxbackups" yaml:"maxbackups"`// LocalTime determines if the time used for formatting the timestamps in// backup files is the computer's local time. The default is to use UTC// time.//是否使用本地時間,默認使用UTC時間LocalTime bool `json:"localtime" yaml:"localtime"`// Compress determines if the rotated log files should be compressed// using gzip.// 是否壓縮日志文件,壓縮方法gzipCompress bool `json:"compress" yaml:"compress"`size int64 //記錄當前日志文件的字節數file *os.File //當前的日志文件mu sync.MutexmillCh chan boolstartMill sync.Once }??
核心方法Write
func (l *Logger) Write(p []byte) (n int, err error) {l.mu.Lock()defer l.mu.Unlock()writeLen := int64(len(p))if writeLen > l.max() {return 0, fmt.Errorf("write length %d exceeds maximum file size %d", writeLen, l.max(),)}if l.file == nil {if err = l.openExistingOrNew(len(p)); err != nil {return 0, err}}//如果寫入將導致日志文件大于MaxSize,則調用rotate方法進行日志文件的切換if l.size+writeLen > l.max() {if err := l.rotate(); err != nil {return 0, err}}n, err = l.file.Write(p) //將數據寫入日志文件l.size += int64(n)return n, err }? ? ?? Write方法實現io.Writer接口,用于向日志文件中寫入信息,如果寫入將導致日志文件大于MaxSize,則將當前文件關閉,將其重命名為包括當前時間的時間戳,并使用原始日志文件名創建新的日志文件。如果一次寫入的長度大于MaxSize,則返回錯誤。
???? 從Write方法中我們看到每次寫入日志前都會檢測本次的寫入是否會導致當前日志文件的大小大于MaxSize,如果大于則調用
rotate方法進行處理。
? ? rotate方法
func (l *Logger) rotate() error {//關閉當前日志文件if err := l.close(); err != nil {return err}//把當前日志文件進行重命名,并創建一個新的日志文件用于寫入日志if err := l.openNew(); err != nil {return err}l.mill()return nil }? ? ?? rotate方法用于日志切換,關閉現有的日志文件,并調用openNew方法把當前關閉的日志文件重命名,并創建一個新的日志文件進行寫入日志。調用mill方法根據配置進行日志刪除或壓縮操作。
?
openNew方法
func (l *Logger) openNew() error {//如果目錄不存在,則進行創建err := os.MkdirAll(l.dir(), 0744)if err != nil {return fmt.Errorf("can't make directories for new logfile: %s", err)}name := l.filename()mode := os.FileMode(0644)info, err := os_Stat(name) //獲取當前文件的信息if err == nil {// Copy the mode off the old logfile.mode = info.Mode()// move the existing filenewname := backupName(name, l.LocalTime) //獲取要轉換的日志名稱if err := os.Rename(name, newname); err != nil { //將當前文件重命名return fmt.Errorf("can't rename log file: %s", err)}// this is a no-op anywhere but linuxif err := chown(name, info); err != nil { //改變linux系統下文件的權限return err}}// we use truncate here because this should only get called when we've moved// the file ourselves. if someone else creates the file in the meantime,// just wipe out the contents.//創建新的日志文件f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode)if err != nil {return fmt.Errorf("can't open new logfile: %s", err)}l.file = fl.size = 0return nil }openNew方法比較簡單,主要是把當前關閉的日志文件重命名,并創建一個新的日志文件進行寫入日志。
?
mill相關的函數
func (l *Logger) mill() {l.startMill.Do(func() {l.millCh = make(chan bool, 1)go l.millRun()})select {case l.millCh <- true:default:} }func (l *Logger) millRun() {for _ = range l.millCh {// what am I going to do, log this?_ = l.millRunOnce()} }func (l *Logger) millRunOnce() error {if l.MaxBackups == 0 && l.MaxAge == 0 && !l.Compress {return nil}//獲取老的日志文件files, err := l.oldLogFiles()if err != nil {return err}var compress, remove []logInfo//MaxBackups大于0 并且 當前的文件數大于MaxBackups,說明有需要刪除的日志文件if l.MaxBackups > 0 && l.MaxBackups < len(files) {preserved := make(map[string]bool)var remaining []logInfofor _, f := range files { //遍歷每一個文件// Only count the uncompressed log file or the// compressed log file, not both.fn := f.Name() //獲取文件名稱//如果文件名以.gz結尾,則從文件名稱刪除.gzif strings.HasSuffix(fn, compressSuffix) {fn = fn[:len(fn)-len(compressSuffix)]}preserved[fn] = trueif len(preserved) > l.MaxBackups {remove = append(remove, f) //需要刪除的文件列表} else {remaining = append(remaining, f) //保留的文件列表}}files = remaining}if l.MaxAge > 0 {diff := time.Duration(int64(24*time.Hour) * int64(l.MaxAge))cutoff := currentTime().Add(-1 * diff) //需要刪除的時間節點var remaining []logInfofor _, f := range files { //遍歷保留的日志文件if f.timestamp.Before(cutoff) { //需要刪除的日志文件(超過了保留時間)remove = append(remove, f) //需要刪除的文件列表} else {remaining = append(remaining, f)}}files = remaining}if l.Compress { //獲取需要壓縮的文件列表for _, f := range files {if !strings.HasSuffix(f.Name(), compressSuffix) {compress = append(compress, f)}}}for _, f := range remove { //需要刪除的文件列表errRemove := os.Remove(filepath.Join(l.dir(), f.Name()))if err == nil && errRemove != nil {err = errRemove}}for _, f := range compress { //壓縮每一個需要刪除的日志文件fn := filepath.Join(l.dir(), f.Name())errCompress := compressLogFile(fn, fn+compressSuffix)if err == nil && errCompress != nil {err = errCompress}}return err }mill方法會開啟一個goroutine進行處理,處理的核心方法是millRunOnce, millRunOnce方法會根據配置判斷是否需要刪除的歷史日志文件,如果有則刪除。如果配置的壓縮,則會對未壓縮的歷史文件進行壓縮。
?
Rotate方法 func (l *Logger) Rotate() error {l.mu.Lock()defer l.mu.Unlock()return l.rotate() }Rotate方法是對外提供手動切換日志文件的功能,同步調用rotate方法
?
Close方法
func (l *Logger) Close() error {l.mu.Lock()defer l.mu.Unlock()return l.close() }// close closes the file if it is open. //關閉日志文件,將file置為nil func (l *Logger) close() error {if l.file == nil {return nil}err := l.file.Close()l.file = nilreturn err }?
其他的一些方法就不在累述,有興趣可自行閱讀
?
?
總結
以上是生活随笔為你收集整理的Golang日志框架lumberjack包源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql utf8mb4 bin_My
- 下一篇: SAP所有模块用户出口