<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 實戰(zhàn):實現(xiàn)一個簡單的日志庫

          共 6671字,需瀏覽 14分鐘

           ·

          2020-09-02 05:03

          點擊上方藍色“Go語言中文網(wǎng)”關(guān)注我們,領(lǐng)全套Go資料,每天學(xué)習(xí)?Go?語言

          編者按:本文實現(xiàn)的簡單日志庫不一定適合你,但可能會給你一些啟發(fā)、借鑒

          前言

          一個完整的日志庫不僅僅涵蓋日志記錄功能,還要包括日志 level、行號、文件切分,甚至包含統(tǒng)計與分析等,Go 語言中的日志庫也是很多,其中知名度比較高的有:

          庫名star
          logrus[1]14940
          zap[2]9827
          zerolog[3]3386
          seelog[4]1464

          備注:star 數(shù)獲取時間為 2020-05-28 23:26:00

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

          思路

          1. 功能設(shè)計

            根據(jù)自己的需求,我想要的日志記錄功能有:

            • 按照 level 輸出日志
            • 能夠同時輸出到文件和控制臺
            • 控制臺能夠根據(jù) level 將內(nèi)容輸出為不同顏色
            • 日志文件根據(jù)大小進行分割
            • 輸出行號
          2. API 設(shè)計

            一般來說,根據(jù) level 不同,設(shè)計有不同的 API,level 大概可以分為: trace、warn、error、fatal, 也就是說對外的 API 可以概括為: T(...inter), W(...), E(...), F(...)

          type?logger?interface{
          ????T(format?string,?v?...interface{})
          ????W(format?string,?v?...interface{})
          ????E(format?string,?v?...interface{})
          ????F(format?string,?v?...interface{})
          }
          1. 結(jié)構(gòu)設(shè)計

            根據(jù)需求,日志記錄器 logger 的結(jié)構(gòu)需要包含 writers、文件名、文件保存路徑、文件分割大小 完整結(jié)構(gòu)設(shè)計如下:

          type?myLog?struct?{
          ???sync.Once
          ???sync.Mutex?????????????????????//用于outs并發(fā)訪問
          ???outs?????map[logType]io.Writer?//writer集合
          ???file?????*os.File??????????????//文件句柄
          ???fileName?string????????????????//日志名
          ???dir??????string????????????????//日志存放路徑
          ???size?????int64?????????????????//單個日志文件的大小限制
          }
          1. 關(guān)鍵方法實現(xiàn)

            • 日志文件大小檢測
          func?(m?*myLog)?checkLogSize()?{
          ???if?m.file?==?nil?{
          ???????return
          ???}
          ???m.Lock()
          ???defer?m.Unlock()?//此處必須加鎖,否則會出現(xiàn)并發(fā)問題
          ???fileInfo,?err?:=?m.file.Stat()
          ???if?err?!=?nil?{
          ???????panic(err)
          ???}
          ???if?m.size?>?fileInfo.Size()?{
          ???????return
          ???}
          ???//需要分割,重新打開一個新的文件句柄替換老的,并關(guān)閉老的文件句柄,
          ???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)
          ???if?err?!=?nil?{
          ???????panic(err)
          ???}

          ???file,?err?:=?os.OpenFile(name,?os.O_CREATE|os.O_APPEND|os.O_WRONLY,?0755)
          ???if?err?!=?nil?{
          ???????panic(err)
          ???}

          ???m.file.Close()
          ???m.file?=?file
          ???m.outs[logTypeFile]?=?file
          ???return
          }
          • 控制臺帶顏色輸出內(nèi)容
          func?setColor(msg?string,?text?int)?string?{
          ????return?fmt.Sprintf("%c[%dm%s%c[0m",?0x1B,?text,?msg,?0x1B)
          }
          • 獲取行號
          func?shortFileName(file?string)?string?{
          ???short?:=?file
          ???for?i?:=?len(file)?-?1;?i?>?0;?i--?{
          ???????if?file[i]?==?'/'?{
          ???????????short?=?file[i+1:]
          ???????????break
          ???????}
          ???}
          ???return?short
          }

          完整代碼實現(xiàn)

          package?logUtil

          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"

          ????logTypeStd?logType?=?iota?+?1
          ????logTypeFile
          )

          type?(
          ????logType?int

          ????LogOption?func(log?*myLog)

          ????myLog?struct
          ?{
          ????????sync.Once
          ????????sync.Mutex
          ????????outs?????map[logType]io.Writer?//writer集合
          ????????file?????*os.File??????????????//文件句柄
          ????????fileName?string????????????????//日志名
          ????????dir??????string????????????????//日志存放路徑
          ????????size?????int64?????????????????//單個日志文件的大小限制
          ????}
          )

          var?(
          ????defaultLogger?=?&myLog{}
          )

          func?(m?*myLog)?init()?{
          ????if?m.dir?==?""?{
          ????????m.dir?=?defaultLogDir
          ????}
          ????if?m.fileName?==?""?{
          ????????m.fileName?=?defaultLogName
          ????}
          ????if?m.size?==?0?{
          ????????m.size?=?defaultFileSize
          ????}?else?{
          ????????if?m.size?????????????panic(fmt.Sprintf("invalid?size:?%d",?m.size))
          ????????}
          ????}

          ????if?m.outs?==?nil?{
          ????????m.outs?=?make(map[logType]io.Writer)
          ????}
          ????if?!isExist(m.dir)?{
          ????????if?err?:=?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)
          ????if?err?!=?nil?{
          ????????panic(err)
          ????}

          ????m.file?=?file
          ????m.outs[logTypeStd]?=?os.Stdout
          ????m.outs[logTypeFile]?=?file
          }

          func?(m?*myLog)?checkLogSize()?{
          ????if?m.file?==?nil?{
          ????????return
          ????}
          ????m.Lock()
          ????defer?m.Unlock()
          ????fileInfo,?err?:=?m.file.Stat()
          ????if?err?!=?nil?{
          ????????panic(err)
          ????}
          ????if?m.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)
          ????if?err?!=?nil?{
          ????????panic(err)
          ????}

          ????file,?err?:=?os.OpenFile(name,?os.O_CREATE|os.O_APPEND|os.O_WRONLY,?0755)
          ????if?err?!=?nil?{
          ????????panic(err)
          ????}

          ????m.file.Close()
          ????m.file?=?file
          ????m.outs[logTypeFile]?=?file
          ????return
          }

          func?(m?*myLog)?write(level?string,?content?string)?{
          ????m.checkLogSize()
          ????var?colorText?int
          ????switch?level?{
          ????case?levelT:
          ????????colorText?=?colorBlue
          ????case?levelW:
          ????????colorText?=?colorYellow
          ????case?levelE:
          ????????colorText?=?colorRed
          ????}

          ????for?k,?wr?:=?range?m.outs?{
          ????????if?k?==?logTypeStd?{
          ????????????fmt.Fprintf(wr,?setColor(content,?colorText))
          ????????}?else?{
          ????????????fmt.Fprintf(wr,?content)
          ????????}
          ????}
          }

          func?WithSize(size?int64)?LogOption?{
          ????return?func(log?*myLog)?{
          ????????log.size?=?size
          ????}
          }

          func?WithLogDir(dir?string)?LogOption?{
          ????return?func(log?*myLog)?{
          ????????log.dir?=?dir
          ????}
          }

          func?WithFileName(name?string)?LogOption?{
          ????return?func(log?*myLog)?{
          ????????log.fileName?=?name
          ????}
          }

          func?InitLogger(args?...LogOption)?{
          ????defaultLogger.Do(func()?{
          ????????for?_,?af?:=?range?args?{
          ????????????af(defaultLogger)
          ????????}
          ????????defaultLogger.init()
          ????})
          }

          //Info
          func?T(format?string,?v?...interface{})?{
          ????_,?file,?line,?_?:=?runtime.Caller(1)
          ????timeStr?:=?time.Now().Format("2006-01-02?15:04:05.0000")?+?"?"
          ????codeLine?:=?"["?+?timeStr?+?shortFileName(file)?+?":"?+?strconv.Itoa(line)?+?"]"
          ????content?:=?levelT?+?codeLine?+?fmt.Sprintf(format,?v...)?+?"\n"
          ????defaultLogger.write(levelT,?content)
          }

          //Error
          func?E(format?string,?v?...interface{})?{
          ????_,?file,?line,?_?:=?runtime.Caller(1)
          ????timeStr?:=?time.Now().Format("2006-01-02?15:04:05.0000")?+?"?"
          ????codeLine?:=?"["?+?timeStr?+?shortFileName(file)?+?":"?+?strconv.Itoa(line)?+?"]"
          ????content?:=?levelE?+?codeLine?+?fmt.Sprintf(format,?v...)?+?"\n"
          ????defaultLogger.write(levelE,?content)
          }

          //Warn
          func?W(format?string,?v?...interface{})?{
          ????_,?file,?line,?_?:=?runtime.Caller(1)
          ????timeStr?:=?time.Now().Format("2006-01-02?15:04:05.0000")?+?"?"
          ????codeLine?:=?"["?+?timeStr?+?shortFileName(file)?+?":"?+?strconv.Itoa(line)?+?"]"
          ????content?:=?levelW?+?codeLine?+?fmt.Sprintf(format,?v...)?+?"\n"
          ????defaultLogger.write(levelW,?content)
          }

          func?isExist(path?string)?bool?{
          ????_,?err?:=?os.Stat(path)
          ????if?err?!=?nil?{
          ????????if?os.IsExist(err)?{
          ????????????return?true
          ????????}
          ????????return?false
          ????}
          ????return?true
          }

          func?shortFileName(file?string)?string?{
          ????short?:=?file
          ????for?i?:=?len(file)?-?1;?i?>?0;?i--?{
          ????????if?file[i]?==?'/'?{
          ????????????short?=?file[i+1:]
          ????????????break
          ????????}
          ????}
          ????return?short
          }

          func?setColor(msg?string,?text?int)?string?{
          ????return?fmt.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




          推薦閱讀



          學(xué)習(xí)交流 Go 語言,掃碼回復(fù)「進群」即可


          站長 polarisxu

          自己的原創(chuàng)文章

          不限于 Go 技術(shù)

          職場和創(chuàng)業(yè)經(jīng)驗


          Go語言中文網(wǎng)

          每天為你

          分享 Go 知識

          Go愛好者值得關(guān)注


          瀏覽 109
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  伊人久久激情 | 黄在线免费观看 | 黄色大片操逼网站 | 操大香蕉 | 大香蕉在线视频欧美 |