1. 程式人生 > 其它 >go 開啟檔案控制代碼_Go 實戰:實現一個簡單的日誌庫

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

一千個人有一千個需求,不管是哪個開源日誌庫,用著總有不順手的時候,沒關係,那就自己實現一個吧,相信自己,來,就讓咱們先從實現簡單的日誌記錄功能開始吧~「手動狗頭」

思路

  1. 功能設計

    根據自己的需求,我想要的日誌記錄功能有:

  • 按照 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{})
}
  1. 結構設計

    根據需求,日誌記錄器 logger 的結構需要包含 writers、檔名、檔案儲存路徑、檔案分割大小 完整結構設計如下:

typemyLogstruct{
sync.Once
sync.Mutex//用於outs併發訪問
outsmap[logType]io.Writer//writer集合
file*os.File//檔案控制代碼
fileNamestring//日誌名
dirstring//日誌存放路徑
sizeint64//單個日誌檔案的大小限制
}
  1. 關鍵方法實現

  • 日誌檔案大小檢測
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 實現進度條功能

學習交流 Go 語言,掃碼回覆「進群」即可

16f633a35c6b25c7e1f237ee7475350f.png

站長 polarisxu

自己的原創文章

不限於 Go 技術

職場和創業經驗

Go語言中文網

每天為你

分享 Go 知識

Go愛好者值得關注

a161d168edb641ce3fcda35aedf14212.png