Forzend's channel
367 subscribers
30 photos
2 videos
1 file
89 links
Download Telegram
Forwarded from smoky code (Михаил)
This media is not supported in your browser
VIEW IN TELEGRAM
grmon - как htop только для горутин.

#golang
📖 Про slog

Ещё недавно в Go не было встроенного структурированного логгера (логгера по уровням, со всякими key=value полями, который может писать json в разные системы сбора). Поэтому в большинстве проектов использовались сторонние логгеры (хотя, есть и люди, которые пишут log.Printf("[DEBUG] something happend") и радуются жизни, но я не из таких). Я же, в свою очередь предпочел zerolog.

zerolog предлагает возможность (хотя и не заставляет) прокидывать логгер через контекст. Таким образом вы можете внутри middleware добавлять в логер какие-то поля, такие как request_id, и использовать "расширенный" логгер в хендлере.
Хотя и "фу, context.WithValue", я привык к такому решению и мне оно казалось стандартным допущением.

Однако в стандартной библиотеке появился log/slog. К моему удивлению, функционала для вставки и извлечения логгера из/в контекст не оказалось. Меня это удивило и я, не долго думая, сам реализовал этот функционал. Но потом задумался: "разработчики go ведь не идиоты (хотя я лично знаю много людей, готовых спорить с этим утверждением), как они предлагают использовать логгер?".

slog умеет держать логгер в глобальном atomic.Pointer. И я начал анализировать, а нормально ли использовать глобальный логгер для приложения? С одной стороны весьма удобно, импортировал и юзаешь. Но это немного усложняет тестирование, выходит не очень явно и в целом как-то так себе, глобалы всё-таки...

Но главной причиной отказа от этого решения для меня являлось не понимание того, как же мне заткнуть request_id в логгер и прокинуть в handler логгер вместе с ним. Ведь не буду я в каждом логе руками request_id прописывать.

Хочу подчеркнуть, проблемой было непонимание. Здесь я углубился: на что способен slog? И был приятно удивлён. После стандартного логгера в python примитивность slog меня огорчила. Ведь всё, что у нас есть, текстовый и json логгер (handler) и пара методов для записи логов. Но в моменте мне открылась сила декораторов. Дело в том, что декорируя Handler, который использует логгер, вы можете сделать всё. Буквально всё. От цветного вывода в консоль и записи в несколько источников (хоть в telegram сообщения шли), до тех же самых request_id.

Осознав это, я будто познал тайны мироздания...
Вот, как я решил проблему request_id:
type RequestIDLogger struct {
slog.Handler
}

func (l *RequestIDLogger) Handle(ctx context.Context, r slog.Record) error {
request := getRequestID(ctx)

r.AddAttrs(slog.String("request_id", request))

err := l.Handler.Handle(ctx, r)
if err != nil {
return fmt.Errorf("failed to run parent of RequestIDLogger handler: %w", err)
}

return nil
}


Но использовать глобальный логгер я всё равно не захотел, поэтому распихал их по структурам, как обычно в Go делается dependency injection.
Кажется, это наилучший компромисс между всеми подходами.

Теперь middleware генерирует для каждого запроса request_id и добавляет его в контекст, а handler в slog.Logger извлекает его и добавляет при записи логов.

Кстати, slog уже оброс кучей всяких handler. Много интересного можно найти в awesome slog (думаю, тут есть всё, что вам может пригодиться).

#go #golang #logging #slog