go 開啟檔案控制代碼_Go 實戰:實現一個簡單的日誌庫
技術標籤:go 開啟檔案控制代碼
點選上方藍色“ Go語言中文網 ”關注我們, 領全套Go資料 ,每天學習Go語言編者按:本文實現的簡單日誌庫不一定適合你,但可能會給你一些啟發、借鑑
前言
一個完整的日誌庫不僅僅涵蓋日誌記錄功能,還要包括日誌 level、行號、檔案切分,甚至包含統計與分析等,Go 語言中的日誌庫也是很多,其中知名度比較高的有:
庫名 | star |
---|---|
logrus[1] | 14940 |
zap[2] | 9827 |
zerolog[3] | 3386 |
seelog[4] | 1464 |
備註:star 數獲取時間為 2020-05-28 23:26:00
一千個人有一千個需求,不管是哪個開源日誌庫,用著總有不順手的時候,沒關係,那就自己實現一個吧,相信自己,來,就讓咱們先從實現簡單的日誌記錄功能開始吧~「手動狗頭」
思路
功能設計
根據自己的需求,我想要的日誌記錄功能有:
- 按照 level 輸出日誌
- 能夠同時輸出到檔案和控制檯
- 控制檯能夠根據 level 將內容輸出為不同顏色
- 日誌檔案根據大小進行分割
- 輸出行號
API 設計
一般來說,根據 level 不同,設計有不同的 API,level 大概可以分為: trace、warn、error、fatal, 也就是說對外的 API 可以概括為: T(...inter), W(...), E(...), F(...)
typeloggerinterface{
T(formatstring,v...interface{})
W(formatstring,v...interface{})
E(formatstring,v...interface{})
F(formatstring,v...interface{})
}
結構設計
根據需求,日誌記錄器 logger 的結構需要包含 writers、檔名、檔案儲存路徑、檔案分割大小 完整結構設計如下:
typemyLogstruct{
sync.Once
sync.Mutex//用於outs併發訪問
outsmap[logType]io.Writer//writer集合
file*os.File//檔案控制代碼
fileNamestring//日誌名
dirstring//日誌存放路徑
sizeint64//單個日誌檔案的大小限制
}
關鍵方法實現
- 日誌檔案大小檢測
func(m*myLog)checkLogSize(){
ifm.file==nil{
return
}
m.Lock()
deferm.Unlock()//此處必須加鎖,否則會出現併發問題
fileInfo,err:=m.file.Stat()
iferr!=nil{
panic(err)
}
ifm.size>fileInfo.Size(){
return
}
//需要分割,重新開啟一個新的檔案控制代碼替換老的,並關閉老的檔案控制代碼,
newName:=path.Join(m.dir,time.Now().Format("2006_01_02_15:04:03")+".log")
name:=path.Join(m.dir,m.fileName)
err=os.Rename(name,newName)
iferr!=nil{
panic(err)
}
file,err:=os.OpenFile(name,os.O_CREATE|os.O_APPEND|os.O_WRONLY,0755)
iferr!=nil{
panic(err)
}
m.file.Close()
m.file=file
m.outs[logTypeFile]=file
return
}
- 控制檯帶顏色輸出內容
funcsetColor(msgstring,textint)string{
returnfmt.Sprintf("%c[%dm%s%c[0m",0x1B,text,msg,0x1B)
}
- 獲取行號
funcshortFileName(filestring)string{
short:=file
fori:=len(file)-1;i>0;i--{
iffile[i]=='/'{
short=file[i+1:]
break
}
}
returnshort
}
完整程式碼實現
packagelogUtil
import(
"fmt"
"io"
"os"
"path"
"runtime"
"strconv"
"sync"
"time"
)
const(
colorRed=31
colorYellow=33
colorBlue=34
levelT="[T]"
levelE="[E]"
levelW="[W]"
defaultFileSize=60*1024*1024
minFileSize=1*1024*1024
defaultLogDir="log"
defaultLogName="default.log"
logTypeStdlogType=iota+1
logTypeFile
)
type(
logTypeint
LogOptionfunc(log*myLog)myLogstruct{
sync.Once
sync.Mutex
outsmap[logType]io.Writer//writer集合
file*os.File//檔案控制代碼
fileNamestring//日誌名
dirstring//日誌存放路徑
sizeint64//單個日誌檔案的大小限制
}
)
var(
defaultLogger=&myLog{}
)
func(m*myLog)init(){
ifm.dir==""{
m.dir=defaultLogDir
}
ifm.fileName==""{
m.fileName=defaultLogName
}
ifm.size==0{
m.size=defaultFileSize
}else{
ifm.sizepanic(fmt.Sprintf("invalidsize:%d",m.size))
}
}
ifm.outs==nil{
m.outs=make(map[logType]io.Writer)
}
if!isExist(m.dir){
iferr:=os.Mkdir(m.dir,0777);err!=nil{
panic(err)
}
}
name:=path.Join(m.dir,m.fileName)
file,err:=os.OpenFile(name,os.O_CREATE|os.O_APPEND|os.O_WRONLY,0755)
iferr!=nil{
panic(err)
}
m.file=file
m.outs[logTypeStd]=os.Stdout
m.outs[logTypeFile]=file
}
func(m*myLog)checkLogSize(){
ifm.file==nil{
return
}
m.Lock()
deferm.Unlock()
fileInfo,err:=m.file.Stat()
iferr!=nil{
panic(err)
}
ifm.size>fileInfo.Size(){
return
}
//需要分割
newName:=path.Join(m.dir,time.Now().Format("2006_01_02_15:04:03")+".log")
name:=path.Join(m.dir,m.fileName)
err=os.Rename(name,newName)
iferr!=nil{
panic(err)
}
file,err:=os.OpenFile(name,os.O_CREATE|os.O_APPEND|os.O_WRONLY,0755)
iferr!=nil{
panic(err)
}
m.file.Close()
m.file=file
m.outs[logTypeFile]=file
return
}
func(m*myLog)write(levelstring,contentstring){
m.checkLogSize()
varcolorTextint
switchlevel{
caselevelT:
colorText=colorBlue
caselevelW:
colorText=colorYellow
caselevelE:
colorText=colorRed
}
fork,wr:=rangem.outs{
ifk==logTypeStd{
fmt.Fprintf(wr,setColor(content,colorText))
}else{
fmt.Fprintf(wr,content)
}
}
}
funcWithSize(sizeint64)LogOption{
returnfunc(log*myLog){
log.size=size
}
}
funcWithLogDir(dirstring)LogOption{
returnfunc(log*myLog){
log.dir=dir
}
}
funcWithFileName(namestring)LogOption{
returnfunc(log*myLog){
log.fileName=name
}
}
funcInitLogger(args...LogOption){
defaultLogger.Do(func(){
for_,af:=rangeargs{
af(defaultLogger)
}
defaultLogger.init()
})
}
//Info
funcT(formatstring,v...interface{}){
_,file,line,_:=runtime.Caller(1)
timeStr:=time.Now().Format("2006-01-0215:04:05.0000")+""
codeLine:="["+timeStr+shortFileName(file)+":"+strconv.Itoa(line)+"]"
content:=levelT+codeLine+fmt.Sprintf(format,v...)+"\n"
defaultLogger.write(levelT,content)
}
//Error
funcE(formatstring,v...interface{}){
_,file,line,_:=runtime.Caller(1)
timeStr:=time.Now().Format("2006-01-0215:04:05.0000")+""
codeLine:="["+timeStr+shortFileName(file)+":"+strconv.Itoa(line)+"]"
content:=levelE+codeLine+fmt.Sprintf(format,v...)+"\n"
defaultLogger.write(levelE,content)
}
//Warn
funcW(formatstring,v...interface{}){
_,file,line,_:=runtime.Caller(1)
timeStr:=time.Now().Format("2006-01-0215:04:05.0000")+""
codeLine:="["+timeStr+shortFileName(file)+":"+strconv.Itoa(line)+"]"
content:=levelW+codeLine+fmt.Sprintf(format,v...)+"\n"
defaultLogger.write(levelW,content)
}
funcisExist(pathstring)bool{
_,err:=os.Stat(path)
iferr!=nil{
ifos.IsExist(err){
returntrue
}
returnfalse
}
returntrue
}
funcshortFileName(filestring)string{
short:=file
fori:=len(file)-1;i>0;i--{
iffile[i]=='/'{
short=file[i+1:]
break
}
}
returnshort
}
funcsetColor(msgstring,textint)string{
returnfmt.Sprintf("%c[%dm%s%c[0m",0x1B,text,msg,0x1B)
}
最後
如有不足,還請不吝指教!
附上程式碼地址:logUtil[5],歡迎指正!
參考資料
[1]logrus: https://github.com/sirupsen/logrus
[2]zap: https://github.com/uber-go/zap
[3]zerolog: https://github.com/rs/zerolog
[4]seelog: https://github.com/cihub/seelog
[5]logUtil: https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fpyihe%2Futil%2Ftree%2Fmaster%2FlogUtil
本文作者:pyihe
原文連結:
https://pyihe.github.io/2020/05/31/Go%E8%AF%AD%E8%A8%80%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E7%9A%84%E6%97%A5%E5%BF%97%E8%AE%B0%E5%BD%95%E5%8A%9F%E8%83%BD.html
推薦閱讀
實戰專案:用 Go 實現進度條功能
站長 polarisxu
自己的原創文章
不限於 Go 技術
職場和創業經驗
Go語言中文網
每天為你
分享 Go 知識
Go愛好者值得關注