Golang官方log包详解

以下全是代码, 详解在注释中, 请从头到尾看

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // Package log implements a simple logging package. It defines a type, Logger,
// with methods for formatting output. It also has a predefined 'standard'
// Logger accessible through helper functions Print[f|ln], Fatal[f|ln], and
// Panic[f|ln], which are easier to use than creating a Logger manually.
// That logger writes to standard error and prints the date and time
// of each logged message.
// Every log message is output on a separate line: if the message being
// printed does not end in a newline, the logger will add one.
// The Fatal functions call os.Exit(1) after writing the log message.
// The Panic functions call panic after writing the log message.
// 官方包注释, 这个包让你自己不需要手动实现日志包, 但是还是我建议还是使用第三方包!
package golog import (
"fmt"
"io"
"os"
"runtime"
"sync"
"time"
) // 稍加注释!
// These flags define which text to prefix to each log entry generated by the Logger.
const (
// Bits or'ed together to control what's printed.
// There is no control over the order they appear (the order listed
// here) or the format they present (as described in the comments).
// The prefix is followed by a colon only when Llongfile or Lshortfile
// is specified.
// For example, flags Ldate | Ltime (or LstdFlags) produce,
// 2009/01/23 01:23:23 message
// while flags Ldate | Ltime | Lmicroseconds | Llongfile produce,
// 2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
// 二进制或标志!
Ldate = 1 << iota // the date in the local time zone: 2009/01/23
Ltime // the time in the local time zone: 01:23:23
Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime.
Llongfile // full file name and line number: /a/b/c/d.go:23
Lshortfile // final file name element and line number: d.go:23. overrides Llongfile
LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone
// 默认不带文件名
LstdFlags = Ldate | Ltime // initial values for the standard 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
} // 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}
} // SetOutput sets the output destination for the logger.
// 设置日志输出的地方
func (l *Logger) SetOutput(w io.Writer) {
l.mu.Lock()
defer l.mu.Unlock()
l.out = w
} // 默认记录对象, 输出为标准输出, 日志没前缀, 格式是标准: 2009/01/23 01:23:23 message
var std = New(os.Stderr, "", LstdFlags) // Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
// 数字填充, 方便时间对齐
func itoa(buf *[]byte, i int, wid int) {
// Assemble decimal in reverse order.
// 填充后最长长度20!
var b [20]byte
bp := len(b) - 1 // 类似辗转相除法取最大公约数, 此处是每次取数字的后一位, 等到数字取不到位了就按照wid开始填充
// 比如 i=106 wid=5:
// wid q byte i
// 4 10 6 106
// 3 1 0 10
// 2 0 1 1
// 1 0 0 0 ===> 不符合i>=0 || wid>1, 跳出
// 00106
for i >= 10 || wid > 1 {
wid--
// 商, 如果第一次i是106, 那么就变成10
// 填到q为0时
q := i / 10
// 余数, 第一次i-q*10就是6 ==> '0' + 6 ==> '6' 原封不动
// 填到q为0时, i也为0了, 这时就按wid开始不断填'0'
b[bp] = byte('0' + i - q*10)
bp--
//fmt.Println(wid,q,string(byte('0' + i - q*10)),i)
i = q
}
// i < 10
b[bp] = byte('0' + i)
*buf = append(*buf, b[bp:]...)
} // formatHeader writes log header to buf in following order:
// * l.prefix (if it's not blank),
// * date and/or time (if corresponding flags are provided),
// * file and line number (if corresponding flags are provided).
// 日志输出时日志头部的格式化
func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {
// 前缀增加, 如果前缀为空, 啥都没发生!
*buf = append(*buf, l.prefix...)
// 二进制或的魅力来了
if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
// 以上表示有使用了时间格式
// 如果有, 转换为UTC时间
if l.flag&LUTC != 0 {
t = t.UTC()
} // 日期转换
if l.flag&Ldate != 0 {
year, month, day := t.Date()
// 年份填充, 长度4位
itoa(buf, year, 4)
*buf = append(*buf, '/')
itoa(buf, int(month), 2)
*buf = append(*buf, '/')
itoa(buf, day, 2) // 空一点后面接文件名或者日志消息
*buf = append(*buf, ' ')
} // 时间或微妙格式
if l.flag&(Ltime|Lmicroseconds) != 0 {
hour, min, sec := t.Clock()
itoa(buf, hour, 2)
*buf = append(*buf, ':')
itoa(buf, min, 2)
*buf = append(*buf, ':')
itoa(buf, sec, 2) // 正常时间完, 是否有微秒?
if l.flag&Lmicroseconds != 0 {
*buf = append(*buf, '.')
itoa(buf, t.Nanosecond()/1e3, 6) // 纳秒转微秒, 长度6位
}
// 空一点后面接文件名或者日志消
*buf = append(*buf, ' ')
}
} // 长文件名, 短文件名输出
if l.flag&(Lshortfile|Llongfile) != 0 {
// 短文件名那么截取最后一个, 如a/b/c ==> c
if l.flag&Lshortfile != 0 {
short := file
for i := len(file) - 1; i > 0; i-- {
if file[i] == '/' {
short = file[i+1:]
break
}
}
file = short
} // 写入缓冲
*buf = append(*buf, file...)
*buf = append(*buf, ':') // 行数, 代码打日志所在的地方, wid为-1表示不填冲
itoa(buf, line, -1) // 后面开始接消息, :再空一点!
*buf = append(*buf, ": "...)
}
} // Output writes the output for a logging event. The string s contains
// the text to print after the prefix specified by the flags of the
// Logger. A newline is appended if the last character of s is not
// already a newline. Calldepth is used to recover the PC and is
// provided for generality, although at the moment on all pre-defined
// paths it will be 2.
// 日志输出最重要的地方来了!
func (l *Logger) Output(calldepth int, s string) error {
now := time.Now() // get this early.
var file string
var line int
l.mu.Lock()
defer l.mu.Unlock() // 使用文件标志格式, 可能会产生bug!
// 此篇文章: https://www.cnblogs.com/zhangym/p/6709282.html
// 重复加锁时协程会阻塞, 直到锁被解开, 只要不让主协程阻塞, 就不会死锁! 要及时解开锁!
if l.flag&(Lshortfile|Llongfile) != 0 {
// Release lock while getting caller info - it's expensive.
// 先将锁释放, 方便其他协程能更快获取到调用的文件路径. 文件调用获取路径是一个昂贵的过程
// 在这个解锁期间, 其他协程中的一个又会进来Output进行加锁, 加锁的这一个瞬间, 如果获取路径过程结束了, 之前的协程会再次加锁, 两次加锁会报错!
// may BE BUG
l.mu.Unlock()
var ok bool
// 0 表示获取自己的路径
// 1 表示获取上一层调用的路径
// 越往上那么逐次加一 // 在此, 直接调用Output, 那么calldepth为1时可以知道是谁调用了Output, 但是Output被Print等又封装了一层, 所以是2!
_, file, line, ok = runtime.Caller(calldepth) // 昂贵!
if !ok {
file = "???"
line = 0
} // 路径获取完后, 在加锁之后, 其他协程可能也进入开头加锁! Maybe bug
l.mu.Lock()
// 下面这个测试, 可以Lock两次? 只要保证主进程不阻塞即可?
//go func(){
// time.Sleep(time.Duration(2)*time.Second)
// l.mu.Unlock() // 最终还是会解开锁, Golang的智能检测会发现死锁? 如果主协程在一定时间内还没有响应!
//}()
//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
} // 下面的都是采用Output包装而来
// Printf calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Printf.
func (l *Logger) Printf(format string, v ...interface{}) {
l.Output(2, fmt.Sprintf(format, v...))
} // Print calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Print.
func (l *Logger) Print(v ...interface{}) { l.Output(2, fmt.Sprint(v...)) } // Println calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Println.
func (l *Logger) Println(v ...interface{}) { l.Output(2, fmt.Sprintln(v...)) } // Fatal is equivalent to l.Print() followed by a call to os.Exit(1).
func (l *Logger) Fatal(v ...interface{}) {
l.Output(2, fmt.Sprint(v...))
os.Exit(1)
} // Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1).
func (l *Logger) Fatalf(format string, v ...interface{}) {
l.Output(2, fmt.Sprintf(format, v...))
os.Exit(1)
} // Fatalln is equivalent to l.Println() followed by a call to os.Exit(1).
func (l *Logger) Fatalln(v ...interface{}) {
l.Output(2, fmt.Sprintln(v...))
os.Exit(1)
} // Panic is equivalent to l.Print() followed by a call to panic().
func (l *Logger) Panic(v ...interface{}) {
s := fmt.Sprint(v...)
l.Output(2, s)
panic(s)
} // Panicf is equivalent to l.Printf() followed by a call to panic().
func (l *Logger) Panicf(format string, v ...interface{}) {
s := fmt.Sprintf(format, v...)
l.Output(2, s)
panic(s)
} // Panicln is equivalent to l.Println() followed by a call to panic().
func (l *Logger) Panicln(v ...interface{}) {
s := fmt.Sprintln(v...)
l.Output(2, s)
panic(s)
} // 以下都是原子更新
// Flags returns the output flags for the logger.
func (l *Logger) Flags() int {
l.mu.Lock()
defer l.mu.Unlock()
return l.flag
} // SetFlags sets the output flags for the logger.
func (l *Logger) SetFlags(flag int) {
l.mu.Lock()
defer l.mu.Unlock()
l.flag = flag
} // Prefix returns the output prefix for the logger.
func (l *Logger) Prefix() string {
l.mu.Lock()
defer l.mu.Unlock()
return l.prefix
} // SetPrefix sets the output prefix for the logger.
func (l *Logger) SetPrefix(prefix string) {
l.mu.Lock()
defer l.mu.Unlock()
l.prefix = prefix
} // SetOutput sets the output destination for the standard logger.
func SetOutput(w io.Writer) {
std.mu.Lock()
defer std.mu.Unlock()
std.out = w
} // Flags returns the output flags for the standard logger.
func Flags() int {
return std.Flags()
} // SetFlags sets the output flags for the standard logger.
func SetFlags(flag int) {
std.SetFlags(flag)
} // Prefix returns the output prefix for the standard logger.
func Prefix() string {
return std.Prefix()
} // SetPrefix sets the output prefix for the standard logger.
func SetPrefix(prefix string) {
std.SetPrefix(prefix)
} // These functions write to the standard logger. // Print calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Print.
func Print(v ...interface{}) {
std.Output(2, fmt.Sprint(v...))
} // Printf calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
func Printf(format string, v ...interface{}) {
std.Output(2, fmt.Sprintf(format, v...))
} // 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...))
} // Fatal is equivalent to Print() followed by a call to os.Exit(1).
func Fatal(v ...interface{}) {
std.Output(2, fmt.Sprint(v...))
os.Exit(1)
} // Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
func Fatalf(format string, v ...interface{}) {
std.Output(2, fmt.Sprintf(format, v...))
os.Exit(1)
} // Fatalln is equivalent to Println() followed by a call to os.Exit(1).
func Fatalln(v ...interface{}) {
std.Output(2, fmt.Sprintln(v...))
os.Exit(1)
} // Panic is equivalent to Print() followed by a call to panic().
func Panic(v ...interface{}) {
s := fmt.Sprint(v...)
std.Output(2, s)
panic(s)
} // Panicf is equivalent to Printf() followed by a call to panic().
func Panicf(format string, v ...interface{}) {
s := fmt.Sprintf(format, v...)
std.Output(2, s)
panic(s)
} // Panicln is equivalent to Println() followed by a call to panic().
func Panicln(v ...interface{}) {
s := fmt.Sprintln(v...)
std.Output(2, s)
panic(s)
} // Output writes the output for a logging event. The string s contains
// the text to print after the prefix specified by the flags of the
// Logger. A newline is appended if the last character of s is not
// already a newline. Calldepth is the count of the number of
// frames to skip when computing the file name and line number
// if Llongfile or Lshortfile is set; a value of 1 will print the details
// for the caller of Output. // 默认的让你再包装一层的函数...
func Output(calldepth int, s string) error {
return std.Output(calldepth+1, s) // +1 for this frame.
}

