diff --git a/clients.go b/clients.go index f57150f..791992b 100644 --- a/clients.go +++ b/clients.go @@ -17,6 +17,7 @@ import ( "context" "sync" + "github.com/attestantio/esd/util" eth2client "github.com/attestantio/go-eth2-client" autoclient "github.com/attestantio/go-eth2-client/auto" "github.com/pkg/errors" @@ -41,7 +42,7 @@ func fetchClient(ctx context.Context, address string) (eth2client.Service, error if client, exists = clients[address]; !exists { var err error client, err = autoclient.New(ctx, - autoclient.WithLogLevel(logLevel(viper.GetString("eth2client.log-level"))), + autoclient.WithLogLevel(util.LogLevel("eth2client")), autoclient.WithTimeout(viper.GetDuration("eth2client.timeout")), autoclient.WithAddress(address)) if err != nil { diff --git a/go.mod b/go.mod index ad8bb0e..bb127f0 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.17.0 + github.com/stretchr/testify v1.8.4 github.com/wealdtech/go-eth2-types/v2 v2.8.2 github.com/wealdtech/go-majordomo v1.1.1 go.opencensus.io v0.24.0 // indirect @@ -28,6 +29,7 @@ require ( cloud.google.com/go/secretmanager v1.11.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fatih/color v1.15.0 // indirect github.com/ferranbt/fastssz v0.1.3 // indirect github.com/go-logr/logr v1.2.4 // indirect @@ -49,6 +51,7 @@ require ( github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect @@ -67,7 +70,7 @@ require ( golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/sync v0.4.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/sys v0.14.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/api v0.148.0 // indirect diff --git a/go.sum b/go.sum index 629cd17..a9776f3 100644 --- a/go.sum +++ b/go.sum @@ -104,6 +104,7 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -282,6 +283,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -567,8 +569,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/logging.go b/logging.go index 03aa0ec..c47da74 100644 --- a/logging.go +++ b/logging.go @@ -15,8 +15,8 @@ package main import ( "os" - "strings" + "github.com/attestantio/esd/util" "github.com/pkg/errors" "github.com/rs/zerolog" zerologger "github.com/rs/zerolog/log" @@ -42,30 +42,7 @@ func initLogging() error { } // Set the local logger from the global logger. - log = zerologger.Logger.With().Logger().Level(logLevel(viper.GetString("log-level"))) + log = zerologger.Logger.With().Logger().Level(util.LogLevel("")) return nil } - -// logLevel converts a string to a log level. -// It returns the user-supplied level by default. -func logLevel(input string) zerolog.Level { - switch strings.ToLower(input) { - case "none": - return zerolog.Disabled - case "trace": - return zerolog.TraceLevel - case "debug": - return zerolog.DebugLevel - case "warn", "warning": - return zerolog.WarnLevel - case "info", "information": - return zerolog.InfoLevel - case "err", "error": - return zerolog.ErrorLevel - case "fatal": - return zerolog.FatalLevel - default: - return log.GetLevel() - } -} diff --git a/main.go b/main.go index 8bc5510..ab59667 100644 --- a/main.go +++ b/main.go @@ -33,10 +33,10 @@ import ( nullmetrics "github.com/attestantio/esd/services/metrics/null" prometheusmetrics "github.com/attestantio/esd/services/metrics/prometheus" headslashings "github.com/attestantio/esd/services/slashings/head" + "github.com/attestantio/esd/util" "github.com/aws/aws-sdk-go/aws/credentials" homedir "github.com/mitchellh/go-homedir" "github.com/pkg/errors" - "github.com/rs/zerolog" zerologger "github.com/rs/zerolog/log" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -50,7 +50,7 @@ import ( ) // ReleaseVersion is the release version for the code. -var ReleaseVersion = "1.2.2" +var ReleaseVersion = "1.2.3" func main() { os.Exit(main2()) @@ -145,6 +145,7 @@ func fetchConfig() error { pflag.String("slashings.attester-slashed-script", "", "Script to run when attester is slashed") pflag.String("slashings.proposer-slashed-script", "", "Script to run when proposer is slashed") pflag.Bool("test-scripts", false, "Test scripts using validator index 12345678 and exit") + pflag.String("test-block", "", "Test scripts using supplied block and exit") pflag.Parse() if err := viper.BindPFlags(pflag.CommandLine); err != nil { return errors.Wrap(err, "failed to bind pflags to viper") @@ -204,7 +205,7 @@ func startServices(ctx context.Context, monitor metrics.Service, _ majordomo.Ser } _, err = headslashings.New(ctx, - headslashings.WithLogLevel(logLevel(viper.GetString("slashings.log-level"))), + headslashings.WithLogLevel(util.LogLevel("slashings")), headslashings.WithMonitor(monitor), headslashings.WithETH2Client(eth2Client), headslashings.WithAttesterSlashedScript(viper.GetString("slashings.attester-slashed-script")), @@ -228,6 +229,10 @@ func runCommands(ctx context.Context) (bool, error) { return runTestScripts(ctx) } + if viper.GetString("test-block") != "" { + return runTestBlock(ctx) + } + return false, nil } @@ -238,7 +243,7 @@ func runTestScripts(ctx context.Context) (bool, error) { } slashings, err := headslashings.New(ctx, - headslashings.WithLogLevel(zerolog.Disabled), + headslashings.WithLogLevel(util.LogLevel("slashings")), headslashings.WithETH2Client(eth2Client), headslashings.WithAttesterSlashedScript(viper.GetString("slashings.attester-slashed-script")), headslashings.WithProposerSlashedScript(viper.GetString("slashings.proposer-slashed-script")), @@ -270,6 +275,26 @@ func runTestScripts(ctx context.Context) (bool, error) { return true, nil } +func runTestBlock(ctx context.Context) (bool, error) { + eth2Client, err := fetchClient(ctx, viper.GetString("eth2client.address")) + if err != nil { + return false, errors.Wrap(err, fmt.Sprintf("failed to fetch client %q", viper.GetString("eth2client.address"))) + } + + _, err = headslashings.New(ctx, + headslashings.WithLogLevel(util.LogLevel("slashings")), + headslashings.WithETH2Client(eth2Client), + headslashings.WithAttesterSlashedScript(viper.GetString("slashings.attester-slashed-script")), + headslashings.WithProposerSlashedScript(viper.GetString("slashings.proposer-slashed-script")), + headslashings.WithBlock(viper.GetString("test-block")), + ) + if err != nil { + return false, errors.Wrap(err, "failed to create slashings service") + } + + return true, nil +} + func logModules() { buildInfo, ok := debug.ReadBuildInfo() if ok { @@ -307,7 +332,7 @@ func startMonitor(ctx context.Context) (metrics.Service, error) { if viper.Get("metrics.prometheus") != nil { var err error monitor, err = prometheusmetrics.New(ctx, - prometheusmetrics.WithLogLevel(logLevel(viper.GetString("metrics.prometheus.log-level"))), + prometheusmetrics.WithLogLevel(util.LogLevel("metrics.prometheus")), prometheusmetrics.WithAddress(viper.GetString("metrics.prometheus.listen-address")), ) if err != nil { @@ -324,14 +349,14 @@ func startMonitor(ctx context.Context) (metrics.Service, error) { func initMajordomo(ctx context.Context) (majordomo.Service, error) { majordomo, err := standardmajordomo.New(ctx, - standardmajordomo.WithLogLevel(logLevel(viper.GetString("majordomo.log-level"))), + standardmajordomo.WithLogLevel(util.LogLevel("majordomo")), ) if err != nil { return nil, errors.Wrap(err, "failed to create majordomo service") } directConfidant, err := directconfidant.New(ctx, - directconfidant.WithLogLevel(logLevel(viper.GetString("majordomo.confidants.direct.log-level"))), + directconfidant.WithLogLevel(util.LogLevel("majordomo.confidants.direct")), ) if err != nil { return nil, errors.Wrap(err, "failed to create direct confidant") @@ -341,7 +366,7 @@ func initMajordomo(ctx context.Context) (majordomo.Service, error) { } fileConfidant, err := fileconfidant.New(ctx, - fileconfidant.WithLogLevel(logLevel(viper.GetString("majordomo.confidants.file.log-level"))), + fileconfidant.WithLogLevel(util.LogLevel("majordomo.confidants.file")), ) if err != nil { return nil, errors.Wrap(err, "failed to create file confidant") @@ -360,7 +385,7 @@ func initMajordomo(ctx context.Context) (majordomo.Service, error) { ) } asmConfidant, err := asmconfidant.New(ctx, - asmconfidant.WithLogLevel(logLevel(viper.GetString("majordomo.confidants.asm.log-level"))), + asmconfidant.WithLogLevel(util.LogLevel("majordomo.confidants.asm")), asmconfidant.WithCredentials(asmCredentials), asmconfidant.WithRegion(viper.GetString("majordomo.asm.region")), ) @@ -374,7 +399,7 @@ func initMajordomo(ctx context.Context) (majordomo.Service, error) { if viper.GetString("majordomo.gsm.credentials") != "" { gsmConfidant, err := gsmconfidant.New(ctx, - gsmconfidant.WithLogLevel(logLevel(viper.GetString("majordomo.confidants.gsm.log-level"))), + gsmconfidant.WithLogLevel(util.LogLevel("majordomo.confidants.gsm")), gsmconfidant.WithCredentialsPath(resolvePath(viper.GetString("majordomo.gsm.credentials"))), gsmconfidant.WithProject(viper.GetString("majordomo.gsm.project")), ) diff --git a/services/slashings/head/handler.go b/services/slashings/head/handler.go index 840ac9b..6d19059 100644 --- a/services/slashings/head/handler.go +++ b/services/slashings/head/handler.go @@ -31,23 +31,23 @@ func (s *Service) OnHeadUpdated( // Fetch the block. block, err := s.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, fmt.Sprintf("%#x", blockRoot)) if err != nil { - log.Error().Err(err).Msg("Failed to obtain block") + s.log.Error().Err(err).Msg("Failed to obtain block") return } - log.Trace().Str("block_root", fmt.Sprintf("%#x", blockRoot)).Msg("Obtained block") + s.log.Trace().Str("block_root", fmt.Sprintf("%#x", blockRoot)).Msg("Obtained block") attesterSlashings, err := block.AttesterSlashings() if err != nil { - log.Error().Err(err).Msg("Failed to obtain attester slashings") + s.log.Error().Err(err).Msg("Failed to obtain attester slashings") } proposerSlashings, err := block.ProposerSlashings() if err != nil { - log.Error().Err(err).Msg("Failed to obtain proposer slashings") + s.log.Error().Err(err).Msg("Failed to obtain proposer slashings") } if len(attesterSlashings) == 0 && len(proposerSlashings) == 0 { - log.Trace().Msg("No slashings") + s.log.Trace().Msg("No slashings") return } @@ -61,9 +61,9 @@ func (s *Service) attesterSlashings(ctx context.Context, slashings []*spec.Attes for _, slashing := range slashings { slashedIndices := intersection(slashing.Attestation1.AttestingIndices, slashing.Attestation2.AttestingIndices) for _, validatorIndex := range slashedIndices { - log.Info().Uint64("validator_index", uint64(validatorIndex)).Msg("Validator slashed (attester)") + s.log.Info().Uint64("validator_index", uint64(validatorIndex)).Msg("Validator slashed (attester)") if err := s.OnAttesterSlashed(ctx, validatorIndex); err != nil { - log.Error().Err(err).Msg("Failed to run script") + s.log.Error().Err(err).Msg("Failed to run script") } slashingFound(ctx, validatorIndex) } @@ -73,9 +73,9 @@ func (s *Service) attesterSlashings(ctx context.Context, slashings []*spec.Attes func (s *Service) proposerSlashings(ctx context.Context, slashings []*spec.ProposerSlashing) { for _, slashing := range slashings { validatorIndex := slashing.SignedHeader1.Message.ProposerIndex - log.Info().Uint64("validator_index", uint64(validatorIndex)).Msg("Validator slashed (proposer)") + s.log.Info().Uint64("validator_index", uint64(validatorIndex)).Msg("Validator slashed (proposer)") if err := s.OnProposerSlashed(ctx, validatorIndex); err != nil { - log.Error().Err(err).Msg("Failed to run script") + s.log.Error().Err(err).Msg("Failed to run script") } slashingFound(ctx, validatorIndex) } diff --git a/services/slashings/head/parameters.go b/services/slashings/head/parameters.go index d531ed7..46b2552 100644 --- a/services/slashings/head/parameters.go +++ b/services/slashings/head/parameters.go @@ -27,6 +27,7 @@ type parameters struct { monitor metrics.Service attesterSlashedScript string proposerSlashedScript string + block string } // Parameter is the interface for service parameters. @@ -75,6 +76,13 @@ func WithProposerSlashedScript(script string) Parameter { }) } +// WithBlock sets the block to run against. +func WithBlock(block string) Parameter { + return parameterFunc(func(p *parameters) { + p.block = block + }) +} + // parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct. func parseAndCheckParameters(params ...Parameter) (*parameters, error) { parameters := parameters{ diff --git a/services/slashings/head/scripts.go b/services/slashings/head/scripts.go index bc74e52..d22b2bf 100644 --- a/services/slashings/head/scripts.go +++ b/services/slashings/head/scripts.go @@ -28,11 +28,11 @@ func (s *Service) OnProposerSlashed(_ context.Context, index spec.ValidatorIndex return nil } - log.Trace().Str("script", s.attesterSlashedScript).Msg("Calling script for slashed proposer") + s.log.Trace().Str("script", s.attesterSlashedScript).Msg("Calling script for slashed proposer") //nolint:gosec output, err := exec.Command(s.proposerSlashedScript, fmt.Sprintf("%d", index)).CombinedOutput() if err != nil { - log.Warn().Str("output", string(output)).Msg("Run information") + s.log.Warn().Str("output", string(output)).Msg("Run information") return errors.Wrap(err, "failed to run proposer slashing script") } @@ -45,11 +45,11 @@ func (s *Service) OnAttesterSlashed(_ context.Context, index spec.ValidatorIndex return nil } - log.Info().Str("script", s.attesterSlashedScript).Msg("Calling script for slashed attester") + s.log.Info().Str("script", s.attesterSlashedScript).Msg("Calling script for slashed attester") //nolint:gosec output, err := exec.Command(s.attesterSlashedScript, fmt.Sprintf("%d", index)).CombinedOutput() if err != nil { - log.Warn().Str("output", string(output)).Msg("Run information") + s.log.Warn().Str("output", string(output)).Msg("Run information") return errors.Wrap(err, "failed to run attester slashing script") } diff --git a/services/slashings/head/service.go b/services/slashings/head/service.go index 32c1d69..b34452c 100644 --- a/services/slashings/head/service.go +++ b/services/slashings/head/service.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Attestant Limited. +// Copyright © 2021, 2023 Attestant Limited. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -25,14 +25,12 @@ import ( // Service is slashings services that watches blocks for slashings. type Service struct { + log zerolog.Logger eth2Client eth2client.Service attesterSlashedScript string proposerSlashedScript string } -// module-wide log. -var log zerolog.Logger - // New creates a new service. func New(ctx context.Context, params ...Parameter) (*Service, error) { parameters, err := parseAndCheckParameters(params...) @@ -41,17 +39,38 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) { } // Set logging. - log = zerologger.With().Str("service", "slashings").Str("impl", "head").Logger() + log := zerologger.With().Str("service", "slashings").Str("impl", "head").Logger() if parameters.logLevel != log.GetLevel() { log = log.Level(parameters.logLevel) } svc := &Service{ + log: log, eth2Client: parameters.eth2Client, attesterSlashedScript: parameters.attesterSlashedScript, proposerSlashedScript: parameters.proposerSlashedScript, } + if parameters.block != "" { + // Require running for a specific slot (for test purposes). + block, err := parameters.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, parameters.block) + if err != nil { + return nil, errors.Wrap(err, "failed to obtain block") + } + slot, err := block.Slot() + if err != nil { + return nil, errors.Wrap(err, "failed to obtain slot") + } + root, err := block.Root() + if err != nil { + return nil, errors.Wrap(err, "failed to obtain root") + } + svc.OnHeadUpdated(ctx, slot, root) + // Service is not initialised, so do not return it. + //nolint:nilnil + return nil, nil + } + if parameters.monitor != nil { if err := registerMetrics(ctx, parameters.monitor); err != nil { return nil, errors.Wrap(err, "failed to register metrics") @@ -69,7 +88,7 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) { eventData, isEventData := event.Data.(*api.HeadEvent) if !isEventData { - log.Error().Msg("event data is not from a head event; cannot process") + svc.log.Error().Msg("event data is not from a head event; cannot process") return } diff --git a/util/logging.go b/util/logging.go new file mode 100644 index 0000000..b5b53dd --- /dev/null +++ b/util/logging.go @@ -0,0 +1,65 @@ +// Copyright © 2021 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "fmt" + "strings" + + "github.com/rs/zerolog" + zerologger "github.com/rs/zerolog/log" + "github.com/spf13/viper" +) + +// LogLevel returns the best log level for the path. +func LogLevel(path string) zerolog.Level { + if path == "" { + return stringToLevel(viper.GetString("log-level")) + } + + key := fmt.Sprintf("%s.log-level", path) + if viper.GetString(key) != "" { + return stringToLevel(viper.GetString(key)) + } + // Lop off the child and try again. + lastPeriod := strings.LastIndex(path, ".") + if lastPeriod == -1 { + return LogLevel("") + } + + return LogLevel(path[0:lastPeriod]) +} + +// stringtoLevel converts a string to a log level. +// It returns the user-supplied level by default. +func stringToLevel(input string) zerolog.Level { + switch strings.ToLower(input) { + case "none": + return zerolog.Disabled + case "trace": + return zerolog.TraceLevel + case "debug": + return zerolog.DebugLevel + case "warn", "warning": + return zerolog.WarnLevel + case "info", "information": + return zerolog.InfoLevel + case "err", "error": + return zerolog.ErrorLevel + case "fatal": + return zerolog.FatalLevel + default: + return zerologger.Logger.GetLevel() + } +} diff --git a/util/logging_test.go b/util/logging_test.go new file mode 100644 index 0000000..d911bdb --- /dev/null +++ b/util/logging_test.go @@ -0,0 +1,91 @@ +// Copyright © 2021 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util_test + +import ( + "testing" + + "github.com/attestantio/esd/util" + "github.com/rs/zerolog" + zerologger "github.com/rs/zerolog/log" + "github.com/spf13/viper" + "github.com/stretchr/testify/require" +) + +func TestLogLevel(t *testing.T) { + zerologger.Logger = zerologger.Logger.Level(zerolog.DebugLevel) + + tests := []struct { + name string + vars map[string]string + path string + level zerolog.Level + }{ + { + name: "Empty", + path: "", + level: zerolog.DebugLevel, + }, + { + name: "Root", + path: ".", + level: zerolog.DebugLevel, + }, + { + name: "TopLevel", + vars: map[string]string{ + "log-level": "info", + }, + path: "", + level: zerolog.InfoLevel, + }, + { + name: "SingleLevel", + vars: map[string]string{ + "log-level": "info", + }, + path: "a", + level: zerolog.InfoLevel, + }, + { + name: "MultiLevel", + vars: map[string]string{ + "log-level": "info", + }, + path: "a.b.c", + level: zerolog.InfoLevel, + }, + { + name: "Override", + vars: map[string]string{ + "log-level": "info", + "a.b.log-level": "Warn", + }, + path: "a.b.c", + level: zerolog.WarnLevel, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + viper.Reset() + + for k, v := range test.vars { + viper.Set(k, v) + } + level := util.LogLevel(test.path) + require.Equal(t, test.level, level) + }) + } +}