Golang 源码剖析:log 标准库

原文地址:Golang 源码剖析:log 标准库

日志

输出


  1. 2018/09/28 20:03:08 EDDYCJY Blog...

构成

[日期]<空格>[时分秒]<空格>[内容]<n>

源码剖析

Logger


  1. type Logger struct {
  2. mu sync.Mutex
  3. prefix string
  4. flag int
  5. out io.Writer
  6. buf []byte
  7. }

(1) mu:互斥锁,用于确保原子的写入
(2) prefix:每行需写入的日志前缀内容
(3) flag:设置日志辅助信息(时间、文件名、行号)的写入。可选如下标识位:


  1. const (
  2. Ldate = 1 &lt;&lt; iota // value: 1
  3. Ltime // value: 2
  4. Lmicroseconds // value: 4
  5. Llongfile // value: 8
  6. Lshortfile // value: 16
  7. LUTC // value: 32
  8. LstdFlags = Ldate | Ltime // value: 3
  9. )
  • Ldate:当地时区的格式化日期:2009/01/23
  • Ltime:当地时区的格式化时间:01:23:23
  • Lmicroseconds:在 Ltime 的基础上,增加微秒的时间数值显示
  • Llongfile:完整的文件名和行号:/a/b/c/d.go:23
  • Lshortfile:当前文件名和行号:d.go:23,会覆盖 Llongfile 标识
  • LUTC:如果设置 Ldate 或 Ltime,且设置 LUTC,则优先使用 UTC 时区而不是本地时区
  • LstdFlags:Logger 的默认初始值(Ldate 和 Ltime)

(4) out:io.Writer
(5) buf:用于存储将要写入的日志内容

New


  1. func New(out io.Writer, prefix string, flag int) *Logger {
  2. return &amp;Logger{out: out, prefix: prefix, flag: flag}
  3. }
  4. var std = New(os.Stderr, "", LstdFlags)

New 方法用于初始化 Logger,接受三个初始参数,可以定制化而在 log 包内默认会初始一个 std,它指向标准输入流。而默认的标准输出、标准错误就是显示器(输出到屏幕上),标准输入就是键盘。辅助的时间信息默认为 Ldate | Ltime,也就是 2009/01/23 01:23:23


  1. // os
  2. var (
  3. Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
  4. Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
  5. Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
  6. )
  • Stdin:标准输入
  • Stdout:标准输出
  • Stderr:标准错误

Getter

  • Flags
  • Prefix

Setter

  • SetFlags
  • SetPrefix
  • SetOutput

Print.., Fatal.., Panic..


  1. func Print(v ...interface{}) {
  2. std.Output(2, fmt.Sprint(v...))
  3. }
  4. func Printf(format string, v ...interface{}) {
  5. std.Output(2, fmt.Sprintf(format, v...))
  6. }
  7. func Println(v ...interface{}) {
  8. std.Output(2, fmt.Sprintln(v...))
  9. }
  10. func Fatal(v ...interface{}) {
  11. std.Output(2, fmt.Sprint(v...))
  12. os.Exit(1)
  13. }
  14. func Panic(v ...interface{}) {
  15. s := fmt.Sprint(v...)
  16. std.Output(2, s)
  17. panic(s)
  18. }
  19. ...

这一部分介绍最常用的日志写入方法,从源码可得知 XrintlnXrintf 函数 换行可变参数都是通过 fmt 标准库的方法去实现的

FatalPanic 是通过 os.Exit(1)panic(s) 集成实现的。而具体的组装逻辑是通过 Output 方法实现的

Logger.Output


  1. func (l *Logger) Output(calldepth int, s string) error {
  2. now := time.Now() // get this early.
  3. var file string
  4. var line int
  5. l.mu.Lock()
  6. defer l.mu.Unlock()
  7. if l.flag&amp;(Lshortfile|Llongfile) != 0 {
  8. // Release lock while getting caller info - it's expensive.
  9. l.mu.Unlock()
  10. var ok bool
  11. _, file, line, ok = runtime.Caller(calldepth)
  12. if !ok {
  13. file = "???"
  14. line = 0
  15. }
  16. l.mu.Lock()
  17. }
  18. l.buf = l.buf[:0]
  19. l.formatHeader(&amp;l.buf, now, file, line)
  20. l.buf = append(l.buf, s...)
  21. if len(s) == 0 || s[len(s)-1] != '\n' {
  22. l.buf = append(l.buf, '\n')
  23. }
  24. _, err := l.out.Write(l.buf)
  25. return err
  26. }

