improve feature flag logging (#1986)

For cscli: it should provide a terse output, not nag users with configuration details. Although it's usually important that cscli and crowdsec have the same enabled features, having it list them every time the command is invoked can be too much.

For crowdsec: when features are set from the environment, it's too early to log where we should. So we can use log.Debug at activation time, and list them again once logging is configured.

 - wrap some functions in csconfig for convenience and DRY
 - for each enabled feature, log.Debug
 - log all enabled features once as Info (crowdsec) or Debug (cscli)
 - file does not exist -> log.Trace
This commit is contained in:
mmetc 2023-01-13 13:42:42 +01:00 committed by GitHub
parent 73663ff9e7
commit 51800132cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 67 additions and 40 deletions

View file

@ -66,9 +66,14 @@ func initConfig() {
csConfig = csconfig.NewDefaultConfig() csConfig = csconfig.NewDefaultConfig()
} }
featurePath := filepath.Join(csConfig.ConfigPaths.ConfigDir, "feature.yaml") if err := csconfig.LoadFeatureFlagsFile(csConfig, log.StandardLogger()); err != nil {
if err = fflag.Crowdsec.SetFromYamlFile(featurePath, log.StandardLogger()); err != nil { log.Fatal(err)
log.Fatalf("File %s: %s", featurePath, err) }
// recap of the enabled feature flags, because logging
// was not enabled when we set them from envvars
if fflist := csconfig.ListFeatureFlags(); fflist != "" {
log.Debugf("Enabled feature flags: %s", fflist)
} }
if csConfig.Cscli == nil { if csConfig.Cscli == nil {
@ -141,10 +146,8 @@ func main() {
log.Fatalf("failed to register features: %s", err) log.Fatalf("failed to register features: %s", err)
} }
// some features can require configuration or command-line options, if err := csconfig.LoadFeatureFlagsEnv(log.StandardLogger()); err != nil {
// so we need to parse them asap. we'll load from feature.yaml later. log.Fatalf("failed to set feature flags from env: %s", err)
if err := fflag.Crowdsec.SetFromEnv(log.StandardLogger()); err != nil {
log.Fatalf("failed to set features from environment: %s", err)
} }
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{

View file

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
_ "net/http/pprof" _ "net/http/pprof"
"os" "os"
"path/filepath"
"runtime" "runtime"
"strings" "strings"
"time" "time"
@ -261,35 +260,16 @@ func LoadConfig(cConfig *csconfig.Config) error {
return err return err
} }
err := LoadFeatureFlags(cConfig, log.StandardLogger()) if err := csconfig.LoadFeatureFlagsFile(cConfig, log.StandardLogger()); err != nil {
if err != nil {
return err return err
} }
return nil // recap of the enabled feature flags, because logging
} // was not enabled when we set them from envvars
if fflist := csconfig.ListFeatureFlags(); fflist != "" {
log.Infof("Enabled feature flags: %s", fflist)
// LoadFeatureFlags parses {ConfigDir}/feature.yaml to enable/disable features.
//
// Since CROWDSEC_FEATURE_ envvars are parsed before config.yaml,
// when the logger is not yet initialized, we also log here a recap
// of what has been enabled.
func LoadFeatureFlags(cConfig *csconfig.Config, logger *log.Logger) error {
featurePath := filepath.Join(cConfig.ConfigPaths.ConfigDir, "feature.yaml")
if err := fflag.Crowdsec.SetFromYamlFile(featurePath, logger); err != nil {
return fmt.Errorf("file %s: %s", featurePath, err)
} }
enabledFeatures := fflag.Crowdsec.GetEnabledFeatures()
msg := "<none>"
if len(enabledFeatures) > 0 {
msg = strings.Join(enabledFeatures, ", ")
}
logger.Infof("Enabled features: %s", msg)
return nil return nil
} }
@ -324,8 +304,8 @@ func main() {
// some features can require configuration or command-line options, // some features can require configuration or command-line options,
// so wwe need to parse them asap. we'll load from feature.yaml later. // so wwe need to parse them asap. we'll load from feature.yaml later.
if err := fflag.Crowdsec.SetFromEnv(log.StandardLogger()); err != nil { if err := csconfig.LoadFeatureFlagsEnv(log.StandardLogger()); err != nil {
log.Fatalf("failed set features from environment: %s", err) log.Fatalf("failed to set feature flags from environment: %s", err)
} }
crowdsecT0 = time.Now() crowdsecT0 = time.Now()

44
pkg/csconfig/fflag.go Normal file
View file

@ -0,0 +1,44 @@
package csconfig
import (
"fmt"
"path/filepath"
"strings"
log "github.com/sirupsen/logrus"
"github.com/crowdsecurity/crowdsec/pkg/fflag"
)
// LoadFeatureFlagsEnv parses the environment variables to enable feature flags.
func LoadFeatureFlagsEnv(logger *log.Logger) error {
if err := fflag.Crowdsec.SetFromEnv(logger); err != nil {
return err
}
return nil
}
// LoadFeatureFlags parses {ConfigDir}/feature.yaml to enable feature flags.
func LoadFeatureFlagsFile(cConfig *Config, logger *log.Logger) error {
featurePath := filepath.Join(cConfig.ConfigPaths.ConfigDir, "feature.yaml")
if err := fflag.Crowdsec.SetFromYamlFile(featurePath, logger); err != nil {
return fmt.Errorf("file %s: %s", featurePath, err)
}
return nil
}
// ListFeatureFlags returns a list of the enabled feature flags.
func ListFeatureFlags() string {
enabledFeatures := fflag.Crowdsec.GetEnabledFeatures()
msg := "<none>"
if len(enabledFeatures) > 0 {
msg = strings.Join(enabledFeatures, ", ")
}
return msg
}

View file

@ -181,7 +181,7 @@ func (fr *FeatureRegister) SetFromEnv(logger *logrus.Logger) error {
return err return err
} }
logger.Infof("Feature flag: %s=%t (from envvar). %s", featureName, enable, feat.Description) logger.Debugf("Feature flag: %s=%t (from envvar). %s", featureName, enable, feat.Description)
} }
return nil return nil
@ -224,7 +224,7 @@ func (fr *FeatureRegister) SetFromYaml(r io.Reader, logger *logrus.Logger) error
return err return err
} }
logger.Infof("Feature flag: %s=true (from config file). %s", k, feat.Description) logger.Debugf("Feature flag: %s=true (from config file). %s", k, feat.Description)
} }
return nil return nil
@ -234,7 +234,7 @@ func (fr *FeatureRegister) SetFromYamlFile(path string, logger *logrus.Logger) e
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
logger.Debugf("Feature flags config file '%s' does not exist", path) logger.Tracef("Feature flags config file '%s' does not exist", path)
return nil return nil
} }

