造轮子-golang日志系统

栏目: Go · 发布时间: 7年前

内容简介:golang的log包已经提供了比较完善的功能,我们只是做了一些简单的封装主要就是构造了第一步就是在终端输出,其实就是写到终端里

介绍

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 项目入口文件

分析

首先在 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日志系统》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

高效程序员的45个习惯

高效程序员的45个习惯

Venkat Subramaniam、Andy Hunt / 钱安川、郑柯 / 人民邮电出版社 / 2010-01 / 35.00元

“书中‘切身感受’的内容非常有价值——通过它我们可以做到学有所思,思有所悟,悟有所行。” ——Nathaniel T. Schutta,《Ajax基础教程》作者 “此书通过常理和经验,阐述了为什么你应该在项目中使用敏捷方法。最难得的是,这些行之有效的实战经验,竟然从一本书中得到了。” ——Matthew Johnson,软件工程师 十年来,软件行业发生了翻天覆地的变化。敏捷......一起来看看 《高效程序员的45个习惯》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具