什么不记录
通常,您不应记录任何形式的敏感业务数据或个人身份信息。这包括但不限于:
- 名字
- IP地址
- 银行卡号码
从工程的角度来看,这些限制会使日志的用处不大,但是它们使您的应用程序更安全。在许多情况下,GDPR和HIPAA等法规可能会禁止记录个人数据。
日志包介绍
Go标准库具有一个内置log
软件包,可提供大多数基本日志记录功能。尽管它没有日志级别(例如调试,警告或错误),但它仍然提供了设置基本日志策略所需的一切。
这是最基本的日志记录示例:
package main
import "log"
func main() {
log.Println("Hello world!")
}
上面的代码输出文本“ Hello world!”。标准错误,但其中还包括日期和时间,这对于按日期过滤日志消息非常方便。
2019/12/09 17:21:53 Hello world!
默认情况下,
log
程序包将打印到标准错误(stderr
)输出流,但是您可以使其写入本地文件或支持该io.Writer
接口的任何目标位置。它还在没有任何其他配置的情况下向日志消息添加时间戳。
记录到文件
如果需要将日志消息存储在文件中,可以通过创建新文件或打开现有文件并将其设置为日志输出来进行。这是一个例子:
package main
import (
"log"
"os"
)
func main() {
// If the file doesn't exist, create it or append to the file
file, err := os.OpenFile("logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
log.SetOutput(file)
log.Println("Hello world!")
}
当我们运行代码时,将以下内容写入 logs.txt.
2019/12/09 17:22:47 Hello world!
如前所述,您基本上可以将日志输出到实现该io.Writer
接口的任何目标,因此在决定在应用程序中的日志记录位置时,您具有很大的灵活性。
创建自定义记录器
尽管该log
程序包实现了一个logger
写入标准错误的预定义内容,但是我们可以使用该log.New()
方法创建自定义记录器类型。
创建新的记录器时,您需要将三个参数传递给log.New()
:
-
out
:实现io.Writer
接口的任何类型,即将日志数据写入接口的类型 -
prefix
:附加到每个日志行开头的字符串 -
flag
:一组常量,使我们能够定义在记录器生成的每个日志条目中包括哪些记录属性(在下一节中将对此进行详细说明)
我们可以利用此功能来创建自定义记录器。下面是一个例子,它实现Info
,Warning
并Error
记录器:
package main
import (
"log"
"os"
)
var (
WarningLogger *log.Logger
InfoLogger *log.Logger
ErrorLogger *log.Logger
)
func init() {
file, err := os.OpenFile("logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
InfoLogger = log.New(file, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
WarningLogger = log.New(file, "WARNING: ", log.Ldate|log.Ltime|log.Lshortfile)
ErrorLogger = log.New(file, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
}
func main() {
InfoLogger.Println("Starting the application...")
InfoLogger.Println("Something noteworthy happened")
WarningLogger.Println("There is something you should know about")
ErrorLogger.Println("Something went wrong")
}
在函数logs.txt
顶部创建或打开文件后init
,我们然后通过提供输出目标,前缀字符串和日志标志来初始化三个定义的记录器。
在该main
函数中,通过调用该Println
函数来利用记录器,该函数将新的日志条目写入日志文件。当您运行该程序时,以下内容将被写入logs.txt
。
INFO: 2019/12/09 12:01:06 main.go:26: Starting the application...
INFO: 2019/12/09 12:01:06 main.go:27: Something noteworthy happened
WARNING: 2019/12/09 12:01:06 main.go:28: There is something you should know about
ERROR: 2019/12/09 12:01:06 main.go:29: Something went wrong
请注意,在此示例中,我们正在记录到单个文件,但是您可以通过在创建记录器时传递一个不同的文件来为每个记录器使用单独的文件。
日志标志
您可以使用日志标志常量来提供其他上下文信息,例如文件,行号,日期和时间,以丰富日志消息。例如,通过带有如下所示标志组合的记录器传递消息“发生了错误”:
log.Ldate|log.Ltime|log.Lshortfile
将打印
2019/12/09 12:01:06 main.go:29: Something went wrong
不幸的是,无法控制它们的显示顺序或显示格式。
日志记录框架介绍
log
当获取快速反馈比生成丰富的结构化日志更为重要时,使用此软件包非常适合本地开发。除此之外,使用日志记录框架可能会更好。
使用日志记录框架的主要优点是,它有助于标准化日志数据。这意味着:
- 阅读和理解日志数据更加容易。
- 从多个来源收集日志并将它们提供给要分析的中央平台更加容易。
另外,日志记录几乎是一个已解决的问题。为什么要重新发明轮子?
选择日志记录框架
由于有多种选择,因此决定使用哪种框架可能是一个挑战。
Go的两个最受欢迎的日志记录框架似乎是 glog和logrus。glog的流行是令人惊讶的,因为它已经几年没有更新了。logrus可以更好地维护并在Docker等流行项目中使用,因此我们将重点关注它。
Logrus入门
安装logrus就像在终端中运行以下命令一样简单:
go get "github.com/Sirupsen/logrus"
logrus的一大优点是,它与log
标准库的软件包完全兼容,因此您可以在任何地方替换您的日志导入,log "github.com/sirupsen/logrus"
并且可以正常工作!
让我们修改我们先前使用日志包的“ hello world”示例,并改用logrus:
package main
import (
log "github.com/sirupsen/logrus"
)
func main() {
log.Println("Hello world!")
}
运行以下代码将产生输出:
INFO[0000] Hello world!
再简单不过了!
登录JSON
logrus
非常适合JSON中的结构化日志记录,因为JSON是一个定义良好的标准,因此外部服务可以轻松解析您的日志,还可以通过使用字段使向日志消息中添加上下文相对简单,如图所示下面:
package main
import (
log "github.com/sirupsen/logrus"
)
func main() {
log.SetFormatter(&log.JSONFormatter{})
log.WithFields(
log.Fields{
"foo": "foo",
"bar": "bar",
},
).Info("Something happened")
}
生成的日志输出将是一个JSON对象,其中包括消息,日志级别,时间戳和包含的字段。
{"bar":"bar","foo":"foo","level":"info","msg":"Something happened","time":"2019-12-09T15:55:24+01:00"}
如果您不希望将日志输出为JSON,请注意,日志有多个第三方格式化程序,您可以在其Github页面上查看这些格式化程序。如果愿意,您甚至可以编写自己的格式化程序。
日志级别
与标准日志包不同,logrus支持日志级别。
logrus具有七个日志级别:跟踪,调试,信息,警告,错误,致命和紧急。每个级别的严重性随列表的增加而增加。
log.Trace("Something very low level.")
log.Debug("Useful debugging information.")
log.Info("Something noteworthy happened!")
log.Warn("You should probably take a look at this.")
log.Error("Something failed but I'm not quitting.")
// Calls os.Exit(1) after logging
log.Fatal("Bye.")
// Calls panic() after logging
log.Panic("I'm bailing.")
通过在记录器上设置日志记录级别,您可以根据环境仅记录所需的条目。默认情况下,logrus将记录任何信息或更高级别的信息(警告,错误,致命或紧急)。
package main
import (
log "github.com/sirupsen/logrus"
)
func main() {
log.SetFormatter(&log.JSONFormatter{})
log.Debug("Useful debugging information.")
log.Info("Something noteworthy happened!")
log.Warn("You should probably take a look at this.")
log.Error("Something failed but I'm not quitting.")
}
运行上面的代码将产生以下输出:
{"level":"info","msg":"Something noteworthy happened!","time":"2019-12-09T16:18:21+01:00"}
{"level":"warning","msg":"You should probably take a look at this.","time":"2019-12-09T16:18:21+01:00"}
{"level":"error","msg":"Something failed but I'm not quitting.","time":"2019-12-09T16:18:21+01:00"}
请注意未打印调试级别消息。要将其包括在日志中,请设置log.Level
为equal log.DebugLevel
:
log.SetLevel(log.DebugLevel)
包起来
在本文中,我们探讨了内置日志包的用法,并确定仅应将其用于琐碎的应用程序或构建快速原型时。对于其他所有方面,必须使用主流的日志记录框架。
我们还研究了确保日志中包含的信息一致且易于分析的方法,尤其是在集中式平台上进行汇总时。
有疑问加站长微信联系(非本文作者)