一、前言¶
本文主要以下几方面介绍Go语言中包管理:
- 什么是包
- 为什么要使用包
- 内置包和第三方包
- 如何使用包
- 如何管理包
- 如何自定义包
- 常用的包
二、什么是包¶
Go语言的包(package)是一种源码封装的方式,可以被看做是一组相关的,并且通用的代码集合。这些包都有自己独立的功能,然后在编写代码时,如果需要用到这些功能,可以导入包直接使用。
三、为什么要使用包¶
使用包管理一般有以下几个主要原因:
- 模块化:包可以将代码模块化,将不同功能的代码分组到不同的包中,使代码更易于管理和维护。这有助于避免代码变得过于庞大和混乱。
- 重用性:通过将相关的功能封装在包中,可以更容易地在不同的项目中重用代码。其他程序员也可以使用你的包,这有助于推广和分享代码。
- 命名空间:包提供了命名空间,可以防止命名冲突。不同的包可以拥有相同的函数或变量名,它们不会相互干扰,因为每个包都有自己的命名空间。
- 可维护性:包提供了封装和隐藏的机制,可以将内部实现细节隐藏起来,只暴露需要公开的接口。这有助于降低代码的耦合度,使代码更容易维护和更新。
- 增强可读性:将相关功能放在不同的包中,有助于代码的组织和清晰性。开发者可以更容易地理解代码的结构和目的。
四、内置包和第三方包¶
4.1 什么是内置包和第三方包¶
内置包(Built-in Packages):内置包是Go语言标准库中包含的包,它们是Go语言的一部分,随着Go的安装而自动安装在系统中。这些包包含了许多基本的功能和工具,如字符串处理、输入输出、并发、网络通信等。点击Go官网包查询后,输入包名称即可查询相关的内置包信息。
第三方包(Third-party Packages):第三方包是由Go社区或其他开发者编写的,不包含在Go标准库中的包。这些包通常提供了各种各样的功能和工具,用于扩展Go语言的功能和满足特定需求。我们可以使用Go的包管理工具(如go get、go mod等)来下载和安装第三方包。
4.2 区别内置包和第三方包¶
内置包和第三方包之间的主要区别如下:
- 来源:内置包是Go语言标准库的一部分,随着Go的安装而自动包含在系统中。而第三方包是由社区或其他开发者创建和维护的,需要使用包管理工具来下载和安装。
- 功能:内置包提供了Go语言的基本功能和工具,如
fmt用于格式化输出、net/http用于构建Web应用等。第三方包提供了各种各样的额外功能,例如数据库连接库、图形库、框架等,用于扩展Go的功能。 - 更新:内置包的更新通常需要升级Go语言本身,因为它们与Go的版本绑定在一起。第三方包可以独立更新,可以使用包管理工具来获取最新版本。
- 社区维护:内置包由Go语言的核心团队维护,因此它们具有高度的可靠性和稳定性。第三方包的质量和可靠性则取决于包的作者和社区支持,因此有时可能会有质量参差不齐的包存在。
五、如何使用包¶
针对不同的使用场景,我们区分出包导入三种不同的方式:
- 使用
import()一次导入多个包 - 使用别名导入
- 特殊的导入,执行
init函数
5.1 使用 import() 一次导入多个包¶
在Go语言中,可以使用 import() 语法一次导入多个包,这些包将以圆括号内的形式列出。这种方式非常方便,特别是需要导入多个相关或共享功能的包时。
使用语法:
import (
"fmt"
"math"
"net/http"
)
在上面的示例中,我们一次性导入了三个不同的包:fmt、math 和 net/http。
使用场景:
- 适用于一次导入多个相关或共享功能的包。
- 用于将多个包的导入语句合并到一个代码块中,以提高代码的可读性。
5.2 使用别名导入¶
Go语言允许使用别名来导入包,这可以防止导入的包名与你的代码中的其他标识符冲突,或者可以用来更改包名的可读性。
使用语法:
import (
"fmt"
m "math" // 使用别名 m 导入 math 包
)
func main() {
x := m.Sqrt(25) // 使用别名 m 调用 math 包中的函数
fmt.Println(x)
}
在上面的示例中,我们使用别名 m 来导入 math 包,然后在代码中使用别名 m 调用 math 包中的函数。
使用场景:
- 用于避免导入包名与你的代码中的其他标识符冲突。
- 可以用于提高包名的可读性,尤其是当导入的包名很长或不太直观时。
5.3 特殊的导入,执行 init 函数¶
在Go语言中,包可以包含一个特殊的函数 init(),它在包被导入时自动执行。这个函数通常用于执行一些初始化操作,例如配置加载、资源分配等。当一个包被导入时,所有包级别的 init() 函数都会按照它们在包中的声明顺序被执行。
使用语法:
package mypackage
import "fmt"
func init() {
fmt.Println("Initializing mypackage...")
}
在上面的示例中,mypackage 包包含一个 init() 函数,在包被导入时会自动执行。这可以用来确保包的初始化操作在包被使用之前得到执行。
使用场景:
- 用于执行包级别的初始化操作,如配置加载、资源分配等。
init函数在包被导入时自动执行,确保初始化操作在包被使用之前得到执行。
六、如何管理包¶
Go Modules是Golang 1.11版本引入的官方包管理工具,Go mod使用go.mod管理项目依赖包的版本、依赖关系、导入和删除信息。使用Go Mod可以非常方便的管理项目依赖的包,以及可以自动处理包和包之间的依赖关系、版本等问题。
6.1 go.mod和go.sum介绍¶
Go Modules包含几个核心文件,用于管理项目的依赖关系、版本信息和模块配置。以下是 Go Modules 的核心文件:
- go.mod:
go.mod文件是 Go Modules 的核心文件之一,用于定义项目的模块路径、依赖关系、版本信息和构建配置。它是必不可少的文件,用于确定项目的模块根路径、Go 版本以及项目依赖的其他模块。 - go.sum:
go.sum文件也是 Go Modules 的核心文件之一,用于记录项目依赖模块的校验和信息。这些校验和用于验证依赖模块的完整性,以确保它们没有被篡改。go.sum文件与go.mod文件一起工作,用于保障项目的安全性和稳定性。
下面是一个典型的 go.mod 文件的示例,将对其内容进行详细分析:
module example.com/mymodule //指定模块的名称
go 1.17 //指定所需Go的最低版本
require ( //本模块所依赖的其他包及版本
github.com/some/package v1.2.3
golang.org/x/net v0.0.0-20211201163146-12345abcdef
)
exclude ( //排除某些依赖项
github.com/unwanted/package v1.0.0
)
replace ( //用来重定向某个依赖项的版本,或者将其替换为另一个模块
golang.org/x/somepackage => github.com/myfork/somepackage v1.0.0
)
-
module example.com/mymodule:这是模块声明,指定了当前项目的模块路径。模块路径是项目的唯一标识符,可以是一个域名或任何全局唯一的字符串。它通常对应于项目的版本控制仓库。 -
go 1.17:这是指定项目所使用的 Go 版本。这个版本号表示项目要求 Go 1.17 或更高版本来构建。这有助于确保在不同环境中构建项目时使用相同的 Go 版本。 require部分:这个部分列出了项目所依赖的外部模块以及它们的版本信息。每个依赖项都以模块路径和版本号的形式列出。Go Modules 使用这些信息来下载正确的包版本。在示例中,项目依赖了github.com/some/package版本v1.2.3和golang.org/x/net版本v0.0.0-20211201163146-12345abcdef。exclude部分:这个部分列出了需要排除的依赖包版本。这些包不会被下载或使用,即使它们在require部分中被列出。在示例中,项目排除了github.com/unwanted/package版本v1.0.0。replace部分:这个部分用于指定替代依赖包的路径和版本。这可以用来指定自定义的依赖包或使用本地修改的包。在示例中,golang.org/x/somepackage被替代为github.com/myfork/somepackage版本v1.0.0。
注意:我们一般不主动修改go.mod文件,一般都是使用go mod命令自动更新go mod文件以反映更改
下面是一个简化的 go.sum 文件的示例,将对其内容进行详细分析:
github.com/some/package v1.2.3 h1:1234567890abcdef1234567890abcdef12345678
github.com/some/package v1.2.3/go.mod h1:1234567890abcdef1234567890abcdef12345678
golang.org/x/net v0.0.0-20211201163146-12345abcdef h1:1234567890abcdef1234567890abcdef12345678
golang.org/x/net v0.0.0-20211201163146-12345abcdef/go.mod h1:1234567890abcdef1234567890abcdef12345678
github.com/some/package v1.2.3:这一行列出了一个依赖模块github.com/some/package的版本v1.2.3。这是一个依赖项的记录。h1:1234567890abcdef1234567890abcdef12345678:这是一个校验和,用于确保依赖模块的完整性。它是通过计算依赖模块的内容的哈希值生成的。在构建项目时,Go 会使用这个校验和来验证依赖模块是否被篡改。如果校验和与实际的哈希值不匹配,构建将会失败。github.com/some/package v1.2.3/go.mod:这一行列出了github.com/some/package模块的go.mod文件的版本。go.mod文件中包含了模块的元数据,如模块路径、依赖关系等。h1:1234567890abcdef1234567890abcdef12345678:与前一行相同,这是go.mod文件的校验和,用于验证go.mod文件的完整性。golang.org/x/net v0.0.0-20211201163146-12345abcdef和golang.org/x/net v0.0.0-20211201163146-12345abcdef/go.mod:这两行记录了另一个依赖模块golang.org/x/net的版本以及其go.mod文件的版本,同样包括了相应的校验和。
注意:我们一般不主动修改go.sum文件
6.2 go.mod使用¶
1.开启Go Modules
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct //开启代理
除了上面on参数,也有其他参数来一起说明:
- on:开启go mod,从go.mod文件中读取依赖关系
- off:关闭go mod,完全依赖GOPATH和vendor目录(用于存放项目依赖包的本地副本的目录)
- auto:开启,只有项目目录下有go.mod文件时才会生效
当然如果想验证上面参数是否生效,使用go env命令进行查看即可
$ go env
set GO111MODULE=on
...
...
...
set GOPROXY=https://goproxy.cn,direct
...
...
...
说明:这里只是摘取了上面配置的参数,用于验证而已,实际上有很多输出内容
2.常用Go Modules命令
go mod init <module-name>:用于初始化一个新的Go模块,创建一个go.mod文件,其中<module-name>是你的模块路径。这个命令应该在项目根目录下执行。go mod tidy:用于清理并更新go.mod文件和go.sum文件,删除不再需要的依赖项,并添加新依赖项。go mod download:下载项目依赖项,但不安装它们。这可以用于预先下载依赖项,以提高后续构建的速度。go mod vendor:将项目的依赖项复制到项目的vendor目录下,以便在项目中引用。这可以用于确保项目在没有网络连接的情况下仍然能够构建。go list -m all:列出项目的所有依赖模块及其版本。go get <package-name>:用于安装并更新特定依赖包的版本。如果不提供版本信息,则默认安装最新版本。go get -u:用于更新所有依赖包到它们的最新版本。
七、如何自定义包¶
7.1 什么是自定义包¶
自定义包是用户在 Go 语言中创建的包,与标准库或第三方包不同。这些包可以包含函数、变量、结构体、方法和其他类型的 Go 代码,用于解决特定的问题或提供特定的功能。自定义包可以包含一个或多个 .go 文件,这些文件通常位于同一个目录下,以形成一个包。包的名称通常与目录名一致,并用 package 关键字指定。
7.2 为什么要自定义包¶
自定义包有以下几个重要的用途和优势:
- 模块化和组织性:自定义包允许将代码分割成模块,每个模块负责不同的功能。这提高了代码的组织性和可读性。
- 代码重用:通过将一些常用的功能封装在自定义包中,您可以在多个项目中重复使用这些功能,而不必重新编写相同的代码。
- 团队协作:自定义包可以被多个团队成员共享,从而促进团队协作,每个人都可以专注于自己的任务。
- 版本控制:自定义包可以独立于项目进行版本控制,以确保项目的依赖关系清晰可见。
- 测试和维护:通过将功能封装在包中,可以更轻松地进行单元测试,同时也更容易维护和更新这些功能。
通过自定义包,Go 语言提供了一种强大的方式来组织和管理项目代码,使代码更具可维护性、可读性和可重用性。
7.3 一级目录多个文件¶
关于创建、组织和使用自定义包的不同需求,可能会有一级目录多个文件这种情况。针对这种情况的步骤进行详细描述
步骤1:创建包目录
在logproject目录下创建一个logprint目录
logproject/
├── main.go
├── go.mod
├── logprint/
│ ├── debug.go
│ ├── info.go
│ ├── warn.go
如果没有go.mod,需要执行以下命令在logproject目录下进行模块初始化,自动会在该目录下创建go.mod文件
$ go mod init logproject
步骤2:编写包代码
在logprint目录下,创建一个名为debug.go的Go源文件以组织相关日志功能。
// 包名最好和上级目录保持一致,最好不要带下划线
package logprint
import (
"fmt"
"time"
)
func Debug(msg interface{}) {
t := time.Now()
// 定义debug日志格式
fmt.Printf("[debug] %s: %s\n", t.Format("2006-01-02 15:04:05"), msg)
}
针对上面代码分为几个部分进行详细说明:
部分 1 - 包声明和导入
package logprint
import (
"fmt"
"time"
)
package logprint:这是包的声明,指定了包的名称为logprint。包名应该是小写的,根据Go的命名规范,不包含下划线。import声明导入了两个标准库包,fmt和time,这些包用于格式化输出和处理时间。
部分 2 - Debug 函数
func Debug(msg interface{}) {
t := time.Now()
// 定义 debug 日志格式
fmt.Printf("[debug] %s: %s\n", t.Format("2006-01-02 15:04:05"), msg)
}
这部分包括了一个名为 Debug 的函数,该函数用于打印调试消息。以下是函数的关键部分:
func Debug(msg interface{}):这是一个函数声明,它定义了一个名为Debug的函数,该函数接受一个参数msg,该参数可以是任何类型的数据(interface{})。t := time.Now():这一行代码获取了当前时间,并将其存储在变量t中,用于记录日志的时间戳。fmt.Printf("[debug] %s: %s\n", t.Format("2006-01-02 15:04:05"), msg):这一行使用fmt.Printf函数来格式化输出调试信息。它包括了时间戳、消息内容等信息,以便在调试时更容易理解日志。
继续在logprint目录下,创建一个名为info.go的Go源文件以组织相关日志功能。
// 包名最好和上级目录保持一致,最好不要带下划线
package logprint
import (
"fmt"
"time"
)
func Info(msg interface{}) {
t := time.Now()
// 定义info日志格式
fmt.Printf("[info] %s: %s\n", t.Format("2006-01-02 15:04:05"), msg)
}
继续在logprint目录下,创建一个名为warn.go的Go源文件以组织相关日志功能。
// 包名最好和上级目录保持一致,最好不要带下划线
package logprint
import (
"fmt"
"time"
)
func Warn(msg interface{}) {
t := time.Now()
// 定义warn日志格式
fmt.Printf("[warn] %s: %s\n", t.Format("2006-01-02 15:04:05"), msg)
}
步骤3:使用自定义包
在main.go文件中使用导入语句引入自定义包并调用包中的函数
package main
import (
"fmt"
"logproject/logprint"
)
func main() {
fmt.Println("程序开始启动...")
fmt.Println("运行中...")
fmt.Println("程序运行结束")
// 调用自定义包的方法,进行打印日志
logprint.Debug("这是一个debug日志")
logprint.Info("这是一个info日志")
logprint.Warn("这是一个warn日志")
}
针对上面代码分为几个部分进行详细说明:
部分 1 - 包导入
import (
"fmt"
"logproject/logprint"
)
import部分用于导入所需的包。您导入了两个包,fmt和logproject/logprint。fmt是Go标准库中的包,用于格式化输出,而logproject/logprint是您自定义的包,用于记录调试日志。
部分 2 - main函数
func main() {
// 主函数
}
main函数是Go程序的入口点。它是程序启动时首先执行的函数。
部分 3 - 程序启动和结束信息输出
fmt.Println("程序开始启动...")
fmt.Println("运行中...")
fmt.Println("程序运行结束")
- 在
main函数内部,您使用fmt.Println函数打印了一些程序的启动和运行过程中的信息。这些信息将在程序运行时显示在控制台上。
部分 4 - 自定义包的使用
logprint.Debug("这是一个debug日志")
logprint.Info("这是一个info日志")
logprint.Warn("这是一个warn日志")
- 这几行代码调用了自定义包中的
Debug函数、Info函数和Warn函数,用于记录调试日志。具体来说,它传递了一个字符串消息 "这是一个debug日志" 作为参数给Debug函数、Info函数和Warn函数。这个函数将打印包括时间戳在内的调试信息到控制台。
运行main.go文件,观察到调用自定义包成功
$ go run .\main.go
程序开始启动...
运行中...
程序运行结束
[debug] 2023-09-06 16:13:51: 这是一个debug日志
[info] 2023-09-06 16:13:51: 这是一个info日志
[warn] 2023-09-06 16:13:51: 这是一个warn日志
7.4 多级目录多个文件¶
如果自定义包需要更复杂的组织结构,可以创建多级目录。
步骤1:创建包目录
在humanproject目录下创建man目录和woman目录,再在man目录和woman目录中分别创建man.go和woman.go文件,并定义一些简单的函数。
humanproject/
├── main.go
├── go.mod
├── man/
│ ├── man.go
├── woman/
│ ├── woman.go
如果没有go.mod,需要执行以下命令在humanproject目录下进行模块初始化,自动会在该目录下创建go.mod文件
$ go mod init humanproject
步骤2:编写包代码
在man目录下,创建一个名为man.go的Go源文件以组织相关功能。
// humanproject/man/man.go
package man
import "fmt"
func Speak() {
fmt.Println("男人说:我是男人。")
}
在woman目录下,创建一个名为woman.go的Go源文件以组织相关功能。
// humanproject/woman/woman.go
package woman
import "fmt"
func Speak() {
fmt.Println("女人说:我是女人。")
}
步骤3:使用自定义包
在main.go文件中使用导入语句引入自定义包并调用包中的函数
// humanproject/main.go
package main
import (
"humanproject/man" // 导入男人的包
"humanproject/woman" // 导入女人的包
)
func main() {
man.Speak() // 调用男人的 Speak 函数
woman.Speak() // 调用女人的 Speak 函数
}
运行main.go文件,观察到调用自定义包成功
$ go run .\main.go
男人说:我是男人。
女人说:我是女人。
7.5 上传自定义包到Github¶
1.本地自定义文件
创建名为log的文件夹,分别定义三个名为debug.go、info.go、warn.go的文件
debug.go文件
// 包名最好和上级目录保持一致,最好不要带下划线
package logprint
import (
"fmt"
"time"
)
func Debug(msg interface{}) {
t := time.Now()
// 定义debug日志格式
fmt.Printf("[debug] %s: %s\n", t.Format("2006-01-02 15:04:05"), msg)
}
info.go文件
// 包名最好和上级目录保持一致,最好不要带下划线
package logprint
import (
"fmt"
"time"
)
func Info(msg interface{}) {
t := time.Now()
// 定义info日志格式
fmt.Printf("[info] %s: %s\n", t.Format("2006-01-02 15:04:05"), msg)
}
warn.go文件
// 包名最好和上级目录保持一致,最好不要带下划线
package logprint
import (
"fmt"
"time"
)
func Warn(msg interface{}) {
t := time.Now()
// 定义warn日志格式
fmt.Printf("[warn] %s: %s\n", t.Format("2006-01-02 15:04:05"), msg)
}
2.GitHub添加SSH-key
打开Git Bash,粘贴下面的文本(替换为自己的 GitHub 电子邮件地址)
ssh-keygen -t ed25519 -C "1904763431@qq.com"
连续三次按回车生成公钥和私钥,并按照生成的路径找到公钥文件,复制其内容

