zap 自定义日志格式_程序猿学Go: 日志系统

e8985958d84bbbb96815fdd881e3e49d.png

一:log日志包

Golang的log包短小精悍,可以非常轻松的实现日志打印转存功能。不用多说,log支持并发操作(即协程安全-相对于JAVA中的线程安全而言),其结构定义如下:

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 // 用于指定日志输出位置,理论上可以是任务地方,只要实现了io.Writer接口就行
 buf    []byte // for accumulating text to write // 日志内容
}

log包定义了一些日志格式标志:

Ldate         = 1 << iota     // 形如 2009/01/23 的日期
Ltime                         // 形如 01:23:23   的时间
Lmicroseconds                 // 形如 01:23:23.123123   的时间
Llongfile                     // 全路径文件名和行号: /a/b/c/d.go:23 
Lshortfile                    // 文件名和行号: d.go:23
LstdFlags     = Ldate | Ltime // 日期和时间

上述这些标志可以在创建Logger对象时指定(通过下面的New函数创建),也可以通过Logger.setFlat()方法动态指定。

Logger对象通过函数New创建

// 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}
}

log包已默认提供了一个日志对象,并封装了包级别的常用函数,该对象将日志信息输出到标准输出设备中(开箱即用)。

如果只是想输出到终端而不保存到文件等其它地方时,可以直接通过log.Xxxx()方式直接调用,因为这些包级别的函数只是对std对象相关方法的简单封装,如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...))
}

Golang's log模块主要提供了3类接口。分别是 “Print Panic Fatal ”,对每一类接口其提供了3中调用方式,分别是 "Xxxx 、 Xxxxln 、Xxxxf",基本和fmt中的相关函数类似,下面是一个Print的示例:

代码示例:

package main
 
import (
    "log"
)
 
func main(){
    arr := []int {2,3}
    log.Print("Print array ",arr,"n")
    log.Println("Println array",arr)
    log.Printf("Printf array with item [%d,%d]n",arr[0],arr[1])
}

• 对于 log.Fatal 接口,会先将日志内容打印到标准输出,接着调用系统的 os.exit(1) 接口,退出程序并返回状态 1 。但是有一点需要注意,由于是直接调用系统接口退出,defer函数不会被调用,下面是一个Fatal的示例:

package main
import (
 "fmt"
 "log"
)
func test_deferfatal() {
 defer func() {
  fmt.Println("--first--")
 }()
 log.Fatalln("test for defer Fatal")
}
func main() {
 test_deferfatal()
}
 

• 对于log.Panic接口,该函数把日志内容刷到标准错误后调用 panic 函数,下面是一个Panic的示例:

package main
 
import (
 "fmt"
 "log"
)
func test_deferpanic() {
 defer func() {
  fmt.Println("--first--")
  if err := recover(); err != nil {
   fmt.Println(err)
  }
 }()
 log.Panicln("test for defer Panic")
 defer func() {
  fmt.Println("--second--")
 }()
}
func main() {
 test_deferpanic()
}
 

你也可以自定义Logger类型, log.Logger提供了一个New方法用来创建对象:

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

该函数一共有三个参数:

(1)输出位置out,是一个io.Writer对象,该对象可以是一个文件也可以是实现了该接口的对象。通常我们可以用这个来指定日志输出到哪个文件。

(2)prefix 我们在前面已经看到,就是在日志内容前面的东西。我们可以将其置为 "[Info]" 、 "[Warning]"等来帮助区分日志级别。

(3) flags 是一个选项,显示日志开头的东西,可选的值见前面所述

package main
 
import (
 "log"
 "os"
)
 
func main() {
 fileName := "Info_First.log"
 logFile, err := os.Create(fileName)
 defer logFile.Close()
 if err != nil {
  log.Fatalln("open file error")
 }
 debugLog := log.New(logFile, "[Info]", log.Llongfile)
 debugLog.Println("A Info message here")
 debugLog.SetPrefix("[Debug]")
 debugLog.Println("A Debug Message here ")
}
 
 

综合案例分析:

package main
 
import (
 "fmt"
 "log"
 "os"
)
 
func main() {
 fmt.Println("begin TestLog ...")
 file, err := os.Create("test.log")
 if err != nil {
  log.Fatalln("fail to create test.log file!")
 }
 logger := log.New(file, "", log.LstdFlags|log.Llongfile)
 log.Println("111.Println log with log.LstdFlags ...")
 logger.Println("1.Println log with log.LstdFlags ...")
 
 logger.SetFlags(log.LstdFlags)
 
 log.Println("222.Println log without log.LstdFlags ...")
 logger.Println("2.Println log without log.LstdFlags ...")
 
 
 fmt.Println("3 Will this statement be execute ?")
 logger.Panicln("3.Panicln log without log.LstdFlags ...")
 
 // log.Fatal("555.std Fatal log without log.LstdFlags ...")
 // fmt.Println("5 Will this statement be execute ?")
 // logger.Fatal("5.Fatal log without log.LstdFlags ...")
}
 
 

二:Zap日志包使用

