Использование логгера / конфигов в мультипакетах, лучшая практика для продуктивного Golang - PullRequest
0 голосов
/ 19 октября 2018

У меня следующая структура проекта:

myGithubProject/
    |---- cmd
      |----- command
        |----- hello.go
    |---- internal
        |----- template
           |----- template.go
        |----- log
          |----- logger.go
    main.go

log и template находятся на одном уровне (в рамках внутреннего пакета) В logger.go Я использую logrus в качестве регистраторас некоторыми настройками песка я хочу использовать объект logger.go внутри пакета template.Как мне это сделать чистым способом?

В настоящее время я использую его с import logger внутри моего template.go файла,

И в пакете internal у меня есть 6 больше packages, для которого требуется это logger.и каждый из них зависит от этого.(На пакете log), есть ли хороший способ справиться с этим?

обновление:

на случай, если у меня будет больше вещей, которые мне нужныpass (как logger) какой здесь будет подход / паттерн?может быть, используя dependency injection?interface?другой чистый подход ...

Мне нужен лучший пример полной практики как я могу использовать logger внутри файла hello.go, а также в template.go.

это мой проект

cliProject/main.go

package main

import (
    "cliProject/cmd"
    "cliProject/internal/logs"
)

func main() {
    cmd.Execute()
    logs.Logger.Error("starting")
}


**cliProject/cmd/root.go**

package cmd

import (
    "fmt"
    "github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
    Use:   "cliProject",
    Short: "A brief description of your application",
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
    }
}

**cliProject/cmd/serve.go**

package cmd

import (
    "cliProject/internal/logs"
    "fmt"

    "github.com/spf13/cobra"
)

// serveCmd represents the serve command
var serveCmd = &cobra.Command{
    Use:   "serve",
    Short: "A brief description of your command",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("serve called")
        startServe()
        stoppingServe()
    },
}

