日志處理有三類使用環境,開發環境DE,測試環境TE,生產環境PE。
前兩類可以看成是一類,重要的是屏幕顯示——termlog。生產環境中主要用的是socklog 和 filelog,即網絡傳輸日志和文件日志。
基本框架
網絡和文件日志的基本框架非常簡單:
-
Open file
-
Write log message
-
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字節,效率更高。在另一篇文章中已經討論過了。詳見:
原來的日志滾動處理
接下來要考慮是日志文件內容日積月累,越來越大怎么辦?
在開發和測試環境中,這不是問題。因此常常被忽略,結果進入生產環境后磁盤滿溢,系統癱瘓。
記得還是上世紀94年的時候,半夜坐火車到客戶那里,在 Novell 服務器上的執行 purge 命令,運行了半個多小時……
所以,日志的滾動處理非常重要。假設滾動日志文件數為1。
-
日志文件超過一定的大小,觸發滾動處理。
-
將原來存在的文件
log.1刪除 -
將當前文件重命名為
log.1 -
等寫入新的日志時,再判斷文件狀態,建立並打開新的日志文件。
於是,日志文件的大小被自動控制在一定范圍內。使服務器的磁盤自動保持清潔高效的狀態。
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
}
}
}
這意味着:
-
精確控制日志文件的大小
-
每次寫入日志信息都要做一系列復雜判斷
-
隨時可能進行滾動日志處理
-
當
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()
}
希望這段程序能達到以下目的:
-
需要時啟動例程。
-
只有一個啟動例程。
-
退出系統時,等待例程結束,最多10秒。
newLog的格式為:
newLog := f.filename + time.Now().Format(".20060102-150405")
即使滾動日志處理出現問題,日志也能保存下來。
那么,最后的問題是,log4go可以進入生產環境嗎?不試一試?
