go标准库-log包源码学习

log包是go语言提供的一个简单的日志记录功能,其中定义了一个结构体类型 Logger,是整个包的基础部分,包中的其他方法都是围绕这整个结构体创建的.

Logger结构

Logger结构的定义如下:

type Logger struct {
    mu sync.Mutex
    prefix string
    flag int
    out io.Writer
    buf []byte
}
  • mu 是sync.Mutex,它是一个同步互斥锁,用于保证日志记录的原子性.
  • prefix 是输入的日志每一行的前缀
  • flag 是一个标志,用于设置日志的打印格式
  • out 日志的输出目标,需要是一个实现了 io.Writer接口的对象,如: os.Stdout, os.Stderr, os.File等等
  • buf 用于缓存数据

与此同时还提供了一个构造方法用于创建 Logger:

func New(out io.Writer, prefix string, flag int) *Logger {
    return &Logger{out: out, prefix: prefix, flag: flag}
}

还有围绕Logger结构的几个参数定义的方法:

func (l *Logger) SetOutput(w io.Writer)    // 用于设置日志输出目标
func (l *Logger) SetPrefix(prefix string)  // 用于设置每一行日志的前缀
func (l *Logger) Prefix() string           // 获取当前使用的前缀
func (l *Logger) SetFlags(flag int)        // 用于设置使用的输出标志
func (l *Logger) Flags() int               // 获取当前使用的标志

这些方法都很简单,只是给我们提供了一个可以修改和获取当前日志器的设置的方式.

flag可选值

在 log 包中,定义了一系列的常亮用于表示 flag,如下:

const (
    Ldate         = 1 << iota     // 1 << 0 当地时区的日期: 2009/01/23
    Ltime                         // 1 << 1 当地时区的时间: 01:23:23
    Lmicroseconds                 // 1 << 2 显示精度到微秒: 01:23:23.123123 (应该和Ltime一起使用)
    Llongfile                     // 1 << 3 显示完整文件路径和行号: /a/b/c/d.go:23
    Lshortfile                    // 1 << 4 显示当前文件名和行号: d.go:23 (如果与Llongfile一起出现,此项优先)
    LUTC                          // 1 << 5如果设置了Ldata或者Ltime, 最好使用 UTC 时间而不是当地时区
    LstdFlags     = Ldate | Ltime // 标准日志器的初始值
)

使用方法:

  • 可以单独使用某一个标志,此时只会显示对应的信息
  • 可以多个合并使用,只需要将多个标志使用 | 连接即可

例如:

Ldate | Ltime   // 2017/07/31 08:01:20
Ldate | Ltime | Lmicroseconds | Llongfile   // 2017/07/31 08:01:20.123123 /a/b/c/d.go:23

常用方法

在 log 包中,定义了下面几组方法:

func (l *Logger) Printf(format string, v ...interface{})
func (l *Logger) Print(v ...interface{})
func (l *Logger) Println(v ...interface{}) 

func (l *Logger) Fatal(v ...interface{})
func (l *Logger) Fatalf(format string, v ...interface{})
func (l *Logger) Fatalln(v ...interface{})

func (l *Logger) Panic(v ...interface{})
func (l *Logger) Panicf(format string, v ...interface{})
func (l *Logger) Panicln(v ...interface{})

即 Print*, Fatal*, Painc*, 这里方法结尾的 f 或者 ln 就跟 fmt.Print 的含义是相同的,因此上面这九个方法的使用方式其实与 fmt.Print/f/ln 是一样的.我们直接以没有 f 或 ln 的方法为例来看看三组方法的代码:

func (l *Logger) Print(v ...interface{}) {
    l.Output(2, fmt.Sprint(v...))
}

func (l *Logger) Fatal(v ...interface{}) {
    l.Output(2, fmt.Sprint(v...))
    os.Exit(1)
}

func (l *Logger) Panic(v ...interface{}) {
    s := fmt.Sprint(v...)
    l.Output(2, s)
    panic(s)
}

可以看到其实三个方法 都调用了接收者(也就是Logger类型的实例或指针)的 Output 方法,这个方法后面在说,其实就是字面的意思,即用来输出我们传入进去的字符串(fmt.Sprint方法将我们传入的参数转换为字符串后返回)

