log4go的日志滾動處理——生產環境的適配


日志處理有三類使用環境,開發環境DE,測試環境TE,生產環境PE。

前兩類可以看成是一類,重要的是屏幕顯示——termlog。生產環境中主要用的是socklog 和 filelog,即網絡傳輸日志和文件日志。

基本框架

網絡和文件日志的基本框架非常簡單:

  1. Open file

  2. Write log message

  3. Close file

golang log 都支持。

// New creates a new Logger. The out variable sets the
// destination to which log data will be written.
// The prefix appears at the beginning of each generated log line.
// The flag argument defines the logging properties.
func New(out io.Writer, prefix string, flag int) *Logger {
	return &Logger{out: out, prefix: prefix, flag: flag}
}

設置不同的io.Writer而已。

type FileWriter struct {
	filename string
	fileflush  int

	file   *os.File
	bufWriter *bufio.Writer
	writer io.Writer
}

func (fw *FileWriter) openFile(flag int) (*os.File, error) {
	fd, err := os.OpenFile(fw.filename, flag, DefaultFilePerm)
	if err != nil {
		return nil, err
	}

	fw.file = fd
	fw.writer = fw.file

	if fw.fileflush > 0 {
		fw.bufWriter = bufio.NewWriterSize(fw.file, fw.fileflush)
		fw.writer = fw.bufWriter
	}
	return fd, nil
}

當然,帶緩沖寫文件的 bufio,一次寫入4k或者8k字節,效率更高。在另一篇文章中已經討論過了。詳見:

log4go的一些改進設想

原來的日志滾動處理

接下來要考慮是日志文件內容日積月累,越來越大怎么辦?

在開發和測試環境中,這不是問題。因此常常被忽略,結果進入生產環境后磁盤滿溢,系統癱瘓。
記得還是上世紀94年的時候,半夜坐火車到客戶那里,在 Novell 服務器上的執行 purge 命令,運行了半個多小時……

所以,日志的滾動處理非常重要。假設滾動日志文件數為1。

  1. 日志文件超過一定的大小,觸發滾動處理。

  2. 將原來存在的文件log.1刪除

  3. 將當前文件重命名為log.1

  4. 等寫入新的日志時,再判斷文件狀態,建立並打開新的日志文件。

於是,日志文件的大小被自動控制在一定范圍內。使服務器的磁盤自動保持清潔高效的狀態。

log4go v4 的觸發滾動處理如下:

func (w *FileLogWriter) LogWrite(rec *LogRecord) {
	now := time.Now()

	if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) ||
		(w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) ||
		(w.daily && now.Day() != w.daily_opendate.Day()) {
		// open the file for the first time
		if err := w.intRotate(); err != nil {
			fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
			return
		}
	}
}

這意味着:

  1. 精確控制日志文件的大小

  2. 每次寫入日志信息都要做一系列復雜判斷

  3. 隨時可能進行滾動日志處理

  4. rotate = 0 時,不進行日志滾動處理。這可能是個坑。開發和測試的時候,日志文件的滾動處理可能被忽略。

其實,在生產環境中,什么時候進行滾動處理,才是真正重要的。通常都會選擇每天凌晨系統較為空閑的時候。

如果是一個24小時滿載的系統,或者對系統穩定性要求特別高,或者對日志的可靠性要求特別高,建議用socklog。
將日志信息發送給專門的日志服務程序進行處理。參照log4go的示例程序,SimpleNetLogServer.go

按照生產環境的要求重寫

  • 始終進行日志滾動處理。當日志滾動數為0時,超過缺省的文件大小,關閉並刪除當前的日志文件。

  • 按照生產環境的要求設置缺省文件名,目錄,大小,滾動處理的時間和間隔。

  • 精確控制滾動處理的時間。詳見日志:http://www.cnblogs.com/ccpaging/p/7203431.html

  • 支持任意的時間間隔檢查日志文件大小。可以每次都轉存日志。

  • 平時寫入日志時不再判斷文件大小。

  • 簡化處理流程。寫信息時判斷並建立打開當前日志文件。進行滾動處理時,關閉當前日志文件。

  • 繼續寫新的日志與滾動日志文件處理,可並行。為將來壓縮日志文件提供了可能。

保證只啟動一個日志滾動處理例程

調用日志處理就一句話:

func (f *FileLogWriter) intRotate() {
	f.Lock()
	defer f.Unlock()

	if n, _ := f.SeekFile(0, os.SEEK_CUR); n <= f.maxsize {
		return
	}
	
	// File existed and File size > maxsize
	
	if len(f.footer) > 0 { // Append footer
		f.WriteString(FormatLogRecord(f.footer, &LogRecord{Created: time.Now()}))
	}

	f.CloseFile() 

	if f.rotate <= 0 {
		os.Remove(f.filename)
		return
	}

	// File existed. File size > maxsize. Rotate
	newLog := f.filename + time.Now().Format(".20060102-150405")
	err := os.Rename(f.filename, newLog)
	if err != nil {
		fmt.Fprintf(os.Stderr, "FileLogWriter(%q): Rename to %s. %v\n", f.filename, newLog, err)
		return
	}
	
	go FileRotate(f.filename, f.rotate, newLog)  // 調用日志滾動處理
}

總覺得哪里不對?如果滾動日志檢查的時間間隔短,處理的時間意外地長,就有可能出現同時調用兩個例程的情況。
這種情況肯定很少發生。一旦發生,就是個深坑。運維的童鞋要罵娘了……此處省略若干字。

好吧。趕緊做一段程序壓壓驚。

package main

import (
	"fmt"
	"time"
)

type FileRotate struct {
	rotCount int
	rotFiles chan string
}

var (
	DefaultRotateLen = 5
)

func (r *FileRotate) InitRot() {
	r.rotCount = 0
	r.rotFiles = make(chan string, DefaultRotateLen)
}

func (r *FileRotate) RotFile(filename string, rotate int, newLog string) {
	r.rotFiles <- newLog 
	if r.rotCount > 0 {
		fmt.Println("queued", newLog)
		return
	}

	r.rotCount++
	fmt.Println("start")
	for len(r.rotFiles) > 0 {
		file, _ := <- r.rotFiles
		fmt.Println("handle", file)
		time.Sleep(2 * time.Second)
	}
	fmt.Println("quit")
	r.rotCount--
}

func (r *FileRotate) CloseRot() {
	for i := 10; i > 0; i-- {
		if r.rotCount <= 0 {
			break
		}
		time.Sleep(1 * time.Second)
	}

	close(r.rotFiles)

	// drain the files not rotated
	for file := range r.rotFiles {
		fmt.Println(file)
	}
}

func main() {
	var r FileRotate;
	r.InitRot()
	for i := 0; i < 5; i++ {
		go r.RotFile("filename", 10, fmt.Sprintf("file%d", i))
		time.Sleep(1 * time.Second)
	}
	time.Sleep(5 * time.Second)
	for i := 5; i < 10; i++ {
		go r.RotFile("filename", 10, fmt.Sprintf("file%d", i))
		time.Sleep(1 * time.Second)
	}
	r.CloseRot()
}

希望這段程序能達到以下目的:

  1. 需要時啟動例程。

  2. 只有一個啟動例程。

  3. 退出系統時,等待例程結束,最多10秒。

newLog的格式為:

newLog := f.filename + time.Now().Format(".20060102-150405")

即使滾動日志處理出現問題,日志也能保存下來。


那么,最后的問題是,log4go可以進入生產環境嗎?不試一試?

https://github.com/ccpaging/log4go


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM