关于在Go应用程序中执行日志的介绍

643 阅读11分钟

关于在Go应用程序中执行日志的介绍

Go是用来构建基于后台的应用程序的。作为一个开发者,你可能需要知道你正在运行的应用程序的幕后情况。例如,你需要知道某个活动是什么时候进行的,以跟踪你的应用程序的进出情况。因此,你需要设置一个日志封装器,它将帮助你了解更多你的服务器正在做的任务。

本指南介绍了使用Go编程语言进行日志记录的概念。首先,我们将解释这个概念以及它是如何工作的。然后构建一个在Golang应用程序中具有日志记录功能的应用程序。

什么是日志记录

当你创建了一个用于生产的应用程序后,可能会发生许多你无法控制和意料之外的问题。修复这类问题最难的部分是确定问题出在哪里。同样的应用程序也可能需要报告显示你的用户是如何与你的应用程序的不同模块和服务进行互动的。而每当事情出错时,你想知道你的应用程序代码库发生了什么。在这种情况下,你会希望同一个应用程序能在你的代码执行的幕后给你反馈。

日志的概念试图为你解决这个问题。但是,不幸的是,它留下了一串面包屑。因此,每当出错时,你可以在正确的时间确定原因,并在它搞砸你的应用程序之前解决它。日志允许你把你的应用程序的状态信息写到文件、数据库或其他输出流中。这些信息包含了你的代码的哪些部分被执行了,以及可能出现了哪些问题的信息。

为什么要进行日志记录

作为一个开发者,日志在修复你的应用程序中的错误时起着关键作用。首先,它可以帮助你确定你的代码的哪一部分引起了问题。这基本上可以帮助你调试你的应用程序,让你一步一步地跟踪你的应用程序的执行。有几个工具我们可以用来调试。然而,记录信息可以说是调试你的代码的最有效方法之一。

为了帮助你优先处理哪些代码问题需要更多的关注,日志将日志信息分类到不同的日志级别。一个日志级别就像一个消息过滤器。这些日志包括Trace < Debug < Info < Warn < Error < Fatal,基于优先顺序。

记录几乎可以应用于任何编程语言。因此,让我们深入研究,看看如何为Go应用程序设置一些基本的日志。

为Go应用程序设置日志

首先,你需要在你的电脑上初始化一个Go应用程序。因此,你需要安装Go。如果你还没有安装,请检查Golang的安装。安装完毕后,运行go version ,检查Go是否已经安装。然后创建并导航到一个项目文件夹,用go mod init go-logging-app 来初始化Go。这将设置一些准备编写的Go代码。

Go有几个标准包,允许你在你的应用程序中设置记录器。然而,Go有一个为本地Golang建立的日志包。

要用Go设置一个基本的日志,请在项目文件夹的根部创建一个main.go 。为了开始简单的日志记录,你可以使用日志包来创建一个简单的基础日志器。继续并在你的main.go 文件中添加以下内容。

package main

import (
    "log"
)

func main() {
    log.Printf("This is my first baci Golang")
}

然后使用go run main.go ,运行你的应用程序。这将向你的控制台记录一条基本信息,即:2022/01/29 16:51:08 This is my first basic Golang 。默认情况下,它显示这个日志的创建日期和时间以及这个日志产生的消息。这就是日志的用武之地。它给你一个时间戳,以达到这个日志被记录的确切时间。

让我们看看如何创建一个日志,并在日志信息中附加一些日志级别。下面是一个例子

func main() {

    // pass any values to Println method
    log.Println("INFO: This is Info an log message")
    log.Println("WARNING: This is a Warning log message")
    log.Println("Error: This is an log message")
    log.Println("Fatal: This is a Fatal Error log message")
}

这将把一些基本信息记录到你的控制台。如果你想把这些日志信息保存到一个文件中呢。下面是我们如何做到这一点。

首先,导入以下软件包以帮助你访问你的计算机系统。接下来,我们需要创建一个目录和一个文件来保存日志。这些包将帮助我们这样做。

import (
    "fmt"
    "log"
    "os"
    "time"
)

这些日志将被写入你的计算机的文件系统中。下面的函数将帮助建立一个目录和文件以及你项目的根。

