목차
기본 제공 " >기본 제공
Handler" >Handler
Text Handler" >Text Handler
JSON Handler" >JSON Handler
日志选项" >日志选项
SetDefault() 设置默认 Logger" >SetDefault() 设置默认 Logger
两种记录日志的方式" >两种记录日志的方式
如何绑定一组属性" >如何绑定一组属性
如何绑定公共的属性" >如何绑定公共的属性
通过 context 存储或提取 Logger" >通过 context 存储或提取 Logger
如何集成第三方日志包" >如何集成第三方日志包
백엔드 개발 Golang slog: Go의 공식 구조화된 로그 패키지 개발은 어떻게 진행되고 있나요? 사용 방법?

slog: Go의 공식 구조화된 로그 패키지 개발은 어떻게 진행되고 있나요? 사용 방법?

Aug 04, 2023 pm 05:01 PM
go

Go에 익숙한 학생들은 Go 언어 표준 라이브러리 로그에 로그 분류 없음, 구조 없음(JSON 형식 없음), 확장성 부족 등 많은 문제점이 있다는 것을 알고 있습니다. 이러한 문제를 해결하기 위해, Go는 구조화된 로그 패키지 slog를 공식 출시했습니다. 이 라이브러리는 현재 개발 중이며 실험용 라이브러리인 golang.org/x/exp/slog에 포함되었습니다. 현재 버전은 v0.0.0입니다.

이번 글에서는 slog 패키지 사용법을 살펴볼까요?

Installation

다음 명령을 사용하여 설치합니다.

go get golang.org/x/exp/slog
로그인 후 복사

기본 제공

func main() {
 slog.Info("Go is best language!", "公众号", "Golang来啦")
}
로그인 후 복사

출력:

2023/01/23 10:23:37 INFO Go is best language! 公众号=Golang来啦
로그인 후 복사

출력과 다소 유사한 출력을 살펴봅니다. 표준 라이브러리 로그의 slog 라이브러리에서 매우 중요한 구조는 Logger이며, 이를 통해 Info(), Debug() 등의 로깅 함수를 호출할 수 있습니다. 이에 대한 로거를 만들지 않았으며 기본 로거를 사용하겠습니다. 클릭하면 소스 코드를 볼 수 있습니다.

Handler

Handler는 slog의 확장성을 높일 수 있는 인터페이스로 정의됩니다. slog는 TextHandler와 JSONHandler라는 두 가지 내장 핸들러 구현을 제공합니다. 또한 타사 로그를 기반으로 정의할 수도 있습니다. 패키지 또는 우리 자신 나중에 이야기할 핸들러의 구현을 정의합니다.

type Handler interface {
 Enabled(Level) bool
 Handle(r Record) error
 WithAttrs(attrs []Attr) Handler
 WithGroup(name string) Handler
}
로그인 후 복사

Text Handler

TextHandler는 표준 라이브러리 로그 패키지와 마찬가지로 로그를 텍스트 줄로 출력합니다.

func main() {
 textHandler := slog.NewTextHandler(os.Stdout)
 logger := slog.New(textHandler)
 logger.Info("Go is best language!", "公众号", "Golang来啦")
}
로그인 후 복사

출력:

time=2023-01-23T10:48:41.365+08:00 level=INFO msg="Go is best language!" 公众号=Golang来啦
로그인 후 복사

출력 로그가 "key1=value1 key2=value2 ... keyN=valueN" 형식으로 표시되는 것을 볼 수 있습니다.

JSON Handler

위의 NewTextHandler()를 NewJSONHandler()

func main() {
 textHandler := slog.NewJSONHandler(os.Stdout)
 logger := slog.New(textHandler)
 logger.Info("Go is best language!", "公众号", "Golang来啦")
}
로그인 후 복사

출력:

로 대체합니다.
{"time":"2023-01-23T11:02:27.1606485+08:00","level":"INFO","msg":"Go is best language!","公众号":"Golang来啦"}
로그인 후 복사

从输出可以看到,日志已 json 格式记录,这样的结构化日志非常适合机器解析。

日志选项

日常开发中我们一般都会在日志里面记录在哪个文件哪一行记录了这条日志,这样有利于排查问题。或者,有时候需要更改日志级别,那这些该怎么实现呢?

如果我们翻看源码就能发现,上面提到的 TextHandler 和 JSONHandler 都使用默认的 HandlerOptions,它是一个结构体。