Golang官方log包详解的更多相关文章

  1. Golang Context 包详解

    Golang Context 包详解 0. 引言 在 Go 语言编写的服务器程序中,服务器通常要为每个 HTTP 请求创建一个 goroutine 以并发地处理业务.同时,这个 goroutine 也 ...

  2. 常见 jar包详解

        常见 jar包详解 jar包 用途 axis.jar SOAP引擎包 commons-discovery-0.2.jar 用来发现.查找和实现可插入式接口,提供一些一般类实例化.单件的生命周期 ...

  3. Spring 3.x jar 包详解 与 依赖关系

    以下的内容我会持续更新(当然是我有新发现的时候); 以下内容是我在网上搜索.整理.修改的而成的内容.由于很多内容都是转载了,无法追溯到源头,因此无法一一对原作者进行道谢. 这几天,我查阅大量的官方的文 ...

  4. Spring 3.x jar 包详解 与 依赖关系(转)

    以下的内容我会持续更新(当然是我有新发现的时候); 以下内容是我在网上搜索.整理.修改的而成的内容.由于很多内容都是转载了,无法追溯到源头,因此无法一一对原作者进行道谢. 这几天,我查阅大量的官方的文 ...

  5. 转:android Support 兼容包详解

    本文转自stormzhang的ANDROID SUPPORT兼容包详解 背景 来自于知乎上邀请回答的一个问题Android中AppCompat和Holo的一个问题?, 看来很多人还是对这些兼容包搞不清 ...

  6. Android SDK中的Support兼容包详解

    这篇文章主要介绍了Android SDK中的Support兼容包详解,本文详细区分了Support Library的版本区别.各种Theme的概念和使用注意事项等内容,需要的朋友可以参考下 背景 来自 ...

  7. Spring jar包详解

    Spring jar包详解 org.springframework.aop ——Spring的面向切面编程,提供AOP(面向切面编程)的实现 org.springframework.asm——spri ...

  8. Spring——jar包详解(转)

    Spring——jar包详解 org.springframework.aop ——Spring的面向切面编程,提供AOP(面向切面编程)的实现 org.springframework.asm——spr ...

  9. 2.TCP_IP互联线缆_TCP_UDP报文抓包详解

    TCP_IP互联线缆_TCP_UDP报文抓包详解 2.1网线标准 直通线 交叉线 异种设备互联使用直通线 同种设备互联使用交叉线 TCP和UDP 端口寻址 TCP数据格式 TCP三次握手 UDP数据格 ...