Output 方法,简单来讲就是将写入的日志事件信息组装并输出,它会根据 flag 标识位的不同来使用 runtime.Caller 去获取当前 goroutine 所执行的函数文件、行号等调用信息(log 标准库中默认深度为 2)。另外如果结尾不是换行符 \n,将自动补全一个换行

Logger.formatHeader


  1. func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {
  2. *buf = append(*buf, l.prefix...)
  3. if l.flag&amp;(Ldate|Ltime|Lmicroseconds) != 0 {
  4. if l.flag&amp;LUTC != 0 {
  5. t = t.UTC()
  6. }
  7. if l.flag&amp;Ldate != 0 {
  8. year, month, day := t.Date()
  9. itoa(buf, year, 4)
  10. *buf = append(*buf, '/')
  11. itoa(buf, int(month), 2)
  12. *buf = append(*buf, '/')
  13. itoa(buf, day, 2)
  14. *buf = append(*buf, ' ')
  15. }
  16. if l.flag&amp;(Ltime|Lmicroseconds) != 0 {
  17. hour, min, sec := t.Clock()
  18. itoa(buf, hour, 2)
  19. *buf = append(*buf, ':')
  20. itoa(buf, min, 2)
  21. *buf = append(*buf, ':')
  22. itoa(buf, sec, 2)
  23. if l.flag&amp;Lmicroseconds != 0 {
  24. *buf = append(*buf, '.')
  25. itoa(buf, t.Nanosecond()/1e3, 6)
  26. }
  27. *buf = append(*buf, ' ')
  28. }
  29. }
  30. if l.flag&amp;(Lshortfile|Llongfile) != 0 {
  31. if l.flag&amp;Lshortfile != 0 {
  32. short := file
  33. for i := len(file) - 1; i &gt; 0; i-- {
  34. if file[i] == '/' {
  35. short = file[i+1:]
  36. break
  37. }
  38. }
  39. file = short
  40. }
  41. *buf = append(*buf, file...)
  42. *buf = append(*buf, ':')
  43. itoa(buf, line, -1)
  44. *buf = append(*buf, ": "...)
  45. }
  46. }

该方法主要是用于格式化日志头(前缀),根据入参不同的标识位,添加分隔符和对应的值到日志信息中。执行流程如下:

(1)如果不是空值,则将 prefix 写入 buf

(2)如果设置 LdateLtimeLmicroseconds,则对应将日期和时间写入 buf

(3)如果设置 LshortfileLlongfile,则对应将文件和行号信息写入 buf

Logger.itoa


  1. func itoa(buf *[]byte, i int, wid int) {
  2. // Assemble decimal in reverse order.
  3. var b [20]byte
  4. bp := len(b) - 1
  5. for i &gt;= 10 || wid &gt; 1 {
  6. wid--
  7. q := i / 10
  8. b[bp] = byte('0' + i - q*10)
  9. bp--
  10. i = q
  11. }
  12. // i &lt; 10
  13. b[bp] = byte('0' + i)
  14. *buf = append(*buf, b[bp:]...)
  15. }

该方法主要用于将整数转换为定长的十进制 ASCII,同时给出负数宽度避免左侧补 0。另外会以相反的顺序组合十进制

如何定制化 Logger

在标准库内,可通过其开放的 New 方法来实现各种各样的自定义 Logger 组件,但是为什么也可以直接 log.Print* 等方法呢?


  1. func New(out io.Writer, prefix string, flag int) *Logger

其实是在标准库内,如果你刚刚细心的看了前面的小节,不难发现其默认实现了一个 Logger 组件


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

这也是一个小小的精妙之处 ⭕️

总结

通过查阅 log 标准库的源码,可得知最简单的一个日志包应该如何编写。另外 log 包是在所有涉及到 Logger 的地方都对 sync.Mutex 进行操作(以此解决原子问题),其余逻辑均为组装日志信息和转换数值格式,该包较为经典,可以多读几遍