const (
    LogsDirpath = "logs"
)

type LogDir struct {
    LogDirectory string
}

func New() *LogDir {
    err := os.Mkdir(LogsDirpath, 0666)
    if err != nil {
        return nil
    }
    return &LogDir{
        LogDirectory: LogsDirpath,
    }
}

func SetLogFile() *os.File {
    year, month, day := time.Now().Date()
    fileName := fmt.Sprintf("%v-%v-%v.log", day, month.String(), year)
    filePath, _ := os.OpenFile(LogsDirpath+"/"+fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)

    return filePath
}

这将创建一个目录logs 。在这里,我们设置一个文件名,根据日志是信息创建的时间来生成。在这种情况下,我们将创建一个文件,并将其命名为保存日志的当前日期。例如,29-January-2022.log

现在让我们来写一下将执行不同日志级别的函数。

func (l *LogDir) Info() *log.Logger {
    getFilePath := SetLogFile()
    return log.New(getFilePath, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
}

func (l *LogDir) Warning() *log.Logger {
    getFilePath := SetLogFile()
    return log.New(getFilePath, "WARNING: ", log.Ldate|log.Ltime|log.Lshortfile)
}

func (l *LogDir) Error() *log.Logger {
    getFilePath := SetLogFile()
    return log.New(getFilePath, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
}

func (l *LogDir) Fatal() *log.Logger {
    getFilePath := SetLogFile()
    return log.New(getFilePath, "FATAL: ", log.Ldate|log.Ltime|log.Lshortfile)
}

每个函数将执行一个级别,并将一条信息保存到文件路径中。在这种情况下,我们添加了参数来格式化日志输出。这些参数包括

  • log.Ldate:这将记录日志消息产生的确切日期。
  • log.Ltime:这将记录日志信息产生的确切时间。
  • log.Lshortfile:这将是产生该日志的文件。它还将添加生成该消息的准确代码行。

现在添加一个main() ,包裹并执行上述函数。

func main() {
    
    appLogger := New()

    appLogger.Info().Println("This is Info an log message")
    appLogger.Warning().Println("This is a Fatal Error log message")
    appLogger.Error().Println("This is a Warning log message")
    appLogger.Fatal().Println("This is an log message")
}

前往项目目录,运行go run main.go 。这将自动创建一个logs 目录和一个新文件,并以这些消息产生的当前日期命名。而如果你打开那个文件,它将包含以下日志信息。

INFO: 2022/01/29 19:22:03 main.go:59: This is Info and logs message
WARNING: 2022/01/29 19:22:03 main.go:60: This is a Fatal Error log message
ERROR: 2022/01/29 19:22:03 main.go:61: This is a Warning log message
FATAL: 2022/01/29 19:22:03 main.go:62: This is a log message

正如你所看到的,追踪消息产生的时间和地点就更容易了。

创建自定义日志

上述过程涉及简单的日志。现在让我们看看你如何添加应用程序的逻辑执行所产生的日志。

假设你有一个涉及除法操作的应用程序。在这种情况下,用户不能将任何数值除以0。而如果发生这种情况,你可能想捕捉到这个错误,并将其保存为错误跟踪。因此,让我们看看如何处理这个操作并捕捉这样的错误。

首先,将errors 包添加到你的main.go 进口中。然后创建一个执行潜水操作的函数,如下图所示。

//Divide return number from a value divided by b value and error if any
func Divide(a int, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("cannot divide any value with zero")
    }

    return a / b, nil
}

创建一个将被执行的函数,将生成的错误保存到文件系统中。

func (l *LogDir) Error1() *log.Logger {
    getFilePath := SetLogFile()
    return log.New(getFilePath, "Error: ", log.Ldate|log.Ltime|log.Lshortfile)
}

这定义了在日志信息将被记录的格式。最后,将Divide 逻辑添加到主函数中。

_, err := Divide(10, 5)
if err != nil {
appLogger.Error1().Println("error : ", err)
log.Println("error : ", err)
}

使用go run main.go ,运行你的应用程序。这个执行不会记录日志信息。在这个例子中,我们有两个值,Divide(10, 5) ,也就是说,我们是用10除以5,这是一个真实的操作。因此,没有产生错误。现在继续,用Divide(10, 0) 。注意这里我们是用10除以0,这是一个无效的操作。这个操作将产生一个错误,我们想得到这个错误并将其保存在我们的日志文件中。因此,继续用go run main.go ,重新运行你的应用程序。这次我们期待一个错误,如果你导航到你的日志文件,将记录一个带有上述无效操作的信息,如下图所示。

Error: 2022/01/29 20:26:38 main.go:56: error : cannot divide any value with zero

就这样,你可以根据你要执行的逻辑,使用日志来记录任何可能的错误。

在Go服务器上设置日志记录

Go也被用来创建网络服务器和API。在这种情况下,它们要发送响应并接收来自客户端的请求。您可能想在这样的服务器上实现日志记录,以记录GET和POST等给定方法何时被执行以及这些方法返回的数据。

日志可以被添加到服务器上,以监视这种情况的发生,并从你的服务器上获得客户的访问。让我们建立一个非常基本的服务器,它具有Go日志功能。

继续,在你的main.go 文件中创建一个新的导入,如下图所示。

import (
    "fmt"
    "log"
    "net/http"
    "net/http/httputil"
)

我们将使用本地HTTP模块创建一个简单的服务器。但是,首先,我们要创建一个中间件记录器,我们将用它来登录服务器的请求。

func HelloHandler(w http.ResponseWriter, r *http.Request) {
    dump, err := httputil.DumpRequest(r, true)
    if err != nil {
        http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
        return
    }

    fmt.Printf("%q", dump)
    fmt.Fprintf(w, "The server Endpoint Request have been excuted")
}

这个中间件函数将使用httputil.DumpRequest 。这将访问HTTP请求并写入其细节,请求的字段也包括在DumpRequest

我们将调用这个中间件到我们将用来访问服务器的路由/端点。但是,首先,我们要做的是使用Gomain() 函数,如下面的代码块所示。

func main() {
    http.HandleFunc("/", HelloHandler)
    fmt.Println("Server started at port 8080")
    log.Fatal(http.ListenAndServe("0.0.0.0:8080", nil))
}

使用go run main.go 运行服务器。这将在本地主机上运行服务器,端口为8080 。每当你使用端点http://localhost:8080/ 作为服务器时。一旦你打开了http://localhost:8080/ ,检查你的控制台。这将记录一个请求,如下图所示。

server-log

正如你所看到的,HandleFunc() 执行了一个Get 的请求,以及与执行的端点相关的一切。

Go日志工具的多样化

Go是一种时髦的语言。它有一个很好的生态系统,有很多库,你可以用它们来构建你的基于Go的应用程序。在本教程中,我们使用了Go的日志本地模块。然而,有许多库可以用来记录你的应用程序。这些库可以是本地库,也可以是第三方库。

你可以选择使用以下的本地日志库。

  • fmt -fmt可以用来打印代码执行情况,如变量、错误和函数。它使用fmt.Printf ,在你的应用程序中打印日志,就像日志模块一样。
  • Context -context是Go的一个本地日志管理模块。

除了这些惊人的本地库,Go生态系统还有不同的第三方和开源库,你仍然可以选择使用这些库进行日志管理。它们包括。

  • Zap是一个用于Go应用程序的结构化和水平化的日志包。它的主要核心是通过避免序列化开销来产生快速的日志中间件。
  • Zerolog提供有JSON输出的日志器。
  • Logrus提供了与本地/标准库兼容的结构化日志,如a log,帮助你扩展日志进程。
  • Apex/log的灵感来自Logrus。它为Logrus增加了更多的处理程序来处理日志事件。这些处理程序包括彩色文本输出、JSON处理程序输出、CLI输出、级别过滤器处理程序等。

总结

日志是一个伟大的实践,你可以在你的应用程序中实施。它可以让你了解你的应用程序中正在发生什么。在出现错误的情况下,追踪它们并及时解决它们会变得更容易。建议只记录有意义的信息,以避免不必要的日志。每条日志都应该有一个级别,描述被封闭的错误的严重程度。这样,你就可以在适当的时候优先处理那些会对你的应用程序造成更大伤害的日志。