type HandlerOptions struct {
 AddSource bool
 Level Leveler
 ReplaceAttr func(groups []string, a Attr) Attr
}
로그인 후 복사

通过 slog 的源代码注释可以看出,如果 AddSource 设置为 true,则记录日志时会以 ("source", "file:line") 的方式记录来源;Level 用于调整日志级别。

默认情况下,slog 只会记录 Info 及以上级别的日志,不会记录 Debug 级别的日志。

func main() {
 logger := slog.New(slog.NewJSONHandler(os.Stdout))
 logger.Debug("记录日志-debug",
  "公众号", "Golang来啦",
  "time", time.Since(time.Now()))
 logger.Info("记录日志-info",
  "公众号", "Golang来啦",
  "time", time.Since(time.Now()))
}
로그인 후 복사

输出:

{"time":"2023-01-23T15:36:14.8610328+08:00","level":"INFO","msg":"记录日志-info","公众号":"Golang来啦","time":0}
로그인 후 복사

这样的话,我们就可以自定义 option。

func main() {
 opt := slog.HandlerOptions{   // 自定义option
  AddSource: true,
  Level:     slog.LevelDebug,   // slog 默认日志级别是 info
 }

 logger := slog.New(opt.NewJSONHandler(os.Stdout))
 logger.Debug("记录日志-debug",
  "公众号", "Golang来啦",
  "time", time.Since(time.Now()))
 logger.Info("记录日志-info",
  "公众号", "Golang来啦",
  "time", time.Since(time.Now()))
}
로그인 후 복사

输出:

{"time":"2023-01-23T15:38:45.3747228+08:00","level":"DEBUG","source":"D:/examples/context/demo1/demo1.go:81","msg":"记录日志-debug","公众号":"Golang来啦","time":0}
{"time":"2023-01-23T15:38:45.3949544+08:00","level":"INFO","source":"D:/examples/context/demo1/demo1.go:84","msg":"记录日志-info","公众号":"Golang来啦","time":0}
로그인 후 복사

从输出可以看到记录日志的时候显示了来源,同时也记录了 debug 级别的日志。

SetDefault() 设置默认 Logger

有一点值得注意的是,slog.SetDefault() 会将传进来的 logger 作为默认的 Logger,所以下面这两行输出是一样的:

func main() {
 textHandler := slog.NewJSONHandler(os.Stdout)
 logger := slog.New(textHandler)
 slog.SetDefault(logger)

 logger.Info("Go is best language!", "公众号", "Golang来啦")
 slog.Info("Go is best language!", "公众号", "Golang来啦")
}
로그인 후 복사

输出:

{"time":"2023-01-23T11:17:32.7518696+08:00","level":"INFO","msg":"Go is best language!","公众号":"Golang来啦"}
{"time":"2023-01-23T11:17:32.7732035+08:00","level":"INFO","msg":"Go is best language!","公众号":"Golang来啦"}
로그인 후 복사

另外,如果设置里默认的 Logger,调用 log 包方法时也会使用默认的:

func main() {
 textHandler := slog.NewJSONHandler(os.Stdout)
 logger := slog.New(textHandler)
 slog.SetDefault(logger)

 log.Print("something went wrong")
 log.Fatalln("something went wrong")
}
로그인 후 복사

输出:

{"time":"2023-01-23T11:18:31.5850509+08:00","level":"INFO","msg":"something went wrong"}
{"time":"2023-01-23T11:18:31.6043829+08:00","level":"INFO","msg":"something went wrong"}
exit status 1
로그인 후 복사

两种记录日志的方式

通过 slog 包记录日志除了上面提到的这种方式:

logger.Info("Go is best language!", "公众号", "Golang来啦")
로그인 후 복사

这种方式会涉及到额外的内存分配,主要是为了简介设计的。

另外一种记录日志方式就像下面这样:

logger.LogAttrs(slog.LevelInfo, "Go is best language!", slog.String("公众号", "Golang来啦"))
로그인 후 복사

这两种输出日志格式都是一样的,第二种为了提高记录日志的性能而设计的,需要自己指定日志级别、参数属性(以键值对的方式指定)。

目前 slog 包支持下面这些属性:

String
Int64
Int
Uint64
Float64
Bool
Time
Duration
로그인 후 복사

我们还可以多指定一些属性:

logger.LogAttrs(slog.LevelInfo, "Go is best language!", slog.String("公众号", "Golang来啦"), slog.Int("age", 18))
로그인 후 복사

输出:

