一:log日志包

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

type Logger struct {mu     sync.Mutex // ensures atomic writes; protects the following fieldsprefix 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类接口。分别是 “PrintPanicFatal ”,对每一类接口其提供了3中调用方式,分别是 "Xxxx 、 Xxxxln 、Xxxxf",基本和fmt中的相关函数类似,下面是一个Print的示例:

代码示例:

package mainimport ("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 mainimport ("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 mainimport ("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 mainimport ("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 mainimport ("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 = alevellogger, 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 mainimport ("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,    // megabytesMaxBackups: 3,       // 最多保留3个备份MaxAge:     7,       //daysCompress:   true,    // 是否压缩 disabled by default}w := zapcore.AddSync(&hook)var level zapcore.Levelswitch loglevel {case "debug":level = zap.DebugLevelcase "info":level = zap.InfoLevelcase "error":level = zap.ErrorLeveldefault:level = zap.InfoLevel}encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncodercore := 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

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

状态 缩写状态 含义
+local l 本地包,即项目自身的包组织
+external e 外部包,即被 $GOPATH 管理,但不在 vendor 目录下
+vendor v 已被 govendor 管理,即在 vendor 目录下
+std s 标准库中的包
+unused u 未使用的包,即包在 vendor 目录下,但项目并没有用到
+missing m 代码引用了依赖包,但该包并没有找到
+program p 主程序包,意味着可以编译为执行文件
+outside o 外部包和缺失的包
+all a 所有的包

• 说明

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

fmt打印不显示 go_程序猿学Go: 日志系统相关推荐

  1. SpringBoot系列(三)----某程序猿竟然因为“日志”问题一夜秃头

    某程序猿竟然因为"日志"问题一夜秃头.这是道德的沦丧还是因为什么.... 在一个夜深人静的凌晨两点.一个兢兢业业的程序猿–小张,正在面对着电脑敲着键盘.他正在开发一个公司发不下来的 ...

  2. Web应用程序,简单的日志系统解决方案

    一.简介 今天介绍一下,当你的程序没有日志系统时,如何快速方便查看当前程序日志的解决方案.如果你的程序有日志系统,可以不看本篇博客哈.本文实例是使用 C# 讲解,当然实现的核心思想适用于其他语言开发的 ...

  3. fmt打印不显示 go_golang打印输出fmt和log

    fmt %v 值的默认格式表示.当输出结构体时,扩展标志(%+v)会添加字段名 %#v 值的Go语法表示 %T 值的类型的Go语法表示 %t 单词true或false %% 百分号 %p 表示为十六进 ...

  4. 不同用户同时并发测压_程序猿学GO:并发

    一:并发介绍 Go是并发语言,而不是并行语言.在讨论Go并发性之前,我们必须首先了解什么是并发,以及并发与并行的区别. • 什么是并发 并发(concurrency)是指一次处理大量事情的能力.让我们 ...

  5. 打印心形图案---程序猿才有的浪漫---入门级---C语言实现

    ❤❤❤给初学者们的浪漫❤❤❤ 1.效果展示 2.打印思路 3.配色优化 1.效果展示 2.打印思路 我们整体上可以将心形图案划分为三个部分: 1.心形上方的两个梯形(3行) 2.心形中间的一个矩形(3 ...

  6. 《程序猿的呐喊》读书笔记(下)

    接着<程序猿的呐喊>读书笔记(上).继续分享下篇.这次干货比較多哦.有静动态类型的优缺点.强弱类型系统的对抗.设计模式.程序猿的数学.编译器的重要性以及保守派自由派的较量,一时消化不了的建 ...

  7. 花一千多学python值吗_Python爬虫应该怎么学?程序猿花了一周整理的学习技巧,请收下...

    原标题:Python爬虫应该怎么学?程序猿花了一周整理的学习技巧,请收下 Python爬虫为什么受欢迎 如果你仔细观察,就不难发现,懂爬虫.学习爬虫的人越来越多,一方面,互联网可以获取的数据越来越多, ...

  8. 模仿下列程序自己打印一个趣味图案c语言,趣味程序导学C语言(28页)-原创力文档...

    C 趣味程序导学 语言 (请到附件里下载源代码与课件) 电脑游戏,一个熟悉而诱人的字眼,常常不经意地浮现在我们的脑海当中. 有梦幻神奇的 <传奇>,有惊险刺激的 <反恐精英>, ...

  9. 程序猿,这里有你想学的10门机器学习课程 | 资源

    Hung栗 编译自 Hackernoon  量子位 出品 | 公众号 QbitAI 一个程序猿,如果想在机器学习或者数据科学上补充一下能量,有哪些资源可以用? 来自印度的Javin Paul就是一只这 ...

最新文章

  1. 《自然语言处理入门》不是 NLP 学习路上的万能药
  2. mysql停止服务命令_0789不停止MySQL服务重做备库的方法
  3. Django 聚合查询
  4. VUEJS-checkbox全选全不选
  5. python链表排序_链表排序+末尾各种排序
  6. [Bugku][Web][CTF] 16-29 write up
  7. vs 警告被视为错误
  8. 一文教你安全的关闭线程池
  9. 基于变分模态分解与麻雀优化最小二乘支持向量机的短期电力负荷预测(VMD-SSA-LSSVM)
  10. MyBatisPlus的代码生成器
  11. 中软干两年有用吗_跳槽去中软国际的两大理由?
  12. websocket+kafka+springcloud+springboot实现报文(json)传输,解析,推送
  13. HTML5 视频网站
  14. CTA认证system_process定位联网未明示问题
  15. 全概率公式及贝叶斯公式---先验概率、后验概率
  16. 推荐|微信朋友圈营销的新尝试
  17. 猿辅导-2019-校招笔试
  18. 【优化训练】RePr:Improved Training of Convolutional Filters论文笔记
  19. 新手看spdlog源码做笔记以及附上简单使用手册
  20. 固网应用程序请重启计算机,固网USB打印服务器安装设置指引_固网 HP-1008MFP_办公打印评测试用-中关村在线...

热门文章

  1. Python中classmethod与staticmethod区别
  2. 相关与卷积(数字信号处理)的数学原理及 Python 实现
  3. Python中有几种办法交换两个变量的值?
  4. 7 个习惯帮你提升Python运行性能
  5. Linux下VSCode的安装和使用(VScode C/C++配置 CMake的使用)(GCC、GDB)(各类插件 Snippets、Code Runner、Include Autocomplete)
  6. linux 汇编 gdb报错:Invalid register `eip‘(64位系统没有eip只有rip寄存器)
  7. 测试socket udp 单次传输数据上限(sendto()函数data不能超过65507字节。udp头占8字节,ip头占20字节,加起来正好65535字节)
  8. python 将布尔数组取反的方法 True False(b=(a==False))
  9. python 如何理解 numpy 数组操作中的 axis 参数?
  10. python基础知识四——局部作用域和//运算符