View file

@ -286,7 +286,7 @@ func TestSetFromEnv(t *testing.T) {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
logger, hook := logtest.NewNullLogger() logger, hook := logtest.NewNullLogger()
logger.SetLevel(logrus.InfoLevel) logger.SetLevel(logrus.DebugLevel)
t.Setenv(tc.envvar, tc.value) t.Setenv(tc.envvar, tc.value)
err := fr.SetFromEnv(logger) err := fr.SetFromEnv(logger)
cstest.RequireErrorMessage(t, err, tc.expectedErr) cstest.RequireErrorMessage(t, err, tc.expectedErr)
@ -346,7 +346,7 @@ func TestSetFromYaml(t *testing.T) {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
logger, hook := logtest.NewNullLogger() logger, hook := logtest.NewNullLogger()
logger.SetLevel(logrus.InfoLevel) logger.SetLevel(logrus.DebugLevel)
err := fr.SetFromYaml(strings.NewReader(tc.yml), logger) err := fr.SetFromYaml(strings.NewReader(tc.yml), logger)
cstest.RequireErrorMessage(t, err, tc.expectedErr) cstest.RequireErrorMessage(t, err, tc.expectedErr)
for _, expectedMessage := range tc.expectedLog { for _, expectedMessage := range tc.expectedLog {
@ -369,7 +369,7 @@ func TestSetFromYamlFile(t *testing.T) {
fr := setUp(t) fr := setUp(t)
logger, hook := logtest.NewNullLogger() logger, hook := logtest.NewNullLogger()
logger.SetLevel(logrus.InfoLevel) logger.SetLevel(logrus.DebugLevel)
err = fr.SetFromYamlFile(tmpfile.Name(), logger) err = fr.SetFromYamlFile(tmpfile.Name(), logger)
require.NoError(t, err) require.NoError(t, err)