造轮子-golang日志系统

豆瓣奶茶 · · 723 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

介绍

golang的log包已经提供了比较完善的功能,我们只是做了一些简单的封装

主要就是构造了Writer,这个writer每次调用Write()的时候,执行了两步
第一步就是在终端输出,其实就是写到终端里
第二部就是写入到我们的日志文件。写入到日志文件的时候,有个大小判断,超过预定的大小后,就会进行分割和压缩

golang原生日志系统

log包简单实用

func main() {
    log.Println("this is my first log")
}

进入到log包中,看Println()函数

// Println calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Println.
func Println(v ...interface{}) {
    std.Output(2, fmt.Sprintln(v...))
}

再网上追溯

Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
var std = New(os.Stderr, "", LstdFlags)
// New creates a new Logger. The out variable sets the
// destination to which log data will be written.
// The prefix appears at the beginning of each generated log line.
// The flag argument defines the logging properties.
func New(out io.Writer, prefix string, flag int) *Logger {
    return &Logger{out: out, prefix: prefix, flag: flag}
}

这里的Logger就是一切的重点了

// A Logger represents an active logging object that generates lines of
// output to an io.Writer. Each logging operation makes a single call to
// the Writer's Write method. A Logger can be used simultaneously from
// multiple goroutines; it guarantees to serialize access to the Writer.
type Logger struct {
    mu     sync.Mutex // ensures atomic writes; protects the following fields
    prefix string     // prefix to write at beginning of each line
    flag   int        // properties
    out    io.Writer  // destination for output
    buf    []byte     // for accumulating text to write
}

我们再看Output 函数

func (l *Logger) Output(calldepth int, s string) error {
    ...
    ...
    _, err := l.out.Write(l.buf)
    return err
}

其实就是把日志写入到一个文件中。只是终端有默认的文件而已。

构建我们自己的Logger

上面的例子,当我们没有创建Logger时,系统会使用默认的Logger,也就是Stdout.
接下来,我们构建一个自己的Logger ,让日志输出到我们自己指定的文件中

func main() {
    //logFile,_ := os.Open("./test.log") /
    logFile, _ := os.OpenFile("test.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    logger := log.New(logFile, "TEST", log.Lshortfile|log.Ldate|log.Ltime)
    for i := 0; i < 10; i++ {
        logger.Printf("this is my %v test log",i)
    }
}

注意两点

  1. open(filename) 默认是以只读方式打开,那我们就不能写入了
func Open(name string) (*File, error) {
    return OpenFile(name, O_RDONLY, 0)
}
  1. os.O_APPEND以追加模式
    test.log
TEST2019/01/10 14:25:35 test01.go:13: this is my 0 test log
TEST2019/01/10 14:25:35 test01.go:13: this is my 1 test log
TEST2019/01/10 14:25:35 test01.go:13: this is my 2 test log
TEST2019/01/10 14:25:35 test01.go:13: this is my 3 test log
TEST2019/01/10 14:25:35 test01.go:13: this is my 4 test log
...

开始造轮子

第一步就是在终端输出,其实就是写到终端里
第二部就是写入到我们的日志文件。写入到日志文件的时候,有个大小判断,超过预定的大小后,就会进行分割和压缩
接下来展示一下目录结构

mlog  定义了我们日志系统,还有日志子系统
    log.go  日志系统接口实现和子系统的创建
    interface.go  日志系统接口
rotator  
    rotator.go  日志写入文件、日志分割、日志压缩
log.go  项目的日志配置文件,创建日志系统及子系统
config.go 项目的配置文件,加载日志等级,用配置的日志文件初始化rotator
main.go 项目入口文件

源码
https://github.com/naichadouban/logProject

分析

首先在log.go文件中,初始化后台日志系统,还有日志子系统。

var (
    backendLog = mlog.NewBackend(logWriter{})  //往终端中写入日志 
    logRotator *rotator.Rotator  // 往文件中写入日志
    Mainlog     = backendLog.Logger("HCD")  //日志子系统
    TESTlog = backendLog.Logger("TEST")  // 日志子系统
)

然后我们会在config.go 会初始化 logRatator,设置日志等级

initLogRotator("./test.log")  // 这里已经设置了日志输出文件
setLogLevels(DebugLevel)

当我们调用子日志系统打印日志

Mainlog.Infof("this si my log %v",i)

判断日志级别后,调用Backend打印日志,BackendLog统一管理各个子日志系统的日志

func (l *slog) Infof(format string, args ...interface{}) {
    lvl := l.Level()
    if lvl <= LevelInfo {
        l.b.printf("INF", l.tag, format, args...)
    }
}

然后我们再看Backend.printf()函数

    b.mu.Lock()
    b.w.Write(*bytebuf)
    b.mu.Unlock()

b.w.Write(*bytebuf),这个w.Write()是我们实现io.Writer接口的Write函数。
他内部有两个Write()

type logWriter struct{}
func (logWriter) Write(p []byte)(n int,err error){
    os.Stdout.Write(p)  //标准输出,就是我们看到的前台显示
    logRotator.Write(p)  // rotator的输出,就是文件记录中的操作
    return len(p),nil
}
backendLog = mlog.NewBackend(logWriter{})

我们再分析logWriter的Write方法

os.Stdout.Write(p) 

这个是系统的标准数据,暂时不讨论

logRotator.Write(p) 
func (r *Rotator) Write(p []byte) (n int, err error) {
   // 写入文件
    n, _ = r.out.Write(p)
    r.size += int64(n)
  // 日志拆分,压缩
    if r.size >= r.threshold && len(p) > 0 && p[len(p)-1] == '\n' {
        err := r.rotate()
        if err != nil {
            return 0, err
        }
        r.size = 0
    }

    return n, nil
}

具体的日志压缩,可以看源码


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:豆瓣奶茶

查看原文:造轮子-golang日志系统

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

723 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传