实现异步的日志库输出

需求分析:

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日志)
在这里插入图片描述
有专门的一个日志文件收集全部的错误日志
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值