go web: 2 封裝日誌包log
阿新 • • 發佈:2019-02-06
在web專案中,記日誌是非常重要的。所以,我做的第一件事,就是向log包動手。
和Python相比,log包功能上遜色不少,但它給我們提供了基礎的構架,讓我們能自己稍微封裝下。
需求
對日誌包我的要求很低,只要滿足:
1. 提供Error, Info方法即可
2. 日誌按天分割,即每隔一天,把昨天的日誌儲存為 logname.20170823這樣的檔案
程式碼
在原來的基礎上,我們在src中建立資料夾logger,在裡面建立檔案logger.go
現在檔案結構如下:
src--|
handlers--|
test--|
test.go
logger--|
logger.go
|
main.go
這個檔案程式碼有點長,所以放附錄了。
要使用,只需要在main.go裡呼叫:
logger.InitLogging("8080", logger.DEBUG)
logger.Errorln("%s %s", "hi", "my boy")
然後,在bin檔案的同級,手工建立logs資料夾。執行程式,日誌功能就開始執行了。
測試了一下效率,在mac pro上。10萬行日誌大概400毫秒。湊合著用還行。
附錄logger.go程式碼
// Package logger 是系統日誌的封裝,主要在之上封裝了Error,Info兩個函式。並提供了跨日期
// 自動分割日誌檔案的功能。
// 可以在InitLogging 後直接使用logger.Error, logger.Info操作預設的日誌物件。
// 也可以用logger.New 建立一個自己的日誌物件。
package logger
import (
"bytes"
"fmt"
"io"
"log"
"os"
"path/filepath"
"runtime"
"strconv"
"time"
)
//logging 是一個預設的日誌物件,提供全域性的Error, Info函式供使用,必須呼叫InitLogging
//函式進行初始化
var logging *Logger
var DEBUG = 0
var INFO = 3
var ERROR = 5
//InitLogging 初始化預設的日誌物件,初始化後,就能使用Error,Info函式記錄日誌
func InitLogging(inputfilename string, level int) {
logging = New(inputfilename, true, false,
level, 3)
}
//Error 預設日誌物件方法,記錄一條錯誤日誌,需要先初始化
func Error(format string, v ...interface{}) {
logging.Error(format, v...)
}
//Errorln 預設日誌物件方法,記錄一條訊息日誌,需要先初始化
func Errorln(args ...interface{}) {
logging.Errorln(args...)
}
//Info 預設日誌物件方法,記錄一條訊息日誌,需要先初始化
func Info(format string, v ...interface{}) {
logging.Info(format, v...)
}
//Infoln 預設日誌物件方法,記錄一條訊息日誌,需要先初始化
func Infoln(args ...interface{}) {
logging.Infoln(args...)
}
//Debug 預設日誌物件方法,記錄一條訊息日誌,需要先初始化
func Debug(format string, v ...interface{}) {
logging.Debug(format, v...)
}
//Debugln 預設日誌物件方法,記錄一條除錯日誌,需要先初始化
func Debugln(args ...interface{}) {
logging.Debugln(args...)
}
type Logger struct {
level int // debug 0 info 3 err 5
innerLogger *log.Logger
curFile *os.File
todaydate string
filename string
runtimeCaller int
logFilePath bool
logFunc bool
msgQueue chan string // 所有的日誌先到這來
closed bool
}
//New 建立一個自己的日誌物件。
// filename:在logs資料夾下建立的檔名
// logFilePath: 日誌中記錄檔案路徑
// logFunc: 日誌中記錄呼叫函式
// level: 列印等級。DEBUG, INFO, ERROR
// runtimeCaller: 檔案路徑深度,設定適當的值,否則檔案路徑不正確
func New(filename string, logFilePath bool,
logFunc bool, level int, runtimeCaller int) *Logger {
// result := newLogger(logFile, flag)
result := new(Logger)
result.msgQueue = make(chan string, 1000)
result.closed = false
var multi io.Writer
if filename != "" {
dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
logFile, err := os.OpenFile(dir+"/logs/"+filename,
os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Println(err.Error())
}
result.curFile = logFile
fmt.Println("newLogger use MultiWriter")
multi = io.MultiWriter(logFile, os.Stdout)
} else {
result.curFile = nil
fmt.Println("newLogger use stdout")
multi = os.Stdout
}
result.innerLogger = log.New(multi, "", 0)
result.filename = filename
result.runtimeCaller = runtimeCaller
result.logFilePath = logFilePath
result.logFunc = logFunc
result.level = level
result.todaydate = time.Now().Format("2006-01-02")
// 啟動日誌切換
go result.logworker()
return result
}
// Close 關閉這一個日誌物件
func (logobj *Logger) Close() error {
logobj.closed = true
return nil
}
func (logobj *Logger) getFormat(prefix, format string) string {
var buf bytes.Buffer
// 增加時間
buf.WriteString(time.Now().Format("2006-01-02 15:04:05 "))
buf.WriteString(prefix)
// 增加檔案和行號
funcName, file, line, ok := runtime.Caller(logobj.runtimeCaller)
if ok {
if logobj.logFilePath {
buf.WriteString(filepath.Base(file))
buf.WriteString(":")
buf.WriteString(strconv.Itoa(line))
buf.WriteString(" ")
}
if logobj.logFunc {
buf.WriteString(runtime.FuncForPC(funcName).Name())
buf.WriteString(" ")
}
buf.WriteString(format)
format = buf.String()
}
return format
}
//Error 記錄一條錯誤日誌
func (logobj *Logger) Error(format string, v ...interface{}) {
if logging.level > 5 {
return
}
format = logobj.getFormat("ERROR ", format)
logobj.msgQueue <- fmt.Sprintf(format, v...)
}
//Errorln 列印一行錯誤日誌
func (logobj *Logger) Errorln(args ...interface{}) {
if logging.level > 5 {
return
}
prefix := logobj.getFormat("ERROR ", "")
logobj.msgQueue <- fmt.Sprintln(append([]interface{}{prefix}, args...)...)
}
//Info 記錄一條訊息日誌
func (logobj *Logger) Info(format string, v ...interface{}) {
if logging.level > 3 {
return
}
format = logobj.getFormat("INFO ", format)
logobj.msgQueue <- fmt.Sprintf(format, v...)
}
//Infoln 列印一行訊息日誌
func (logobj *Logger) Infoln(args ...interface{}) {
if logging.level > 3 {
return
}
prefix := logobj.getFormat("INFO ", "")
logobj.msgQueue <- fmt.Sprintln(append([]interface{}{prefix}, args...)...)
}
//Debug 記錄一條訊息日誌
func (logobj *Logger) Debug(format string, v ...interface{}) {
if logging.level > 0 {
return
}
format = logobj.getFormat("DEBUG ", format)
logobj.msgQueue <- fmt.Sprintf(format, v...)
}
//Debugln 列印一行除錯日誌
func (logobj *Logger) Debugln(args ...interface{}) {
if logging.level > 0 {
return
}
prefix := logobj.getFormat("DEBUG ", "")
logobj.msgQueue <- fmt.Sprintln(append([]interface{}{prefix}, args...)...)
}
func (logobj *Logger) logworker() {
for logobj.closed == false {
msg := <-logobj.msgQueue
logobj.innerLogger.Println(msg)
//跨日改時間,後臺啟動
nowDate := time.Now().Format("2006-01-02")
if nowDate != logobj.todaydate {
logobj.Debug("doRotate run %v %v", nowDate, logging.todaydate)
logobj.doRotate()
}
}
}
func (logobj *Logger) doRotate() {
// 日誌按天切換檔案,日誌物件記錄了程式啟動時的時間,噹噹前時間和程式啟動的時間不一致
// 則會啟動到這個函式來改變檔案
// 首先關閉檔案控制代碼,把當前日誌改名為昨天,再建立新的檔案控制代碼,將這個檔案控制代碼賦值給log物件
// 最後嘗試刪除5天前的日誌
fmt.Println("doRotate run")
defer func() {
rec := recover()
if rec != nil {
fmt.Printf("doRotate %v", rec)
}
}()
if logobj.curFile == nil {
fmt.Println("doRotate curfile nil, return")
return
}
dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
prefile := logobj.curFile
_, err := prefile.Stat()
if err == nil {
filePath := dir + "/logs/" + logobj.filename
err := prefile.Close()
fmt.Printf("doRotate close err %v", err)
nowTime := time.Now()
time1dAgo := nowTime.Add(-1 * time.Hour * 24)
err = os.Rename(filePath, filePath+"."+time1dAgo.Format("2006-01-02"))
fmt.Printf("doRotate rename err %v", err)
}
if logobj.filename != "" {
nextfile, err := os.OpenFile(dir+"/logs/"+logobj.filename,
os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Println(err.Error())
}
logobj.curFile = nextfile
fmt.Println("newLogger use MultiWriter")
multi := io.MultiWriter(nextfile, os.Stdout)
logobj.innerLogger.SetOutput(multi)
}
fmt.Println("doRotate ending")
// 更新標記,這個標記決定是否會啟動檔案切換
nowDate := time.Now().Format("2006-01-02")
logobj.todaydate = nowDate
logobj.deleteHistory()
}
func (logobj *Logger) deleteHistory() {
// 嘗試刪除5天前的日誌
fmt.Println("deleteHistory run")
nowTime := time.Now()
time5dAgo := nowTime.Add(-1 * time.Hour * 24 * 5)
dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
filePath := dir + "/logs/" + logobj.filename + "." + time5dAgo.Format("2006-01-02")
_, err := os.Stat(filePath)
if err == nil {
os.Remove(filePath)
}
}