日志项目

日志库需求分析

  • 日志库产生的背景

    • 程序运行是个黑盒
    • 日志是程序之外的表现
    • 通过日志,可以知道程序的健康状态
  • 日志打印的级别

    • Debug:日志最详细,对程序的影响比较大
    • Trace: 用来追踪问题
    • Info: 比较重要的信息,比如访问日志
    • Warn:警告日志,表明程序存在问题
    • Error: 错误日志,运行程序时发生的错误
    • Fatal: 严重错误日志
  • 日志存储的位置

    • 直接输出到控制台
    • 打印到文件里
    • 直接打印到网络中,比如kafka
  • 为什么使用接口

    • 定义日志库的标准或者规范
    • 易于扩展
    • 利于程序维护
  • 日志库的设计
    • 打印各个level的日志
    • 设置级别
    • 构造函数

日志库接口设计

log_base.go 基类

package xlog

import (
   "fmt"
   "os"
   "path/filepath"
   "time"
)

type LogData struct {
   timeStr  string
   levelStr string
   module   string
   filename string
   funcName string
   lineNo   int
   data     string
}

type XLogBase struct {
   level   int
   module  string
}

func (l *XLogBase) writeLog(file *os.File,logData *LogData) {
   fmt.Fprintf(file,"%s %s %s (%s:%s:%d) %s\n",
      logData.timeStr, logData.levelStr, logData.module,
      logData.filename, logData.funcName, logData.lineNo, logData.data)
}

func (l *XLogBase) formatLogger(level int, module, format string, args ...interface{}) *LogData {
   now := time.Now()
   timeStr := now.Format("2006-01-02 15:04:05.000")
   levelStr := getLevelStr(level)
   filename, funcName, lineNo := GetLineInfo(3)
   filename = filepath.Base(filename)
   data := fmt.Sprintf(format, args...)
   //fmt.Printf("%s %s %s (%s:%s:%d) %s\n",timeStr,leveStr,module,filename,funcName,lineNo,data)
   return &LogData{
      timeStr:  timeStr,
      levelStr: levelStr,
      module:   module,
      filename: filename,
      funcName: funcName,
      lineNo:   lineNo,
      data:     data,
   }
}

log.go

package xlog

type XLog interface {
   Init() error   //文件初始化
   LogDebug(format string, args ...interface{})
   LogTrace(format string, args ...interface{})
   LogInfo(format string, args ...interface{})
   LogWarn(format string, args ...interface{})
   LogError(format string, args ...interface{})
   LogFatal(format string, args ...interface{})
   Close()
   SetLevel(level int)
}

func NewXLog(logType, level int, filename, module string) XLog {
   //定义接口
   var logger XLog
   switch logType {
   case XLogTypeFile:
      logger = NewXFile(level,filename, module)
   case XLogTypeConsole:
      logger = NewXConsole(level, module)
   default:
      logger = NewXFile(level,filename, module)
   }
   return logger
}

level.go

package xlog

const (
   XLogLevelDebug = iota
   XLogLevelTrace
   XLogLevelInfo
   XLogLevelWarn
   XLogLevelError
   XLogLevelFatal
)

const (
   XLogTypeFile = iota
   XLogTypeConsole
)

func getLevelStr(level int) string {
   switch level {
   case XLogLevelDebug:
      return "DEBUG"
   case XLogLevelTrace:
      return "TRACE"
   case XLogLevelInfo:
      return "INFO"
   case XLogLevelWarn:
      return "WARN"
   case XLogLevelError:
      return "ERROR"
   case XLogLevelFatal:
      return "FATAL"
   default:
      return "UNKNOWN"
   }
}

tool.go: 获取程序运行所在行以及函数名

package xlog

import "runtime"

func GetLineInfo(skip int) (filename, funcName string, lineNo int) {
   pc, file, line, ok := runtime.Caller(skip)
   if ok {
      fun := runtime.FuncForPC(pc)
      funcName = fun.Name()
   }
   filename = file
   lineNo = line
   return
}

文件日志库开发

package xlog

import (
   "os"
)

type XFile struct {
   *XLogBase
   filename string
   file     *os.File
}

func (c *XFile) Init() (err error) {
   c.file,err = os.OpenFile(c.filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY,0755)
   if err != nil {
      return
   }
   return
}

func NewXFile(level int, filename, module string) XLog {
   logger := &XFile{
      filename: filename,
   }
   logger.XLogBase = &XLogBase{
      module: module,
      level: level,
   }
   return logger
}

