实现异步的日志库输出
发布日期:2021-05-06 23:58:20 浏览次数:27 分类:精选文章

本文共 5560 字,大约阅读时间需要 18 分钟。

需求分析:

1.支持往不同的地方输出日志(终端还是指定的文件)

2.日志分级别(DEBUG,WANRING,ERROR…)

3.支持开关控制,能设置某级别以上日志才输出

4.完整的日志要有时间,行号,文件名,日志级别,日志信息。

5.日志文件要切割(按文件大小或者日期切割)

func main()  {      logger := logfile.NewFileLogger("DEBUG","D:/","guoyilin",1*1024)   name := "郭10"   for  {         logger.Debug("DEBUG日志 %s",name)      logger.Warning("Warning日志")      logger.Error("Error日志")      logger.Panic("Panic日志")      time.Sleep(time.Second)   }}
type LogLevel uint16//底层的几种日志级别都是int类型const  (   Unknown  LogLevel = iota   Debug   Warning   Error   Panic)type  FileLogger struct {      level LogLevel   filePath string   fileName string   maxFileSize int64   fileObj     *os.File         //用作写文件时的os.File对象   fileErrObj  *os.File			   logChan     chan *logMsg	    //用作将来把日志的全部信息传入通道,再由后台goroutine从通道读出来}// 放到通道中的日志全部信息, 传入通道时只需传入指针,不需要传整个结构体(数据太大,并且不好控制数据类型)type logMsg struct {      level LogLevel   logMessage string   fileName string   funcName string   timeStamp string   line int}//main方法中,传入的参数是debug,warning等字符串,需要转型到我们自定义的 日志级别类型func  ParseLeverStTOLogLevel(level string) LogLevel {      s:=strings.ToLower(level)   switch s {      case "debug": return Debug   case "warning": return  Warning   case "error": return  Error   case "panic": return  Panic   default:      return  Unknown   }}//构造方法,生成logger对象func NewFileLogger (logLevelStr ,filePath ,fileName string ,maxFileSize int64) *FileLogger  {      loglevel :=ParseLeverStTOLogLevel(logLevelStr)   f:= &FileLogger{         level:       loglevel,      filePath:    filePath,      fileName:    fileName,      maxFileSize: maxFileSize,      logChan:     make(chan *logMsg,50000),    //注意channel是引用类型,必须初始化,否则会报空指针   }   err :=f.InitFileObj()   if err!=nil {         panic(err)   }   return f}//初始化fileObj 和用于错误级别日志的fileErrObj对象func (f *FileLogger)InitFileObj () error{      //path.join自动组合带有反斜线的 文件名称   fullName := path.Join(f.filePath,f.fileName)   fmt.Printf("打印path.join方法 fullname值: %s \n",fullName)   fileObj ,err :=os.OpenFile(fullName,os.O_APPEND|os.O_WRONLY|os.O_CREATE ,0644)   if err != nil{         fmt.Println("OpenFile failed ")      return  err   }   errObj ,err :=os.OpenFile(fullName+"err.txt",os.O_APPEND|os.O_WRONLY|os.O_CREATE ,0644)   if err != nil{         fmt.Println("ErrFile failed ")      return  err   }   //日志文件打开后 进行对象赋值   f.fileObj = fileObj   f.fileErrObj =errObj   //defer fileObj.Close()   //defer errObj.Close()   return  nil}//获取logger对象调用时,方法名,行号func getInfo(skip int) (fileName,funcName string ,line int)  {      pc,fileName,line ,ok:=runtime.Caller(skip)   if !ok{         fmt.Println("runtime ----error")      return   }   funcName =runtime.FuncForPC(pc).Name()   //取当前路径, 否则默认fileName为绝对路径   //fileName = path.Base(file)   return fileName,funcName,line}//后台gorountine 从通道中取值,并且写入文件func (f *FileLogger) WriteBackGround()  {      for   {      //先判断是否超过单个日志文件的最大内存,大于的话就分割,重命名一个新文件      if f.checkSize(f.fileObj) {            f.splitFile()      }      logTmp := <- f.logChan      _, error := fmt.Fprintf(f.fileObj,"[%v ][%s] [%s %s 行号:%v ]%s \n", logTmp.level, logTmp.timeStamp, logTmp.fileName, logTmp.funcName, logTmp.line, logTmp.logMessage)      if error != nil {            fmt.Println(error)      }      //设置大于Error级别的日志,输出到专门的文档      if logTmp.level >= Debug {            fmt.Fprintf(f.fileErrObj,"[%v ][%s] [%s %s 行号:%v ]%s \n", logTmp.level, logTmp.timeStamp, logTmp.fileName, logTmp.funcName, logTmp.line, logTmp.logMessage)      }   }}func (f *FileLogger)log (lever LogLevel ,msg string,a...interface{   }) {      if f.enable(lever) {         //拼接成字符串信息      LogMessage := fmt.Sprintf(msg, a...)      //logRange := ParseLeverIntToString(lever)      now := time.Now()      fileName, funcName, line := getInfo(3)      //把日志发送到通道中      logTmp := &logMsg{            level:      lever,         logMessage: LogMessage,         fileName:   fileName,         funcName:   funcName,         timeStamp:  now.Format("2006-01-02 15:04:05"),         line:       line,      }      select {         case f.logChan <- logTmp:         for i:=0;i<10 ;i++  {               go f.WriteBackGround()         }      default:      }   }}//检查单个日志文件是否超出 默认的设置大小func (f *FileLogger)checkSize(file *os.File) bool {      fileInfo,err :=file.Stat()   fmt.Println(fileInfo.Size())   if err != nil {         fmt.Printf("get file info failed err: %s" ,err)      return  false   }   return  fileInfo.Size() >=f.maxFileSize}//切割文件,传入一个新的OpenFile参数路径,返回一个新的FileObj,并赋值给 f.fileObj, 用于接下来的写文件func (f *FileLogger)splitFile ()  {      now := time.Now()   nowStr := now.Format("2006-01-02-15040500.txt")   OldFileName  := path.Join(f.filePath ,f.fileName)   NewFilename := OldFileName+nowStr   fmt.Println(NewFilename)   //3.打开一个新的日志文件   fileNewObj,err := os.OpenFile(NewFilename,os.O_CREATE|os.O_WRONLY|os.O_APPEND ,0644)   if err != nil {         fmt.Printf("log 方法中 180行 出错 err信息 := %s" ,err)   }   f.fileObj = fileNewObj}// 控制日志级别输出func (f *FileLogger)enable(level LogLevel) bool{      return  level >= f.level}// a... interface{} 方便以后Debug的时候,可以加入任意的信息,比如error信息。func (f *FileLogger)Debug (msg string ,a... interface{   })  {      if f.enable(Debug){         f.log(Debug,msg,a...)   }}func (f *FileLogger)Warning (msg string ,a... interface{   })  {      if f.enable(Warning){         f.log(Warning,msg,a...)   }}func (f *FileLogger)Error (msg string ,a... interface{   })  {      if f.enable(Error){         f.log(Error,msg,a...)   }}func (f *FileLogger)Panic (msg string ,a... interface{   })  {      if f.enable(Panic){         f.log(Panic,msg,a...)   }}

成果展示:

在这里插入图片描述

可以控制Error级别日志以上才输出(比如生产环境上不需要展示DEBUG日志)

在这里插入图片描述
有专门的一个日志文件收集全部的错误日志
在这里插入图片描述

上一篇:Go mod包管理摒弃传统的Go Path开发
下一篇:并发编程

发表评论

最新留言

关注你微信了!
[***.104.42.241]2025年04月15日 19时04分25秒