Golang日志框架lumberjack包源码分析
github地址:? https://github.com/natefinch/lumberjack
?
獲取源碼
go get gopkg.in/natefinch/lumberjack.v2
?
介紹
? ? ?? lumberjack用于記錄日志,但是它可以控制每個(gè)日志文件的大小,以及可以按規(guī)則刪除歷史的日志文件,甚至可以對歷史的日志文件進(jìn)行壓縮.
? ? ?? Logger會(huì)首先打開或創(chuàng)建logFile文件,如果logFile文件已存在并且該文件的大小沒有超過設(shè)置的MaxSize,就會(huì)在打開該文件并進(jìn)行追加日志。否則會(huì)創(chuàng)建新的日志文件。
? ? ? 當(dāng)前日志文件超過MaxSize MB,就會(huì)關(guān)閉當(dāng)前文件,并將其重命名,并使用原始名稱創(chuàng)建一個(gè)新的日志文件。因此,最新的日志輸出都在原始名稱的文件中。
?
保留的日志文件
存留的歷史文件名稱為:name-timestamp.ext? [name是給定的文件名,timestamp是日志輪換格式的時(shí)間(2006-01-02T15-04-05.000)]
?
清理舊的日志文件策略
?每當(dāng)創(chuàng)建新的日志文件時(shí),舊的日志文件都可能被刪除。刪除會(huì)根據(jù)MaxAge和MaxBackups的參數(shù)設(shè)置
?1. 時(shí)間戳早于MaxAge天的文件都會(huì)被刪除,如果MaxAge為0,則不會(huì)根據(jù)MaxAge刪除日志文件
?2. MaxBackups是要保留的最大舊日志文件數(shù),用來控制該程序日志文件的最大大小。早于MaxBackups數(shù)之前的文件都會(huì)被刪除,如果MaxBackups為0,則不會(huì)根據(jù)MaxBackups進(jìn)行刪除日志文件
?3. 如果MaxAge 和 MaxBackups都為0,則不會(huì)刪除日志文件
?
源碼分析
? ? 核心結(jié)構(gòu)體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.//每個(gè)日志文件長度的最大大小,默認(rèn)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.//日志保留的最大天數(shù)(只保留最近多少天的日志)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.)//只保留最近多少個(gè)日志文件,用于控制程序總?cè)罩镜拇笮axBackups 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.//是否使用本地時(shí)間,默認(rèn)使用UTC時(shí)間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 //記錄當(dāng)前日志文件的字節(jié)數(shù)file *os.File //當(dāng)前的日志文件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}}//如果寫入將導(dǎo)致日志文件大于MaxSize,則調(diào)用rotate方法進(jìn)行日志文件的切換if l.size+writeLen > l.max() {if err := l.rotate(); err != nil {return 0, err}}n, err = l.file.Write(p) //將數(shù)據(jù)寫入日志文件l.size += int64(n)return n, err }? ? ?? Write方法實(shí)現(xiàn)io.Writer接口,用于向日志文件中寫入信息,如果寫入將導(dǎo)致日志文件大于MaxSize,則將當(dāng)前文件關(guān)閉,將其重命名為包括當(dāng)前時(shí)間的時(shí)間戳,并使用原始日志文件名創(chuàng)建新的日志文件。如果一次寫入的長度大于MaxSize,則返回錯(cuò)誤。
???? 從Write方法中我們看到每次寫入日志前都會(huì)檢測本次的寫入是否會(huì)導(dǎo)致當(dāng)前日志文件的大小大于MaxSize,如果大于則調(diào)用
rotate方法進(jìn)行處理。
? ? rotate方法
func (l *Logger) rotate() error {//關(guān)閉當(dāng)前日志文件if err := l.close(); err != nil {return err}//把當(dāng)前日志文件進(jìn)行重命名,并創(chuàng)建一個(gè)新的日志文件用于寫入日志if err := l.openNew(); err != nil {return err}l.mill()return nil }? ? ?? rotate方法用于日志切換,關(guān)閉現(xiàn)有的日志文件,并調(diào)用openNew方法把當(dāng)前關(guān)閉的日志文件重命名,并創(chuàng)建一個(gè)新的日志文件進(jìn)行寫入日志。調(diào)用mill方法根據(jù)配置進(jìn)行日志刪除或壓縮操作。
?
openNew方法
func (l *Logger) openNew() error {//如果目錄不存在,則進(jìn)行創(chuàng)建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) //獲取當(dāng)前文件的信息if err == nil {// Copy the mode off the old logfile.mode = info.Mode()// move the existing filenewname := backupName(name, l.LocalTime) //獲取要轉(zhuǎn)換的日志名稱if err := os.Rename(name, newname); err != nil { //將當(dāng)前文件重命名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系統(tǒng)下文件的權(quán)限r(nóng)eturn 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.//創(chuàng)建新的日志文件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方法比較簡單,主要是把當(dāng)前關(guān)閉的日志文件重命名,并創(chuàng)建一個(gè)新的日志文件進(jìn)行寫入日志。
?
mill相關(guān)的函數(shù)
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 并且 當(dāng)前的文件數(shù)大于MaxBackups,說明有需要?jiǎng)h除的日志文件if l.MaxBackups > 0 && l.MaxBackups < len(files) {preserved := make(map[string]bool)var remaining []logInfofor _, f := range files { //遍歷每一個(gè)文件// Only count the uncompressed log file or the// compressed log file, not both.fn := f.Name() //獲取文件名稱//如果文件名以.gz結(jié)尾,則從文件名稱刪除.gzif strings.HasSuffix(fn, compressSuffix) {fn = fn[:len(fn)-len(compressSuffix)]}preserved[fn] = trueif len(preserved) > l.MaxBackups {remove = append(remove, f) //需要?jiǎng)h除的文件列表} 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) //需要?jiǎng)h除的時(shí)間節(jié)點(diǎn)var remaining []logInfofor _, f := range files { //遍歷保留的日志文件if f.timestamp.Before(cutoff) { //需要?jiǎng)h除的日志文件(超過了保留時(shí)間)remove = append(remove, f) //需要?jiǎng)h除的文件列表} 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 { //需要?jiǎng)h除的文件列表errRemove := os.Remove(filepath.Join(l.dir(), f.Name()))if err == nil && errRemove != nil {err = errRemove}}for _, f := range compress { //壓縮每一個(gè)需要?jiǎng)h除的日志文件fn := filepath.Join(l.dir(), f.Name())errCompress := compressLogFile(fn, fn+compressSuffix)if err == nil && errCompress != nil {err = errCompress}}return err }mill方法會(huì)開啟一個(gè)goroutine進(jìn)行處理,處理的核心方法是millRunOnce, millRunOnce方法會(huì)根據(jù)配置判斷是否需要?jiǎng)h除的歷史日志文件,如果有則刪除。如果配置的壓縮,則會(huì)對未壓縮的歷史文件進(jìn)行壓縮。
?
Rotate方法 func (l *Logger) Rotate() error {l.mu.Lock()defer l.mu.Unlock()return l.rotate() }Rotate方法是對外提供手動(dòng)切換日志文件的功能,同步調(diào)用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. //關(guān)閉日志文件,將file置為nil func (l *Logger) close() error {if l.file == nil {return nil}err := l.file.Close()l.file = nilreturn err }?
其他的一些方法就不在累述,有興趣可自行閱讀
?
?
總結(jié)
以上是生活随笔為你收集整理的Golang日志框架lumberjack包源码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql utf8mb4 bin_My
- 下一篇: SAP所有模块用户出口