Go 日志记录


前言

Go 的日志记录库有很多,功能也很强大,但是在如何记录日志上却比较缺少合适的经验使用上还是有一些讲究的。
下面简要介绍三种使用的 Go 代码写法:

类型定位优点缺陷
完全非侵入适用于对代码修改要求较高的场景,保持原有代码的纯净性无需修改业务代码,只需通过接口进行二次封装。
代码变动小,维护成本相对较低。
记录信息较为简陋,只能记录输入输出的基本信息。
无法记录更详细的操作过程。
对原始代码有较为严格的规范性要求(调用时需要使用接口)
最小侵入适用于需要更灵活记录日志但又不希望对原始代码做过多修改的情况。能记录更多信息,如函数调用参数、返回值等。
对原始代码的修改要求相对较低,适用于规范性较差的代码环境。
需要对原始代码进行一定程度的修改,增加维护成本。
可能影响代码的简洁性和可读性。
侵入式提供最为详细的日志信息,适用于需要深度监控和分析的场景能记录函数执行过程中的各种细节,提供更全面的日志信息。
适用于对日志记录有严格要求的项目。
对原始代码产生大量修改,增加维护成本。
对 coder 的要求较高,需时刻注意进行记录。
可能影响代码的简洁性和可读性。

1. 完全非侵入

// cal.go
package main

import "errors"

type Cal interface {
	Do(input int) (out int, err error)
}

type cal struct {
}

func (cal) Do(input int) (output int, err error) {
	if input < 0 {
		return 0, errors.New("input should be greater than 0")
	}
	input++
	input *= input
	input--
	return 0, err
}

func NewCal() Cal {
	c := new(cal)
	return c
}

此处直接新开一个文件,代表这个是一个新的需求,在 git 日志上也更加直观

// calWithLog.go
package main

import "log"

type calWithLog struct {
	c *cal
}

func (cl calWithLog) Do(input int) (output int, err error) {
	defer func() {
		log.Printf("input: %d\noutput: %d\nerr: %v\n", input, output, err)
	}()
	return cl.c.Do(input)
}

func NewCalWithLog() Cal {
	cl := new(calWithLog)
	cl.c = new(cal)
	return cl
}
package main

import "fmt"

func main() {
	// without log
	c := NewCal()
	Print(c)
	// with log
	cl := NewCalWithLog()
	Print(cl)
}

func Print(c Cal) {
	fmt.Println(c.Do(1))
	fmt.Println(c.Do(-1))
}

可以很清楚的看到,我们对原本的函数调用方基本是没有产生变动的,对于需要日志记录的,我们使用 calWithLog,对于以前就使用的无需进行日志记录的功能,就都不需要进行改变

2. 最小侵入

这种方式对原有代码略有改动,主要是在关键业务逻辑点增加日志输出语句:

// cal.go
package main

import (
    "errors"
    "log"
)

func Do(input int) (output int, err error) {
    log.Printf("input: %d\n", input)
    
    if input < 0 {
        err = errors.New("input should be greater than 0")
        log.Printf("err: %v\n", err)
        return
    }
    
    input++
    input *= input  
    input--

    output = input
    log.Printf("output: %d\n", output)
    return 
}

调用时代码不变:
Copy code
package main

import "fmt"

func main() {
    fmt.Println(Do(1))
    fmt.Println(Do(-1))
}

相比完全非侵入式,这种写法能记录到更多业务细节,同时改动也不算太大。比较适合一些原有代码规范性不高,但又希望尽量少修改的项目。

3. 侵入式

这种写法需要在原有代码中嵌入大量的日志逻辑,甚至为此对代码进行重构。比如把日志功能抽象成一个Logger类:

// logger.go
package main

import "log"

type Logger interface {
    Info(fmtStr string, args ...interface{})
    Warning(fmtStr string, args ...interface{}) 
    Error(fmtStr string, args ...interface{})
}

type logger struct {}

func (logger) Info(fmtStr string, args ...interface{}) {
    log.Printf("[INFO] " + fmtStr, args...)
}

func (logger) Warning(fmtStr string, args ...interface{}) {
    log.Printf("[WARNING] " + fmtStr, args...)  
}

func (logger) Error(fmtStr string, args ...interface{}) {
    log.Printf("[ERROR] " + fmtStr, args...)
}

业务代码中嵌入日志输出:

// cal.go
package main

import "errors"

func Do(input int, l Logger) (output int, err error) {
    l.Info("enter Do, input: %d", input)
    
    if input < 0 {
        err = errors.New("input should be greater than 0")
        l.Error("err: %v", err) 
        return
    }
    
    input++
    l.Info("after add: %d", input)
    
    input *= input
    l.Info("after multiply: %d", input)
    
    input--  
    l.Info("after subtract: %d", input)

    output = input
    return
}

这种写法能自由定义日志格式,记录任意想要的内容,但大大增加了开发成本,代码侵入性强,可读性也会受到一定影响。


如果本文帮助到了你,帮我点个广告可以咩(o′┏▽┓`o)


评论
  目录