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 {
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

View File

@ -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.

View File

@ -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,
}
}

View File

@ -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