func (c *XFile) LogDebug(format string, args ...interface{}) {
   if c.level > XLogLevelDebug {
      return
   }
   logData := c.formatLogger(XLogLevelDebug, c.module, format, args...)
   c.writeLog(c.file,logData)
}
func (c *XFile) LogTrace(format string, args ...interface{}) {
   if c.level > XLogLevelTrace {
      return
   }
   logData := c.formatLogger(XLogLevelTrace, c.module, format, args...)
   c.writeLog(c.file,logData)
}
func (c *XFile) LogInfo(format string, args ...interface{}) {
   if c.level > XLogLevelInfo {
      return
   }
   logData := c.formatLogger(XLogLevelInfo, c.module, format, args...)
   c.writeLog(c.file,logData)
}
func (c *XFile) LogWarn(format string, args ...interface{}) {
   if c.level > XLogLevelWarn {
      return
   }
   logData := c.formatLogger(XLogLevelWarn, c.module, format, args...)
   c.writeLog(c.file,logData)
}
func (c *XFile) LogError(format string, args ...interface{}) {
   if c.level > XLogLevelError {
      return
   }
   logData := c.formatLogger(XLogLevelError, c.module, format, args...)
   c.writeLog(c.file,logData)
}
func (c *XFile) LogFatal(format string, args ...interface{}) {
   if c.level > XLogLevelFatal {
      return
   }
   logData := c.formatLogger(XLogLevelFatal, c.module, format, args...)
   c.writeLog(c.file,logData)
}

func (c *XFile) SetLevel(level int) {
   c.level = level
}

func (c *XFile)Close()  {
   if c.file != nil {
      c.file.Close()
   }
}

Console日志开发

package xlog

import (
   "os"
)

type XConsole struct {
   *XLogBase   //指针实现
}

func NewXConsole(level int, module string) XLog {
   logger := &XConsole{}
   //初始化指针,防止panic
   logger.XLogBase = &XLogBase{
      level: level,
      module: module,
   }
   return logger
}

//不需要初始化文件写入
func (c *XConsole)Init() error {
   return nil
}

func (c *XConsole) LogDebug(format string, args ...interface{}) {
   if c.level > XLogLevelDebug {
      return
   }
   logData := c.formatLogger(XLogLevelDebug, c.module, format, args...)
   c.writeLog(os.Stdout,logData)
}

func (c *XConsole) LogTrace(format string, args ...interface{}) {
   if c.level > XLogLevelTrace {
      return
   }
   logData := c.formatLogger(XLogLevelTrace, c.module, format, args...)
   c.writeLog(os.Stdout,logData)
}

func (c *XConsole) LogInfo(format string, args ...interface{}) {
   if c.level > XLogLevelInfo {
      return
   }
   logData := c.formatLogger(XLogLevelInfo, c.module, format, args...)
   c.writeLog(os.Stdout,logData)
}

func (c *XConsole) LogWarn(format string, args ...interface{}) {
   if c.level > XLogLevelWarn {
      return
   }
   logData := c.formatLogger(XLogLevelWarn, c.module, format, args...)
   c.writeLog(os.Stdout,logData)
}

func (c *XConsole) LogError(format string, args ...interface{}) {
   if c.level > XLogLevelError {
      return
   }
   logData := c.formatLogger(XLogLevelError, c.module, format, args...)
   c.writeLog(os.Stdout,logData)
}

func (c *XConsole) LogFatal(format string, args ...interface{}) {
   if c.level > XLogLevelFatal {
      return
   }
   logData := c.formatLogger(XLogLevelFatal, c.module, format, args...)
   c.writeLog(os.Stdout,logData)
}
func (c *XConsole) SetLevel(level int) {
   c.level = level
}

func (c *XConsole) Close() {}

日志使用以及测试

xlog_example/main.go

package main

import (
   "flag"
   "fmt"
   _"fmt"
   "oldBoy/day9/xlog"
)

func logic(logger xlog.XLog) {
   logger.LogDebug("dads1,user_id:%d,username:%s",12331,"sadsaf")
   logger.LogTrace("dads2")
   logger.LogInfo("dads3")
   logger.LogWarn("dads4")
   logger.LogError("sss")
   logger.LogFatal("sss")
}
/*
func testGetLine() {
   //skip =2 深度为2的调用位置,也就是main下的22行
   filename,funcName,lineNo := xlog.GetLineInfo(2)
   fmt.Printf("filename=%s,funcname=%s,linenum=%d\n",filename,funcName,lineNo)
}
 */
func main() {
   //testGetLine()
   var logTypeStr string
   flag.StringVar(&logTypeStr,"type","file","please input a logger type")
   flag.Parse()

   var logType int
   if (logTypeStr == "file") {
      logType = xlog.XLogTypeFile
   }else {
      logType = xlog.XLogTypeConsole
   }
   logger := xlog.NewXLog(logType,xlog.XLogLevelDebug,"./xlog.log","xlog_example")
   err := logger.Init()
   if err != nil {
      fmt.Printf("init error:%v\n",err)
      return
   }
   logic(logger)
}

备注

  • 缺少的功能:
    • 异步写盘
    • 日志切分