{"time":"2023-01-23T11:45:11.7921124+08:00","level":"INFO","msg":"Go is best language!","公众号":"Golang来啦","age":18}
로그인 후 복사

如何绑定一组属性

学到这里我就在想,假如我想在一个 key 下面绑定一组 key-value 值该怎么做呢?这种需求在日常开发中是很常见的,我翻了翻源码,slog 还真的提供了相关方法 -- slog.Group()。

func main() {
 textHandler := slog.NewJSONHandler(os.Stdout)
 logger := slog.New(textHandler)
 slog.SetDefault(logger)

 logger.Info("Usage Statistics",
  slog.Group("memory",
   slog.Int("current", 50),
   slog.Int("min", 20),
   slog.Int("max", 80)),
  slog.Int("cpu", 10),
  slog.String("app-version", "v0.0.0"),
 )
}
로그인 후 복사

输出:

{"time":"2023-01-23T13:45:26.9179901+08:00","level":"INFO","msg":"Usage Statistics","memory":{"current":50,"min":20,"max":80},"cpu":10,"app-version":"v0.0.0"}
로그인 후 복사

memory 元素下面对应不同的 key-value。

如何绑定公共的属性

日常开发中,可能会遇到每一条日志需要记录一些相同的公共信息,比如 app-version。

...

logger.Info("Usage Statistics",
  slog.Group("memory",
   slog.Int("current", 50),
   slog.Int("min", 20),
   slog.Int("max", 80)),
  slog.Int("cpu", 10),
  slog.String("app-version", "v0.0.0"),
 )
 logger.Info("记录日志",
  "公众号", "Golang来啦",
  "time", time.Since(time.Now()), slog.String("app-version", "v0.0.0"))

...
로그인 후 복사

如果想上面这样,每次都记录一次 app-version 的话就有点繁琐了。好在 slog 自带的 TextHandler 和 JSONHandler 提供了 WithAttrs() 方法可以实现绑定公共属性。

func main() {
 textHandler := slog.NewJSONHandler(os.Stdout).WithAttrs([]slog.Attr{slog.String("app-version", "v0.0.0")})
 logger := slog.New(textHandler)
 slog.SetDefault(logger)

 logger.Info("Usage Statistics",
  slog.Group("memory",
   slog.Int("current", 50),
   slog.Int("min", 20),
   slog.Int("max", 80)),
  slog.Int("cpu", 10),
 )
 logger.Info("记录日志",
  "公众号", "Golang来啦",
  "time", time.Since(time.Now()))
}
로그인 후 복사

输出:

{"time":"2023-01-23T14:01:46.2845325+08:00","level":"INFO","msg":"Usage Statistics","app-version":"v0.0.0","memory":{"current":50,"min":20,"max":80},"cpu":10}
{"time":"2023-01-23T14:01:46.303597+08:00","level":"INFO","msg":"记录日志","app-version":"v0.0.0","公众号":"Golang来啦","time":0}
로그인 후 복사

从输出可以看到两条日志都记录了 app-version,这种记录方式就简洁多了。

通过 context 存储或提取 Logger

slog 的 Logger 还与 context.Context 结合在一起,比如通过 slog.WithContext() 存储 Logger、通过 slog.FromContext() 提取 Logger。这样我们就可以在不同函数之间通过 context 传递 Logger。

func main() {
 logger := slog.New(slog.NewJSONHandler(os.Stdout))
 http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
  l := logger.With("path", r.URL.Path).With("user-agent", r.UserAgent()) // With() 绑定额外的信息

  ctx := slog.NewContext(r.Context(), l) // 生成 context

  handleRequest(w, r.WithContext(ctx))
 })

 http.ListenAndServe(":8080", nil)
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
 logger := slog.FromContext(r.Context()) // 提取 Logger

 logger.Info("handling request",
  "status", http.StatusOK)

 w.Write([]byte("Hello World"))
}
로그인 후 복사

执行程序并访问地址: http://127.0.0.1:8080/hello

输出:

{"time":"2023-01-23T14:36:26.6303067+08:00","level":"INFO","msg":"handling request","path":"/hello","user-agent":"curl/7.83.1","status":200}
로그인 후 복사

上面这种使用 Logger 的方式是不是还挺方便的,不过很遗憾的是,在最新的 slog 包里,这两个方法已经被作者移除掉了。

slog: Go의 공식 구조화된 로그 패키지 개발은 어떻게 진행되고 있나요? 사용 방법?