func init() {
    rootCmd.AddCommand(serveCmd)
    serveCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

func startServe() {
    logs.Logger.Error("starting from function serve")
}

func stoppingServe() {
    logs.Logger.Error("stoping from function serve")
}


**cliProject/cmd/start.go**

package cmd

import (
    "cliProject/internal/logs"
    "github.com/spf13/cobra"
)

// startCmd represents the start command
var startCmd = &cobra.Command{
    Use:   "start",
    Short: "Start command",
    Run: func(cmd *cobra.Command, args []string) {
        // Print the logs via logger from internal
        logs.Logger.Println("start called inline")
        // example of many functions which should use the logs...
        start()
        stopping()

    },
}

func init() {
    logs.NewLogger()
    rootCmd.AddCommand(startCmd)
    startCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

func start() {
    logs.Logger.Error("starting from function start")
}

func stopping() {
    logs.Logger.Error("stoping from function start")
}


**cliProject/internal/logs/logger.go**

package logs

import (
    "github.com/sirupsen/logrus"
    "github.com/x-cray/logrus-prefixed-formatter"
    "os"
)

var Logger *logrus.Logger

func NewLogger() *logrus.Logger {

    var level logrus.Level
    level = LogLevel("info")
    logger := &logrus.Logger{
        Out:   os.Stdout,
        Level: level,
        Formatter: &prefixed.TextFormatter{
            DisableColors:   true,
            TimestampFormat: "2009-06-03 11:04:075",
        },
    }
    Logger = logger
    return Logger
}

func LogLevel(lvl string) logrus.Level {
    switch lvl {
    case "info":
        return logrus.InfoLevel
    case "error":
        return logrus.ErrorLevel
    default:
        panic("Not supported")
    }
}

вот так это выглядит

enter image description here

Ответы [ 3 ]

0 голосов
/ 20 октября 2018

Есть несколько возможностей, каждая из которых имеет свои собственные компромиссы.

  1. Передача в явном виде зависимостей
  2. Передача в контексте со всеми зависимостями
  3. Использование структуры для контекста для методов
  4. Использование пакета global иimport

Все они имеют свое место в разных обстоятельствах и имеют разные компромиссы:

  1. Это очень ясно, но может стать очень запутанным и загромождать ваши функциимного зависимостей.Это делает тесты легкими для насмешек, если это ваша вещь.
  2. Это мой наименее любимый вариант, так как сначала он соблазнителен, но быстро превращается в объект бога, который смешивает множество не связанных между собой проблем.Избегайте.
  3. Это может быть очень полезно во многих случаях, например, многие люди обращаются к db access таким образом.Также легко издеваться, если требуется.Это позволяет вам устанавливать / обмениваться зависимостями без изменения кода в точке использования - в основном инвертировать управление аккуратнее, чем передавать явные параметры.
  4. Это имеет силу ясности и ортогональности.Вам потребуется добавить отдельную настройку, скажем, для тестов и производства, чтобы инициализировать пакет до надлежащего состояния перед его использованием.Некоторым это не нравится по этой причине.

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

Примеры в псевдокоде для краткости:

// 1. Pass in dependencies explicitly
func MyFunc(log LoggerInterface, param, param)


// 2. Pass in a context with all dependencies

func MyFunc(c *ContextInterface, param, param)

// 3. Use a struct for context to methods

func (controller *MyController) MyFunc(param, param) {
   controller.Logger.Printf("myfunc: doing foo:%s to bar:%s",foo, bar) 
}

// 4. Use a package global and import

package log 

var DefaultLogger PrintfLogger

func Printf(format, args) {DefaultLogger.Printf(format, args)}

// usage: 

import "xxx/log"

log.Printf("myfunc: doing foo:%s to bar:%s",foo, bar) 

Я предпочитаю эту опцию 4 для регистрации и настройки, и даже для доступа к базе данных, поскольку она является явной, гибкой и позволяет легко отключать другой регистратор - просто измените импорт, не изменяя интерфейсы.Расчет, который лучше всего, зависит от обстоятельств - если вы писали библиотеку для широкого использования, вы могли бы предпочесть установить регистратор на уровне структуры.

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

0 голосов
/ 22 октября 2018

И под внутренним пакетом у меня есть еще 6 пакетов, которым нужен этот логгер.и каждый из них зависит от этого.(В пакете журналов) есть ли хорошее начало, чтобы справиться с этим?

Хороший общий принцип - уважать выбор приложения (регистрировать или нет)вместо установки политики.

  1. Пакеты Let Go в вашем каталоге internal будут пакетами поддержки , которые

    • будут возвращать только error в случае проблем
    • не будет регистрироваться (консоль или иным образом)
    • не будет panic
  2. Пусть ваше приложение(пакеты в вашей директории cmd) решают, какое поведение следует предпринять в случае ошибки (запись / постепенное завершение работы / восстановление до 100% целостности)

Это упростит разработку благодаря ведению журналатолько на определенном слое.Примечание: не забудьте дать приложению достаточный контекст для определения действия

internal / process / process.go

package process

import (
    "errors"
)

var (
    ErrNotFound = errors.New("Not Found")
    ErrConnFail = errors.New("Connection Failed")
)

// function Process is a dummy function that returns error for certain arguments received
func Process(i int) error {
    switch i {
    case 6:
        return ErrNotFound
    case 7:
        return ErrConnFail
    default:
        return nil
    }
}

cmd / servi / main.go

package main

import (
    "log"

    p "../../internal/process"
)

func main() {
    // sample: generic logging on any failure
    err := p.Process(6)
    if err != nil {
        log.Println("FAIL", err)
    }

    // sample: this application decides how to handle error based on context
    err = p.Process(7)
    if err != nil {
        switch err {
        case p.ErrNotFound:
            log.Println("DOESN'T EXIST. TRY ANOTHER")
        case p.ErrConnFail:
            log.Println("UNABLE TO CONNECT; WILL RETRY LATER")
        }
    }
}

на случай, если у меня будет больше вещей, которые мне нужно передать (например, логгер), какой здесь будет подход / шаблон

Зависимостьинъекция всегда хороший выбор.Рассматривайте другие только тогда, когда простейшей реализации недостаточно.

Код ниже 'связывает' пакеты template и logger вместе, используя внедрение зависимостей и первоклассную передачу функций.

internal / logs / logger.go

package logger

import (
    "github.com/sirupsen/logrus"
    "github.com/x-cray/logrus-prefixed-formatter"
    "os"
)

var Logger *logrus.Logger

func NewLogger() *logrus.Logger {

    var level logrus.Level
    level = LogLevel("info")
    logger := &logrus.Logger{
        Out:   os.Stdout,
        Level: level,
        Formatter: &prefixed.TextFormatter{
            DisableColors:   true,
            TimestampFormat: "2009-06-03 11:04:075",
        },
    }
    Logger = logger
    return Logger
}

func LogLevel(lvl string) logrus.Level {
    switch lvl {
    case "info":
        return logrus.InfoLevel
    case "error":
        return logrus.ErrorLevel
    default:
        panic("Not supported")
    }
}

внутренняя / template / template.go

package template

import (
    "fmt"
    "github.com/sirupsen/logrus"
)

type Template struct {
    Name   string
    logger *logrus.Logger
}

// factory function New accepts a logging function and some data
func New(logger *logrus.Logger, data string) *Template {
    return &Template{data, logger}
}

// dummy function DoSomething should do something and log using the given logger
func (t *Template) DoSomething() {
    t.logger.Info(fmt.Sprintf("%s, %s", t.Name, "did something"))
}

cmd / servi2 / main.go

package main

import (
    "../../internal/logs"
    "../../internal/template"
)

func main() {
    // wire our template and logger together
    loggingFunc := logger.NewLogger()

    t := template.New(loggingFunc, "Penguin Template")

    // use the stuff we've set up
    t.DoSomething()
}

Надеюсь, это поможет.Cheers,

0 голосов
/ 19 октября 2018

Я всегда явно передавал *logrus.Logger в функции (или иногда объекты), которые нуждались в этом.Это позволяет избежать странных циклов зависимости, делает явным то, что ведение журнала является частью того, что делает эта функция, и облегчает повторное использование функции или модуля в другом месте.Исходный объект журнала создается и настраивается в моей основной функции (вероятно, после некоторой обработки аргументов командной строки).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...