随机推荐

  1. Confluence 实现公司wiki【转】

    Confluence是一个企业级的Wiki软件,可用于在企业.部门.团队内部进行信息共享和协同编辑一.安装过程1 安装并配置mysql [root@vm1 ~]# /etc/my.cnf charac ...

  2. word: 插入或修改文字时后面的字消失 解决办法

    在编辑Word文档中的文字时,我们有时需要插入或修改文字,可是在插入或修改时会发现改动处后面的文字会消失.比如插入或修改3个字,后面的文字随之也会消失3个,这时该怎么办呢? 点击-“文件”-“选项”- ...

  3. Docs-.NET-C#-指南-语言参考-关键字-值类型:bool

    ylbtech-Docs-.NET-C#-指南-语言参考-关键字-值类型:bool 1.返回顶部 1. bool(C# 参考) 2015/07/20 bool 关键字是 System.Boolean  ...

  4. flutter 数据存储 SP和sqlite

    添加插件: shared_preferences: ^0.4.2 path_provider: ^1.2.0 sqflite: ^0.12.0 import 'dart:async'; import ...

  5. IDEA使用tomcat插件

    在实际项目开发中,特别是分布式项目,往往有N多个子项目需要同时启动测试. 这一切靠本地安装的tomcat是远远不够的,而且繁琐. 这里就需要用到tomca插件. 在pom.xml中引入: <bu ...

  6. Qt编写气体安全管理系统6-地图监控

    一.前言 地图监控主要是提供一个地图(可以是平面的也可以是立体彩色的,一般建议鸟瞰图,有3D感),然后设备在对应的地图上,可以切换地图来查看对应区域的设备,一般来说一个区域会有一个地图文件,设备在地图 ...

  7. PP篇7 生产替代料齐套后处理

    扫描可关注本人技术分享公众号,与您一起学习新知! 对于计划订单和生产订单, 当存在替代料的时候,完成齐套性检查后,按照可齐套量进行拆单,并删除不能齐套的替代料(有个删除规则).不能齐套就按照优先级最高 ...

  8. Flink 异步IO访问外部数据(mysql篇)

    接上篇:[翻译]Flink 异步I / O访问外部数据 最近看了大佬的博客,突然想起Async I/O方式是Blink 推给社区的一大重要功能,可以使用异步的方式获取外部数据,想着自己实现以下,项目上 ...

  9. [转]Xmind 8 pro 软件破解版

    链接地址:https://blog.csdn.net/qq_16093323/article/details/80967867 作者博客:http://www.carrotchou.blog/

  10. 【linux基础-err】 tar命令-stdin: not in gzip format

    problem gzip: stdin: not in gzip format tar: Error is not recoverable: exiting now 解决方法 最后发现下载的压缩文件有 ...