Fcorpion
@Fcorpion
Per aspera ad astra = Через терни к звёздам.

Slog. Нужно ли предавать логгер через параметры или объявить его на уровне модуля и обращаться к нему?

Я не могу разобраться передавать ли логгер через параметр каждой функции (вариант с передачей его каждой структуры чтобы у неё была ссылка на него кажется сомнительным) или объявить его в модули main или app и обращаться к нему так каждый раз. Второй вариант кажется привлекательнее так как не надо в аргументах каждой функции передавать ей логгер. Хотя беспокоюсь насчёт того что во время обращения к api несколько обработчиков запросов будут параллельно нему обращаться. Какой вариант лучше? Логгер который я использую - slog.
  • Вопрос задан
  • 140 просмотров
Решения вопроса 1
Насчёт параллельных обращений. slog очень гибкий и продуманный логгер. Не стоит беспокоиться насчёт параллельных обращений, там всё отлично с этим.

Насчёт того, использовать ли логгер глобально...
На этот вопрос трудно ответить, не зная, какой у вас проект.

Лично я предпочитаю пользоваться принципом "Бритвы Оккама" и не вводить новых сущностей без необходимости.

Сначала надо задаться вопросом: а нужны ли мне разные варианты логгера в одном и том же окружении? Что я такого буду логгировать в разные места и разным образом, чтобы мне надо было иметь кучу разных логгеров и передавать их как зависимость в каждую функцию или в каждый ваш синглтончик. И если не нужны, то зачем городить огород?

Тем более, что даже для тестов вы можете написать для slog какой-то особый хэндлер, который будет работать так, как надо в тестах.

Поэтому я последнее время всегда использую глобальный slog. В начале программы загружаю конфиг, переменные окружения, определяю, какой мне в этом окружении будет нужен логгер, и конфигурирую дефолтный slog с нужным мне хендлером.

Вот вам крайне увлекательное и крайне полезное видео, как из глобального slog можно сделать просто монстра логгирования на все случаи жизни.
https://www.youtube.com/watch?v=p9XiOOU52Qw

Вот пример того, как я настраивал логгер в одном из маленьких проектов. Он определяет по конфигу, с какого уровня надо логгировать, в каком формате, и в какой поток (stdout, stderr, файл)

package app

import (
	"fmt"
	"io"
	"log/slog"
	"os"
)

// SetupLogger создаёт логгер на основании конфига
// Конфигурируем дефолтный slog и возвращаем функцию закрытия файла логов, 
// если был выбран файл как место логгирования.
func SetupLogger(cfg config.Config) func() error {
	closer := func() error { return nil }

	level := cfg.LogLevel

	var w io.Writer

	switch cfg.LogOutput {
	case config.LogOutputStderr:
		w = os.Stderr
	case config.LogOutputStdout:
		w = os.Stdout
	case config.LogOutputFile:
		w, closer = getFileWriter(cfg)
	}

	var handler slog.Handler
	switch cfg.LogFormat {
	case config.LogFormatText:
		handler = slog.Handler(slog.NewTextHandler(w, &slog.HandlerOptions{Level: level}))
	case config.LogFormatJSON:
		handler = slog.Handler(slog.NewJSONHandler(w, &slog.HandlerOptions{Level: level}))
	}

	slog.SetDefault(slog.New(handler))

	return closer
}

// getFileWriter возвращает файл, куда писать логи и функцию закрытия этого файла
func getFileWriter(cfg config.Config) (*os.File, func() error) {
	logDir := fmt.Sprintf("%s/%s", cfg.VarDir, cfg.LogDir)
	err := os.MkdirAll(logDir, 0755)
	if err != nil {
		panic(fmt.Sprintf("error creating directory: %s;  %v", logDir, err))
	}

	filename := fmt.Sprintf("%s/%s.log", logDir, cfg.AppEnv)
	f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		panic(fmt.Sprintf("error opening file: %s %v", filename, err))
	}
	return f, f.Close
}


package main

import "app"

func main() {

        // .....

	closeLogFile := app.SetupLogger(cfg)
	defer closeLogFile()

        // ...
}
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы