gocommon/log/config.go
Matthieu 'JP' DERASSE 45c43b44a0
All checks were successful
continuous-integration/drone/push Build is passing
feat(go): Upgrade to go 1.21
2023-08-11 19:16:12 +00:00

204 lines
5.6 KiB
Go

package log
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/juju/errors"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
"git.dev.m-and-m.ovh/mderasse/gocommon/convert"
"git.dev.m-and-m.ovh/mderasse/gocommon/log/hooks/file"
"git.dev.m-and-m.ovh/mderasse/gocommon/log/hooks/gelf"
)
// envPrefix is the prefix that will be used for any environnement variable used to configure the logging system.
const envPrefix = "LOG_"
// CONFIG_FILE is the default file that will be searched to apply configuration from the filesystem.
const defaultConfigFile = "conf/log.yaml"
// SECRET_NAME is the default name of the secret that will be searched in vault.
const defaultSecretName = "log"
// ConfigStruct represent the configuration of our logger system.
type ConfigStruct struct {
EnableStdOut *bool `yaml:"enable_std_out"`
ExtrasFields map[string]interface{} `yaml:"extra_fields"`
FileConfig *file.ConfigStruct `yaml:"file_config"`
GelfConfig *gelf.ConfigStruct `yaml:"gelf_config"`
Level *string `yaml:"level"`
Providers []ProviderName `yaml:"providers"`
}
func newDefaultConfig() *ConfigStruct {
return &ConfigStruct{
Level: convert.ToPointer("debug"),
EnableStdOut: convert.ToPointer(true),
Providers: []ProviderName{ProviderName_NONE},
}
}
func loadConfigFromVault(secret string) (*ConfigStruct, error) {
_ = secret
return nil, os.ErrNotExist
}
// loadConfigFromFile will read the given file and return a config struct.
func loadConfigFromFile(path string) (*ConfigStruct, error) {
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
return nil, errors.Trace(err)
}
//nolint:gosec // we did compute the file path
f, err := os.ReadFile(path)
if err != nil {
return nil, errors.Trace(err)
}
var config ConfigStruct
if err := yaml.Unmarshal(f, &config); err != nil {
return nil, errors.Trace(err)
}
return &config, nil
}
func loadConfig() (*ConfigStruct, error) {
c, err := loadConfigFromVault(defaultSecretName)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
} else if err == nil {
return c, nil
}
c, err = loadConfigFromFile(defaultConfigFile)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
} else if err == nil {
return c, nil
}
return newDefaultConfig(), nil
}
// applyEnv will retrieve info from environment variable and overide those got by config / vault.
// validity of the value is not checked here and will be check in a IsValid method.
func (c *ConfigStruct) applyEnv() error {
if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "LEVEL")); v != "" {
c.Level = convert.ToPointer(v)
}
if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "ENABLE_STDOUT")); v != "" {
b, err := strconv.ParseBool(v)
if err != nil {
return errors.NewNotValid(err, fmt.Sprintf("Invalid %s%s environment variable. Should be a boolean", envPrefix, "ENABLE_STDOUT"))
}
c.EnableStdOut = convert.ToPointer(b)
}
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
}
// Extra Fields
if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "EXTRA_FIELDS")); v != "" {
if c.ExtrasFields == nil {
c.ExtrasFields = make(map[string]interface{})
}
extraFieldsPart := strings.Split(v, ",")
for _, efp := range extraFieldsPart {
extraFieldKV := strings.SplitN(efp, ":", 2)
if len(extraFieldKV) != 2 {
return errors.NotValidf(fmt.Sprintf("Invalid extra_field %s in environment variable. Should be a key1:value1,key2:value2 format", efp))
}
c.ExtrasFields[extraFieldKV[0]] = extraFieldKV[1]
}
}
return nil
}
// applyDefault will check the gaven config and will apply default on empty field.
func (c *ConfigStruct) applyDefault() {
defaultConfig := newDefaultConfig()
if c.Level == nil || *c.Level == "" {
c.Level = defaultConfig.Level
}
if c.EnableStdOut == nil {
c.EnableStdOut = defaultConfig.EnableStdOut
}
if c.Providers == nil || len(c.Providers) == 0 {
c.Providers = defaultConfig.Providers
}
}
// IsValid will check a config struct.
func (c *ConfigStruct) IsValid() error {
if c.EnableStdOut == nil {
return errors.NotValidf("EnableStdOut is empty")
}
if c.Level == nil {
return errors.NotValidf("Level is empty")
} else if _, err := logrus.ParseLevel(*c.Level); err != nil {
return errors.NotValidf("Invalid Level. Allowed values: %s", strings.Join(convert.StringerSliceToStringSlice(logrus.AllLevels), ", "))
}
if c.Providers == nil || len(c.Providers) == 0 {
return errors.NotValidf("Providers is empty")
}
// Validating configuration for the chosen provider.
for _, provider := range c.Providers {
if !provider.IsValid() {
return errors.NotValidf("Invalid Provider %s. Allowed values: %s", provider.String(), strings.Join(convert.StringerSliceToStringSlice(GetListProviderName()), ", "))
}
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")
}
}
return nil
}