日志作为整个代码行为的记录,是程序执行逻辑和异常最直接的反馈。对于整个系统来说,日志是至关重要的组成部分。通过分析日志我们不仅可以发现系统的问题,同时日志中也蕴含了大量有价值可以被挖掘的信息,因此合理地记录日志是十分必要的。

绝大多数的代码中的写日志通常通过各式各样的日志库来实现。日志库提供了丰富的功能,对于 Go 开发者来说大家常用的日志组件通常会有以下几种,下面简单的总结了常用的日志组件的特点:

• seelog: 最早的日志组件之一,功能强大但是性能不佳,不过给社区后来的日志库在设计上提供了很多的启发。

• logrus: 代码清晰简单,同时提供结构化的日志,性能较好。

• zap: uber 开源的高性能日志库,面向高性能并且也确实做到了高性能。

Zap 代码并不是很多,不到 5000 行,比 seelog 少多了( 8000 行左右), 但比logrus(不到 2000 行)要多很多。

下面我们具体使用Zap来进行我们的学习。

package main
import (
 "go.uber.org/zap"
 _ "go.uber.org/zap/zapcore"
 "time"
)
func main() {
 var url string = "Hello"
 logger, _ := zap.NewProduction()
 //logger, _ := zap.NewDevelopment()
 defer logger.Sync()
 logger.Info("failed to fetch URL",
  // Structured context as strongly typed Field values.
  zap.String("url", url),
  zap.Int("attempt", 3),
  zap.Duration("backoff", time.Second),
 )
 logger.Warn("debug log", zap.String("level", url))
 logger.Error("Error Message", zap.String("error", url))
 logger.Panic("Panic log", zap.String("level", url))
}
 

下面我们看下,如何通过HTTP接口动态的改变日志级别

package main
 
import (
 "fmt"
 "go.uber.org/zap"
 "net/http"
 "time"
)
 
func main() {
 alevel := zap.NewAtomicLevel()
 http.HandleFunc("/handle/level", alevel.ServeHTTP)
 go func() {
  if err := http.ListenAndServe(":9090", nil); err != nil {
   panic(err)
  }
 }()
 // 默认是Info级别
 logcfg := zap.NewProductionConfig()
 logcfg.Level = alevel
 
 logger, err := logcfg.Build()
 if err != nil {
  fmt.Println("err", err)
 }
 
 defer logger.Sync()
 for i := 0; i < 1000; i++ {
  time.Sleep(1 * time.Second)
  logger.Debug("debug log", zap.String("level", alevel.String()))
  logger.Info("Info log", zap.String("level", alevel.String()))
 }
}
 
 

查看日志级别

 curl http://localhost:9090/handle/level

输出

 {"level":"info"}

调整日志级别(可选值 “debug” “info” “warn” “error” 等)

 curl -XPUT --data '{"level":"debug"}' http://localhost:9090/handle/level