我很好奇作者为什么把这两个方法移除掉,后面翻到 slog 提案[1] 下面作者留言[2],大意是说这种使用方式有比较大的争议(主要是函数之间能否使用 context),而且如果使用者喜欢这种使用方式的话,也可以自己实现,所以把这两个方法移除了。

如果需要自己实现通过 context 储存和提取 Logger,你知道怎么实现吗?欢迎留言区交流,嘻嘻。

如何集成第三方日志包

在讲 Handler 那一节时提到过,如果我们实现了 Handler 接口,就可以将第三方 log 与 Logger 集成,那该怎么实现呢?我们就拿 logrus 日志包举例吧。

package main

import (
 "fmt"
 "github.com/sirupsen/logrus"
 "golang.org/x/exp/slog"
 "net"
 "net/http"
 "os"
)

func init() {
 // 设置logrus
 logrus.SetFormatter(&logrus.JSONFormatter{})
 logrus.SetOutput(os.Stdout)
 logrus.SetLevel(logrus.DebugLevel)
}

func main() {
 // 将 Logrus 与 Logger 集成在一块
 logger := slog.New(&LogrusHandler{
  logger: logrus.StandardLogger(),
 })

 logger.Error("something went wrong", net.ErrClosed,
  "status", http.StatusInternalServerError)
}

type LogrusHandler struct {
 logger *logrus.Logger
}

func (h *LogrusHandler) Enabled(_ slog.Level) bool {
 return true
}

func (h *LogrusHandler) Handle(rec slog.Record) error {
 fields := make(map[string]interface{}, rec.NumAttrs())

 rec.Attrs(func(a slog.Attr) {
  fields[a.Key] = a.Value.Any()
 })

 entry := h.logger.WithFields(fields)

 switch rec.Level {
 case slog.LevelDebug:
  entry.Debug(rec.Message)
 case slog.LevelInfo:
  entry.Info(rec.Message)
 case slog.LevelWarn:
  entry.Warn(rec.Message)
 case slog.LevelError:
  entry.Error(rec.Message)
 }

 fmt.Println("测试是否走了这个方法:记录日志")

 return nil
}

func (h *LogrusHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
 // 为了演示,此方法就没有实现,但不影响效果
 return h
}

func (h *LogrusHandler) WithGroup(name string) slog.Handler {
 // 为了演示,此方法就没有实现,但不影响效果
 return h
}
로그인 후 복사

输出:

{"err":"use of closed network connection","level":"error","msg":"something went wrong","status":500,"time":"2023-01-23T16:07:40+08:00"}
测试是否走了这个方法:记录日志
로그인 후 복사

追查代码发现,通过调用 slog 的方法记录日志时都会调用 logPC() 方法生成一条 Record,最终会交给 Handler 接口的具体实现方法 Handle(),这里就是我们自己实现的方法

func (h *LogrusHandler) Handle(rec slog.Record) error {}
로그인 후 복사

从输出就可以看出,最终调用了自己实现的 Handle() 方法,走的是 logrus 包的方法 entry.Error()。

总结

这篇文章主要介绍了 slog 包的一些主要方法的使用,简单说了下里面一些函数、方法的实现,更详细的细节大家可以自行查看源码。目前中文社区关于 slog 的文章不多(可能是我没发现,欢迎补充),我发现比较好的已经在底部的参考文章里列出来了,作为补充可以深入了解 slog 包。另外感兴趣的同学可以看下关于 slog 的提案(里面会实时更新一些信息以及社区开发者的讨论)和 slog 包的设计文档,具体链接看参考文章。欢迎留言交流,一起学习成长。

위 내용은 slog: Go의 공식 구조화된 로그 패키지 개발은 어떻게 진행되고 있나요? 사용 방법?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

Golang 함수 수명주기 및 변수 범위에 대한 심층적인 이해 Golang 함수 수명주기 및 변수 범위에 대한 심층적인 이해 Apr 19, 2024 am 11:42 AM

Go에서 함수 수명주기에는 정의, 로드, 연결, 초기화, 호출 및 반환이 포함됩니다. 변수 범위는 함수 수준과 블록 수준으로 구분됩니다. 함수 내의 변수는 내부적으로 표시되지만 블록 내의 변수는 블록 내에서만 표시됩니다. .

Go WebSocket 메시지를 보내는 방법은 무엇입니까? Go WebSocket 메시지를 보내는 방법은 무엇입니까? Jun 03, 2024 pm 04:53 PM

