From 99bca634d27005a0512bc3a98b46d690d626f50d Mon Sep 17 00:00:00 2001 From: Matthieu 'JP' DERASSE Date: Tue, 13 Dec 2022 20:29:35 +0000 Subject: [PATCH] fix(logs): Small refacto and allow multiple providers at the same time --- log/config.go | 72 ++++++++++++++++++++----------------- log/hooks/file/file_hook.go | 42 ++++++++++------------ log/hooks/file/hook.go | 19 +++++++++- log/log.go | 52 ++++++++++++++------------- 4 files changed, 104 insertions(+), 81 deletions(-) diff --git a/log/config.go b/log/config.go index ebb16d3..044270c 100644 --- a/log/config.go +++ b/log/config.go @@ -28,7 +28,7 @@ const defaultSecretName = "log" type ConfigStruct struct { Level *string `yaml:"level"` EnableStdOut *bool `yaml:"ensable_std_out"` - Provider *ProviderName `yaml:"provider"` + Providers []ProviderName `yaml:"providers"` FileConfig *file.ConfigStruct `yaml:"file_config"` GelfConfig *gelf.ConfigStruct `yaml:"gelf_config"` } @@ -37,7 +37,7 @@ func newDefaultConfig() *ConfigStruct { return &ConfigStruct{ Level: convert.ToPointer("debug"), EnableStdOut: convert.ToPointer(true), - Provider: convert.ToPointer(ProviderName_NONE), + Providers: []ProviderName{ProviderName_NONE}, } } @@ -102,9 +102,13 @@ func (c *ConfigStruct) applyEnv() error { c.EnableStdOut = convert.ToPointer(b) } - if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "PROVIDER")); v != "" { - p := ProviderName(v) - c.Provider = convert.ToPointer(p) + if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "PROVIDERS")); v != "" { + providersStr := strings.Split(v, ",") + var providers []ProviderName + for _, ps := range providersStr { + providers = append(providers, ProviderName(ps)) + } + c.Providers = providers } return nil @@ -123,8 +127,8 @@ func (c *ConfigStruct) applyDefault() { c.EnableStdOut = defaultConfig.EnableStdOut } - if c.Provider == nil || *c.Provider == "" { - c.Provider = defaultConfig.Provider + if c.Providers == nil || len(c.Providers) == 0 { + c.Providers = defaultConfig.Providers } } @@ -141,36 +145,40 @@ func (c *ConfigStruct) IsValid() error { return errors.NotValidf("Invalid Level. Allowed values: %s", strings.Join(convert.StringerSliceToStringSlice(logrus.AllLevels), ", ")) } - if c.Provider == nil { - return errors.NotValidf("Provider is empty") - } else if !c.Provider.IsValid() { - return errors.NotValidf("Invalid Provider. Allowed values: %s", strings.Join(convert.StringerSliceToStringSlice(GetListProviderName()), ", ")) + if c.Providers == nil || len(c.Providers) == 0 { + return errors.NotValidf("Providers is empty") } // Validating configuration for the chosen provider. - switch *c.Provider { - case ProviderName_FILE: - if c.FileConfig == nil { - return errors.NotValidf("file configuration is empty") + for _, provider := range c.Providers { + if !provider.IsValid() { + return errors.NotValidf("Invalid Provider %s. Allowed values: %s", provider.String(), strings.Join(convert.StringerSliceToStringSlice(GetListProviderName()), ", ")) } - err := c.FileConfig.IsValid() - if err != nil { - return errors.Trace(err) + + switch provider { + case ProviderName_FILE: + if c.FileConfig == nil { + return errors.NotValidf("file configuration is empty") + } + err := c.FileConfig.IsValid() + if err != nil { + return errors.Trace(err) + } + case ProviderName_GELF: + if c.GelfConfig == nil { + return errors.NotValidf("GELF configuration is empty") + } + err := c.GelfConfig.IsValid() + if err != nil { + return errors.Trace(err) + } + case ProviderName_NONE: + if !*c.EnableStdOut { + return errors.NotValidf("Provider set to none with StdOut disabled make no sense") + } + default: + return errors.NotValidf("Provider not handled") } - case ProviderName_GELF: - if c.GelfConfig == nil { - return errors.NotValidf("GELF configuration is empty") - } - err := c.GelfConfig.IsValid() - if err != nil { - return errors.Trace(err) - } - case ProviderName_NONE: - if !*c.EnableStdOut { - return errors.NotValidf("Provider set to none with StdOut disabled make no sense") - } - default: - return errors.NotValidf("Provider not handled") } return nil diff --git a/log/hooks/file/file_hook.go b/log/hooks/file/file_hook.go index 4201260..5269b7d 100644 --- a/log/hooks/file/file_hook.go +++ b/log/hooks/file/file_hook.go @@ -16,6 +16,13 @@ import ( // be available in the queue. var BufSize uint = 8192 +// defaultFormater will be use if no formatter is given. +var defaultFormater = &logrus.TextFormatter{ + DisableColors: true, + TimestampFormat: time.RFC3339Nano, + QuoteEmptyFields: true, +} + // Hook will write logs to a file. type Hook struct { Level logrus.Level @@ -27,19 +34,21 @@ type Hook struct { wg sync.WaitGroup } -// XXX: Maybe just take a formatter in input - // NewFileHook creates a hook to be added to an instance of logger. -func NewFileHook(w io.Writer, of OutputFormat) *Hook { +func NewFileHook(w io.Writer, f logrus.Formatter) *Hook { if w == nil { logrus.Error("Can't create File Hook with an empty writer") return nil } + if f == nil { + f = defaultFormater + } + hook := &Hook{ Level: logrus.DebugLevel, synchronous: true, - f: handleFormat(of), + f: f, w: w, } @@ -49,16 +58,20 @@ func NewFileHook(w io.Writer, of OutputFormat) *Hook { // NewAsyncFileHook creates a hook to be added to an instance of logger. // The hook created will be asynchronous, and it's the responsibility of the user to call the Flush method // before exiting to empty the log queue. -func NewAsyncFileHook(w io.Writer, of OutputFormat) *Hook { +func NewAsyncFileHook(w io.Writer, f logrus.Formatter) *Hook { if w == nil { logrus.Error("Can't create File Hook with an empty writer") return nil } + if f == nil { + f = defaultFormater + } + hook := &Hook{ Level: logrus.DebugLevel, buf: make(chan logrus.Entry, BufSize), - f: handleFormat(of), + f: f, w: w, } @@ -80,23 +93,6 @@ func (hook *Hook) Flush() { hook.wg.Wait() } -// handleFormat will take a OutputFormat and will transform it in a formatter. -func handleFormat(of OutputFormat) logrus.Formatter { - - if of == OutputFormat_JSON { - return &logrus.JSONFormatter{ - PrettyPrint: false, - TimestampFormat: time.RFC3339Nano, - } - } - - return &logrus.TextFormatter{ - DisableColors: true, - TimestampFormat: time.RFC3339Nano, - QuoteEmptyFields: true, - } -} - // Fire is called when a log event is fired. // We assume the entry will be altered by another hook, // otherwise we might be logging something wrong to Graylog. diff --git a/log/hooks/file/hook.go b/log/hooks/file/hook.go index 8a69e48..bb5e3c0 100644 --- a/log/hooks/file/hook.go +++ b/log/hooks/file/hook.go @@ -70,7 +70,24 @@ func NewHook(c *ConfigStruct) (logrus.Hook, error) { w = fh } - h := NewAsyncFileHook(w, outputFormat) + h := NewAsyncFileHook(w, handleFormat(outputFormat)) return h, nil } + +// handleFormat will take a OutputFormat and will transform it in a formatter. +func handleFormat(of OutputFormat) logrus.Formatter { + + if of == OutputFormat_JSON { + return &logrus.JSONFormatter{ + PrettyPrint: false, + TimestampFormat: time.RFC3339Nano, + } + } + + return &logrus.TextFormatter{ + DisableColors: true, + TimestampFormat: time.RFC3339Nano, + QuoteEmptyFields: true, + } +} diff --git a/log/log.go b/log/log.go index 1f6efd5..5a321ff 100644 --- a/log/log.go +++ b/log/log.go @@ -11,8 +11,8 @@ import ( "git.dev.m-and-m.ovh/mderasse/gocommon/log/hooks/gelf" ) -// InitLog will try to initialize logger by trying to retrieve config from multiple source. -func InitLog() (*logrus.Logger, error) { +// Init will try to initialize logger by trying to retrieve config from multiple source. +func Init() (*logrus.Logger, error) { // loading configuration c, err := loadConfig() @@ -23,8 +23,8 @@ func InitLog() (*logrus.Logger, error) { return initFromSource(c) } -// InitLogFromCustomVaultSecret will initialize logger with a vault secret. -func InitLogFromCustomVaultSecret(secret string) (*logrus.Logger, error) { +// InitFromCustomVaultSecret will initialize logger with a vault secret. +func InitFromCustomVaultSecret(secret string) (*logrus.Logger, error) { c, err := loadConfigFromVault(secret) if err != nil { @@ -34,8 +34,8 @@ func InitLogFromCustomVaultSecret(secret string) (*logrus.Logger, error) { return initFromSource(c) } -// InitLogFromCustomFile will initialize logger with a config file. -func InitLogFromCustomFile(path string) (*logrus.Logger, error) { +// InitFromCustomFile will initialize logger with a config file. +func InitFromCustomFile(path string) (*logrus.Logger, error) { c, err := loadConfigFromFile(path) if err != nil { @@ -53,11 +53,11 @@ func initFromSource(c *ConfigStruct) (*logrus.Logger, error) { c.applyDefault() - return InitLogFromCustomConfig(c) + return InitFromCustomConfig(c) } -// InitLogFromCustomConfig will initialize logger from a gaven config. -func InitLogFromCustomConfig(c *ConfigStruct) (*logrus.Logger, error) { +// InitFromCustomConfig will initialize logger from a gaven config. +func InitFromCustomConfig(c *ConfigStruct) (*logrus.Logger, error) { err := c.IsValid() if err != nil { @@ -79,23 +79,25 @@ func InitLogFromCustomConfig(c *ConfigStruct) (*logrus.Logger, error) { log.SetOutput(io.Discard) } - switch *c.Provider { - case ProviderName_FILE: - hook, err := file.NewHook(c.FileConfig) - if err != nil { - return nil, errors.Trace(err) + for _, provider := range c.Providers { + switch provider { + case ProviderName_FILE: + hook, err := file.NewHook(c.FileConfig) + if err != nil { + return nil, errors.Trace(err) + } + log.AddHook(hook) + case ProviderName_GELF: + hook, err := gelf.NewHook(c.GelfConfig) + if err != nil { + return nil, errors.Trace(err) + } + log.AddHook(hook) + case ProviderName_NONE: + fallthrough + default: + return nil, errors.BadRequestf("Provider is not handled.") } - log.AddHook(hook) - case ProviderName_GELF: - hook, err := gelf.NewHook(c.GelfConfig) - if err != nil { - return nil, errors.Trace(err) - } - log.AddHook(hook) - case ProviderName_NONE: - fallthrough - default: - return nil, errors.BadRequestf("Provider is not handled.") } return log, nil