输出

 {“level":"debug"}

• 下面我们将日志进行序列化文件

uber开源的高性能日志库zap, 除了性能远超logrus之外,还有很多诱人的功能,比如支持日志采样、支持通过HTTP服务动态调整日志级别。不过他原生不支持文件归档,如果要支持文件按大小或者时间归档,必须要使用第三方库, 根据官方资料参考资料1,官方推荐的是 natefinch/lumberjack

package main
 
import (
 "go.uber.org/zap"
 "go.uber.org/zap/zapcore"
 "gopkg.in/natefinch/lumberjack.v2"
)
 
// logpath 日志文件路径
// loglevel 日志级别
func initLogger(logpath string, loglevel string) *zap.Logger {
 
 hook := lumberjack.Logger{
  Filename:   logpath, // 日志文件路径
  MaxSize:    1024,    // megabytes
  MaxBackups: 3,       // 最多保留3个备份
  MaxAge:     7,       //days
  Compress:   true,    // 是否压缩 disabled by default
 }
 w := zapcore.AddSync(&hook)
 
 var level zapcore.Level
 switch loglevel {
 case "debug":
  level = zap.DebugLevel
 case "info":
  level = zap.InfoLevel
 case "error":
  level = zap.ErrorLevel
 default:
  level = zap.InfoLevel
 }
 encoderConfig := zap.NewProductionEncoderConfig()
 encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
 core := zapcore.NewCore(
  zapcore.NewConsoleEncoder(encoderConfig),
  w,
  level,
 )
 
 logger := zap.New(core)
 logger.Info("DefaultLogger init success")
 
 return logger
}
 
func main() {
 logger := initLogger("all.log", "info")
 logger.Info("test log", zap.Int("line", 47))
 logger.Warn("testlog", zap.Int("line", 47))
 
}

内容相当多,大家去github查阅对应的资料即可。

三:依赖管理govendor

长期以来,golang 对外部依赖都没有很好的管理方式,只能从 $GOPATH 下查找依赖。这就造成不同用户在安装同一个项目适合可能从外部获取到不同的依赖库版本,同时当无法联网时,无法编译依赖缺失的项目。

自 1.5 版本开始引入 govendor 工具,该工具将项目依赖的外部包放到项目下的 vendor 目录下(对比 nodejs 的 node_modules 目录),并通过 vendor.json 文件来记录依赖包的版本,方便用户使用相对稳定的依赖。

对于 govendor 来说,主要存在三种位置的包:项目自身的包组织为本地(local)包;传统的存放在 $GOPATH 下的依赖包为外部(external)依赖包;被 govendor 管理的放在 vendor 目录下的依赖包则为 vendor 包。

通过指定包类型,可以过滤仅对指定包进行操作。

命令 功能

命令含义
init初始化 vendor 目录
list列出所有的依赖包
add添加包到 vendor 目录,如 govendor add +external 添加所有外部包
add PKG_PATH添加指定的依赖包到 vendor 目录
update从 $GOPATH 更新依赖包到 vendor 目录
remove从 vendor 管理中删除依赖
status列出所有缺失、过期和修改过的包
fetch添加或更新包到本地 vendor 目录
sync本地存在 vendor.json 时候拉去依赖包,匹配所记录的版本
get类似 go get 目录,拉取依赖包到 vendor 目录

具体使用流程:

1 下载govendor

 go get -u github.com/kardianos/govendor

2 初始化

 govendor init

进入src/projectname,生成vendor文件,里面包含一个vendor.json

3 将GOPATH中本工程使用到的依赖包自动移动到vendor目录中

#说明:如果本地GOPATH没有依赖包,先go get相应的依赖包

 govendor add +external

4 #Go 1.6以上版本默认开启 GO15VENDOREXPERIMENT 环境变量,可忽略该步骤。

#通过设置环境变量 GO15VENDOREXPERIMENT=1 使用vendor文件夹构建文件。

#可以选择 export GO15VENDOREXPERIMENT=1 或 GO15VENDOREXPERIMENT=1 go build 执行编译

 export GO15VENDOREXPERIMENT=1

5 govendor —help

6: 从远程拉取包到vendor下并记录进vendor.json,gopath目录下不会有拉下来的包

govendor fetch [包链接,如:github.com/BurntSushi/toml]

7:根据已有的vendor.json从远程拉取包到vendor目录下,gopath目录下不会有拉下来的包 可以把现有的json文件copy过来

govendor sync

8 :添加包

添加包会同时在json文件中记录

把gopath下的包(只会是被项目引用到的包)添加到vendor目录下 必须vendor目录下没有,且vendor.json中没有记录这个包的时候才会添加,其中一个存在则命令无效亦不报错。

 govendor add +external(+e)

9:把项目中的包添加到vendor目录下 必须vendor目录下没有,且vendor.json中没有记录这个包的时候才会添加,其中一个存在则命令无效亦不报错

govendor add +local(+l)

10:把标准库的包添加到vendor目录下 必须vendor目录下没有,且vendor.json中没有记录这个包的时候才会添加,其中一个存在则命令无效亦不报错。

govendor add +std(+s)

11 把主程序包(main包)添加到vendor目录下 必须vendor目录下没有,且vendor.json中没有记录这个包的时候才会添加,其中一个存在则命令无效亦不报错

govendor add +program(+p)

12:把指定包添加进vendor目录 vendor目录下已存在会报错,vendor.json存在该包记录不会报错会直接覆盖该记录。

govendor add [包链接,如:http://github.com/BurntSushi/toml]

13:添加所有的包,包括gopath、go标准库、项目中的包 gopath和标准库下的包必须是被项目或者项目中引用到的包引用的才会添加

govendor add +all(+a)

14:移除包的时候会把vendor目录和json文件的记录一起移除,只有vendor或者只有json中有仍然会移除而不提示

govendor remove +local(+l)

15:移除未被项目引用的包

govendor remove +unused(+u)

16 :移除指定包

govendor remove [包链接,如:github.com/BurntSushi/toml]

17 :移除vendor下所有的包

govendor remove +vendor

关于’+'后面的包类型的说明

状态缩写状态含义
+locall本地包,即项目自身的包组织
+externale外部包,即被 $GOPATH 管理,但不在 vendor 目录下
+vendorv已被 govendor 管理,即在 vendor 目录下
+stds标准库中的包
+unusedu未使用的包,即包在 vendor 目录下,但项目并没有用到
+missingm代码引用了依赖包,但该包并没有找到
+programp主程序包,意味着可以编译为执行文件
+outsideo外部包和缺失的包
+alla所有的包

• 说明

govendor只是用来管理项目的依赖包,如果GOPATH中本身没有项目的依赖包,则需要通过go get先下载到GOPATH中,再通过govendor add +external拷贝到vendor目录中。Go 1.6以上版本默认开启GO15VENDOREXPERIMENT环境变量。

• 其他常见命令

# View your work.
govendor list
 
# Look at what is using a package
govendor list -v fmt
 
# Specify a specific version or revision to fetch
govendor fetch golang.org/x/net/context@a4bbce9fcae005b22ae5443f6af064d80a6f5a55
govendor fetch golang.org/x/net/context@v1   # Get latest v1.*.* tag or branch.
govendor fetch golang.org/x/net/context@=v1  # Get the tag or branch named "v1".
 
# Update a package to latest, given any prior version constraint
govendor fetch golang.org/x/net/context
 
# Format your repository only
govendor fmt +local
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值