一、前言

本文主要以下几方面介绍Go语言中包管理:

  • 什么是包
  • 为什么要使用包
  • 内置包和第三方包
  • 如何使用包
  • 如何管理包
  • 如何自定义包
  • 常用的包

二、什么是包

Go语言的包(package)是一种源码封装的方式,可以被看做是一组相关的,并且通用的代码集合。这些包都有自己独立的功能,然后在编写代码时,如果需要用到这些功能,可以导入包直接使用。

三、为什么要使用包

使用包管理一般有以下几个主要原因:

  • 模块化:包可以将代码模块化,将不同功能的代码分组到不同的包中,使代码更易于管理和维护。这有助于避免代码变得过于庞大和混乱。
  • 重用性:通过将相关的功能封装在包中,可以更容易地在不同的项目中重用代码。其他程序员也可以使用你的包,这有助于推广和分享代码。
  • 命名空间:包提供了命名空间,可以防止命名冲突。不同的包可以拥有相同的函数或变量名,它们不会相互干扰,因为每个包都有自己的命名空间。
  • 可维护性:包提供了封装和隐藏的机制,可以将内部实现细节隐藏起来,只暴露需要公开的接口。这有助于降低代码的耦合度,使代码更容易维护和更新。
  • 增强可读性:将相关功能放在不同的包中,有助于代码的组织和清晰性。开发者可以更容易地理解代码的结构和目的。

四、内置包和第三方包

4.1 什么是内置包和第三方包

内置包(Built-in Packages):内置包是Go语言标准库中包含的包,它们是Go语言的一部分,随着Go的安装而自动安装在系统中。这些包包含了许多基本的功能和工具,如字符串处理、输入输出、并发、网络通信等。点击Go官网包查询后,输入包名称即可查询相关的内置包信息。

第三方包(Third-party Packages):第三方包是由Go社区或其他开发者编写的,不包含在Go标准库中的包。这些包通常提供了各种各样的功能和工具,用于扩展Go语言的功能和满足特定需求。我们可以使用Go的包管理工具(如go getgo 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"
)

在上面的示例中,我们一次性导入了三个不同的包:fmtmathnet/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.modgo.mod 文件是 Go Modules 的核心文件之一,用于定义项目的模块路径、依赖关系、版本信息和构建配置。它是必不可少的文件,用于确定项目的模块根路径、Go 版本以及项目依赖的其他模块。
  • go.sumgo.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.3golang.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-12345abcdefgolang.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 声明导入了两个标准库包,fmttime,这些包用于格式化输出和处理时间。

部分 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 部分用于导入所需的包。您导入了两个包,fmtlogproject/logprintfmt 是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.gowoman.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.goinfo.gowarn.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"

连续三次按回车生成公钥和私钥,并按照生成的路径找到公钥文件,复制其内容

GitHub添加SSH-key-1

注意:如果你使用的是不支持 Ed25519 算法的旧系统,使用以下命令:

$ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

点击个人头像后,继续点击【Settings】

GitHub添加SSH-key-2

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

GitHub添加SSH-key-3

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

GitHub添加SSH-key-4

3.GitHub创建log目录

点击【New】

GitHub创建目录-1

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

GitHub创建目录-2

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

验证,观察到代码已成功上传

上传自定义文件到GitHub-1

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.InfoLevel
  • logrus.WarnLevel
  • logrus.ErrorLevel
  • logrus.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环境变量

右键【此电脑】选择【属性】后,继续选择【高级系统设置】

image-20231020181630894

点击【环境变量】

image-20231020181748158

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

image-20231020182137071

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,那么只有比调试级别更高的日志级别(例如 InfoWarningError)才会被记录,而调试级别的日志将不会被记录。

要解决这个问题,可以考虑以下两种方法之一:

1、设置调试级别并记录 "程序的配置信息":如果您希望 "程序的配置信息" 以调试级别记录,可以将 log.SetLevel(log.DebugLevel) 放在 log.SetReportCaller(true) 前面,确保它们之间没有其他更改日志级别的代码。这样做会将日志级别设置为调试级别,并记录所有级别的日志消息,包括调试级别。

2、记录 "程序的配置信息" 为信息级别:如果您希望 "程序的配置信息" 以信息级别记录,可以在记录这条日志消息时明确指定日志级别。例如:

log.WithFields(log.Fields{
    "日志级别":   logLevel,
    "数据库用户名": dbUsername,
    "数据库端口号": dbPort,
}).Info("程序的配置信息")

这将确保 "程序的配置信息" 以信息级别 (Info) 记录,而不受全局的日志级别设置影响。