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)

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

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

  1. Go语言 context包源码学习

    你必须非常努力,才能看起来毫不费力! 微信搜索公众号[ 漫漫Coding路 ],一起From Zero To Hero ! 前言 日常 Go 开发中,Context 包是用的最多的一个了,几乎所有函数 ...

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

    本段源码可以学习的地方: 1. 考虑到效率问题,可以通过上下文的机制,在属性被访问的时候临时构建: 2. 可以重写一些魔术方法,比如 __new__ 方法,在调用 object.__new__(cls ...

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

    Hadoop源码学习笔记(2) ——进入main函数打印包信息 找到了main函数,也建立了快速启动的方法,然后我们就进去看一看. 进入NameNode和DataNode的主函数后,发现形式差不多: ...

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

    在进行gevent源码学习一分析之后,我还对两个比较核心的问题抱有疑问: 1. gevent.Greenlet.join()以及他的list版本joinall()的原理和使用. 2. 关于在使用mon ...

  5. 06.ElementUI 2.X 源码学习:源码剖析之工程化(一)

    0x.00 前言 在用了5章篇幅 ElementUI源码学习:从零开始搭建Vue组件库汇总 讲解了如何编写一个组件.发布npm以及生成展示文档之后.接下来将分析Element项目的代码结构,学习其工程 ...

  6. Vue2.1.7源码学习

    原本文章的名字叫做<源码解析>,不过后来想想,还是用“源码学习”来的合适一点,在没有彻底掌握源码中的每一个字母之前,“解析”就有点标题党了.建议在看这篇文章之前,最好打开2.1.7的源码对 ...

  7. 【iScroll源码学习03】iScroll事件机制与滚动条的实现

    前言 想不到又到周末了,周末的时间要抓紧学习才行,前几天我们学习了iScroll几点基础知识: 1. [iScroll源码学习02]分解iScroll三个核心事件点 2. [iScroll源码学习01 ...

  8. 【iScroll源码学习00】模拟iScroll

    前言 相信对移动端有了解的朋友对iScroll这个库非常熟悉吧,今天我们就来说下我们移动页面的iScroll化 iScroll是我们必学框架之一,我们这次先根据iScroll功能自己实现其功能,然后再 ...

  9. igmpproxy源码学习——igmpProxyInit()

    igmpproxy源码学习--igmpProxyInit()函数具体解释.igmpproxy初始化 在执行igmpproxy的主程序igmpproxyRun()之前须要对igmpproxy进行一些配置 ...

随机推荐

  1. Fragment与Activity之间的相互通信

    https://blog.csdn.net/u012702547/article/details/49786417 https://blog.csdn.net/carson_ho/article/de ...

  2. Python算法:推导、递归和规约

    Python算法:推导.递归和规约 注:本节中我给定下面三个重要词汇的中文翻译分别是:Induction(推导).Recursion(递归)和Reduction(规约) 本节主要介绍算法设计的三个核心 ...

  3. Python创建、删除桌面、启动组快捷方式的例子分享

    一.Python创桌面建快捷方式的2个例子 例子一: 代码如下: import osimport pythoncomfrom win32com.shell import shell    from w ...

  4. 【转】深入浅出JMS(一)--JMS基本概念

    摘要 The Java Message Service (JMS) API is a messaging standard that allows application components bas ...

  5. 同时装了Python3和Python2,怎么用pip

    作者:匿名用户链接:https://www.zhihu.com/question/21653286/answer/95532074来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...

  6. JS 自己实现Map

    function MyMap() { var items = {}; this.has = function (key) { return key in items; }; this.set = fu ...

  7. js发送get 、post请求的方法简介

    POST请求: 发送的参数格式不同,请求头设置不同,具体参照 Http请求中请求头Content-Type讲解 发送的参数格式不同,后台获取方式也不相同 php请看 php获取POST数据的三种方法 ...

  8. MySql数据库细节使用规范

    一.基础规范 (1)必须使用InnoDB存储引擎 解读:支持事务.行级锁.并发性能更好.CPU及内存缓存页优化使得资源利用率更高 (2)必须使用UTF8字符集 解读:万国码,无需转码,无乱码风险,节省 ...

  9. Deep Learning系统实训之一:深度学习基础知识

    K-近邻与交叉验证 1 选取超参数的正确方法是:将原始训练集分为训练集和验证集,我们在验证集上尝试不同的超参数,最后保留表现最好的那个. 2 如果训练数据量不够,使用交叉验证法,它能帮助我们在选取最优 ...

  10. python 全栈开发,Day121(DButils,websocket)

    昨日内容回顾 1.Flask路由 1.endpoint="user" # 反向url地址 2.url_address = url_for("user") 3.m ...