不同的地方在于:

  • Print 仅仅是输出了信息
  • Fatal 不仅仅输出了信息,还使程序停止运行
  • Painc 不仅仅输出了信息,还调用了 panic 抛出错误

所以这三个方法的用处就显而易见了.

Output方法

前面介绍了三组方法的内部都是调用了 Output 方法来实现的,也就是说实际的工作实在 Output 方法中执行的.

func (l *Logger) Output(calldepth int, s string) error {
    now := time.Now()
    var file string
    var line int
    l.mu.Lock()
    defer l.mu.Unlock()
    if l.flag&(Lshortfile|Llongfile) != 0 {
        l.mu.Unlock()
        var ok bool
        _, file, line, ok = runtime.Caller(calldepth)
        if !ok {
            file = "???"
            line = 0
        }
        l.mu.Lock()
    }
    l.buf = l.buf[:0]
    l.formatHeader(&l.buf, now, file, line)
    l.buf = append(l.buf, s...)
    if len(s) == 0 || s[len(s)-1] != ‘\n‘ {
        l.buf = append(l.buf, ‘\n‘)
    }
    _, err := l.out.Write(l.buf)
    return err
}

这里需要提前说一下 runtime.Caller 函数,这个函数用于获取调用Go程的栈上的函数调用所在的文件和行号信息。参数为 skip 表示我们需要获取信息的调用层级,返回值为 程序计数器(pc), 文件名,行号以及获取成功与否的标志。

在 Output 方法中,我们做了下面这些事情:

  1. 获取当前事件
  2. 对 Logger实例进行加锁操作
  3. 判断Logger的标志位是否包含 Lshortfile 或 Llongfile, 如果包含进入步骤4, 如果不包含进入步骤5
  4. 获取当前函数调用所在的文件和行号信息
  5. 格式化数据,并将数据写入到 l.out 中,完成输出
  6. 解锁操作

这里我们注意到有一个 callpath 参数,这个参数是用于获取某个指定层级的信息,前面3组方法中,这里使用的都是2, 这是因为,我们真正需要的文件名和行号是 调用 Print, Fatal, Panic 这些方法的地方,因此在调用 runtime.Caller 方法时,需要获取栈中当前位置的前两个位置处的信息.

快捷方式

log 包除了提供了上述一些需要先创建 Logger 实例才能使用的方法之外,还给我们定义了一些快捷的方法,它的实现方式也很简单,其实就是在 log包内预先定义了一个 Logger 实例叫 std:

var std = New(os.Stderr, "", LstdFlags)

然后定义了一些可以直接使用包来调用的方法:

func Output(calldepth int, s string) error
func Fatal(v ...interface{})
func Fatalf(format string, v ...interface{})
func Fatalln(v ...interface{})
func Panic(v ...interface{})
func Panicf(format string, v ...interface{})
func Panicln(v ...interface{})
func Print(v ...interface{})
func Printf(format string, v ...interface{})
func Println(v ...interface{})
func SetFlags(flag int)
func Flags() int
func SetOutput(w io.Writer)
func SetPrefix(prefix string)
func Prefix() string

这些方法的内部实际上大部分都是直接调用了 std 的对应的方法来实现的,不过 Print*, Panic*, Fatal* 这些方法的内部还是调用了 std.Output 方法来实现的.

前面已经涵盖了 log 包中的所有方法,除了下面两个:

  • func itoa(buf *[]byte, i int, wid int)
  • func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int)

这里就不细说了,主要就是用来完成数据的格式化操作的.

原文地址:https://www.cnblogs.com/itogo/p/8645499.html

时间: 2024-10-07 07:08:47

go标准库-log包源码学习的相关文章

图片懒加载库echo.js源码学习

最近不是在学习设计模式吗,然后就看到了代理模式加载图片的样例,然后自己实现了一下,就发现,自己写的这货每次就只能加载一张图片,而且图片要放在哪也是个很严重的问题 然后就去了 gayhub 找了找了找流行的图片懒加载库,这一找,就找到了一个echo.j是,打开一看,源码只有100多行吧,震惊..,看完源码,哎,木有上代理模式呀 仔细学习了下源码:觉得这种做法比较适合图片位置确定场景的吧,比如文章啊,一篇文章老长了,里面有蛮多图片散落在不同的地方,这样就比较合适,有可能有很多图片读者都不会翻到哪里,

python语言线程标准库threading.local源码解读

本段源码可以学习的地方: 1. 考虑到效率问题,可以通过上下文的机制,在属性被访问的时候临时构建: 2. 可以重写一些魔术方法,比如 __new__ 方法,在调用 object.__new__(cls) 前后进行属性的一些小设置: 3. 在本库中使用的重写魔术方法,上下文这两种基础之上,我们可以想到函数装饰器,类装饰器,异常捕获,以及两种上下文的结构: 灵活运用这些手法,可以让我们在代码架构上更上一层,能够更加省时省力. 1 from weakref import ref # ref用在了构造大

Java多线程之JUC包:ReentrantReadWriteLock源码学习笔记

若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/go2sea/p/5634701.html ReentrantLock提供了标准的互斥操作,但在应用中,我们对一个资源的访问有两种方式:读和写,读操作一般不会影响数据的一致性问题.但如果我们使用ReentrantLock,则在需要在读操作的时候也独占锁,这会导致并发效率大大降低.JUC包提供了读写锁ReentrantReadWriteLock,使得读写锁分离,在上述情

Hadoop源码学习笔记(2) ——进入main函数打印包信息

Hadoop源码学习笔记(2) ——进入main函数打印包信息 找到了main函数,也建立了快速启动的方法,然后我们就进去看一看. 进入NameNode和DataNode的主函数后,发现形式差不多: public static void main(String args[]) {     try {       StringUtils.startupShutdownMessage(DataNode.class, args, LOG);       DataNode datanode = crea

Java多线程之JUC包:Semaphore源码学习笔记

若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/go2sea/p/5625536.html Semaphore是JUC包提供的一个共享锁,一般称之为信号量. Semaphore通过自定义的同步器维护了一个或多个共享资源,线程通过调用acquire获取共享资源,通过调用release释放. 源代码: /* * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to lic

python 协程库gevent学习--gevent源码学习(二)

在进行gevent源码学习一分析之后,我还对两个比较核心的问题抱有疑问: 1. gevent.Greenlet.join()以及他的list版本joinall()的原理和使用. 2. 关于在使用monkey_patchall()之后隐式切换的问题. 下面我将继续通过分析源码及其行为来加以理解和掌握. 1. 关于gevent.Greenlet.join()(以下简称join)先来看一个例子: import gevent def xixihaha(msg): print(msg) gevent.sl

Java多线程之JUC包:ReentrantLock源码学习笔记

若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/go2sea/p/5627539.html ReentrantLock是JUC包提供的一种可重入独占锁,它实现了Lock接口.与Semaphore类似,ReentrantLock也提供了两种工作模式:公平模式&非公平模式,也是通过自定义两种同步器FairSync&NonfairSync来实现的. 源代码: /* * ORACLE PROPRIETARY/CONF

Golang中使用log(二):Golang 标准库log的实现

前一篇文章我们看到了Golang标准库中log模块的使用,那么它是如何实现的呢?下面我从log.Logger开始逐步分析其实现. 其源码可以参考官方地址 1.Logger结构 首先来看下类型Logger的定义: type Logger struct { mu sync.Mutex // ensures atomic writes; protects the following fields prefix string // prefix to write at beginning of each

MINA2 源码学习--源码结构梳理

一.mina的整体框架结构及案例: 1.整体结构图: 简述:以上是一张来自网上比较经典的图,整体上揭示了mina的结构,其中IoService包含客户端IoConnector和服务端IoAcceptor两部分.即无论是客户端还是服务端都是这个结构.IoService封装了网络传输层(TCP和UDP),而IoFilterChain中mina自带的filter做了一些基本的操作之外,支持扩展.经过FilterChain之后最终调用IoHandler,IoHandler是具体实现业务逻辑的处理接口,具