注意:如果你使用的是不支持 Ed25519 算法的旧系统,使用以下命令:
$ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
点击个人头像后,继续点击【Settings】

点击【SSH and GPG keys】-【New SSH key】

填写SSH key名字和公钥内容后,点击【Add SSH key】

3.GitHub创建log目录
点击【New】

填写目录信息以及描述信息后,点击【Create repository】

3.上传自定义文件到GitHub
使用VSCode打开名为logprint的文件夹后,进行初始化后并上传代码
$ go mod init github.com/zq-zq123/logprint
$ git init
$ git add .
$ git commit -m "first commit"
$ git branch -M main
$ git remote add origin https://github.com/zq-zq123/log.git
$ git push -u origin main
如果想要增加版本号,可以通过以下命令来实现
$ git tag v0.0.1
$ git push -u origin main
验证,观察到代码已成功上传

7.6 使用上传的自定义包¶
1.本地自定义文件夹
本地上新建一个名为log1的文件夹,使用VSCode打开
2.生成go.mod核心文件
$ go mod tidy
$ go mod init log
3.定义main.go文件并运行
编写名为main.go的文件,调用7.5上传的自定义包进行打印输出
package main
import logprint "github.com/zq-zq123/log"
func main() {
logprint.Debug("github测试")
}
运行上面代码,观察到调用成功
$ go run .\main.go
[debug] 2023-09-08 09:30:38: github测试
八、常用包¶
Logrus和Viper是两个在Go开发中非常有用的库,它们分别用于日志记录和配置管理。其中两个包的官方文档如下:
8.1 logrus¶
8.1.1 什么是logrus¶
Logrus 是一个功能强大的 Go 语言日志库,它提供了丰富的日志记录功能和高度可定制的输出格式。
8.1.2 如何安装logrus¶
使用以下命令安装 Logrus
$ go get github.com/sirupsen/logrus
说明:如果当前目录下没有go.mod,需要执行以下命令在当前目录下进行模块初始化,自动会在该目录下创建go.mod文件
$ go mod init logrus
8.1.3 logrus常用配置¶
1.日志格式修改成json
logrus.SetFormatter(&logrus.JSONFormatter{TimestapFormat: "2006-01-02 15:04:05"})
2.设置日志文件名和行号
如果需要在日志中包括文件名和行号,可以使用以下设置:
logrus.SetReportCaller(true)
3.设置日志级别
可以设置日志级别,以指定记录哪个级别以上的日志。常见的级别包括:
logrus.InfoLevellogrus.WarnLevellogrus.ErrorLevellogrus.DebugLevel
例如,将日志级别设置为调试级别:
logrus.SetLevel(logrus.DebugLevel)
注意:默认不打印调试级别的日志信息
8.1.4 logrus示例演示¶
下面使用 Logrus 这个日志库来记录不同级别的日志,并将日志输出格式设置为 JSON。
1.本地自定义文件夹
本地上新建一个名为logrus的文件夹,使用VSCode打开。如果没有go.mod,需要执行以下命令在logrus目录下进行模块初始化,自动会在该目录下创建go.mod文件
$ go mod init logrus
2.安装 Logrus
使用以下命令安装 Logrus
$ go get github.com/sirupsen/logrus
3.编写代码文件
定义logrus_demo.go文件
package main
import (
"fmt"
log "github.com/sirupsen/logrus"
)
func main() {
// 将日志级别设置为调试级别,默认调试日志信息不会打印
log.SetLevel(log.DebugLevel)
// 日志中包含文件名和行号
log.SetReportCaller(true)
// 日志格式修改成json
log.SetFormatter(&log.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"})
logMsg := make(map[string]interface{})
logMsg["执行人"] = "zq"
logMsg["age"] = 18
logMsg["sex"] = "男"
log.WithFields(logMsg).Debug("开始打印debug日志")
log.WithFields(logMsg).Info("这是执行人的信息")
logMsg2 := make(map[string]interface{})
logMsg2["id"] = 1
log.WithFields(logMsg2).Warning("删除某条记录")
errMsg := fmt.Sprintf("删除ID: %d 失败", logMsg2["id"])
log.WithFields(logMsg2).Error(errMsg)
}
针对上面代码分为几个部分进行详细说明:
部分 1 - 包导入
import (
"fmt"
log "github.com/sirupsen/logrus"
)
这个部分导入了必要的包,包括 fmt 用于格式化输出和 github.com/sirupsen/logrus 用于 Logrus 日志库。
部分 2 - 主函数
func main() {
// ... 主要的代码 ...
}
这是程序的入口点,其中包含了主要的代码逻辑。
部分 3 - logrus配置
log.SetLevel(log.DebugLevel)
log.SetReportCaller(true)
log.SetFormatter(&log.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"})
这部分配置了 Logrus 日志库的一些参数:
log.SetLevel(log.DebugLevel)设置日志级别为调试级别,以确保记录调试消息。log.SetReportCaller(true)启用了 Logrus 的ReportCaller功能,将文件名和行号包含在日志中。log.SetFormatter(&log.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"})设置日志格式为 JSON,同时指定了时间戳的格式。
部分 4 - 创建日志消息字段
logMsg := make(map[string]interface{})
logMsg["执行人"] = "zq"
logMsg["age"] = 18
logMsg["sex"] = "男"
这部分创建了一个包含多个字段的 map,这些字段将在后续的日志记录中使用。
部分 5 - 记录日志消息
log.WithFields(logMsg).Debug("开始打印 debug 日志")
log.WithFields(logMsg).Info("这是执行人的信息")
这里使用 Logrus 的 WithFields 方法创建了包含字段的日志实例,并记录了不同级别的日志消息。首先记录了一个调试级别的消息,然后记录了一个信息级别的消息。
部分 6 - 继续创建和记录日志
logMsg2 := make(map[string]interface{})
logMsg2["id"] = 1
log.WithFields(logMsg2).Warning("删除某条记录")
这部分创建了另一个包含字段的 map,然后使用 WithFields 方法记录了一个警告级别的日志消息。
部分 7 - 创建和记录错误消息
errMsg := fmt.Sprintf("删除ID: %d 失败", logMsg2["id"])
log.WithFields(logMsg2).Error(errMsg)
最后,这里创建了一个错误消息,使用 fmt.Sprintf 格式化错误消息字符串,然后使用 WithFields 方法记录了一个错误级别的日志消息,包含错误消息。
运行上面代码
$ go run logrus_demo.go
{"age":18,"file":"C:/学习/8-运维开发/Go语言入门到项目实战/课程代码/Go进阶-包管理/Go进阶-包管理/logrus/logrus_demo.go:19","func":"main.main","level":"debug","msg":"开始打印debug日志","sex":"男","time":"2023-10-20 11:21:15","执行人":"zq"}
{"age":18,"file":"C:/学习/8-运维开发/Go语言入门到项目实战/课程代码/Go进阶-包管理/Go进阶-包管理/logrus/logrus_demo.go:20","func":"main.main","level":"info","msg":"这是执行人的信息","sex":"男","time":"2023-10-20 11:21:15","执行人":"zq"}
{"file":"C:/学习/8-运维开发/Go语言入门到项目实战/课程代码/Go进阶-包管理/Go进阶-包管理/logrus/logrus_demo.go:23","func":"main.main","id":1,"level":"warning","msg":"删除某条记录","time":"2023-10-20 11:21:15"}
{"file":"C:/学习/8-运维开发/Go语言入门到项目实战/课程代码/Go进阶-包管理/Go进阶-包管理/logrus/logrus_demo.go:25","func":"main.main","id":1,"level":"error","msg":"删除ID: 1 失败","time":"2023-10-20 11:21:15"}
8.2 viper¶
8.2.1 什么是viper¶
Viper 是一个用于管理配置文件的库,它可以轻松加载和解析各种配置格式,如 JSON、YAML、TOML 等。
8.2.2 如何安装viper¶
使用以下命令安装 viper
$ go get github.com/spf13/viper
8.2.3 viper示例演示¶
1.本地自定义文件夹
本地上新建一个名为viper的文件夹,使用VSCode打开。如果没有go.mod,需要执行以下命令在viper目录下进行模块初始化,自动会在该目录下创建go.mod文件
$ go mod init viper
2.安装 viper
使用以下命令安装 viper
$ go get github.com/spf13/viper
3.编写代码文件
定义viper_demo.go文件
package main
import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
func main() {
log.SetLevel(log.DebugLevel)
// 环境变量加载我们的程序配置
viper.SetDefault("LOG_LEVEL", "debug")
viper.SetDefault("DB_USERNAME", "zq")
viper.SetDefault("DB_PASSWORD", "your-password")
viper.SetDefault("DB_ADDRESS", "127.0.0.1")
viper.SetDefault("DB_PORT", 3306)
// 获取环境变量的配置
viper.AutomaticEnv()
// 获取程序配置
logLevel := viper.GetString("LOG_LEVEL")
dbUsername := viper.GetString("DB_USERNAME")
dbPort := viper.GetInt("DB_PORT")
log.WithFields(log.Fields{
"日志级别": logLevel,
"数据库用户名": dbUsername,
"数据库端口号": dbPort,
}).Debug("程序的配置信息")
// 将日志级别设置为调试级别,默认调试日志信息不会打印
// if logLevel == "debug" {
// logrus.SetLevel(logrus.DebugLevel)
// } else {
// logrus.SetLevel(logrus.InfoLevel)
// }
// 日志中包含文件名和行号
log.SetReportCaller(true)
// 日志格式修改成json
log.SetFormatter(&log.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"})
logMsg := make(map[string]interface{})
logMsg["执行人"] = "zq"
logMsg["age"] = 18
logMsg["sex"] = "男"
log.WithFields(logMsg).Debug("开始打印debug日志")
log.WithFields(logMsg).Info("这是执行人的信息")
logMsg2 := make(map[string]interface{})
logMsg2["id"] = 1
log.WithFields(logMsg2).Warning("删除某条记录")
errMsg := fmt.Sprintf("删除ID: %d 失败", logMsg2["id"])
log.WithFields(logMsg2).Error(errMsg)
}
针对上面代码分为几个部分进行详细说明:
部分 1 - 包导入
import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
这一部分导入了代码所需的依赖包,包括 Logrus(用于记录日志)和 Viper(用于配置管理)。
部分 2 - 程序入口函数 main
func main() {
// ...
}
这是程序的入口函数,所有程序逻辑将从这里开始执行。
部分 3 - 配置Viper和默认值
viper.SetDefault("LOG_LEVEL", "debug")
viper.SetDefault("DB_USERNAME", "zq")
viper.SetDefault("DB_PASSWORD", "your-password")
viper.SetDefault("DB_ADDRESS", "127.0.0.1")
viper.SetDefault("DB_PORT", 3306)
这部分代码使用 Viper 设置了默认的配置值。如果在配置文件或环境变量中未提供特定配置项的值,将使用这些默认值。
部分 4 - 加载环境变量的配置
viper.AutomaticEnv()
这一行代码告诉 Viper 自动从环境变量中加载配置项的值。如果环境变量中有与配置项名称相匹配的值,将覆盖默认值。
部分 5 - 获取程序配置值
logLevel := viper.GetString("LOG_LEVEL")
dbUsername := viper.GetString("DB_USERNAME")
dbPort := viper.GetInt("DB_PORT")
这部分代码使用 Viper 从配置中获取了程序所需的配置值,包括日志级别、数据库用户名和数据库端口。
部分 6 - 记录程序的配置信息
log.WithFields(log.Fields{
"日志级别": logLevel,
"数据库用户名": dbUsername,
"数据库端口号": dbPort,
}).Debug("程序的配置信息")
这段代码使用 Logrus 记录了程序的配置信息,包括日志级别、数据库用户名和数据库端口。这些信息以调试级别 (Debug) 记录,以便在需要时进行调试。
部分 7 - 配置Logrus
log.SetLevel(log.DebugLevel)
log.SetReportCaller(true)
log.SetFormatter(&log.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"})
这部分代码配置了 Logrus 的行为。它将日志级别设置为调试级别,允许记录所有级别的日志消息。它还启用了文件名和行号的记录,并将日志格式设置为 JSON。
部分 8 - 记录不同级别的日志消息
log.WithFields(logMsg).Debug("开始打印debug日志")
log.WithFields(logMsg).Info("这是执行人的信息")
log.WithFields(logMsg2).Warning("删除某条记录")
errMsg := fmt.Sprintf("删除ID: %d 失败", logMsg2["id"])
log.WithFields(logMsg2).Error(errMsg)
这些代码片段记录了不同级别的日志消息,包括调试 (Debug)、信息 (Info)、警告 (Warning) 和错误 (Error)。它们使用 WithFields 方法添加字段信息,以及相关的消息内容。
部分 9 - 运行go
$ go run vip_demo.go
time="2023-10-20T16:38:45+08:00" level=debug msg="程序的配置信息" 数据库用户名=zq 数据库端口号=3306 日志级别=debug
{"age":18,"file":"C:/学习/8-运维开发/Go语言入门到项目实战/课程代码/Go进阶-包管理/Go进阶-包管理/viper/vip_demo.go:47","func":"main.main","level":"debug","msg":"开始打印debug日志","sex":"男","time":"2023-10-20 16:38:45","执行人":"zq"}
{"age":18,"file":"C:/学习/8-运维开发/Go语言入门到项目实战/课程代码/Go进阶-包管理/Go进阶-包管理/viper/vip_demo.go:48","func":"main.main","level":"info","msg":"这是执行人的信息","sex":"男","time":"2023-10-20 16:38:45","执行人":"zq"}
{"file":"C:/学习/8-运维开发/Go语言入门到项目实战/课程代码/Go进阶-包管理/Go进阶-包管理/viper/vip_demo.go:51","func":"main.main","id":1,"level":"warning","msg":"删除某条记录","time":"2023-10-20 16:38:45"}
{"file":"C:/学习/8-运维开发/Go语言入门到项目实战/课程代码/Go进阶-包管理/Go进阶-包管理/viper/vip_demo.go:53","func":"main.main","id":1,"level":"error","msg":"删除ID: 1 失败","time":"2023-10-20 16:38:45"}
4.设置win10环境变量
右键【此电脑】选择【属性】后,继续选择【高级系统设置】

点击【环境变量】

点击【新建】,添加新的用户变量,变量名:LOG_LEVEL,变量值:info,点击两次【确定】

5.再次运行
$ go run vip_demo.go
time="2023-10-20T16:38:45+08:00" level=debug msg="程序的配置信息" 数据库用户名=zq 数据库端口号=3306 日志级别=info
{"age":18,"file":"C:/学习/8-运维开发/Go语言入门到项目实战/课程代码/Go进阶-包管理/Go进阶-包管理/viper/vip_demo.go:47","func":"main.main","level":"debug","msg":"开始打印debug日志","sex":"男","time":"2023-10-20 16:38:45","执行人":"zq"}
{"age":18,"file":"C:/学习/8-运维开发/Go语言入门到项目实战/课程代码/Go进阶-包管理/Go进阶-包管理/viper/vip_demo.go:48","func":"main.main","level":"info","msg":"这是执行人的信息","sex":"男","time":"2023-10-20 16:38:45","执行人":"zq"}
{"file":"C:/学习/8-运维开发/Go语言入门到项目实战/课程代码/Go进阶-包管理/Go进阶-包管理/viper/vip_demo.go:51","func":"main.main","id":1,"level":"warning","msg":"删除某条记录","time":"2023-10-20 16:38:45"}
{"file":"C:/学习/8-运维开发/Go语言入门到项目实战/课程代码/Go进阶-包管理/Go进阶-包管理/viper/vip_demo.go:53","func":"main.main","id":1,"level":"error","msg":"删除ID: 1 失败","time":"2023-10-20 16:38:45"}
8.2.4 注意事项¶
如果在示例演示中无法输出程序的配置信息相关内容 ,建议将log.SetLevel(log.DebugLevel)代码提前到main函数开头位置。因为如果您将日志级别设置为 DebugLevel,那么只有比调试级别更高的日志级别(例如 Info、Warning、Error)才会被记录,而调试级别的日志将不会被记录。
要解决这个问题,可以考虑以下两种方法之一:
1、设置调试级别并记录 "程序的配置信息":如果您希望 "程序的配置信息" 以调试级别记录,可以将 log.SetLevel(log.DebugLevel) 放在 log.SetReportCaller(true) 前面,确保它们之间没有其他更改日志级别的代码。这样做会将日志级别设置为调试级别,并记录所有级别的日志消息,包括调试级别。
2、记录 "程序的配置信息" 为信息级别:如果您希望 "程序的配置信息" 以信息级别记录,可以在记录这条日志消息时明确指定日志级别。例如:
log.WithFields(log.Fields{
"日志级别": logLevel,
"数据库用户名": dbUsername,
"数据库端口号": dbPort,
}).Info("程序的配置信息")
这将确保 "程序的配置信息" 以信息级别 (Info) 记录,而不受全局的日志级别设置影响。