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

    Насчёт параллельных обращений. 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()
    
            // ...
    }
    Ответ написан
    5 комментариев
  • Как использовать строку в качестве названия функции в GoLang?

    sergey-gornostaev
    @sergey-gornostaev
    Седой и строгий
    Добро пожаловать в мир компилируемых языков программирования и статической типизации, здесь такое не нужно, считается очень плохой практикой и достижимо только через чёрные ходы, вроде рефлексии, что снижает производительность и безопасность кода, а также усложняет его сопровождение.
    Ответ написан
    Комментировать