fix(logs): Small refacto and allow multiple providers at the same time

This commit is contained in:
Matthieu 'JP' DERASSE 2022-12-13 20:29:35 +00:00
parent bef0d38f1e
commit 99bca634d2
Signed by: mderasse
GPG Key ID: 55141C777B16A705
4 changed files with 104 additions and 81 deletions

View File

@ -28,7 +28,7 @@ const defaultSecretName = "log"
type ConfigStruct struct { type ConfigStruct struct {
Level *string `yaml:"level"` Level *string `yaml:"level"`
EnableStdOut *bool `yaml:"ensable_std_out"` EnableStdOut *bool `yaml:"ensable_std_out"`
Provider *ProviderName `yaml:"provider"` Providers []ProviderName `yaml:"providers"`
FileConfig *file.ConfigStruct `yaml:"file_config"` FileConfig *file.ConfigStruct `yaml:"file_config"`
GelfConfig *gelf.ConfigStruct `yaml:"gelf_config"` GelfConfig *gelf.ConfigStruct `yaml:"gelf_config"`
} }
@ -37,7 +37,7 @@ func newDefaultConfig() *ConfigStruct {
return &ConfigStruct{ return &ConfigStruct{
Level: convert.ToPointer("debug"), Level: convert.ToPointer("debug"),
EnableStdOut: convert.ToPointer(true), 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) c.EnableStdOut = convert.ToPointer(b)
} }
if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "PROVIDER")); v != "" { if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "PROVIDERS")); v != "" {
p := ProviderName(v) providersStr := strings.Split(v, ",")
c.Provider = convert.ToPointer(p) var providers []ProviderName
for _, ps := range providersStr {
providers = append(providers, ProviderName(ps))
}
c.Providers = providers
} }
return nil return nil
@ -123,8 +127,8 @@ func (c *ConfigStruct) applyDefault() {
c.EnableStdOut = defaultConfig.EnableStdOut c.EnableStdOut = defaultConfig.EnableStdOut
} }
if c.Provider == nil || *c.Provider == "" { if c.Providers == nil || len(c.Providers) == 0 {
c.Provider = defaultConfig.Provider 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), ", ")) return errors.NotValidf("Invalid Level. Allowed values: %s", strings.Join(convert.StringerSliceToStringSlice(logrus.AllLevels), ", "))
} }
if c.Provider == nil { if c.Providers == nil || len(c.Providers) == 0 {
return errors.NotValidf("Provider is empty") return errors.NotValidf("Providers is empty")
} else if !c.Provider.IsValid() {
return errors.NotValidf("Invalid Provider. Allowed values: %s", strings.Join(convert.StringerSliceToStringSlice(GetListProviderName()), ", "))
} }
// Validating configuration for the chosen provider. // Validating configuration for the chosen provider.
switch *c.Provider { for _, provider := range c.Providers {
case ProviderName_FILE: if !provider.IsValid() {
if c.FileConfig == nil { return errors.NotValidf("Invalid Provider %s. Allowed values: %s", provider.String(), strings.Join(convert.StringerSliceToStringSlice(GetListProviderName()), ", "))
return errors.NotValidf("file configuration is empty")
} }
err := c.FileConfig.IsValid()
if err != nil { switch provider {
return errors.Trace(err) 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 return nil

View File

@ -16,6 +16,13 @@ import (
// be available in the queue. // be available in the queue.
var BufSize uint = 8192 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. // Hook will write logs to a file.
type Hook struct { type Hook struct {
Level logrus.Level Level logrus.Level
@ -27,19 +34,21 @@ type Hook struct {
wg sync.WaitGroup wg sync.WaitGroup
} }
// XXX: Maybe just take a formatter in input
// NewFileHook creates a hook to be added to an instance of logger. // 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 { if w == nil {
logrus.Error("Can't create File Hook with an empty writer") logrus.Error("Can't create File Hook with an empty writer")
return nil return nil
} }
if f == nil {
f = defaultFormater
}
hook := &Hook{ hook := &Hook{
Level: logrus.DebugLevel, Level: logrus.DebugLevel,
synchronous: true, synchronous: true,
f: handleFormat(of), f: f,
w: w, 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. // 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 // 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. // 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 { if w == nil {
logrus.Error("Can't create File Hook with an empty writer") logrus.Error("Can't create File Hook with an empty writer")
return nil return nil
} }
if f == nil {
f = defaultFormater
}
hook := &Hook{ hook := &Hook{
Level: logrus.DebugLevel, Level: logrus.DebugLevel,
buf: make(chan logrus.Entry, BufSize), buf: make(chan logrus.Entry, BufSize),
f: handleFormat(of), f: f,
w: w, w: w,
} }
@ -80,23 +93,6 @@ func (hook *Hook) Flush() {
hook.wg.Wait() 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. // Fire is called when a log event is fired.
// We assume the entry will be altered by another hook, // We assume the entry will be altered by another hook,
// otherwise we might be logging something wrong to Graylog. // otherwise we might be logging something wrong to Graylog.

View File

@ -70,7 +70,24 @@ func NewHook(c *ConfigStruct) (logrus.Hook, error) {
w = fh w = fh
} }
h := NewAsyncFileHook(w, outputFormat) h := NewAsyncFileHook(w, handleFormat(outputFormat))
return h, nil 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,
}
}

View File

@ -11,8 +11,8 @@ import (
"git.dev.m-and-m.ovh/mderasse/gocommon/log/hooks/gelf" "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. // Init will try to initialize logger by trying to retrieve config from multiple source.
func InitLog() (*logrus.Logger, error) { func Init() (*logrus.Logger, error) {
// loading configuration // loading configuration
c, err := loadConfig() c, err := loadConfig()
@ -23,8 +23,8 @@ func InitLog() (*logrus.Logger, error) {
return initFromSource(c) return initFromSource(c)
} }
// InitLogFromCustomVaultSecret will initialize logger with a vault secret. // InitFromCustomVaultSecret will initialize logger with a vault secret.
func InitLogFromCustomVaultSecret(secret string) (*logrus.Logger, error) { func InitFromCustomVaultSecret(secret string) (*logrus.Logger, error) {
c, err := loadConfigFromVault(secret) c, err := loadConfigFromVault(secret)
if err != nil { if err != nil {
@ -34,8 +34,8 @@ func InitLogFromCustomVaultSecret(secret string) (*logrus.Logger, error) {
return initFromSource(c) return initFromSource(c)
} }
// InitLogFromCustomFile will initialize logger with a config file. // InitFromCustomFile will initialize logger with a config file.
func InitLogFromCustomFile(path string) (*logrus.Logger, error) { func InitFromCustomFile(path string) (*logrus.Logger, error) {
c, err := loadConfigFromFile(path) c, err := loadConfigFromFile(path)
if err != nil { if err != nil {
@ -53,11 +53,11 @@ func initFromSource(c *ConfigStruct) (*logrus.Logger, error) {
c.applyDefault() c.applyDefault()
return InitLogFromCustomConfig(c) return InitFromCustomConfig(c)
} }
// InitLogFromCustomConfig will initialize logger from a gaven config. // InitFromCustomConfig will initialize logger from a gaven config.
func InitLogFromCustomConfig(c *ConfigStruct) (*logrus.Logger, error) { func InitFromCustomConfig(c *ConfigStruct) (*logrus.Logger, error) {
err := c.IsValid() err := c.IsValid()
if err != nil { if err != nil {
@ -79,23 +79,25 @@ func InitLogFromCustomConfig(c *ConfigStruct) (*logrus.Logger, error) {
log.SetOutput(io.Discard) log.SetOutput(io.Discard)
} }
switch *c.Provider { for _, provider := range c.Providers {
case ProviderName_FILE: switch provider {
hook, err := file.NewHook(c.FileConfig) case ProviderName_FILE:
if err != nil { hook, err := file.NewHook(c.FileConfig)
return nil, errors.Trace(err) 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 return log, nil