分享
  1. 首页
  2. 文章

造轮子-golang日志系统

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

介绍

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

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

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