Golang 源码剖析:log 标准库的更多相关文章

  1. 豌豆夹Redis解决方案Codis源码剖析:Proxy代理

    豌豆夹Redis解决方案Codis源码剖析:Proxy代理 1.预备知识 1.1 Codis Codis就不详细说了,摘抄一下GitHub上的一些项目描述: Codis is a proxy base ...

  2. jdk源码剖析一:OpenJDK-Hotspot源码包目录结构

    开启正文之前,先说一下源码剖析这一系列,就以“死磕到底”的精神贯彻始终,JDK-->JRE-->JVM(以openJDK代替) 最近想看看JDK8源码,但JDK中JVM(安装在本地C:\P ...

  3. petite-vue源码剖析-逐行解读@vue-reactivity之effect

    当我们通过effect将副函数向响应上下文注册后,副作用函数内访问响应式对象时即会自动收集依赖,并在相应的响应式属性发生变化后,自动触发副作用函数的执行. // ./effect.ts export ...

  4. JS魔法堂:mmDeferred源码剖析

    一.前言 avalon.js的影响力愈发强劲,而作为子模块之一的mmDeferred必然成为异步调用模式学习之旅的又一站呢!本文将记录我对mmDeferred的认识,若有纰漏请各位指正,谢谢.项目请见 ...

  5. DICOM医学图形处理:storescp.exe与storescu.exe源码剖析,学习C-STORE请求(续)

    转载:http://blog.csdn.net/zssureqh/article/details/39237649 背景: 上一篇博文中,在对storescp工具源文件storescp.cc和DcmS ...

  6. DICOM医学图像处理:storescp.exe与storescu.exe源码剖析,学习C-STORE请求

    转载:http://blog.csdn.net/zssureqh/article/details/39213817 背景: 上一篇专栏博文中针对PACS终端(或设备终端,如CT设备)与RIS系统之间w ...

  7. strlen源码剖析

      学习高效编程的有效途径之一就是阅读高手写的源代码,CRT(C/C++ Runtime Library)作为底层的函数库,实现必然高效.恰好手中就有glibc和VC的CRT源代码,于是挑了一个相对简 ...

  8. Appuim源码剖析(Bootstrap)

    Appuim源码剖析(Bootstrap) SkySeraph Jan. 26th 2017 Email:skyseraph00@163.com 更多精彩请直接访问SkySeraph个人站点:www. ...

  9. 《STL源码剖析》相关面试题总结

    原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...

随机推荐

  1. Android内存管理-SoftReference的使用

    本文介绍对象的强.软.弱和虚引用的概念.应用及其在UML中的表示. 1.对象的强.软.弱和虚引用 在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象.也就是说,只有 ...

  2. 2018 MAC下安装Redis和Redis可视化工具RDM并连接Redis

    实验环境:一台mac V:10.13.6 一.安装redis brew install redis 二.安装RDM 直接下载安装rdm dmg文件 链接: https://pan.baidu.com/ ...

  3. 06006_redis数据存储类型——String

    1.概述 (1)字符串类型是Redis中最为基础的数据存储类型,它在Redis中是二进制安全的,这意味着该类型可以接受任何格式的数据,如JPEG图像数据或Json对象描述信息等: (2)在Redis中 ...

  4. LoadRunner结果分析 – TPS

    针对吞吐率和 TPS 的关系,这个在结果分析中如何使用,就个人经验和朋友讨论后,提出如下建议指导,欢迎同僚指正. 相关定义 响应时间 = 网络响应时间 + 应用程序响应时间 响应时间 =(N1+N2+ ...

  5. Oracle 高水位(HWM: High Water Mark)

    http://blog.itpub.net/31397003/viewspace-2137246/ http://blog.itpub.net/12778571/viewspace-582695/ h ...

  6. 笔试题&amp;面试题:给定n个数,要求比較次数1.5n同一时候找出最大值和最小值

    写出一个算法,对给定的n个数的序列,返回序列中的最大和最小的数. 设计出一个算法,仅仅须要运行1.5n次比較就能找到序列中最大和最小的数吗?是否能再少? 分析:要求比較次数为1.5n,使用一般的逐个遍 ...

  7. C++中 pair 的使用方法

    #include<iostream> #include<string> #include<map> using namespace std; // pair简单讲就 ...

  8. Android圆角Tag控件的另类实现

    一般的圆角标签控件都是用xml设置shape做实现.可是假设我们想要做一个更加强大通用的的圆角控件,不须要使用者去关心圆角,仅仅设置背景就能够了. 应该怎么实现呢?这个就须要把背景先设置成图片,然后再 ...

  9. NOI.AC: NOIP2018 全国模拟赛习题练习

    闲谈: 最后一个星期还是不浪了,做一下模拟赛(还是有点小虚) #30.candy 题目: 有一个人想买糖吃,有两家商店A,B,A商店中第i个糖果的愉悦度为Ai,B商店中第i个糖果的愉悦度为Bi 给出n ...

  10. DB-MySQL:MySQL 序列使用

    ylbtech-DB-MySQL:MySQL 序列使用 1.返回顶部 1. MySQL 序列使用 MySQL 序列是一组整数:1, 2, 3, ...,由于一张数据表只能有一个字段自增主键, 如果你想 ...