Go에서는 gorilla/websocket 패키지를 사용하여 WebSocket 메시지를 보낼 수 있습니다. 특정 단계: WebSocket 연결을 설정합니다. 문자 메시지 보내기: WriteMessage(websocket.TextMessage,[]byte("Message"))를 호출합니다. 바이너리 메시지 보내기: WriteMessage(websocket.BinaryMessage,[]byte{1,2,3})를 호출합니다.

Go에서 정규식을 사용하여 타임스탬프를 일치시키는 방법은 무엇입니까? Go에서 정규식을 사용하여 타임스탬프를 일치시키는 방법은 무엇입니까? Jun 02, 2024 am 09:00 AM

Go에서는 정규식을 사용하여 타임스탬프를 일치시킬 수 있습니다. ISO8601 타임스탬프를 일치시키는 데 사용되는 것과 같은 정규식 문자열을 컴파일합니다. ^\d{4}-\d{2}-\d{2}T \d{ 2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$ . regexp.MatchString 함수를 사용하여 문자열이 정규식과 일치하는지 확인합니다.

Golang과 Go 언어의 차이점 Golang과 Go 언어의 차이점 May 31, 2024 pm 08:10 PM

Go와 Go 언어는 서로 다른 특성을 지닌 서로 다른 개체입니다. Go(Golang이라고도 함)는 동시성, 빠른 컴파일 속도, 메모리 관리 및 크로스 플랫폼 이점으로 유명합니다. Go 언어의 단점은 다른 언어에 비해 생태계가 덜 풍부하고 구문이 더 엄격하며 동적 타이핑이 부족하다는 점입니다.

Golang 기술 성능 최적화에서 메모리 누수를 방지하는 방법은 무엇입니까? Golang 기술 성능 최적화에서 메모리 누수를 방지하는 방법은 무엇입니까? Jun 04, 2024 pm 12:27 PM

메모리 누수로 인해 파일, 네트워크 연결, 데이터베이스 연결 등 더 이상 사용하지 않는 리소스를 닫는 방식으로 Go 프로그램 메모리가 지속적으로 증가할 수 있습니다. 더 이상 강력하게 참조되지 않는 경우 약한 참조를 사용하여 메모리 누수 및 가비지 수집 대상 개체를 방지합니다. go 코루틴을 사용하면 메모리 누수를 방지하기 위해 종료 시 코루틴 스택 메모리가 자동으로 해제됩니다.

IDE에서 Golang 함수 문서를 보는 방법은 무엇입니까? IDE에서 Golang 함수 문서를 보는 방법은 무엇입니까? Apr 18, 2024 pm 03:06 PM

IDE를 사용하여 Go 함수 문서 보기: 함수 이름 위에 커서를 놓습니다. 단축키(GoLand: Ctrl+Q, VSCode: GoExtensionPack 설치 후 F1을 누르고 "Go:ShowDocumentation" 선택)를 누릅니다.

단위 테스트 Go 동시 기능 가이드 단위 테스트 Go 동시 기능 가이드 May 03, 2024 am 10:54 AM

단위 테스트 동시 기능은 동시 환경에서 올바른 동작을 보장하는 데 도움이 되므로 매우 중요합니다. 동시 기능을 테스트할 때는 상호 배제, 동기화, 격리와 같은 기본 원칙을 고려해야 합니다. 동시 기능은 경쟁 조건을 시뮬레이션하고, 테스트하고, 결과를 확인하여 단위 테스트할 수 있습니다.

Golang의 오류 래퍼를 사용하는 방법은 무엇입니까? Golang의 오류 래퍼를 사용하는 방법은 무엇입니까? Jun 03, 2024 pm 04:08 PM

Golang에서 오류 래퍼를 사용하면 원래 오류에 상황별 정보를 추가하여 새로운 오류를 생성할 수 있습니다. 이는 다양한 라이브러리나 구성 요소에서 발생하는 오류 유형을 통합하여 디버깅 및 오류 처리를 단순화하는 데 사용할 수 있습니다. 단계는 다음과 같습니다. error.Wrap 함수를 사용하여 원래 오류를 새 오류로 래핑합니다. 새 오류에는 원래 오류의 상황별 정보가 포함됩니다. fmt.Printf를 사용하면 래핑된 오류를 출력하여 더 많은 컨텍스트와 실행 가능성을 제공할 수 있습니다. 다양한 유형의 오류를 처리할 때 오류 유형을 통합하려면 오류.Wrap 함수를 사용하세요.

See all articles