diff --git a/accounts/url.go b/accounts/url.go index 47f9d8ee4b20..d5c9c645c725 100644 --- a/accounts/url.go +++ b/accounts/url.go @@ -64,7 +64,7 @@ func (u URL) String() string { func (u URL) TerminalString() string { url := u.String() if len(url) > 32 { - return url[:31] + "…" + return url[:31] + ".." } return url } @@ -76,10 +76,9 @@ func (u URL) MarshalJSON() ([]byte, error) { // Cmp compares x and y and returns: // -// -1 if x < y -// 0 if x == y -// +1 if x > y -// +// -1 if x < y +// 0 if x == y +// +1 if x > y func (u URL) Cmp(url URL) int { if u.Scheme == url.Scheme { return strings.Compare(u.Path, url.Path) diff --git a/cmd/XDC/config.go b/cmd/XDC/config.go index dacc11b497c7..a8ceadb894a6 100644 --- a/cmd/XDC/config.go +++ b/cmd/XDC/config.go @@ -33,8 +33,6 @@ import ( "github.com/XinFinOrg/XDPoSChain/cmd/utils" "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/eth/ethconfig" - "github.com/XinFinOrg/XDPoSChain/internal/debug" - "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/node" "github.com/XinFinOrg/XDPoSChain/params" "github.com/naoina/toml" @@ -143,9 +141,9 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, XDCConfig) { if ctx.GlobalIsSet(utils.StakingEnabledFlag.Name) { cfg.StakeEnable = ctx.GlobalBool(utils.StakingEnabledFlag.Name) } - if !ctx.GlobalIsSet(debug.VerbosityFlag.Name) { - debug.Glogger.Verbosity(log.Lvl(cfg.Verbosity)) - } + // if !ctx.GlobalIsSet(debug.VerbosityFlag.Name) { + // debug.Verbosity(log.Lvl(cfg.Verbosity)) + // } if !ctx.GlobalIsSet(utils.NATFlag.Name) && cfg.NAT != "" { ctx.Set(utils.NATFlag.Name, cfg.NAT) diff --git a/cmd/XDC/main.go b/cmd/XDC/main.go index 88d5a4bd7832..57c77e6f29ae 100644 --- a/cmd/XDC/main.go +++ b/cmd/XDC/main.go @@ -136,6 +136,8 @@ var ( utils.GpoIgnoreGasPriceFlag, //utils.ExtraDataFlag, configFileFlag, + utils.LogDebugFlag, + utils.LogBacktraceAtFlag, utils.AnnounceTxsFlag, utils.StoreRewardFlag, utils.RollbackFlag, diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index c78a1a6c5238..eaf91dc51ae2 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -51,10 +51,10 @@ func main() { ) flag.Parse() - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.Lvl(*verbosity)) + glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false)) + glogger.Verbosity(log.FromLegacyLevel(*verbosity)) glogger.Vmodule(*vmodule) - log.Root().SetHandler(glogger) + log.SetDefault(log.NewLogger(glogger)) natm, err := nat.Parse(*natdesc) if err != nil { diff --git a/cmd/evm/main.go b/cmd/evm/main.go index b5dfbf528b9d..18de3710d2c5 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -110,6 +110,14 @@ var ( Name: "nostack", Usage: "disable stack output", } + DisableStorageFlag = &cli.BoolFlag{ + Name: "nostorage", + Usage: "disable storage output", + } + DisableReturnDataFlag = &cli.BoolFlag{ + Name: "noreturndata", + Usage: "enable return data output", + } ) func init() { diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 3c9e7ac00f6e..643f53a02c4c 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -22,21 +22,18 @@ import ( "fmt" "io" "os" + goruntime "runtime" "runtime/pprof" "time" - "github.com/XinFinOrg/XDPoSChain/core/rawdb" - - goruntime "runtime" - "github.com/XinFinOrg/XDPoSChain/cmd/evm/internal/compiler" "github.com/XinFinOrg/XDPoSChain/cmd/utils" "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/core" + "github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm/runtime" - "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/params" cli "gopkg.in/urfave/cli.v1" ) @@ -71,12 +68,12 @@ func readGenesis(genesisPath string) *core.Genesis { } func runCmd(ctx *cli.Context) error { - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name))) - log.Root().SetHandler(glogger) logconfig := &vm.LogConfig{ - EnableMemory: !ctx.GlobalBool(DisableMemoryFlag.Name), - DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + EnableMemory: !ctx.GlobalBool(DisableMemoryFlag.Name), + DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + DisableStorage: ctx.Bool(DisableStorageFlag.Name), + EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), + Debug: ctx.Bool(DebugFlag.Name), } var ( @@ -95,6 +92,7 @@ func runCmd(ctx *cli.Context) error { } else { debugLogger = vm.NewStructLogger(logconfig) } + if ctx.GlobalString(GenesisFlag.Name) != "" { gen := readGenesis(ctx.GlobalString(GenesisFlag.Name)) db := rawdb.NewMemoryDatabase() diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index 1a236c0001ee..891d6b1e6664 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -24,9 +24,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/vm" - "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/tests" - cli "gopkg.in/urfave/cli.v1" ) @@ -49,16 +47,15 @@ func stateTestCmd(ctx *cli.Context) error { if len(ctx.Args().First()) == 0 { return errors.New("path-to-test argument required") } - // Configure the go-ethereum logger - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name))) - log.Root().SetHandler(glogger) // Configure the EVM logger config := &vm.LogConfig{ - EnableMemory: !ctx.GlobalBool(DisableMemoryFlag.Name), - DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + EnableMemory: !ctx.GlobalBool(DisableMemoryFlag.Name), + DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + DisableStorage: ctx.Bool(DisableStorageFlag.Name), + EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), } + var ( tracer vm.EVMLogger debugger *vm.StructLogger diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 050fc7f80bc9..dcee12c32b4c 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -94,7 +94,7 @@ var ( func main() { // Parse the flags and set up the logger to print everything requested flag.Parse() - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*logFlag), log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.FromLegacyLevel(*logFlag), true))) // Construct the payout tiers amounts := make([]string, *tiersFlag) diff --git a/cmd/puppeth/puppeth.go b/cmd/puppeth/puppeth.go index 5fa0addb2110..51a60e0cb5a6 100644 --- a/cmd/puppeth/puppeth.go +++ b/cmd/puppeth/puppeth.go @@ -45,7 +45,7 @@ func main() { } app.Action = func(c *cli.Context) error { // Set up the logger to print everything and the random generator - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int("loglevel")), log.StreamHandler(os.Stdout, log.TerminalFormat(true)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stdout, log.FromLegacyLevel(c.Int("loglevel")), true))) rand.Seed(time.Now().UnixNano()) network := c.String("network") diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ef937b9d727f..700695516d90 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -618,6 +618,16 @@ var ( Name: "slave", Usage: "Enable slave mode", } + // Deprecated November 2023 + LogBacktraceAtFlag = &cli.StringFlag{ + Name: "log-backtrace", + Usage: "Request a stack trace at a specific logging statement (deprecated)", + Value: "", + } + LogDebugFlag = &cli.BoolFlag{ + Name: "log-debug", + Usage: "Prepends log messages with call-site location (deprecated)", + } ) // MakeDataDir retrieves the currently requested data directory, terminating @@ -1015,6 +1025,13 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(AnnounceTxsFlag.Name) { cfg.AnnounceTxs = ctx.GlobalBool(AnnounceTxsFlag.Name) } + // deprecation notice for log debug flags (TODO: find a more appropriate place to put these?) + if ctx.IsSet(LogBacktraceAtFlag.Name) { + log.Warn("log.backtrace flag is deprecated") + } + if ctx.IsSet(LogDebugFlag.Name) { + log.Warn("log.debug flag is deprecated") + } } func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) { diff --git a/common/types.go b/common/types.go index 162d2c4083ef..ffd3d42d8475 100644 --- a/common/types.go +++ b/common/types.go @@ -107,7 +107,7 @@ func (h Hash) Cmp(other Hash) int { func (h Hash) IsZero() bool { return h == Hash{} } // Get the string representation of the underlying hash -func (h Hash) Str() string { return string(h[:]) } +func (h Hash) Str() string { return string(h[:]) } // Bytes gets the byte representation of the underlying hash. func (h Hash) Bytes() []byte { return h[:] } @@ -116,12 +116,12 @@ func (h Hash) Bytes() []byte { return h[:] } func (h Hash) Big() *big.Int { return new(big.Int).SetBytes(h[:]) } // Hex converts a hash to a hex string. -func (h Hash) Hex() string { return hexutil.Encode(h[:]) } +func (h Hash) Hex() string { return hexutil.Encode(h[:]) } // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. func (h Hash) TerminalString() string { - return fmt.Sprintf("%x…%x", h[:3], h[29:]) + return fmt.Sprintf("%x..%x", h[:3], h[29:]) } // String implements the stringer interface and is used also by the logger when diff --git a/common/types_test.go b/common/types_test.go index fc52878f1565..7c2822854ab8 100644 --- a/common/types_test.go +++ b/common/types_test.go @@ -21,6 +21,7 @@ import ( "math/big" "strings" "testing" + "time" ) func TestBytesConversion(t *testing.T) { @@ -203,3 +204,14 @@ func TestStringToBinaryAddress(t *testing.T) { } } } + +func BenchmarkPrettyDuration(b *testing.B) { + var x = PrettyDuration(time.Duration(int64(1203123912312))) + b.Logf("Pre %s", time.Duration(x).String()) + var a string + b.ResetTimer() + for i := 0; i < b.N; i++ { + a = x.String() + } + b.Logf("Post %s", a) +} diff --git a/contracts/tests/Inherited_test.go b/contracts/tests/Inherited_test.go index a1e4a47203a8..94298fc0e3b4 100644 --- a/contracts/tests/Inherited_test.go +++ b/contracts/tests/Inherited_test.go @@ -19,9 +19,10 @@ var ( ) func TestPriceFeed(t *testing.T) { - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.LvlTrace) - log.Root().SetHandler(glogger) + glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false)) + glogger.Verbosity(log.LevelTrace) + log.SetDefault(log.NewLogger(glogger)) + common.TIPXDCXCancellationFee = big.NewInt(0) // init genesis contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{ diff --git a/core/blockchain.go b/core/blockchain.go index 845b939f657c..2f731331decc 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -440,7 +440,7 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error { // Make sure that both the block as well at its state trie exists block := bc.GetBlockByHash(hash) if block == nil { - return fmt.Errorf("non existent block [%x…]", hash[:4]) + return fmt.Errorf("non existent block [%x..]", hash[:4]) } if _, err := trie.NewSecure(block.Root(), bc.stateCache.TrieDB()); err != nil { return err @@ -1055,7 +1055,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ if blockChain[i].NumberU64() != blockChain[i-1].NumberU64()+1 || blockChain[i].ParentHash() != blockChain[i-1].Hash() { log.Error("Non contiguous receipt insert", "number", blockChain[i].Number(), "hash", blockChain[i].Hash(), "parent", blockChain[i].ParentHash(), "prevnumber", blockChain[i-1].Number(), "prevhash", blockChain[i-1].Hash()) - return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, blockChain[i-1].NumberU64(), + return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, blockChain[i-1].NumberU64(), blockChain[i-1].Hash().Bytes()[:4], i, blockChain[i].NumberU64(), blockChain[i].Hash().Bytes()[:4], blockChain[i].ParentHash().Bytes()[:4]) } } @@ -1075,7 +1075,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ blockHash, blockNumber := block.Hash(), block.NumberU64() // Short circuit if the owner header is unknown if !bc.HasHeader(blockHash, blockNumber) { - return i, fmt.Errorf("containing header #%d [%x…] unknown", blockNumber, blockHash.Bytes()[:4]) + return i, fmt.Errorf("containing header #%d [%x..] unknown", blockNumber, blockHash.Bytes()[:4]) } // Skip if the entire data is already known if bc.HasBlock(blockHash, blockNumber) { @@ -1422,7 +1422,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, [] log.Error("Non contiguous block insert", "number", chain[i].Number(), "hash", chain[i].Hash(), "parent", chain[i].ParentHash(), "prevnumber", chain[i-1].Number(), "prevhash", chain[i-1].Hash()) - return 0, nil, nil, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, chain[i-1].NumberU64(), + return 0, nil, nil, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, chain[i-1].NumberU64(), chain[i-1].Hash().Bytes()[:4], i, chain[i].NumberU64(), chain[i].Hash().Bytes()[:4], chain[i].ParentHash().Bytes()[:4]) } } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index ebf05f31d277..a3c59d27b011 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -1221,13 +1221,13 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) { t.Fatalf("block %d: failed to insert into chain: %v", i, err) } if chain.CurrentBlock().Hash() != chain.CurrentHeader().Hash() { - t.Errorf("block %d: current block/header mismatch: block #%d [%x…], header #%d [%x…]", i, chain.CurrentBlock().Number(), chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4]) + t.Errorf("block %d: current block/header mismatch: block #%d [%x..], header #%d [%x..]", i, chain.CurrentBlock().Number(), chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4]) } if _, err := chain.InsertChain(forks[i : i+1]); err != nil { t.Fatalf(" fork %d: failed to insert into chain: %v", i, err) } if chain.CurrentBlock().Hash() != chain.CurrentHeader().Hash() { - t.Errorf(" fork %d: current block/header mismatch: block #%d [%x…], header #%d [%x…]", i, chain.CurrentBlock().Number(), chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4]) + t.Errorf(" fork %d: current block/header mismatch: block #%d [%x..], header #%d [%x..]", i, chain.CurrentBlock().Number(), chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4]) } } } diff --git a/core/chain_indexer.go b/core/chain_indexer.go index dd6466ab3770..77cfac232bd7 100644 --- a/core/chain_indexer.go +++ b/core/chain_indexer.go @@ -356,7 +356,7 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com } header := GetHeader(c.chainDb, hash, number) if header == nil { - return common.Hash{}, fmt.Errorf("block #%d [%x…] not found", number, hash[:4]) + return common.Hash{}, fmt.Errorf("block #%d [%x..] not found", number, hash[:4]) } else if header.ParentHash != lastHead { return common.Hash{}, errors.New("chain reorged during section processing") } diff --git a/core/headerchain.go b/core/headerchain.go index 424bfb687d6c..b3cdc10f68d5 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -205,7 +205,7 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", chain[i].Hash(), "parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", chain[i-1].Hash()) - return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, chain[i-1].Number, + return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, chain[i-1].Number, chain[i-1].Hash().Bytes()[:4], i, chain[i].Number, chain[i].Hash().Bytes()[:4], chain[i].ParentHash[:4]) } } diff --git a/eth/protocol_test.go b/eth/protocol_test.go index 8c5283cd8b18..858e1da5aab7 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -32,7 +32,7 @@ import ( ) func init() { - // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) + // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, false))) } var testAccount, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") diff --git a/go.mod b/go.mod index 9e5d403aa9a5..a519dba12ac1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/XinFinOrg/XDPoSChain go 1.21 require ( - bazil.org/fuse v0.0.0-20180421153158-65cc252bf669 github.com/VictoriaMetrics/fastcache v1.12.2 github.com/aristanetworks/goarista v0.0.0-20231019142648-8c6f0862ab98 github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 @@ -12,9 +11,7 @@ require ( github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/edsrzf/mmap-go v1.0.0 github.com/fatih/color v1.13.0 - github.com/gizak/termui v2.2.0+incompatible github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 - github.com/go-stack/stack v1.8.1 github.com/golang/protobuf v1.5.3 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/websocket v1.4.2 @@ -38,7 +35,6 @@ require ( github.com/stretchr/testify v1.8.4 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 golang.org/x/crypto v0.15.0 - golang.org/x/net v0.17.0 golang.org/x/sync v0.4.0 golang.org/x/sys v0.24.0 golang.org/x/tools v0.14.0 @@ -48,37 +44,32 @@ require ( gopkg.in/urfave/cli.v1 v1.20.0 ) -require github.com/deckarep/golang-set v1.8.0 +require ( + github.com/deckarep/golang-set v1.8.0 + github.com/dop251/goja v0.0.0-20200106141417-aaec0e7bde29 + github.com/kylelemons/godebug v1.1.0 + github.com/mattn/go-isatty v0.0.17 + github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible + golang.org/x/exp v0.0.0-20231006140011-7918f672742d + gopkg.in/natefinch/lumberjack.v2 v2.2.1 +) require ( github.com/StackExchange/wmi v1.2.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect - github.com/dop251/goja v0.0.0-20200106141417-aaec0e7bde29 // indirect - github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa // indirect github.com/go-ole/go-ole v1.2.5 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/google/uuid v1.3.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/kylelemons/godebug v1.1.0 // indirect - github.com/maruel/panicparse v0.0.0-20160720141634-ad661195ed0e // indirect - github.com/maruel/ut v1.0.2 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect github.com/naoina/go-stringutil v0.1.0 // indirect - github.com/nsf/termbox-go v0.0.0-20170211012700-3540b76b9c77 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect - github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/mod v0.13.0 // indirect golang.org/x/term v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/go.sum b/go.sum index 98d3a63aed14..d21c1f26a13a 100644 --- a/go.sum +++ b/go.sum @@ -1,113 +1,44 @@ -bazil.org/fuse v0.0.0-20180421153158-65cc252bf669 h1:FNCRpXiquG1aoyqcIWVFmpTSKVcx2bQD38uZZeGtdlw= -bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= -github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= -github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= -github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= -github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/aristanetworks/goarista v0.0.0-20231019142648-8c6f0862ab98 h1:7buXGE+m4OPjyo8rUJgA8RmARNMq+m99JJLR+Z+ZWN0= github.com/aristanetworks/goarista v0.0.0-20231019142648-8c6f0862ab98/go.mod h1:DLTg9Gp4FAXF5EpqYBQnUeBbRsNLY7b2HR94TE5XQtE= -github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 h1:Eey/GGQ/E5Xp1P2Lyx1qj007hLZfbi0+CoVeJruGCtI= github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= -github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18/go.mod h1:HD5P3vAIAh+Y2GAxg0PrPN1P8WkepXGpjbUPDHJqqKM= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= -github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= -github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmakYiSlqu2425CHyFXLZZnvm7PDpU8M= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200106141417-aaec0e7bde29 h1:Ewd9K+mC725sITA12QQHRqWj78NU4t7EhlFVVgdlzJg= github.com/dop251/goja v0.0.0-20200106141417-aaec0e7bde29/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= -github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 h1:qwcF+vdFrvPSEUDSX5RVoRccG8a5DhOdWdQ4zN62zzo= -github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= -github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= -github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= -github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa h1:XKAhUk/dtp+CV0VO6mhG2V7jA9vbcGcnYF/Ay9NjZrY= -github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= -github.com/ethereum/go-ethereum v1.13.5 h1:U6TCRciCqZRe4FPXmy1sMGxTfuk8P7u2UoinF3VbaFk= -github.com/ethereum/go-ethereum v1.13.5/go.mod h1:yMTu38GSuyxaYzQMViqNmQ1s3cE84abZexQmTgenWk0= -github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/gizak/termui v2.2.0+incompatible h1:qvZU9Xll/Xd/Xr/YO+HfBKXhy8a8/94ao6vV9DSXzUE= -github.com/gizak/termui v2.2.0+incompatible/go.mod h1:PkJoWUt/zacQKysNfQtcw1RW+eK2SxkieVBtl+4ovLA= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -117,7 +48,6 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -125,48 +55,29 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= -github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= -github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= -github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= -github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb v1.7.9 h1:uSeBTNO4rBkbp1Be5FKRsAmglM9nlx25TzVQRQt1An4= github.com/influxdata/influxdb v1.7.9/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= -github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/karalabe/hid v1.0.0 h1:+/CIMNXhSU/zIJgnIvBD2nKHxS/bnRHhhs9xBryLpPo= github.com/karalabe/hid v1.0.0/go.mod h1:Vr51f8rUOLYrfrWDFlV12GGQgM5AT8sVh+2fY4MPeu8= -github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -175,145 +86,86 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/maruel/panicparse v0.0.0-20160720141634-ad661195ed0e h1:e2z/lz9pvtRrEOgKWaLW2Dw02Nqd3/fqv0qWTQ8ByZE= -github.com/maruel/panicparse v0.0.0-20160720141634-ad661195ed0e/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI= -github.com/maruel/ut v1.0.2 h1:mQTlQk3jubTbdTcza+hwoZQWhzcvE4L6K6RTtAFlA1k= -github.com/maruel/ut v1.0.2/go.mod h1:RV8PwPD9dd2KFlnlCc/DB2JVvkXmyaalfc5xvmSrRSs= -github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= -github.com/nsf/termbox-go v0.0.0-20170211012700-3540b76b9c77 h1:gKl78uP/I7JZ56OFtRf7nc4m1icV38hwV0In5pEGzeA= -github.com/nsf/termbox-go v0.0.0-20170211012700-3540b76b9c77/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/prometheus v1.7.2-0.20170814170113-3101606756c5 h1:K2PKeDFZidfjUWpXk05Gbxhwm8Rnz1l4O+u/bbbcCvc= github.com/prometheus/prometheus v1.7.2-0.20170814170113-3101606756c5/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s= -github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1vJb5vwEjIp5kBj/eu99p/bl0Ay2goiPe5xE= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -323,39 +175,21 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.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/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.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.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -369,24 +203,20 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 h1:hhsSf/5z74Ck/DJYc+R8zpq8KGm7uJvpdLRQED/IedA= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= -gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/internal/debug/api.go b/internal/debug/api.go index 4702f7907667..8f1b60ddb37e 100644 --- a/internal/debug/api.go +++ b/internal/debug/api.go @@ -23,6 +23,7 @@ package debug import ( "errors" "io" + "log/slog" "os" "os/user" "path/filepath" @@ -55,19 +56,13 @@ type HandlerT struct { // Verbosity sets the log verbosity ceiling. The verbosity of individual packages // and source files can be raised using Vmodule. func (*HandlerT) Verbosity(level int) { - Glogger.Verbosity(log.Lvl(level)) + glogger.Verbosity(slog.Level(level)) } // Vmodule sets the log verbosity pattern. See package log for details on the // pattern syntax. func (*HandlerT) Vmodule(pattern string) error { - return Glogger.Vmodule(pattern) -} - -// BacktraceAt sets the log backtrace location. See package log for details on -// the pattern syntax. -func (*HandlerT) BacktraceAt(location string) error { - return Glogger.BacktraceAt(location) + return glogger.Vmodule(pattern) } // MemStats returns detailed runtime memory statistics. diff --git a/internal/debug/flags.go b/internal/debug/flags.go index 16a87c9810c1..223e9c6fa551 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -19,38 +19,73 @@ package debug import ( "fmt" "io" + "log/slog" "net/http" _ "net/http/pprof" "os" + "path/filepath" "runtime" "github.com/XinFinOrg/XDPoSChain/log" - "github.com/XinFinOrg/XDPoSChain/log/term" "github.com/XinFinOrg/XDPoSChain/metrics" "github.com/XinFinOrg/XDPoSChain/metrics/exp" - colorable "github.com/mattn/go-colorable" + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" + "gopkg.in/natefinch/lumberjack.v2" "gopkg.in/urfave/cli.v1" ) var ( - VerbosityFlag = cli.IntFlag{ + verbosityFlag = cli.IntFlag{ Name: "verbosity", Usage: "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail", Value: 3, } + logVmoduleFlag = &cli.StringFlag{ + Name: "log-vmodule", + Usage: "Per-module verbosity: comma-separated list of = (e.g. eth/*=5,p2p=4)", + Value: "", + } vmoduleFlag = cli.StringFlag{ Name: "vmodule", Usage: "Per-module verbosity: comma-separated list of = (e.g. eth/*=5,p2p=4)", Value: "", } - backtraceAtFlag = cli.StringFlag{ - Name: "backtrace", - Usage: "Request a stack trace at a specific logging statement (e.g. \"block.go:271\")", - Value: "", + logjsonFlag = &cli.BoolFlag{ + Name: "log-json", + Usage: "Format logs with JSON", + Hidden: true, + } + logFormatFlag = &cli.StringFlag{ + Name: "log-format", + Usage: "Log format to use (json|logfmt|terminal)", + } + logFileFlag = &cli.StringFlag{ + Name: "log-file", + Usage: "Write logs to a file", + } + logRotateFlag = &cli.BoolFlag{ + Name: "log-rotate", + Usage: "Enables log file rotation", + } + logMaxSizeMBsFlag = &cli.IntFlag{ + Name: "log-maxsize", + Usage: "Maximum size in MBs of a single log file", + Value: 100, + } + logMaxBackupsFlag = &cli.IntFlag{ + Name: "log-maxbackups", + Usage: "Maximum number of log files to retain", + Value: 10, + } + logMaxAgeFlag = &cli.IntFlag{ + Name: "log-maxage", + Usage: "Maximum number of days to retain a log file", + Value: 30, } - debugFlag = cli.BoolFlag{ - Name: "debug", - Usage: "Prepends log messages with call-site location (file and line number)", + logCompressFlag = &cli.BoolFlag{ + Name: "log-compress", + Usage: "Compress the log files", } pprofFlag = cli.BoolFlag{ Name: "pprof", @@ -95,10 +130,17 @@ var ( // Flags holds all command-line flags required for debugging. var Flags = []cli.Flag{ - VerbosityFlag, - //vmoduleFlag, - //backtraceAtFlag, - debugFlag, + verbosityFlag, + logVmoduleFlag, + vmoduleFlag, + logjsonFlag, + logFormatFlag, + logFileFlag, + logRotateFlag, + logMaxSizeMBsFlag, + logMaxBackupsFlag, + logMaxAgeFlag, + logCompressFlag, pprofFlag, pprofAddrFlag, pprofPortFlag, @@ -110,26 +152,117 @@ var Flags = []cli.Flag{ debugDataDirFlag, } -var Glogger *log.GlogHandler +var ( + glogger *log.GlogHandler + logOutputFile io.WriteCloser + defaultTerminalHandler *log.TerminalHandler +) func init() { - usecolor := term.IsTty(os.Stderr.Fd()) && os.Getenv("TERM") != "dumb" - output := io.Writer(os.Stderr) - if usecolor { - output = colorable.NewColorableStderr() + defaultTerminalHandler = log.NewTerminalHandler(os.Stderr, false) + glogger = log.NewGlogHandler(defaultTerminalHandler) + glogger.Verbosity(log.LvlInfo) + log.SetDefault(log.NewLogger(glogger)) +} + +func ResetLogging() { + if defaultTerminalHandler != nil { + defaultTerminalHandler.ResetFieldPadding() } - Glogger = log.NewGlogHandler(log.StreamHandler(output, log.TerminalFormat(usecolor))) } // Setup initializes profiling and logging based on the CLI flags. // It should be called as early as possible in the program. func Setup(ctx *cli.Context) error { + var ( + handler slog.Handler + terminalOutput = io.Writer(os.Stderr) + output io.Writer + logFmtFlag = ctx.String(logFormatFlag.Name) + ) + var ( + logFile = ctx.String(logFileFlag.Name) + rotation = ctx.Bool(logRotateFlag.Name) + ) + if len(logFile) > 0 { + if err := validateLogLocation(filepath.Dir(logFile)); err != nil { + return fmt.Errorf("failed to initiatilize file logger: %v", err) + } + } + context := []interface{}{"rotate", rotation} + if len(logFmtFlag) > 0 { + context = append(context, "format", logFmtFlag) + } else { + context = append(context, "format", "terminal") + } + if rotation { + // Lumberjack uses -lumberjack.log in is.TempDir() if empty. + // so typically /tmp/geth-lumberjack.log on linux + if len(logFile) > 0 { + context = append(context, "location", logFile) + } else { + context = append(context, "location", filepath.Join(os.TempDir(), "geth-lumberjack.log")) + } + logOutputFile = &lumberjack.Logger{ + Filename: logFile, + MaxSize: ctx.Int(logMaxSizeMBsFlag.Name), + MaxBackups: ctx.Int(logMaxBackupsFlag.Name), + MaxAge: ctx.Int(logMaxAgeFlag.Name), + Compress: ctx.Bool(logCompressFlag.Name), + } + output = io.MultiWriter(terminalOutput, logOutputFile) + } else if logFile != "" { + var err error + if logOutputFile, err = os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644); err != nil { + return err + } + output = io.MultiWriter(logOutputFile, terminalOutput) + context = append(context, "location", logFile) + } else { + output = terminalOutput + } + + switch { + case ctx.Bool(logjsonFlag.Name): + // Retain backwards compatibility with `--log-json` flag if `--log-format` not set + defer log.Warn("The flag '--log-json' is deprecated, please use '--log-format=json' instead") + handler = log.JSONHandlerWithLevel(output, log.LevelInfo) + case logFmtFlag == "json": + handler = log.JSONHandlerWithLevel(output, log.LevelInfo) + case logFmtFlag == "logfmt": + handler = log.LogfmtHandler(output) + case logFmtFlag == "", logFmtFlag == "terminal": + useColor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" + if useColor { + terminalOutput = colorable.NewColorableStderr() + if logOutputFile != nil { + output = io.MultiWriter(logOutputFile, terminalOutput) + } else { + output = terminalOutput + } + } + handler = log.NewTerminalHandler(output, useColor) + default: + // Unknown log format specified + return fmt.Errorf("unknown log format: %v", ctx.String(logFormatFlag.Name)) + } + + glogger = log.NewGlogHandler(handler) + // logging - log.PrintOrigins(ctx.GlobalBool(debugFlag.Name)) - Glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name))) - Glogger.Vmodule(ctx.GlobalString(vmoduleFlag.Name)) - Glogger.BacktraceAt(ctx.GlobalString(backtraceAtFlag.Name)) - log.Root().SetHandler(Glogger) + verbosity := log.FromLegacyLevel(ctx.Int(verbosityFlag.Name)) + glogger.Verbosity(verbosity) + vmodule := ctx.String(logVmoduleFlag.Name) + if vmodule == "" { + // Retain backwards compatibility with `--vmodule` flag if `--log-vmodule` not set + vmodule = ctx.String(vmoduleFlag.Name) + if vmodule != "" { + defer log.Warn("The flag '--vmodule' is deprecated, please use '--log-vmodule' instead") + } + } + glogger.Vmodule(vmodule) + + log.SetDefault(log.NewLogger(glogger)) // profiling, tracing runtime.MemProfileRate = ctx.GlobalInt(memprofilerateFlag.Name) @@ -164,6 +297,11 @@ func Setup(ctx *cli.Context) error { } }() } + + if len(logFile) > 0 || rotation { + log.Info("Logging configured", context...) + } + return nil } @@ -172,4 +310,21 @@ func Setup(ctx *cli.Context) error { func Exit() { Handler.StopCPUProfile() Handler.StopGoTrace() + if logOutputFile != nil { + logOutputFile.Close() + } +} + +func validateLogLocation(path string) error { + if err := os.MkdirAll(path, os.ModePerm); err != nil { + return fmt.Errorf("error creating the directory: %w", err) + } + // Check if the path is writable by trying to create a temporary file + tmp := filepath.Join(path, "tmp") + if f, err := os.Create(tmp); err != nil { + return err + } else { + f.Close() + } + return os.Remove(tmp) } diff --git a/internal/debug/loudpanic.go b/internal/debug/loudpanic.go index 572ebcefa14f..a7296e7b3f33 100644 --- a/internal/debug/loudpanic.go +++ b/internal/debug/loudpanic.go @@ -14,8 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// +build go1.6 - package debug import "runtime/debug" diff --git a/internal/debug/loudpanic_fallback.go b/internal/debug/loudpanic_fallback.go deleted file mode 100644 index 4ce4985da7c9..000000000000 --- a/internal/debug/loudpanic_fallback.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build !go1.6 - -package debug - -// LoudPanic panics in a way that gets all goroutine stacks printed on stderr. -func LoudPanic(x interface{}) { - panic(x) -} diff --git a/internal/debug/trace.go b/internal/debug/trace.go index 74e3a9b22f05..eb5f801c2f1d 100644 --- a/internal/debug/trace.go +++ b/internal/debug/trace.go @@ -14,8 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -//+build go1.5 - package debug import ( diff --git a/internal/debug/trace_fallback.go b/internal/debug/trace_fallback.go deleted file mode 100644 index 4118ff4087ee..000000000000 --- a/internal/debug/trace_fallback.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -//+build !go1.5 - -// no-op implementation of tracing methods for Go < 1.5. - -package debug - -import "errors" - -func (*HandlerT) StartGoTrace(string) error { - return errors.New("tracing is not supported on Go < 1.5") -} - -func (*HandlerT) StopGoTrace() error { - return errors.New("tracing is not supported on Go < 1.5") -} diff --git a/internal/testlog/testlog.go b/internal/testlog/testlog.go new file mode 100644 index 000000000000..855e8c9b7f56 --- /dev/null +++ b/internal/testlog/testlog.go @@ -0,0 +1,207 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package testlog provides a log handler for unit tests. +package testlog + +import ( + "bytes" + "context" + "fmt" + "log/slog" + "sync" + "testing" + + "github.com/XinFinOrg/XDPoSChain/log" +) + +const ( + termTimeFormat = "01-02|15:04:05.000" +) + +// logger implements log.Logger such that all output goes to the unit test log via +// t.Logf(). All methods in between logger.Trace, logger.Debug, etc. are marked as test +// helpers, so the file and line number in unit test output correspond to the call site +// which emitted the log message. +type logger struct { + t *testing.T + l log.Logger + mu *sync.Mutex + h *bufHandler +} + +type bufHandler struct { + buf []slog.Record + attrs []slog.Attr + level slog.Level +} + +func (h *bufHandler) Handle(_ context.Context, r slog.Record) error { + h.buf = append(h.buf, r) + return nil +} + +func (h *bufHandler) Enabled(_ context.Context, lvl slog.Level) bool { + return lvl <= h.level +} + +func (h *bufHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + records := make([]slog.Record, len(h.buf)) + copy(records[:], h.buf[:]) + return &bufHandler{ + records, + append(h.attrs, attrs...), + h.level, + } +} + +func (h *bufHandler) WithGroup(_ string) slog.Handler { + panic("not implemented") +} + +// Logger returns a logger which logs to the unit test log of t. +func Logger(t *testing.T, level slog.Level) log.Logger { + handler := bufHandler{ + []slog.Record{}, + []slog.Attr{}, + level, + } + return &logger{ + t: t, + l: log.NewLogger(&handler), + mu: new(sync.Mutex), + h: &handler, + } +} + +// LoggerWithHandler returns +func LoggerWithHandler(t *testing.T, handler slog.Handler) log.Logger { + var bh bufHandler + return &logger{ + t: t, + l: log.NewLogger(handler), + mu: new(sync.Mutex), + h: &bh, + } +} + +func (l *logger) Handler() slog.Handler { + return l.l.Handler() +} + +func (l *logger) Write(level slog.Level, msg string, ctx ...interface{}) {} + +func (l *logger) Enabled(ctx context.Context, level slog.Level) bool { + return l.l.Enabled(ctx, level) +} + +func (l *logger) Trace(msg string, ctx ...interface{}) { + l.t.Helper() + l.mu.Lock() + defer l.mu.Unlock() + l.l.Trace(msg, ctx...) + l.flush() +} + +func (l *logger) Log(level slog.Level, msg string, ctx ...interface{}) { + l.t.Helper() + l.mu.Lock() + defer l.mu.Unlock() + l.l.Log(level, msg, ctx...) + l.flush() +} + +func (l *logger) Debug(msg string, ctx ...interface{}) { + l.t.Helper() + l.mu.Lock() + defer l.mu.Unlock() + l.l.Debug(msg, ctx...) + l.flush() +} + +func (l *logger) Info(msg string, ctx ...interface{}) { + l.t.Helper() + l.mu.Lock() + defer l.mu.Unlock() + l.l.Info(msg, ctx...) + l.flush() +} + +func (l *logger) Warn(msg string, ctx ...interface{}) { + l.t.Helper() + l.mu.Lock() + defer l.mu.Unlock() + l.l.Warn(msg, ctx...) + l.flush() +} + +func (l *logger) Error(msg string, ctx ...interface{}) { + l.t.Helper() + l.mu.Lock() + defer l.mu.Unlock() + l.l.Error(msg, ctx...) + l.flush() +} + +func (l *logger) Crit(msg string, ctx ...interface{}) { + l.t.Helper() + l.mu.Lock() + defer l.mu.Unlock() + l.l.Crit(msg, ctx...) + l.flush() +} + +func (l *logger) With(ctx ...interface{}) log.Logger { + return &logger{l.t, l.l.With(ctx...), l.mu, l.h} +} + +func (l *logger) New(ctx ...interface{}) log.Logger { + return l.With(ctx...) +} + +// terminalFormat formats a message similarly to the NewTerminalHandler in the log package. +// The difference is that terminalFormat does not escape messages/attributes and does not pad attributes. +func (h *bufHandler) terminalFormat(r slog.Record) string { + buf := &bytes.Buffer{} + lvl := log.LevelAlignedString(r.Level) + attrs := []slog.Attr{} + r.Attrs(func(attr slog.Attr) bool { + attrs = append(attrs, attr) + return true + }) + + attrs = append(h.attrs, attrs...) + + fmt.Fprintf(buf, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Message) + if length := len(r.Message); length < 40 { + buf.Write(bytes.Repeat([]byte{' '}, 40-length)) + } + + for _, attr := range attrs { + fmt.Fprintf(buf, " %s=%s", attr.Key, string(log.FormatSlogValue(attr.Value, nil))) + } + buf.WriteByte('\n') + return buf.String() +} + +// flush writes all buffered messages and clears the buffer. +func (l *logger) flush() { + l.t.Helper() + for _, r := range l.h.buf { + l.t.Logf("%s", l.h.terminalFormat(r)) + } + l.h.buf = nil +} diff --git a/light/txpool.go b/light/txpool.go index 578323914429..a1d9190a3874 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -450,7 +450,10 @@ func (p *TxPool) add(ctx context.Context, tx *types.Transaction) error { } // Print a log message if low enough level is set - log.Debug("Pooled new transaction", "hash", hash, "from", log.Lazy{Fn: func() common.Address { from, _ := types.Sender(p.signer, tx); return from }}, "to", tx.To()) + if log.Enabled(log.LevelDebug) { + from, _ := types.Sender(p.signer, tx) + log.Debug("Pooled new transaction", "hash", hash, "from", from, "to", tx.To()) + } return nil } diff --git a/log/CONTRIBUTORS b/log/CONTRIBUTORS deleted file mode 100644 index a0866713be09..000000000000 --- a/log/CONTRIBUTORS +++ /dev/null @@ -1,11 +0,0 @@ -Contributors to log15: - -- Aaron L -- Alan Shreve -- Chris Hines -- Ciaran Downey -- Dmitry Chestnykh -- Evan Shaw -- Péter Szilágyi -- Trevor Gattis -- Vincent Vanackere diff --git a/log/LICENSE b/log/LICENSE deleted file mode 100644 index 5f0d1fb6a7bb..000000000000 --- a/log/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2014 Alan Shreve - -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. diff --git a/log/README.md b/log/README.md deleted file mode 100644 index 0951b21cb53f..000000000000 --- a/log/README.md +++ /dev/null @@ -1,77 +0,0 @@ -![obligatory xkcd](http://imgs.xkcd.com/comics/standards.png) - -# log15 [![godoc reference](https://godoc.org/github.com/inconshreveable/log15?status.png)](https://godoc.org/github.com/inconshreveable/log15) [![Build Status](https://travis-ci.org/inconshreveable/log15.svg?branch=master)](https://travis-ci.org/inconshreveable/log15) - -Package log15 provides an opinionated, simple toolkit for best-practice logging in Go (golang) that is both human and machine readable. It is modeled after the Go standard library's [`io`](http://golang.org/pkg/io/) and [`net/http`](http://golang.org/pkg/net/http/) packages and is an alternative to the standard library's [`log`](http://golang.org/pkg/log/) package. - -## Features -- A simple, easy-to-understand API -- Promotes structured logging by encouraging use of key/value pairs -- Child loggers which inherit and add their own private context -- Lazy evaluation of expensive operations -- Simple Handler interface allowing for construction of flexible, custom logging configurations with a tiny API. -- Color terminal support -- Built-in support for logging to files, streams, syslog, and the network -- Support for forking records to multiple handlers, buffering records for output, failing over from failed handler writes, + more - -## Versioning -The API of the master branch of log15 should always be considered unstable. If you want to rely on a stable API, -you must vendor the library. - -## Importing - -```go -import log "github.com/inconshreveable/log15" -``` - -## Examples - -```go -// all loggers can have key/value context -srvlog := log.New("module", "app/server") - -// all log messages can have key/value context -srvlog.Warn("abnormal conn rate", "rate", curRate, "low", lowRate, "high", highRate) - -// child loggers with inherited context -connlog := srvlog.New("raddr", c.RemoteAddr()) -connlog.Info("connection open") - -// lazy evaluation -connlog.Debug("ping remote", "latency", log.Lazy{pingRemote}) - -// flexible configuration -srvlog.SetHandler(log.MultiHandler( - log.StreamHandler(os.Stderr, log.LogfmtFormat()), - log.LvlFilterHandler( - log.LvlError, - log.Must.FileHandler("errors.json", log.JsonFormat())))) -``` - -Will result in output that looks like this: - -``` -WARN[06-17|21:58:10] abnormal conn rate module=app/server rate=0.500 low=0.100 high=0.800 -INFO[06-17|21:58:10] connection open module=app/server raddr=10.0.0.1 -``` - -## Breaking API Changes -The following commits broke API stability. This reference is intended to help you understand the consequences of updating to a newer version -of log15. - -- 57a084d014d4150152b19e4e531399a7145d1540 - Added a `Get()` method to the `Logger` interface to retrieve the current handler -- 93404652ee366648fa622b64d1e2b67d75a3094a - `Record` field `Call` changed to `stack.Call` with switch to `github.com/go-stack/stack` -- a5e7613673c73281f58e15a87d2cf0cf111e8152 - Restored `syslog.Priority` argument to the `SyslogXxx` handler constructors - -## FAQ - -### The varargs style is brittle and error prone! Can I have type safety please? -Yes. Use `log.Ctx`: - -```go -srvlog := log.New(log.Ctx{"module": "app/server"}) -srvlog.Warn("abnormal conn rate", log.Ctx{"rate": curRate, "low": lowRate, "high": highRate}) -``` - -## License -Apache diff --git a/log/README_ETHEREUM.md b/log/README_ETHEREUM.md deleted file mode 100644 index f6c42ccc03da..000000000000 --- a/log/README_ETHEREUM.md +++ /dev/null @@ -1,5 +0,0 @@ -This package is a fork of https://github.com/inconshreveable/log15, with some -minor modifications required by the go-ethereum codebase: - - * Support for log level `trace` - * Modified behavior to exit on `critical` failure diff --git a/log/doc.go b/log/doc.go deleted file mode 100644 index 83ad8c54f64c..000000000000 --- a/log/doc.go +++ /dev/null @@ -1,333 +0,0 @@ -/* -Package log15 provides an opinionated, simple toolkit for best-practice logging that is -both human and machine readable. It is modeled after the standard library's io and net/http -packages. - -This package enforces you to only log key/value pairs. Keys must be strings. Values may be -any type that you like. The default output format is logfmt, but you may also choose to use -JSON instead if that suits you. Here's how you log: - - log.Info("page accessed", "path", r.URL.Path, "user_id", user.id) - -This will output a line that looks like: - - lvl=info t=2014-05-02T16:07:23-0700 msg="page accessed" path=/org/71/profile user_id=9 - -Getting Started - -To get started, you'll want to import the library: - - import log "github.com/inconshreveable/log15" - - -Now you're ready to start logging: - - func main() { - log.Info("Program starting", "args", os.Args()) - } - - -Convention - -Because recording a human-meaningful message is common and good practice, the first argument to every -logging method is the value to the *implicit* key 'msg'. - -Additionally, the level you choose for a message will be automatically added with the key 'lvl', and so -will the current timestamp with key 't'. - -You may supply any additional context as a set of key/value pairs to the logging function. log15 allows -you to favor terseness, ordering, and speed over safety. This is a reasonable tradeoff for -logging functions. You don't need to explicitly state keys/values, log15 understands that they alternate -in the variadic argument list: - - log.Warn("size out of bounds", "low", lowBound, "high", highBound, "val", val) - -If you really do favor your type-safety, you may choose to pass a log.Ctx instead: - - log.Warn("size out of bounds", log.Ctx{"low": lowBound, "high": highBound, "val": val}) - - -Context loggers - -Frequently, you want to add context to a logger so that you can track actions associated with it. An http -request is a good example. You can easily create new loggers that have context that is automatically included -with each log line: - - requestlogger := log.New("path", r.URL.Path) - - // later - requestlogger.Debug("db txn commit", "duration", txnTimer.Finish()) - -This will output a log line that includes the path context that is attached to the logger: - - lvl=dbug t=2014-05-02T16:07:23-0700 path=/repo/12/add_hook msg="db txn commit" duration=0.12 - - -Handlers - -The Handler interface defines where log lines are printed to and how they are formated. Handler is a -single interface that is inspired by net/http's handler interface: - - type Handler interface { - Log(r *Record) error - } - - -Handlers can filter records, format them, or dispatch to multiple other Handlers. -This package implements a number of Handlers for common logging patterns that are -easily composed to create flexible, custom logging structures. - -Here's an example handler that prints logfmt output to Stdout: - - handler := log.StreamHandler(os.Stdout, log.LogfmtFormat()) - -Here's an example handler that defers to two other handlers. One handler only prints records -from the rpc package in logfmt to standard out. The other prints records at Error level -or above in JSON formatted output to the file /var/log/service.json - - handler := log.MultiHandler( - log.LvlFilterHandler(log.LvlError, log.Must.FileHandler("/var/log/service.json", log.JsonFormat())), - log.MatchFilterHandler("pkg", "app/rpc" log.StdoutHandler()) - ) - -Logging File Names and Line Numbers - -This package implements three Handlers that add debugging information to the -context, CallerFileHandler, CallerFuncHandler and CallerStackHandler. Here's -an example that adds the source file and line number of each logging call to -the context. - - h := log.CallerFileHandler(log.StdoutHandler) - log.Root().SetHandler(h) - ... - log.Error("open file", "err", err) - -This will output a line that looks like: - - lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" caller=data.go:42 - -Here's an example that logs the call stack rather than just the call site. - - h := log.CallerStackHandler("%+v", log.StdoutHandler) - log.Root().SetHandler(h) - ... - log.Error("open file", "err", err) - -This will output a line that looks like: - - lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" stack="[pkg/data.go:42 pkg/cmd/main.go]" - -The "%+v" format instructs the handler to include the path of the source file -relative to the compile time GOPATH. The github.com/go-stack/stack package -documents the full list of formatting verbs and modifiers available. - -Custom Handlers - -The Handler interface is so simple that it's also trivial to write your own. Let's create an -example handler which tries to write to one handler, but if that fails it falls back to -writing to another handler and includes the error that it encountered when trying to write -to the primary. This might be useful when trying to log over a network socket, but if that -fails you want to log those records to a file on disk. - - type BackupHandler struct { - Primary Handler - Secondary Handler - } - - func (h *BackupHandler) Log (r *Record) error { - err := h.Primary.Log(r) - if err != nil { - r.Ctx = append(ctx, "primary_err", err) - return h.Secondary.Log(r) - } - return nil - } - -This pattern is so useful that a generic version that handles an arbitrary number of Handlers -is included as part of this library called FailoverHandler. - -Logging Expensive Operations - -Sometimes, you want to log values that are extremely expensive to compute, but you don't want to pay -the price of computing them if you haven't turned up your logging level to a high level of detail. - -This package provides a simple type to annotate a logging operation that you want to be evaluated -lazily, just when it is about to be logged, so that it would not be evaluated if an upstream Handler -filters it out. Just wrap any function which takes no arguments with the log.Lazy type. For example: - - func factorRSAKey() (factors []int) { - // return the factors of a very large number - } - - log.Debug("factors", log.Lazy{factorRSAKey}) - -If this message is not logged for any reason (like logging at the Error level), then -factorRSAKey is never evaluated. - -Dynamic context values - -The same log.Lazy mechanism can be used to attach context to a logger which you want to be -evaluated when the message is logged, but not when the logger is created. For example, let's imagine -a game where you have Player objects: - - type Player struct { - name string - alive bool - log.Logger - } - -You always want to log a player's name and whether they're alive or dead, so when you create the player -object, you might do: - - p := &Player{name: name, alive: true} - p.Logger = log.New("name", p.name, "alive", p.alive) - -Only now, even after a player has died, the logger will still report they are alive because the logging -context is evaluated when the logger was created. By using the Lazy wrapper, we can defer the evaluation -of whether the player is alive or not to each log message, so that the log records will reflect the player's -current state no matter when the log message is written: - - p := &Player{name: name, alive: true} - isAlive := func() bool { return p.alive } - player.Logger = log.New("name", p.name, "alive", log.Lazy{isAlive}) - -Terminal Format - -If log15 detects that stdout is a terminal, it will configure the default -handler for it (which is log.StdoutHandler) to use TerminalFormat. This format -logs records nicely for your terminal, including color-coded output based -on log level. - -Error Handling - -Becasuse log15 allows you to step around the type system, there are a few ways you can specify -invalid arguments to the logging functions. You could, for example, wrap something that is not -a zero-argument function with log.Lazy or pass a context key that is not a string. Since logging libraries -are typically the mechanism by which errors are reported, it would be onerous for the logging functions -to return errors. Instead, log15 handles errors by making these guarantees to you: - -- Any log record containing an error will still be printed with the error explained to you as part of the log record. - -- Any log record containing an error will include the context key LOG15_ERROR, enabling you to easily -(and if you like, automatically) detect if any of your logging calls are passing bad values. - -Understanding this, you might wonder why the Handler interface can return an error value in its Log method. Handlers -are encouraged to return errors only if they fail to write their log records out to an external source like if the -syslog daemon is not responding. This allows the construction of useful handlers which cope with those failures -like the FailoverHandler. - -Library Use - -log15 is intended to be useful for library authors as a way to provide configurable logging to -users of their library. Best practice for use in a library is to always disable all output for your logger -by default and to provide a public Logger instance that consumers of your library can configure. Like so: - - package yourlib - - import "github.com/inconshreveable/log15" - - var Log = log.New() - - func init() { - Log.SetHandler(log.DiscardHandler()) - } - -Users of your library may then enable it if they like: - - import "github.com/inconshreveable/log15" - import "example.com/yourlib" - - func main() { - handler := // custom handler setup - yourlib.Log.SetHandler(handler) - } - -Best practices attaching logger context - -The ability to attach context to a logger is a powerful one. Where should you do it and why? -I favor embedding a Logger directly into any persistent object in my application and adding -unique, tracing context keys to it. For instance, imagine I am writing a web browser: - - type Tab struct { - url string - render *RenderingContext - // ... - - Logger - } - - func NewTab(url string) *Tab { - return &Tab { - // ... - url: url, - - Logger: log.New("url", url), - } - } - -When a new tab is created, I assign a logger to it with the url of -the tab as context so it can easily be traced through the logs. -Now, whenever we perform any operation with the tab, we'll log with its -embedded logger and it will include the tab title automatically: - - tab.Debug("moved position", "idx", tab.idx) - -There's only one problem. What if the tab url changes? We could -use log.Lazy to make sure the current url is always written, but that -would mean that we couldn't trace a tab's full lifetime through our -logs after the user navigate to a new URL. - -Instead, think about what values to attach to your loggers the -same way you think about what to use as a key in a SQL database schema. -If it's possible to use a natural key that is unique for the lifetime of the -object, do so. But otherwise, log15's ext package has a handy RandId -function to let you generate what you might call "surrogate keys" -They're just random hex identifiers to use for tracing. Back to our -Tab example, we would prefer to set up our Logger like so: - - import logext "github.com/inconshreveable/log15/ext" - - t := &Tab { - // ... - url: url, - } - - t.Logger = log.New("id", logext.RandId(8), "url", log.Lazy{t.getUrl}) - return t - -Now we'll have a unique traceable identifier even across loading new urls, but -we'll still be able to see the tab's current url in the log messages. - -Must - -For all Handler functions which can return an error, there is a version of that -function which will return no error but panics on failure. They are all available -on the Must object. For example: - - log.Must.FileHandler("/path", log.JsonFormat) - log.Must.NetHandler("tcp", ":1234", log.JsonFormat) - -Inspiration and Credit - -All of the following excellent projects inspired the design of this library: - -code.google.com/p/log4go - -github.com/op/go-logging - -github.com/technoweenie/grohl - -github.com/Sirupsen/logrus - -github.com/kr/logfmt - -github.com/spacemonkeygo/spacelog - -golang's stdlib, notably io and net/http - -The Name - -https://xkcd.com/927/ - -*/ -package log diff --git a/log/format.go b/log/format.go index 67859351178b..54c071b908c7 100644 --- a/log/format.go +++ b/log/format.go @@ -2,69 +2,26 @@ package log import ( "bytes" - "encoding/json" "fmt" + "log/slog" + "math/big" "reflect" "strconv" - "strings" - "sync" - "sync/atomic" "time" "unicode/utf8" + + "github.com/holiman/uint256" ) const ( - timeFormat = "2006-01-02T15:04:05-0700" - termTimeFormat = "01-02|15:04:05" - floatFormat = 'f' - termMsgJust = 40 + timeFormat = "2006-01-02T15:04:05-0700" + floatFormat = 'f' + termMsgJust = 40 + termCtxMaxPadding = 40 ) -// locationTrims are trimmed for display to avoid unwieldy log lines. -var locationTrims = []string{ - "github.com/XinFinOrg/XDPoSChain/", -} - -// PrintOrigins sets or unsets log location (file:line) printing for terminal -// format output. -func PrintOrigins(print bool) { - if print { - atomic.StoreUint32(&locationEnabled, 1) - } else { - atomic.StoreUint32(&locationEnabled, 0) - } -} - -// locationEnabled is an atomic flag controlling whether the terminal formatter -// should append the log locations too when printing entries. -var locationEnabled uint32 - -// locationLength is the maxmimum path length encountered, which all logs are -// padded to to aid in alignment. -var locationLength uint32 - -// fieldPadding is a global map with maximum field value lengths seen until now -// to allow padding log contexts in a bit smarter way. -var fieldPadding = make(map[string]int) - -// fieldPaddingLock is a global mutex protecting the field padding map. -var fieldPaddingLock sync.RWMutex - -type Format interface { - Format(r *Record) []byte -} - -// FormatFunc returns a new Format object which uses -// the given function to perform record formatting. -func FormatFunc(f func(*Record) []byte) Format { - return formatFunc(f) -} - -type formatFunc func(*Record) []byte - -func (f formatFunc) Format(r *Record) []byte { - return f(r) -} +// 40 spaces +var spaces = []byte(" ") // TerminalStringer is an analogous interface to the stdlib stringer, allowing // own types to have custom shortened serialization formats when printed to the @@ -73,291 +30,334 @@ type TerminalStringer interface { TerminalString() string } -// TerminalFormat formats log records optimized for human readability on -// a terminal with color-coded level output and terser human friendly timestamp. -// This format should only be used for interactive programs or while developing. -// -// [TIME] [LEVEL] MESAGE key=value key=value ... -// -// Example: -// -// [May 16 20:58:45] [DBUG] remove route ns=haproxy addr=127.0.0.1:50002 -// -func TerminalFormat(usecolor bool) Format { - return FormatFunc(func(r *Record) []byte { - var color = 0 - if usecolor { - switch r.Lvl { - case LvlCrit: - color = 35 - case LvlError: - color = 31 - case LvlWarn: - color = 33 - case LvlInfo: - color = 32 - case LvlDebug: - color = 36 - case LvlTrace: - color = 34 - } +func (h *TerminalHandler) format(buf []byte, r slog.Record, usecolor bool) []byte { + msg := escapeMessage(r.Message) + var color = "" + if usecolor { + switch r.Level { + case LevelCrit: + color = "\x1b[35m" + case slog.LevelError: + color = "\x1b[31m" + case slog.LevelWarn: + color = "\x1b[33m" + case slog.LevelInfo: + color = "\x1b[32m" + case slog.LevelDebug: + color = "\x1b[36m" + case LevelTrace: + color = "\x1b[34m" } + } + if buf == nil { + buf = make([]byte, 0, 30+termMsgJust) + } + b := bytes.NewBuffer(buf) - b := &bytes.Buffer{} - lvl := r.Lvl.AlignedString() - if atomic.LoadUint32(&locationEnabled) != 0 { - // Log origin printing was requested, format the location path and line number - location := fmt.Sprintf("%+v", r.Call) - for _, prefix := range locationTrims { - location = strings.TrimPrefix(location, prefix) - } - // Maintain the maximum location length for fancyer alignment - align := int(atomic.LoadUint32(&locationLength)) - if align < len(location) { - align = len(location) - atomic.StoreUint32(&locationLength, uint32(align)) - } - padding := strings.Repeat(" ", align-len(location)) - - // Assemble and print the log heading - if color > 0 { - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s|%s]%s %s ", color, lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg) - } else { - fmt.Fprintf(b, "%s[%s|%s]%s %s ", lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg) - } - } else { - if color > 0 { - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), r.Msg) - } else { - fmt.Fprintf(b, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Msg) - } - } - // try to justify the log output for short messages - length := utf8.RuneCountInString(r.Msg) - if len(r.Ctx) > 0 && length < termMsgJust { - b.Write(bytes.Repeat([]byte{' '}, termMsgJust-length)) - } - // print the keys logfmt style - logfmt(b, r.Ctx, color, true) - return b.Bytes() - }) -} + if color != "" { // Start color + b.WriteString(color) + b.WriteString(LevelAlignedString(r.Level)) + b.WriteString("\x1b[0m") + } else { + b.WriteString(LevelAlignedString(r.Level)) + } + b.WriteString("[") + writeTimeTermFormat(b, r.Time) + b.WriteString("] ") + b.WriteString(msg) + + // try to justify the log output for short messages + //length := utf8.RuneCountInString(msg) + length := len(msg) + if (r.NumAttrs()+len(h.attrs)) > 0 && length < termMsgJust { + b.Write(spaces[:termMsgJust-length]) + } + // print the attributes + h.formatAttributes(b, r, color) -// LogfmtFormat prints records in logfmt format, an easy machine-parseable but human-readable -// format for key/value pairs. -// -// For more details see: http://godoc.org/github.com/kr/logfmt -// -func LogfmtFormat() Format { - return FormatFunc(func(r *Record) []byte { - common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg} - buf := &bytes.Buffer{} - logfmt(buf, append(common, r.Ctx...), 0, false) - return buf.Bytes() - }) + return b.Bytes() } -func logfmt(buf *bytes.Buffer, ctx []interface{}, color int, term bool) { - for i := 0; i < len(ctx); i += 2 { - if i != 0 { - buf.WriteByte(' ') - } +func (h *TerminalHandler) formatAttributes(buf *bytes.Buffer, r slog.Record, color string) { + writeAttr := func(attr slog.Attr, first, last bool) { + buf.WriteByte(' ') - k, ok := ctx[i].(string) - v := formatLogfmtValue(ctx[i+1], term) - if !ok { - k, v = errorKey, formatLogfmtValue(k, term) + if color != "" { + buf.WriteString(color) + buf.Write(appendEscapeString(buf.AvailableBuffer(), attr.Key)) + buf.WriteString("\x1b[0m=") + } else { + buf.Write(appendEscapeString(buf.AvailableBuffer(), attr.Key)) + buf.WriteByte('=') } + val := FormatSlogValue(attr.Value, buf.AvailableBuffer()) - // XXX: we should probably check that all of your key bytes aren't invalid - fieldPaddingLock.RLock() - padding := fieldPadding[k] - fieldPaddingLock.RUnlock() + padding := h.fieldPadding[attr.Key] - length := utf8.RuneCountInString(v) - if padding < length { + length := utf8.RuneCount(val) + if padding < length && length <= termCtxMaxPadding { padding = length - - fieldPaddingLock.Lock() - fieldPadding[k] = padding - fieldPaddingLock.Unlock() + h.fieldPadding[attr.Key] = padding } - if color > 0 { - fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=", color, k) - } else { - buf.WriteString(k) - buf.WriteByte('=') - } - buf.WriteString(v) - if i < len(ctx)-2 { - buf.Write(bytes.Repeat([]byte{' '}, padding-length)) + buf.Write(val) + if !last && padding > length { + buf.Write(spaces[:padding-length]) } } - buf.WriteByte('\n') -} - -// JsonFormat formats log records as JSON objects separated by newlines. -// It is the equivalent of JsonFormatEx(false, true). -func JsonFormat() Format { - return JsonFormatEx(false, true) -} - -// JsonFormatEx formats log records as JSON objects. If pretty is true, -// records will be pretty-printed. If lineSeparated is true, records -// will be logged with a new line between each record. -func JsonFormatEx(pretty, lineSeparated bool) Format { - jsonMarshal := json.Marshal - if pretty { - jsonMarshal = func(v interface{}) ([]byte, error) { - return json.MarshalIndent(v, "", " ") - } + var n = 0 + var nAttrs = len(h.attrs) + r.NumAttrs() + for _, attr := range h.attrs { + writeAttr(attr, n == 0, n == nAttrs-1) + n++ } - - return FormatFunc(func(r *Record) []byte { - props := make(map[string]interface{}) - - props[r.KeyNames.Time] = r.Time - props[r.KeyNames.Lvl] = r.Lvl.String() - props[r.KeyNames.Msg] = r.Msg - - for i := 0; i < len(r.Ctx); i += 2 { - k, ok := r.Ctx[i].(string) - if !ok { - props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i]) - } - props[k] = formatJsonValue(r.Ctx[i+1]) - } - - b, err := jsonMarshal(props) - if err != nil { - b, _ = jsonMarshal(map[string]string{ - errorKey: err.Error(), - }) - return b - } - - if lineSeparated { - b = append(b, '\n') - } - - return b + r.Attrs(func(attr slog.Attr) bool { + writeAttr(attr, n == 0, n == nAttrs-1) + n++ + return true }) + buf.WriteByte('\n') } -func formatShared(value interface{}) (result interface{}) { +// FormatSlogValue formats a slog.Value for serialization to terminal. +func FormatSlogValue(v slog.Value, tmp []byte) (result []byte) { + var value any defer func() { if err := recover(); err != nil { if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() { - result = "nil" + result = []byte("") } else { panic(err) } } }() + switch v.Kind() { + case slog.KindString: + return appendEscapeString(tmp, v.String()) + case slog.KindInt64: // All int-types (int8, int16 etc) wind up here + return appendInt64(tmp, v.Int64()) + case slog.KindUint64: // All uint-types (uint8, uint16 etc) wind up here + return appendUint64(tmp, v.Uint64(), false) + case slog.KindFloat64: + return strconv.AppendFloat(tmp, v.Float64(), floatFormat, 3, 64) + case slog.KindBool: + return strconv.AppendBool(tmp, v.Bool()) + case slog.KindDuration: + value = v.Duration() + case slog.KindTime: + // Performance optimization: No need for escaping since the provided + // timeFormat doesn't have any escape characters, and escaping is + // expensive. + return v.Time().AppendFormat(tmp, timeFormat) + default: + value = v.Any() + } + if value == nil { + return []byte("") + } switch v := value.(type) { - case time.Time: - return v.Format(timeFormat) - + case *big.Int: // Need to be before fmt.Stringer-clause + return appendBigInt(tmp, v) + case *uint256.Int: // Need to be before fmt.Stringer-clause + return appendU256(tmp, v) case error: - return v.Error() - + return appendEscapeString(tmp, v.Error()) + case TerminalStringer: + return appendEscapeString(tmp, v.TerminalString()) case fmt.Stringer: - return v.String() - - default: - return v + return appendEscapeString(tmp, v.String()) } + + // We can use the 'tmp' as a scratch-buffer, to first format the + // value, and in a second step do escaping. + internal := fmt.Appendf(tmp, "%+v", value) + return appendEscapeString(tmp, string(internal)) } -func formatJsonValue(value interface{}) interface{} { - value = formatShared(value) - switch value.(type) { - case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string: - return value - default: - return fmt.Sprintf("%+v", value) +// appendInt64 formats n with thousand separators and writes into buffer dst. +func appendInt64(dst []byte, n int64) []byte { + if n < 0 { + return appendUint64(dst, uint64(-n), true) } + return appendUint64(dst, uint64(n), false) } -// formatValue formats a value for serialization -func formatLogfmtValue(value interface{}, term bool) string { - if value == nil { - return "nil" +// appendUint64 formats n with thousand separators and writes into buffer dst. +func appendUint64(dst []byte, n uint64, neg bool) []byte { + // Small numbers are fine as is + if n < 100000 { + if neg { + return strconv.AppendInt(dst, -int64(n), 10) + } else { + return strconv.AppendInt(dst, int64(n), 10) + } + } + // Large numbers should be split + const maxLength = 26 + + var ( + out = make([]byte, maxLength) + i = maxLength - 1 + comma = 0 + ) + for ; n > 0; i-- { + if comma == 3 { + comma = 0 + out[i] = ',' + } else { + comma++ + out[i] = '0' + byte(n%10) + n /= 10 + } + } + if neg { + out[i] = '-' + i-- } + return append(dst, out[i+1:]...) +} - if t, ok := value.(time.Time); ok { - // Performance optimization: No need for escaping since the provided - // timeFormat doesn't have any escape characters, and escaping is - // expensive. - return t.Format(timeFormat) +// FormatLogfmtUint64 formats n with thousand separators. +func FormatLogfmtUint64(n uint64) string { + return string(appendUint64(nil, n, false)) +} + +// appendBigInt formats n with thousand separators and writes to dst. +func appendBigInt(dst []byte, n *big.Int) []byte { + if n.IsUint64() { + return appendUint64(dst, n.Uint64(), false) } - if term { - if s, ok := value.(TerminalStringer); ok { - // Custom terminal stringer provided, use that - return escapeString(s.TerminalString()) - } + if n.IsInt64() { + return appendInt64(dst, n.Int64()) } - value = formatShared(value) - switch v := value.(type) { - case bool: - return strconv.FormatBool(v) - case float32: - return strconv.FormatFloat(float64(v), floatFormat, 3, 64) - case float64: - return strconv.FormatFloat(v, floatFormat, 3, 64) - case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: - return fmt.Sprintf("%d", value) - case string: - return escapeString(v) - default: - return escapeString(fmt.Sprintf("%+v", value)) + + var ( + text = n.String() + buf = make([]byte, len(text)+len(text)/3) + comma = 0 + i = len(buf) - 1 + ) + for j := len(text) - 1; j >= 0; j, i = j-1, i-1 { + c := text[j] + + switch { + case c == '-': + buf[i] = c + case comma == 3: + buf[i] = ',' + i-- + comma = 0 + fallthrough + default: + buf[i] = c + comma++ + } } + return append(dst, buf[i+1:]...) } -var stringBufPool = sync.Pool{ - New: func() interface{} { return new(bytes.Buffer) }, +// appendU256 formats n with thousand separators. +func appendU256(dst []byte, n *uint256.Int) []byte { + if n.IsUint64() { + return appendUint64(dst, n.Uint64(), false) + } + res := []byte(n.PrettyDec(',')) + return append(dst, res...) } -func escapeString(s string) string { - needsQuotes := false - needsEscape := false +// appendEscapeString writes the string s to the given writer, with +// escaping/quoting if needed. +func appendEscapeString(dst []byte, s string) []byte { + needsQuoting := false + needsEscaping := false for _, r := range s { - if r <= ' ' || r == '=' || r == '"' { - needsQuotes = true + // If it contains spaces or equal-sign, we need to quote it. + if r == ' ' || r == '=' { + needsQuoting = true + continue } - if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' { - needsEscape = true + // We need to escape it, if it contains + // - character " (0x22) and lower (except space) + // - characters above ~ (0x7E), plus equal-sign + if r <= '"' || r > '~' { + needsEscaping = true + break } } - if !needsEscape && !needsQuotes { - return s + if needsEscaping { + return strconv.AppendQuote(dst, s) } - e := stringBufPool.Get().(*bytes.Buffer) - e.WriteByte('"') + // No escaping needed, but we might have to place within quote-marks, in case + // it contained a space + if needsQuoting { + dst = append(dst, '"') + dst = append(dst, []byte(s)...) + return append(dst, '"') + } + return append(dst, []byte(s)...) +} + +// escapeMessage checks if the provided string needs escaping/quoting, similarly +// to escapeString. The difference is that this method is more lenient: it allows +// for spaces and linebreaks to occur without needing quoting. +func escapeMessage(s string) string { + needsQuoting := false for _, r := range s { - switch r { - case '\\', '"': - e.WriteByte('\\') - e.WriteByte(byte(r)) - case '\n': - e.WriteString("\\n") - case '\r': - e.WriteString("\\r") - case '\t': - e.WriteString("\\t") - default: - e.WriteRune(r) + // Allow CR/LF/TAB. This is to make multi-line messages work. + if r == '\r' || r == '\n' || r == '\t' { + continue + } + // We quote everything below (0x20) and above~ (0x7E), + // plus equal-sign + if r < ' ' || r > '~' || r == '=' { + needsQuoting = true + break } } - e.WriteByte('"') - var ret string - if needsQuotes { - ret = e.String() - } else { - ret = string(e.Bytes()[1 : e.Len()-1]) + if !needsQuoting { + return s + } + return strconv.Quote(s) +} + +// writeTimeTermFormat writes on the format "01-02|15:04:05.000" +func writeTimeTermFormat(buf *bytes.Buffer, t time.Time) { + _, month, day := t.Date() + writePosIntWidth(buf, int(month), 2) + buf.WriteByte('-') + writePosIntWidth(buf, day, 2) + buf.WriteByte('|') + hour, min, sec := t.Clock() + writePosIntWidth(buf, hour, 2) + buf.WriteByte(':') + writePosIntWidth(buf, min, 2) + buf.WriteByte(':') + writePosIntWidth(buf, sec, 2) + ns := t.Nanosecond() + buf.WriteByte('.') + writePosIntWidth(buf, ns/1e6, 3) +} + +// writePosIntWidth writes non-negative integer i to the buffer, padded on the left +// by zeroes to the given width. Use a width of 0 to omit padding. +// Adapted from pkg.go.dev/log/slog/internal/buffer +func writePosIntWidth(b *bytes.Buffer, i, width int) { + // Cheap integer to fixed-width decimal ASCII. + // Copied from log/log.go. + if i < 0 { + panic("negative int") + } + // Assemble decimal in reverse order. + var bb [20]byte + bp := len(bb) - 1 + for i >= 10 || width > 1 { + width-- + q := i / 10 + bb[bp] = byte('0' + i - q*10) + bp-- + i = q } - e.Reset() - stringBufPool.Put(e) - return ret + // i < 10 + bb[bp] = byte('0' + i) + b.Write(bb[bp:]) } diff --git a/log/format_test.go b/log/format_test.go new file mode 100644 index 000000000000..d4c1df4abcf9 --- /dev/null +++ b/log/format_test.go @@ -0,0 +1,24 @@ +package log + +import ( + "math/rand" + "testing" +) + +var sink []byte + +func BenchmarkPrettyInt64Logfmt(b *testing.B) { + buf := make([]byte, 100) + b.ReportAllocs() + for i := 0; i < b.N; i++ { + sink = appendInt64(buf, rand.Int63()) + } +} + +func BenchmarkPrettyUint64Logfmt(b *testing.B) { + buf := make([]byte, 100) + b.ReportAllocs() + for i := 0; i < b.N; i++ { + sink = appendUint64(buf, rand.Uint64(), false) + } +} diff --git a/log/handler.go b/log/handler.go index d5594b853de8..56eff6671f1b 100644 --- a/log/handler.go +++ b/log/handler.go @@ -1,361 +1,199 @@ package log import ( + "context" "fmt" "io" - "net" - "os" + "log/slog" + "math/big" "reflect" "sync" + "time" - "github.com/go-stack/stack" + "github.com/holiman/uint256" ) -// A Logger prints its log records by writing to a Handler. -// The Handler interface defines where and how log records are written. -// Handlers are composable, providing you great flexibility in combining -// them to achieve the logging structure that suits your applications. -type Handler interface { - Log(r *Record) error -} +type discardHandler struct{} -// FuncHandler returns a Handler that logs records with the given -// function. -func FuncHandler(fn func(r *Record) error) Handler { - return funcHandler(fn) +// DiscardHandler returns a no-op handler +func DiscardHandler() slog.Handler { + return &discardHandler{} } -type funcHandler func(r *Record) error - -func (h funcHandler) Log(r *Record) error { - return h(r) +func (h *discardHandler) Handle(_ context.Context, r slog.Record) error { + return nil } -// StreamHandler writes log records to an io.Writer -// with the given format. StreamHandler can be used -// to easily begin writing log records to other -// outputs. -// -// StreamHandler wraps itself with LazyHandler and SyncHandler -// to evaluate Lazy objects and perform safe concurrent writes. -func StreamHandler(wr io.Writer, fmtr Format) Handler { - h := FuncHandler(func(r *Record) error { - _, err := wr.Write(fmtr.Format(r)) - return err - }) - return LazyHandler(SyncHandler(h)) +func (h *discardHandler) Enabled(_ context.Context, level slog.Level) bool { + return false } -// SyncHandler can be wrapped around a handler to guarantee that -// only a single Log operation can proceed at a time. It's necessary -// for thread-safe concurrent writes. -func SyncHandler(h Handler) Handler { - var mu sync.Mutex - return FuncHandler(func(r *Record) error { - defer mu.Unlock() - mu.Lock() - return h.Log(r) - }) +func (h *discardHandler) WithGroup(name string) slog.Handler { + panic("not implemented") } -// FileHandler returns a handler which writes log records to the give file -// using the given format. If the path -// already exists, FileHandler will append to the given file. If it does not, -// FileHandler will create the file with mode 0644. -func FileHandler(path string, fmtr Format) (Handler, error) { - f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return nil, err - } - return closingHandler{f, StreamHandler(f, fmtr)}, nil +func (h *discardHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return &discardHandler{} } -// NetHandler opens a socket to the given address and writes records -// over the connection. -func NetHandler(network, addr string, fmtr Format) (Handler, error) { - conn, err := net.Dial(network, addr) - if err != nil { - return nil, err - } - - return closingHandler{conn, StreamHandler(conn, fmtr)}, nil -} +type TerminalHandler struct { + mu sync.Mutex + wr io.Writer + lvl slog.Level + useColor bool + attrs []slog.Attr + // fieldPadding is a map with maximum field value lengths seen until now + // to allow padding log contexts in a bit smarter way. + fieldPadding map[string]int -// XXX: closingHandler is essentially unused at the moment -// it's meant for a future time when the Handler interface supports -// a possible Close() operation -type closingHandler struct { - io.WriteCloser - Handler + buf []byte } -func (h *closingHandler) Close() error { - return h.WriteCloser.Close() +// NewTerminalHandler returns a handler which formats log records at all levels optimized for human readability on +// a terminal with color-coded level output and terser human friendly timestamp. +// This format should only be used for interactive programs or while developing. +// +// [LEVEL] [TIME] MESSAGE key=value key=value ... +// +// Example: +// +// [DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002 +func NewTerminalHandler(wr io.Writer, useColor bool) *TerminalHandler { + return NewTerminalHandlerWithLevel(wr, levelMaxVerbosity, useColor) +} + +// NewTerminalHandlerWithLevel returns the same handler as NewTerminalHandler but only outputs +// records which are less than or equal to the specified verbosity level. +func NewTerminalHandlerWithLevel(wr io.Writer, lvl slog.Level, useColor bool) *TerminalHandler { + return &TerminalHandler{ + wr: wr, + lvl: lvl, + useColor: useColor, + fieldPadding: make(map[string]int), + } } -// CallerFileHandler returns a Handler that adds the line number and file of -// the calling function to the context with key "caller". -func CallerFileHandler(h Handler) Handler { - return FuncHandler(func(r *Record) error { - r.Ctx = append(r.Ctx, "caller", fmt.Sprint(r.Call)) - return h.Log(r) - }) +func (h *TerminalHandler) Handle(_ context.Context, r slog.Record) error { + h.mu.Lock() + defer h.mu.Unlock() + buf := h.format(h.buf, r, h.useColor) + h.wr.Write(buf) + h.buf = buf[:0] + return nil } -// CallerFuncHandler returns a Handler that adds the calling function name to -// the context with key "fn". -func CallerFuncHandler(h Handler) Handler { - return FuncHandler(func(r *Record) error { - r.Ctx = append(r.Ctx, "fn", formatCall("%+n", r.Call)) - return h.Log(r) - }) +func (h *TerminalHandler) Enabled(_ context.Context, level slog.Level) bool { + return level >= h.lvl } -// This function is here to please go vet on Go < 1.8. -func formatCall(format string, c stack.Call) string { - return fmt.Sprintf(format, c) +func (h *TerminalHandler) WithGroup(name string) slog.Handler { + panic("not implemented") } -// CallerStackHandler returns a Handler that adds a stack trace to the context -// with key "stack". The stack trace is formated as a space separated list of -// call sites inside matching []'s. The most recent call site is listed first. -// Each call site is formatted according to format. See the documentation of -// package github.com/go-stack/stack for the list of supported formats. -func CallerStackHandler(format string, h Handler) Handler { - return FuncHandler(func(r *Record) error { - s := stack.Trace().TrimBelow(r.Call).TrimRuntime() - if len(s) > 0 { - r.Ctx = append(r.Ctx, "stack", fmt.Sprintf(format, s)) - } - return h.Log(r) - }) +func (h *TerminalHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return &TerminalHandler{ + wr: h.wr, + lvl: h.lvl, + useColor: h.useColor, + attrs: append(h.attrs, attrs...), + fieldPadding: make(map[string]int), + } } -// FilterHandler returns a Handler that only writes records to the -// wrapped Handler if the given function evaluates true. For example, -// to only log records where the 'err' key is not nil: -// -// logger.SetHandler(FilterHandler(func(r *Record) bool { -// for i := 0; i < len(r.Ctx); i += 2 { -// if r.Ctx[i] == "err" { -// return r.Ctx[i+1] != nil -// } -// } -// return false -// }, h)) -// -func FilterHandler(fn func(r *Record) bool, h Handler) Handler { - return FuncHandler(func(r *Record) error { - if fn(r) { - return h.Log(r) - } - return nil - }) +// ResetFieldPadding zeroes the field-padding for all attribute pairs. +func (h *TerminalHandler) ResetFieldPadding() { + h.mu.Lock() + h.fieldPadding = make(map[string]int) + h.mu.Unlock() } -// MatchFilterHandler returns a Handler that only writes records -// to the wrapped Handler if the given key in the logged -// context matches the value. For example, to only log records -// from your ui package: -// -// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler) -// -func MatchFilterHandler(key string, value interface{}, h Handler) Handler { - return FilterHandler(func(r *Record) (pass bool) { - switch key { - case r.KeyNames.Lvl: - return r.Lvl == value - case r.KeyNames.Time: - return r.Time == value - case r.KeyNames.Msg: - return r.Msg == value - } +type leveler struct{ minLevel slog.Level } - for i := 0; i < len(r.Ctx); i += 2 { - if r.Ctx[i] == key { - return r.Ctx[i+1] == value - } - } - return false - }, h) +func (l *leveler) Level() slog.Level { + return l.minLevel } -// LvlFilterHandler returns a Handler that only writes -// records which are less than the given verbosity -// level to the wrapped Handler. For example, to only -// log Error/Crit records: -// -// log.LvlFilterHandler(log.LvlError, log.StdoutHandler) -// -func LvlFilterHandler(maxLvl Lvl, h Handler) Handler { - return FilterHandler(func(r *Record) (pass bool) { - return r.Lvl <= maxLvl - }, h) +// JSONHandler returns a handler which prints records in JSON format. +func JSONHandler(wr io.Writer) slog.Handler { + return JSONHandlerWithLevel(wr, levelMaxVerbosity) } -// A MultiHandler dispatches any write to each of its handlers. -// This is useful for writing different types of log information -// to different locations. For example, to log to a file and -// standard error: -// -// log.MultiHandler( -// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()), -// log.StderrHandler) -// -func MultiHandler(hs ...Handler) Handler { - return FuncHandler(func(r *Record) error { - for _, h := range hs { - // what to do about failures? - h.Log(r) - } - return nil +// JSONHandlerWithLevel returns a handler which prints records in JSON format that are less than or equal to +// the specified verbosity level. +func JSONHandlerWithLevel(wr io.Writer, level slog.Level) slog.Handler { + return slog.NewJSONHandler(wr, &slog.HandlerOptions{ + ReplaceAttr: builtinReplaceJSON, + Level: &leveler{level}, }) } -// A FailoverHandler writes all log records to the first handler -// specified, but will failover and write to the second handler if -// the first handler has failed, and so on for all handlers specified. -// For example you might want to log to a network socket, but failover -// to writing to a file if the network fails, and then to -// standard out if the file write fails: -// -// log.FailoverHandler( -// log.Must.NetHandler("tcp", ":9090", log.JsonFormat()), -// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()), -// log.StdoutHandler) +// LogfmtHandler returns a handler which prints records in logfmt format, an easy machine-parseable but human-readable +// format for key/value pairs. // -// All writes that do not go to the first handler will add context with keys of -// the form "failover_err_{idx}" which explain the error encountered while -// trying to write to the handlers before them in the list. -func FailoverHandler(hs ...Handler) Handler { - return FuncHandler(func(r *Record) error { - var err error - for i, h := range hs { - err = h.Log(r) - if err == nil { - return nil - } else { - r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err) - } - } - - return err +// For more details see: http://godoc.org/github.com/kr/logfmt +func LogfmtHandler(wr io.Writer) slog.Handler { + return slog.NewTextHandler(wr, &slog.HandlerOptions{ + ReplaceAttr: builtinReplaceLogfmt, }) } -// ChannelHandler writes all records to the given channel. -// It blocks if the channel is full. Useful for async processing -// of log messages, it's used by BufferedHandler. -func ChannelHandler(recs chan<- *Record) Handler { - return FuncHandler(func(r *Record) error { - recs <- r - return nil +// LogfmtHandlerWithLevel returns the same handler as LogfmtHandler but it only outputs +// records which are less than or equal to the specified verbosity level. +func LogfmtHandlerWithLevel(wr io.Writer, level slog.Level) slog.Handler { + return slog.NewTextHandler(wr, &slog.HandlerOptions{ + ReplaceAttr: builtinReplaceLogfmt, + Level: &leveler{level}, }) } -// BufferedHandler writes all records to a buffered -// channel of the given size which flushes into the wrapped -// handler whenever it is available for writing. Since these -// writes happen asynchronously, all writes to a BufferedHandler -// never return an error and any errors from the wrapped handler are ignored. -func BufferedHandler(bufSize int, h Handler) Handler { - recs := make(chan *Record, bufSize) - go func() { - for m := range recs { - _ = h.Log(m) - } - }() - return ChannelHandler(recs) +func builtinReplaceLogfmt(_ []string, attr slog.Attr) slog.Attr { + return builtinReplace(nil, attr, true) } -// LazyHandler writes all values to the wrapped handler after evaluating -// any lazy functions in the record's context. It is already wrapped -// around StreamHandler and SyslogHandler in this library, you'll only need -// it if you write your own Handler. -func LazyHandler(h Handler) Handler { - return FuncHandler(func(r *Record) error { - // go through the values (odd indices) and reassign - // the values of any lazy fn to the result of its execution - hadErr := false - for i := 1; i < len(r.Ctx); i += 2 { - lz, ok := r.Ctx[i].(Lazy) - if ok { - v, err := evaluateLazy(lz) - if err != nil { - hadErr = true - r.Ctx[i] = err - } else { - if cs, ok := v.(stack.CallStack); ok { - v = cs.TrimBelow(r.Call).TrimRuntime() - } - r.Ctx[i] = v - } +func builtinReplaceJSON(_ []string, attr slog.Attr) slog.Attr { + return builtinReplace(nil, attr, false) +} + +func builtinReplace(_ []string, attr slog.Attr, logfmt bool) slog.Attr { + switch attr.Key { + case slog.TimeKey: + if attr.Value.Kind() == slog.KindTime { + if logfmt { + return slog.String("t", attr.Value.Time().Format(timeFormat)) + } else { + return slog.Attr{Key: "t", Value: attr.Value} } } - - if hadErr { - r.Ctx = append(r.Ctx, errorKey, "bad lazy") + case slog.LevelKey: + if l, ok := attr.Value.Any().(slog.Level); ok { + attr = slog.Any("lvl", LevelString(l)) + return attr } - - return h.Log(r) - }) -} - -func evaluateLazy(lz Lazy) (interface{}, error) { - t := reflect.TypeOf(lz.Fn) - - if t.Kind() != reflect.Func { - return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn) - } - - if t.NumIn() > 0 { - return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn) } - if t.NumOut() == 0 { - return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn) - } - - value := reflect.ValueOf(lz.Fn) - results := value.Call([]reflect.Value{}) - if len(results) == 1 { - return results[0].Interface(), nil - } else { - values := make([]interface{}, len(results)) - for i, v := range results { - values[i] = v.Interface() + switch v := attr.Value.Any().(type) { + case time.Time: + if logfmt { + attr = slog.String(attr.Key, v.Format(timeFormat)) + } + case *big.Int: + if v == nil { + attr.Value = slog.StringValue("") + } else { + attr.Value = slog.StringValue(v.String()) + } + case *uint256.Int: + if v == nil { + attr.Value = slog.StringValue("") + } else { + attr.Value = slog.StringValue(v.Dec()) + } + case fmt.Stringer: + if v == nil || (reflect.ValueOf(v).Kind() == reflect.Pointer && reflect.ValueOf(v).IsNil()) { + attr.Value = slog.StringValue("") + } else { + attr.Value = slog.StringValue(v.String()) } - return values, nil - } -} - -// DiscardHandler reports success for all writes but does nothing. -// It is useful for dynamically disabling logging at runtime via -// a Logger's SetHandler method. -func DiscardHandler() Handler { - return FuncHandler(func(r *Record) error { - return nil - }) -} - -// The Must object provides the following Handler creation functions -// which instead of returning an error parameter only return a Handler -// and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler -var Must muster - -func must(h Handler, err error) Handler { - if err != nil { - panic(err) } - return h -} - -type muster struct{} - -func (m muster) FileHandler(path string, fmtr Format) Handler { - return must(FileHandler(path, fmtr)) -} - -func (m muster) NetHandler(network, addr string, fmtr Format) Handler { - return must(NetHandler(network, addr, fmtr)) + return attr } diff --git a/log/handler_glog.go b/log/handler_glog.go index f8b932fd1b64..739f8c5b427d 100644 --- a/log/handler_glog.go +++ b/log/handler_glog.go @@ -17,8 +17,11 @@ package log import ( + "context" "errors" "fmt" + "log/slog" + "maps" "regexp" "runtime" "strconv" @@ -30,28 +33,24 @@ import ( // errVmoduleSyntax is returned when a user vmodule pattern is invalid. var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N") -// errTraceSyntax is returned when a user backtrace pattern is invalid. -var errTraceSyntax = errors.New("expect file.go:234") - // GlogHandler is a log handler that mimics the filtering features of Google's // glog logger: setting global log levels; overriding with callsite pattern // matches; and requesting backtraces at certain positions. type GlogHandler struct { - origin Handler // The origin handler this wraps + origin slog.Handler // The origin handler this wraps - level uint32 // Current log level, atomically accessible - override uint32 // Flag whether overrides are used, atomically accessible - backtrace uint32 // Flag whether backtrace location is set + level atomic.Int32 // Current log level, atomically accessible + override atomic.Bool // Flag whether overrides are used, atomically accessible - patterns []pattern // Current list of patterns to override with - siteCache map[uintptr]Lvl // Cache of callsite pattern evaluations - location string // file:line location where to do a stackdump at - lock sync.RWMutex // Lock protecting the override pattern list + patterns []pattern // Current list of patterns to override with + siteCache map[uintptr]slog.Level // Cache of callsite pattern evaluations + location string // file:line location where to do a stackdump at + lock sync.RWMutex // Lock protecting the override pattern list } // NewGlogHandler creates a new log handler with filtering functionality similar // to Google's glog logger. The returned handler implements Handler. -func NewGlogHandler(h Handler) *GlogHandler { +func NewGlogHandler(h slog.Handler) *GlogHandler { return &GlogHandler{ origin: h, } @@ -61,13 +60,13 @@ func NewGlogHandler(h Handler) *GlogHandler { // and a file pattern to match. type pattern struct { pattern *regexp.Regexp - level Lvl + level slog.Level } // Verbosity sets the glog verbosity ceiling. The verbosity of individual packages // and source files can be raised using Vmodule. -func (h *GlogHandler) Verbosity(level Lvl) { - atomic.StoreUint32(&h.level, uint32(level)) +func (h *GlogHandler) Verbosity(level slog.Level) { + h.level.Store(int32(level)) } // Vmodule sets the glog verbosity pattern. @@ -77,14 +76,14 @@ func (h *GlogHandler) Verbosity(level Lvl) { // // For instance: // -// pattern="gopher.go=3" -// sets the V level to 3 in all Go files named "gopher.go" +// pattern="gopher.go=3" +// sets the V level to 3 in all Go files named "gopher.go" // -// pattern="foo=3" -// sets V to 3 in all files of any packages whose import path ends in "foo" +// pattern="foo=3" +// sets V to 3 in all files of any packages whose import path ends in "foo" // -// pattern="foo/*=3" -// sets V to 3 in all files of any packages whose import path contains "foo" +// pattern="foo/*=3" +// sets V to 3 in all files of any packages whose import path contains "foo" func (h *GlogHandler) Vmodule(ruleset string) error { var filter []pattern for _, rule := range strings.Split(ruleset, ",") { @@ -103,11 +102,13 @@ func (h *GlogHandler) Vmodule(ruleset string) error { return errVmoduleSyntax } // Parse the level and if correct, assemble the filter rule - level, err := strconv.Atoi(parts[1]) + l, err := strconv.Atoi(parts[1]) if err != nil { return errVmoduleSyntax } - if level <= 0 { + level := FromLegacyLevel(l) + + if level == LevelCrit { continue // Ignore. It's harmless but no point in paying the overhead. } // Compile the rule pattern into a regular expression @@ -125,103 +126,89 @@ func (h *GlogHandler) Vmodule(ruleset string) error { matcher = matcher + "$" re, _ := regexp.Compile(matcher) - filter = append(filter, pattern{re, Lvl(level)}) + filter = append(filter, pattern{re, level}) } // Swap out the vmodule pattern for the new filter system h.lock.Lock() defer h.lock.Unlock() h.patterns = filter - h.siteCache = make(map[uintptr]Lvl) - atomic.StoreUint32(&h.override, uint32(len(filter))) + h.siteCache = make(map[uintptr]slog.Level) + h.override.Store(len(filter) != 0) return nil } -// BacktraceAt sets the glog backtrace location. When set to a file and line -// number holding a logging statement, a stack trace will be written to the Info -// log whenever execution hits that statement. -// -// Unlike with Vmodule, the ".go" must be present. -func (h *GlogHandler) BacktraceAt(location string) error { - // Ensure the backtrace location contains two non-empty elements - parts := strings.Split(location, ":") - if len(parts) != 2 { - return errTraceSyntax - } - parts[0] = strings.TrimSpace(parts[0]) - parts[1] = strings.TrimSpace(parts[1]) - if len(parts[0]) == 0 || len(parts[1]) == 0 { - return errTraceSyntax - } - // Ensure the .go prefix is present and the line is valid - if !strings.HasSuffix(parts[0], ".go") { - return errTraceSyntax - } - if _, err := strconv.Atoi(parts[1]); err != nil { - return errTraceSyntax +// Enabled implements slog.Handler, reporting whether the handler handles records +// at the given level. +func (h *GlogHandler) Enabled(ctx context.Context, lvl slog.Level) bool { + // fast-track skipping logging if override not enabled and the provided verbosity is above configured + return h.override.Load() || slog.Level(h.level.Load()) <= lvl +} + +// WithAttrs implements slog.Handler, returning a new Handler whose attributes +// consist of both the receiver's attributes and the arguments. +func (h *GlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + h.lock.RLock() + siteCache := maps.Clone(h.siteCache) + h.lock.RUnlock() + + patterns := []pattern{} + patterns = append(patterns, h.patterns...) + + res := GlogHandler{ + origin: h.origin.WithAttrs(attrs), + patterns: patterns, + siteCache: siteCache, + location: h.location, } - // All seems valid - h.lock.Lock() - defer h.lock.Unlock() - h.location = location - atomic.StoreUint32(&h.backtrace, uint32(len(location))) + res.level.Store(h.level.Load()) + res.override.Store(h.override.Load()) + return &res +} - return nil +// WithGroup implements slog.Handler, returning a new Handler with the given +// group appended to the receiver's existing groups. +// +// Note, this function is not implemented. +func (h *GlogHandler) WithGroup(name string) slog.Handler { + panic("not implemented") } -// Log implements Handler.Log, filtering a log record through the global, local -// and backtrace filters, finally emitting it if either allow it through. -func (h *GlogHandler) Log(r *Record) error { - // If backtracing is requested, check whether this is the callsite - if atomic.LoadUint32(&h.backtrace) > 0 { - // Everything below here is slow. Although we could cache the call sites the - // same way as for vmodule, backtracing is so rare it's not worth the extra - // complexity. - h.lock.RLock() - match := h.location == r.Call.String() - h.lock.RUnlock() - - if match { - // Callsite matched, raise the log level to info and gather the stacks - r.Lvl = LvlInfo - - buf := make([]byte, 1024*1024) - buf = buf[:runtime.Stack(buf, true)] - r.Msg += "\n\n" + string(buf) - } - } +// Handle implements slog.Handler, filtering a log record through the global, +// local and backtrace filters, finally emitting it if either allow it through. +func (h *GlogHandler) Handle(_ context.Context, r slog.Record) error { // If the global log level allows, fast track logging - if atomic.LoadUint32(&h.level) >= uint32(r.Lvl) { - return h.origin.Log(r) - } - // If no local overrides are present, fast track skipping - if atomic.LoadUint32(&h.override) == 0 { - return nil + if slog.Level(h.level.Load()) <= r.Level { + return h.origin.Handle(context.Background(), r) } + // Check callsite cache for previously calculated log levels h.lock.RLock() - lvl, ok := h.siteCache[r.Call.PC()] + lvl, ok := h.siteCache[r.PC] h.lock.RUnlock() // If we didn't cache the callsite yet, calculate it if !ok { h.lock.Lock() + + fs := runtime.CallersFrames([]uintptr{r.PC}) + frame, _ := fs.Next() + for _, rule := range h.patterns { - if rule.pattern.MatchString(fmt.Sprintf("%+s", r.Call)) { - h.siteCache[r.Call.PC()], lvl, ok = rule.level, rule.level, true - break + if rule.pattern.MatchString(fmt.Sprintf("+%s", frame.File)) { + h.siteCache[r.PC], lvl, ok = rule.level, rule.level, true } } // If no rule matched, remember to drop log the next time if !ok { - h.siteCache[r.Call.PC()] = 0 + h.siteCache[r.PC] = 0 } h.lock.Unlock() } - if lvl >= r.Lvl { - return h.origin.Log(r) + if lvl <= r.Level { + return h.origin.Handle(context.Background(), r) } return nil } diff --git a/log/handler_go13.go b/log/handler_go13.go deleted file mode 100644 index 0843ed0e5f38..000000000000 --- a/log/handler_go13.go +++ /dev/null @@ -1,26 +0,0 @@ -// +build !go1.4 - -package log - -import ( - "sync/atomic" - "unsafe" -) - -// swapHandler wraps another handler that may be swapped out -// dynamically at runtime in a thread-safe fashion. -type swapHandler struct { - handler unsafe.Pointer -} - -func (h *swapHandler) Log(r *Record) error { - return h.Get().Log(r) -} - -func (h *swapHandler) Get() Handler { - return *(*Handler)(atomic.LoadPointer(&h.handler)) -} - -func (h *swapHandler) Swap(newHandler Handler) { - atomic.StorePointer(&h.handler, unsafe.Pointer(&newHandler)) -} diff --git a/log/handler_go14.go b/log/handler_go14.go deleted file mode 100644 index 05dedbf2a70e..000000000000 --- a/log/handler_go14.go +++ /dev/null @@ -1,23 +0,0 @@ -// +build go1.4 - -package log - -import "sync/atomic" - -// swapHandler wraps another handler that may be swapped out -// dynamically at runtime in a thread-safe fashion. -type swapHandler struct { - handler atomic.Value -} - -func (h *swapHandler) Log(r *Record) error { - return (*h.handler.Load().(*Handler)).Log(r) -} - -func (h *swapHandler) Swap(newHandler Handler) { - h.handler.Store(&newHandler) -} - -func (h *swapHandler) Get() Handler { - return *h.handler.Load().(*Handler) -} diff --git a/log/logger.go b/log/logger.go index 1a04e4ee9bde..016856c8348d 100644 --- a/log/logger.go +++ b/log/logger.go @@ -1,240 +1,216 @@ package log import ( - "fmt" + "context" + "log/slog" + "math" "os" + "runtime" "time" - - "github.com/go-stack/stack" ) -const timeKey = "t" -const lvlKey = "lvl" -const msgKey = "msg" -const errorKey = "LOG15_ERROR" +const errorKey = "LOG_ERROR" -type Lvl int +const ( + legacyLevelCrit = iota + legacyLevelError + legacyLevelWarn + legacyLevelInfo + legacyLevelDebug + legacyLevelTrace +) const ( - LvlCrit Lvl = iota - LvlError - LvlWarn - LvlInfo - LvlDebug - LvlTrace + levelMaxVerbosity slog.Level = math.MinInt + LevelTrace slog.Level = -8 + LevelDebug = slog.LevelDebug + LevelInfo = slog.LevelInfo + LevelWarn = slog.LevelWarn + LevelError = slog.LevelError + LevelCrit slog.Level = 12 + + // for backward-compatibility + LvlTrace = LevelTrace + LvlInfo = LevelInfo + LvlDebug = LevelDebug ) -// Aligned returns a 5-character string containing the name of a Lvl. -func (l Lvl) AlignedString() string { +// FromLegacyLevel converts from old Geth verbosity level constants +// to levels defined by slog +func FromLegacyLevel(lvl int) slog.Level { + switch lvl { + case legacyLevelCrit: + return LevelCrit + case legacyLevelError: + return slog.LevelError + case legacyLevelWarn: + return slog.LevelWarn + case legacyLevelInfo: + return slog.LevelInfo + case legacyLevelDebug: + return slog.LevelDebug + case legacyLevelTrace: + return LevelTrace + default: + break + } + + // TODO: should we allow use of custom levels or force them to match existing max/min if they fall outside the range as I am doing here? + if lvl > legacyLevelTrace { + return LevelTrace + } + return LevelCrit +} + +// LevelAlignedString returns a 5-character string containing the name of a Lvl. +func LevelAlignedString(l slog.Level) string { switch l { - case LvlTrace: + case LevelTrace: return "TRACE" - case LvlDebug: + case slog.LevelDebug: return "DEBUG" - case LvlInfo: + case slog.LevelInfo: return "INFO " - case LvlWarn: + case slog.LevelWarn: return "WARN " - case LvlError: + case slog.LevelError: return "ERROR" - case LvlCrit: + case LevelCrit: return "CRIT " default: - panic("bad level") + return "unknown level" } } -// Strings returns the name of a Lvl. -func (l Lvl) String() string { +// LevelString returns a string containing the name of a Lvl. +func LevelString(l slog.Level) string { switch l { - case LvlTrace: - return "trce" - case LvlDebug: - return "dbug" - case LvlInfo: + case LevelTrace: + return "trace" + case slog.LevelDebug: + return "debug" + case slog.LevelInfo: return "info" - case LvlWarn: + case slog.LevelWarn: return "warn" - case LvlError: - return "eror" - case LvlCrit: + case slog.LevelError: + return "error" + case LevelCrit: return "crit" default: - panic("bad level") + return "unknown" } } -// Returns the appropriate Lvl from a string name. -// Useful for parsing command line args and configuration files. -func LvlFromString(lvlString string) (Lvl, error) { - switch lvlString { - case "trace", "trce": - return LvlTrace, nil - case "debug", "dbug": - return LvlDebug, nil - case "info": - return LvlInfo, nil - case "warn": - return LvlWarn, nil - case "error", "eror": - return LvlError, nil - case "crit": - return LvlCrit, nil - default: - return LvlDebug, fmt.Errorf("unknown level: %v", lvlString) - } -} - -// A Record is what a Logger asks its handler to write -type Record struct { - Time time.Time - Lvl Lvl - Msg string - Ctx []interface{} - Call stack.Call - KeyNames RecordKeyNames -} - -type RecordKeyNames struct { - Time string - Msg string - Lvl string -} - // A Logger writes key/value pairs to a Handler type Logger interface { - // New returns a new Logger that has this logger's context plus the given context - New(ctx ...interface{}) Logger + // With returns a new Logger that has this logger's attributes plus the given attributes + With(ctx ...interface{}) Logger - // GetHandler gets the handler associated with the logger. - GetHandler() Handler + // New returns a new Logger that has this logger's attributes plus the given attributes. Identical to 'With'. + New(ctx ...interface{}) Logger - // SetHandler updates the logger to write records to the specified handler. - SetHandler(h Handler) + // Log logs a message at the specified level with context key/value pairs + Log(level slog.Level, msg string, ctx ...interface{}) - // Log a message at the given level with context key/value pairs + // Trace log a message at the trace level with context key/value pairs Trace(msg string, ctx ...interface{}) + + // Debug logs a message at the debug level with context key/value pairs Debug(msg string, ctx ...interface{}) + + // Info logs a message at the info level with context key/value pairs Info(msg string, ctx ...interface{}) + + // Warn logs a message at the warn level with context key/value pairs Warn(msg string, ctx ...interface{}) + + // Error logs a message at the error level with context key/value pairs Error(msg string, ctx ...interface{}) + + // Crit logs a message at the crit level with context key/value pairs, and exits Crit(msg string, ctx ...interface{}) + + // Write logs a message at the specified level + Write(level slog.Level, msg string, attrs ...any) + + // Enabled reports whether l emits log records at the given context and level. + Enabled(ctx context.Context, level slog.Level) bool + + // Handler returns the underlying handler of the inner logger. + Handler() slog.Handler } type logger struct { - ctx []interface{} - h *swapHandler + inner *slog.Logger } -func (l *logger) write(msg string, lvl Lvl, ctx []interface{}) { - l.h.Log(&Record{ - Time: time.Now(), - Lvl: lvl, - Msg: msg, - Ctx: newContext(l.ctx, ctx), - Call: stack.Caller(2), - KeyNames: RecordKeyNames{ - Time: timeKey, - Msg: msgKey, - Lvl: lvlKey, - }, - }) +// NewLogger returns a logger with the specified handler set +func NewLogger(h slog.Handler) Logger { + return &logger{ + slog.New(h), + } } -func (l *logger) New(ctx ...interface{}) Logger { - child := &logger{newContext(l.ctx, ctx), new(swapHandler)} - child.SetHandler(l.h) - return child +func (l *logger) Handler() slog.Handler { + return l.inner.Handler() } -func newContext(prefix []interface{}, suffix []interface{}) []interface{} { - normalizedSuffix := normalize(suffix) - newCtx := make([]interface{}, len(prefix)+len(normalizedSuffix)) - n := copy(newCtx, prefix) - copy(newCtx[n:], normalizedSuffix) - return newCtx -} +// Write logs a message at the specified level. +func (l *logger) Write(level slog.Level, msg string, attrs ...any) { + if !l.inner.Enabled(context.Background(), level) { + return + } -func (l *logger) Trace(msg string, ctx ...interface{}) { - l.write(msg, LvlTrace, ctx) -} + var pcs [1]uintptr + runtime.Callers(3, pcs[:]) -func (l *logger) Debug(msg string, ctx ...interface{}) { - l.write(msg, LvlDebug, ctx) + if len(attrs)%2 != 0 { + attrs = append(attrs, nil, errorKey, "Normalized odd number of arguments by adding nil") + } + r := slog.NewRecord(time.Now(), level, msg, pcs[0]) + r.Add(attrs...) + l.inner.Handler().Handle(context.Background(), r) } -func (l *logger) Info(msg string, ctx ...interface{}) { - l.write(msg, LvlInfo, ctx) +func (l *logger) Log(level slog.Level, msg string, attrs ...any) { + l.Write(level, msg, attrs...) } -func (l *logger) Warn(msg string, ctx ...interface{}) { - l.write(msg, LvlWarn, ctx) +func (l *logger) With(ctx ...interface{}) Logger { + return &logger{l.inner.With(ctx...)} } -func (l *logger) Error(msg string, ctx ...interface{}) { - l.write(msg, LvlError, ctx) +func (l *logger) New(ctx ...interface{}) Logger { + return l.With(ctx...) } -func (l *logger) Crit(msg string, ctx ...interface{}) { - l.write(msg, LvlCrit, ctx) - os.Exit(1) +// Enabled reports whether l emits log records at the given context and level. +func (l *logger) Enabled(ctx context.Context, level slog.Level) bool { + return l.inner.Enabled(ctx, level) } -func (l *logger) GetHandler() Handler { - return l.h.Get() +func (l *logger) Trace(msg string, ctx ...interface{}) { + l.Write(LevelTrace, msg, ctx...) } -func (l *logger) SetHandler(h Handler) { - l.h.Swap(h) +func (l *logger) Debug(msg string, ctx ...interface{}) { + l.Write(slog.LevelDebug, msg, ctx...) } -func normalize(ctx []interface{}) []interface{} { - // if the caller passed a Ctx object, then expand it - if len(ctx) == 1 { - if ctxMap, ok := ctx[0].(Ctx); ok { - ctx = ctxMap.toArray() - } - } - - // ctx needs to be even because it's a series of key/value pairs - // no one wants to check for errors on logging functions, - // so instead of erroring on bad input, we'll just make sure - // that things are the right length and users can fix bugs - // when they see the output looks wrong - if len(ctx)%2 != 0 { - ctx = append(ctx, nil, errorKey, "Normalized odd number of arguments by adding nil") - } - - return ctx +func (l *logger) Info(msg string, ctx ...interface{}) { + l.Write(slog.LevelInfo, msg, ctx...) } -// Lazy allows you to defer calculation of a logged value that is expensive -// to compute until it is certain that it must be evaluated with the given filters. -// -// Lazy may also be used in conjunction with a Logger's New() function -// to generate a child logger which always reports the current value of changing -// state. -// -// You may wrap any function which takes no arguments to Lazy. It may return any -// number of values of any type. -type Lazy struct { - Fn interface{} +func (l *logger) Warn(msg string, ctx ...any) { + l.Write(slog.LevelWarn, msg, ctx...) } -// Ctx is a map of key/value pairs to pass as context to a log function -// Use this only if you really need greater safety around the arguments you pass -// to the logging functions. -type Ctx map[string]interface{} - -func (c Ctx) toArray() []interface{} { - arr := make([]interface{}, len(c)*2) - - i := 0 - for k, v := range c { - arr[i] = k - arr[i+1] = v - i += 2 - } +func (l *logger) Error(msg string, ctx ...interface{}) { + l.Write(slog.LevelError, msg, ctx...) +} - return arr +func (l *logger) Crit(msg string, ctx ...interface{}) { + l.Write(LevelCrit, msg, ctx...) + os.Exit(1) } diff --git a/log/logger_test.go b/log/logger_test.go new file mode 100644 index 000000000000..3ec6d2e19c81 --- /dev/null +++ b/log/logger_test.go @@ -0,0 +1,191 @@ +package log + +import ( + "bytes" + "errors" + "fmt" + "io" + "log/slog" + "math/big" + "strings" + "testing" + "time" + + "github.com/holiman/uint256" +) + +// TestLoggingWithVmodule checks that vmodule works. +func TestLoggingWithVmodule(t *testing.T) { + out := new(bytes.Buffer) + glog := NewGlogHandler(NewTerminalHandlerWithLevel(out, LevelTrace, false)) + glog.Verbosity(LevelCrit) + logger := NewLogger(glog) + logger.Warn("This should not be seen", "ignored", "true") + glog.Vmodule("logger_test.go=5") + logger.Trace("a message", "foo", "bar") + have := out.String() + // The timestamp is locale-dependent, so we want to trim that off + // "INFO [01-01|00:00:00.000] a message ..." -> "a message..." + have = strings.Split(have, "]")[1] + want := " a message foo=bar\n" + if have != want { + t.Errorf("\nhave: %q\nwant: %q\n", have, want) + } +} + +func TestTerminalHandlerWithAttrs(t *testing.T) { + out := new(bytes.Buffer) + glog := NewGlogHandler(NewTerminalHandlerWithLevel(out, LevelTrace, false).WithAttrs([]slog.Attr{slog.String("baz", "bat")})) + glog.Verbosity(LevelTrace) + logger := NewLogger(glog) + logger.Trace("a message", "foo", "bar") + have := out.String() + // The timestamp is locale-dependent, so we want to trim that off + // "INFO [01-01|00:00:00.000] a message ..." -> "a message..." + have = strings.Split(have, "]")[1] + want := " a message baz=bat foo=bar\n" + if have != want { + t.Errorf("\nhave: %q\nwant: %q\n", have, want) + } +} + +// Make sure the default json handler outputs debug log lines +func TestJSONHandler(t *testing.T) { + out := new(bytes.Buffer) + handler := JSONHandler(out) + logger := slog.New(handler) + logger.Debug("hi there") + if len(out.String()) == 0 { + t.Error("expected non-empty debug log output from default JSON Handler") + } + + out.Reset() + handler = JSONHandlerWithLevel(out, slog.LevelInfo) + logger = slog.New(handler) + logger.Debug("hi there") + if len(out.String()) != 0 { + t.Errorf("expected empty debug log output, but got: %v", out.String()) + } +} + +func BenchmarkTraceLogging(b *testing.B) { + SetDefault(NewLogger(NewTerminalHandler(io.Discard, true))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Trace("a message", "v", i) + } +} + +func BenchmarkTerminalHandler(b *testing.B) { + l := NewLogger(NewTerminalHandler(io.Discard, false)) + benchmarkLogger(b, l) +} +func BenchmarkLogfmtHandler(b *testing.B) { + l := NewLogger(LogfmtHandler(io.Discard)) + benchmarkLogger(b, l) +} + +func BenchmarkJSONHandler(b *testing.B) { + l := NewLogger(JSONHandler(io.Discard)) + benchmarkLogger(b, l) +} + +func benchmarkLogger(b *testing.B, l Logger) { + var ( + bb = make([]byte, 10) + tt = time.Now() + bigint = big.NewInt(100) + nilbig *big.Int + err = errors.New("oh nooes it's crap") + ) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.Info("This is a message", + "foo", int16(i), + "bytes", bb, + "bonk", "a string with text", + "time", tt, + "bigint", bigint, + "nilbig", nilbig, + "err", err) + } + b.StopTimer() +} + +func TestLoggerOutput(t *testing.T) { + type custom struct { + A string + B int8 + } + var ( + customA = custom{"Foo", 12} + customB = custom{"Foo\nLinebreak", 122} + bb = make([]byte, 10) + tt = time.Time{} + bigint = big.NewInt(100) + nilbig *big.Int + err = errors.New("oh nooes it's crap") + smallUint = uint256.NewInt(500_000) + bigUint = &uint256.Int{0xff, 0xff, 0xff, 0xff} + ) + + out := new(bytes.Buffer) + glogHandler := NewGlogHandler(NewTerminalHandler(out, false)) + glogHandler.Verbosity(LevelInfo) + NewLogger(glogHandler).Info("This is a message", + "foo", int16(123), + "bytes", bb, + "bonk", "a string with text", + "time", tt, + "bigint", bigint, + "nilbig", nilbig, + "err", err, + "struct", customA, + "struct", customB, + "ptrstruct", &customA, + "smalluint", smallUint, + "bigUint", bigUint) + + have := out.String() + t.Logf("output %v", out.String()) + want := `INFO [11-07|19:14:33.821] This is a message foo=123 bytes="[0 0 0 0 0 0 0 0 0 0]" bonk="a string with text" time=0001-01-01T00:00:00+0000 bigint=100 nilbig= err="oh nooes it's crap" struct="{A:Foo B:12}" struct="{A:Foo\nLinebreak B:122}" ptrstruct="&{A:Foo B:12}" smalluint=500,000 bigUint=1,600,660,942,523,603,594,864,898,306,482,794,244,293,965,082,972,225,630,372,095 +` + if !bytes.Equal([]byte(have)[25:], []byte(want)[25:]) { + t.Errorf("Error\nhave: %q\nwant: %q", have, want) + } +} + +const termTimeFormat = "01-02|15:04:05.000" + +func BenchmarkAppendFormat(b *testing.B) { + var now = time.Now() + b.Run("fmt time.Format", func(b *testing.B) { + for i := 0; i < b.N; i++ { + fmt.Fprintf(io.Discard, "%s", now.Format(termTimeFormat)) + } + }) + b.Run("time.AppendFormat", func(b *testing.B) { + for i := 0; i < b.N; i++ { + now.AppendFormat(nil, termTimeFormat) + } + }) + var buf = new(bytes.Buffer) + b.Run("time.Custom", func(b *testing.B) { + for i := 0; i < b.N; i++ { + writeTimeTermFormat(buf, now) + buf.Reset() + } + }) +} + +func TestTermTimeFormat(t *testing.T) { + var now = time.Now() + want := now.AppendFormat(nil, termTimeFormat) + var b = new(bytes.Buffer) + writeTimeTermFormat(b, now) + have := b.Bytes() + if !bytes.Equal(have, want) { + t.Errorf("have != want\nhave: %q\nwant: %q\n", have, want) + } +} diff --git a/log/root.go b/log/root.go index 71b8cef6d4bf..aaf2fe56f60c 100644 --- a/log/root.go +++ b/log/root.go @@ -1,61 +1,120 @@ package log import ( + "context" + "log/slog" "os" + "sync/atomic" ) -var ( - root = &logger{[]interface{}{}, new(swapHandler)} - StdoutHandler = StreamHandler(os.Stdout, LogfmtFormat()) - StderrHandler = StreamHandler(os.Stderr, LogfmtFormat()) -) +var root atomic.Value func init() { - root.SetHandler(DiscardHandler()) + root.Store(&logger{slog.New(DiscardHandler())}) } -// New returns a new logger with the given context. -// New is a convenient alias for Root().New -func New(ctx ...interface{}) Logger { - return root.New(ctx...) +// SetDefault sets the default global logger +func SetDefault(l Logger) { + root.Store(l) + if lg, ok := l.(*logger); ok { + slog.SetDefault(lg.inner) + } } // Root returns the root logger func Root() Logger { - return root + return root.Load().(Logger) } // The following functions bypass the exported logger methods (logger.Debug, -// etc.) to keep the call depth the same for all paths to logger.write so +// etc.) to keep the call depth the same for all paths to logger.Write so // runtime.Caller(2) always refers to the call site in client code. // Trace is a convenient alias for Root().Trace +// +// Log a message at the trace level with context key/value pairs +// +// # Usage +// +// log.Trace("msg") +// log.Trace("msg", "key1", val1) +// log.Trace("msg", "key1", val1, "key2", val2) func Trace(msg string, ctx ...interface{}) { - root.write(msg, LvlTrace, ctx) + Root().Write(LevelTrace, msg, ctx...) } // Debug is a convenient alias for Root().Debug +// +// Log a message at the debug level with context key/value pairs +// +// # Usage Examples +// +// log.Debug("msg") +// log.Debug("msg", "key1", val1) +// log.Debug("msg", "key1", val1, "key2", val2) func Debug(msg string, ctx ...interface{}) { - root.write(msg, LvlDebug, ctx) + Root().Write(slog.LevelDebug, msg, ctx...) } // Info is a convenient alias for Root().Info +// +// Log a message at the info level with context key/value pairs +// +// # Usage Examples +// +// log.Info("msg") +// log.Info("msg", "key1", val1) +// log.Info("msg", "key1", val1, "key2", val2) func Info(msg string, ctx ...interface{}) { - root.write(msg, LvlInfo, ctx) + Root().Write(slog.LevelInfo, msg, ctx...) } // Warn is a convenient alias for Root().Warn +// +// Log a message at the warn level with context key/value pairs +// +// # Usage Examples +// +// log.Warn("msg") +// log.Warn("msg", "key1", val1) +// log.Warn("msg", "key1", val1, "key2", val2) func Warn(msg string, ctx ...interface{}) { - root.write(msg, LvlWarn, ctx) + Root().Write(slog.LevelWarn, msg, ctx...) } // Error is a convenient alias for Root().Error +// +// Log a message at the error level with context key/value pairs +// +// # Usage Examples +// +// log.Error("msg") +// log.Error("msg", "key1", val1) +// log.Error("msg", "key1", val1, "key2", val2) func Error(msg string, ctx ...interface{}) { - root.write(msg, LvlError, ctx) + Root().Write(slog.LevelError, msg, ctx...) } // Crit is a convenient alias for Root().Crit +// +// Log a message at the crit level with context key/value pairs, and then exit. +// +// # Usage Examples +// +// log.Crit("msg") +// log.Crit("msg", "key1", val1) +// log.Crit("msg", "key1", val1, "key2", val2) func Crit(msg string, ctx ...interface{}) { - root.write(msg, LvlCrit, ctx) + Root().Write(LevelCrit, msg, ctx...) os.Exit(1) } + +// New returns a new logger with the given context. +// New is a convenient alias for Root().New +func New(ctx ...interface{}) Logger { + return Root().With(ctx...) +} + +func Enabled(level slog.Level) bool { + return Root().Enabled(context.Background(), level) +} diff --git a/log/syslog.go b/log/syslog.go deleted file mode 100644 index 71a17b30b3e0..000000000000 --- a/log/syslog.go +++ /dev/null @@ -1,57 +0,0 @@ -// +build !windows,!plan9 - -package log - -import ( - "log/syslog" - "strings" -) - -// SyslogHandler opens a connection to the system syslog daemon by calling -// syslog.New and writes all records to it. -func SyslogHandler(priority syslog.Priority, tag string, fmtr Format) (Handler, error) { - wr, err := syslog.New(priority, tag) - return sharedSyslog(fmtr, wr, err) -} - -// SyslogNetHandler opens a connection to a log daemon over the network and writes -// all log records to it. -func SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) (Handler, error) { - wr, err := syslog.Dial(net, addr, priority, tag) - return sharedSyslog(fmtr, wr, err) -} - -func sharedSyslog(fmtr Format, sysWr *syslog.Writer, err error) (Handler, error) { - if err != nil { - return nil, err - } - h := FuncHandler(func(r *Record) error { - var syslogFn = sysWr.Info - switch r.Lvl { - case LvlCrit: - syslogFn = sysWr.Crit - case LvlError: - syslogFn = sysWr.Err - case LvlWarn: - syslogFn = sysWr.Warning - case LvlInfo: - syslogFn = sysWr.Info - case LvlDebug: - syslogFn = sysWr.Debug - case LvlTrace: - syslogFn = func(m string) error { return nil } // There's no syslog level for trace - } - - s := strings.TrimSpace(string(fmtr.Format(r))) - return syslogFn(s) - }) - return LazyHandler(&closingHandler{sysWr, h}), nil -} - -func (m muster) SyslogHandler(priority syslog.Priority, tag string, fmtr Format) Handler { - return must(SyslogHandler(priority, tag, fmtr)) -} - -func (m muster) SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) Handler { - return must(SyslogNetHandler(net, addr, priority, tag, fmtr)) -} diff --git a/log/term/LICENSE b/log/term/LICENSE deleted file mode 100644 index f090cb42f370..000000000000 --- a/log/term/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Simon Eskildsen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/log/term/terminal_appengine.go b/log/term/terminal_appengine.go deleted file mode 100644 index c1b5d2a3b1ad..000000000000 --- a/log/term/terminal_appengine.go +++ /dev/null @@ -1,13 +0,0 @@ -// Based on ssh/terminal: -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build appengine - -package term - -// IsTty always returns false on AppEngine. -func IsTty(fd uintptr) bool { - return false -} diff --git a/log/term/terminal_darwin.go b/log/term/terminal_darwin.go deleted file mode 100644 index d8f351b1b1aa..000000000000 --- a/log/term/terminal_darwin.go +++ /dev/null @@ -1,13 +0,0 @@ -// Based on ssh/terminal: -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. -// +build !appengine - -package term - -import "syscall" - -const ioctlReadTermios = syscall.TIOCGETA - -type Termios syscall.Termios diff --git a/log/term/terminal_freebsd.go b/log/term/terminal_freebsd.go deleted file mode 100644 index cfaceab337a2..000000000000 --- a/log/term/terminal_freebsd.go +++ /dev/null @@ -1,18 +0,0 @@ -package term - -import ( - "syscall" -) - -const ioctlReadTermios = syscall.TIOCGETA - -// Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin. -type Termios struct { - Iflag uint32 - Oflag uint32 - Cflag uint32 - Lflag uint32 - Cc [20]uint8 - Ispeed uint32 - Ospeed uint32 -} diff --git a/log/term/terminal_linux.go b/log/term/terminal_linux.go deleted file mode 100644 index 5290468d698a..000000000000 --- a/log/term/terminal_linux.go +++ /dev/null @@ -1,14 +0,0 @@ -// Based on ssh/terminal: -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !appengine - -package term - -import "syscall" - -const ioctlReadTermios = syscall.TCGETS - -type Termios syscall.Termios diff --git a/log/term/terminal_netbsd.go b/log/term/terminal_netbsd.go deleted file mode 100644 index f9bb9e1c23b5..000000000000 --- a/log/term/terminal_netbsd.go +++ /dev/null @@ -1,7 +0,0 @@ -package term - -import "syscall" - -const ioctlReadTermios = syscall.TIOCGETA - -type Termios syscall.Termios diff --git a/log/term/terminal_notwindows.go b/log/term/terminal_notwindows.go deleted file mode 100644 index c9af534f62f2..000000000000 --- a/log/term/terminal_notwindows.go +++ /dev/null @@ -1,20 +0,0 @@ -// Based on ssh/terminal: -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build linux,!appengine darwin freebsd openbsd netbsd - -package term - -import ( - "syscall" - "unsafe" -) - -// IsTty returns true if the given file descriptor is a terminal. -func IsTty(fd uintptr) bool { - var termios Termios - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) - return err == 0 -} diff --git a/log/term/terminal_openbsd.go b/log/term/terminal_openbsd.go deleted file mode 100644 index f9bb9e1c23b5..000000000000 --- a/log/term/terminal_openbsd.go +++ /dev/null @@ -1,7 +0,0 @@ -package term - -import "syscall" - -const ioctlReadTermios = syscall.TIOCGETA - -type Termios syscall.Termios diff --git a/log/term/terminal_solaris.go b/log/term/terminal_solaris.go deleted file mode 100644 index 033c163246ee..000000000000 --- a/log/term/terminal_solaris.go +++ /dev/null @@ -1,9 +0,0 @@ -package term - -import "golang.org/x/sys/unix" - -// IsTty returns true if the given file descriptor is a terminal. -func IsTty(fd uintptr) bool { - _, err := unix.IoctlGetTermios(int(fd), unix.TCGETA) - return err == nil -} diff --git a/log/term/terminal_windows.go b/log/term/terminal_windows.go deleted file mode 100644 index df3c30c15892..000000000000 --- a/log/term/terminal_windows.go +++ /dev/null @@ -1,26 +0,0 @@ -// Based on ssh/terminal: -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build windows - -package term - -import ( - "syscall" - "unsafe" -) - -var kernel32 = syscall.NewLazyDLL("kernel32.dll") - -var ( - procGetConsoleMode = kernel32.NewProc("GetConsoleMode") -) - -// IsTty returns true if the given file descriptor is a terminal. -func IsTty(fd uintptr) bool { - var st uint32 - r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) - return r != 0 && e == 0 -} diff --git a/mobile/init.go b/mobile/init.go index ff1463ca9b6e..52e2de10f7fb 100644 --- a/mobile/init.go +++ b/mobile/init.go @@ -27,7 +27,7 @@ import ( func init() { // Initialize the logger - log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, false))) // Initialize the goroutine count runtime.GOMAXPROCS(runtime.NumCPU()) diff --git a/mobile/logger.go b/mobile/logger.go index 28295f71f192..444b106b377e 100644 --- a/mobile/logger.go +++ b/mobile/logger.go @@ -24,5 +24,5 @@ import ( // SetVerbosity sets the global verbosity level (between 0 and 6 - see logger/verbosity.go). func SetVerbosity(level int) { - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(level), log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.FromLegacyLevel(level), false))) } diff --git a/p2p/discover/table.go b/p2p/discover/table.go index dff614c1d66e..b930cdf0b8ed 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -23,6 +23,7 @@ package discover import ( + "context" crand "crypto/rand" "encoding/binary" "errors" @@ -72,7 +73,10 @@ type Table struct { rand *mrand.Rand // source of randomness, periodically reseeded ips netutil.DistinctNetSet - db *nodeDB // database of known nodes + db *nodeDB // database of known nodes + log log.Logger + + // loop channels refreshReq chan chan struct{} initDone chan struct{} closeReq chan struct{} @@ -118,9 +122,11 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string if err != nil { return nil, err } + tab := &Table{ net: t, db: db, + log: log.Root(), self: NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)), bonding: make(map[NodeID]*bondproc), bondslots: make(chan struct{}, maxBondingPingPongs), @@ -322,10 +328,10 @@ func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node { // Bump the failure counter to detect and evacuate non-bonded entries fails := tab.db.findFails(n.ID) + 1 tab.db.updateFindFails(n.ID, fails) - log.Trace("Bumping findnode failure counter", "id", n.ID, "failcount", fails) + tab.log.Trace("Bumping findnode failure counter", "id", n.ID, "failcount", fails) if fails >= maxFindnodeFailures { - log.Trace("Too many findnode failures, dropping", "id", n.ID, "failcount", fails) + tab.log.Trace("Too many findnode failures, dropping", "id", n.ID, "failcount", fails) tab.delete(n) } } @@ -455,8 +461,10 @@ func (tab *Table) loadSeedNodes(bond bool) { } for i := range seeds { seed := seeds[i] - age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.bondTime(seed.ID)) }} - log.Debug("Found seed node in database", "id", seed.ID, "addr", seed.addr(), "age", age) + if tab.log.Enabled(context.Background(), log.LevelTrace) { + age := time.Since(tab.db.bondTime(seed.ID)) + tab.log.Debug("Found seed node in database", "id", seed.ID, "addr", seed.addr(), "age", age) + } tab.add(seed) } } @@ -480,16 +488,16 @@ func (tab *Table) doRevalidate(done chan<- struct{}) { b := tab.buckets[bi] if err == nil { // The node responded, move it to the front. - log.Debug("Revalidated node", "b", bi, "id", last.ID) + tab.log.Debug("Revalidated node", "b", bi, "id", last.ID) b.bump(last) return } // No reply received, pick a replacement or delete the node if there aren't // any replacements. if r := tab.replace(b, last); r != nil { - log.Debug("Replaced dead node", "b", bi, "id", last.ID, "ip", last.IP, "r", r.ID, "rip", r.IP) + tab.log.Debug("Replaced dead node", "b", bi, "id", last.ID, "ip", last.IP, "r", r.ID, "rip", r.IP) } else { - log.Debug("Removed dead node", "b", bi, "id", last.ID, "ip", last.IP) + tab.log.Debug("Removed dead node", "b", bi, "id", last.ID, "ip", last.IP) } } @@ -599,7 +607,7 @@ func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16 age := time.Since(tab.db.bondTime(id)) var result error if fails > 0 || age > nodeDBNodeExpiration { - log.Trace("Starting bonding ping/pong", "id", id, "known", node != nil, "failcount", fails, "age", age) + tab.log.Trace("Starting bonding ping/pong", "id", id, "known", node != nil, "failcount", fails, "age", age) tab.bondmu.Lock() w := tab.bonding[id] @@ -724,11 +732,11 @@ func (tab *Table) addIP(b *bucket, ip net.IP) bool { return true } if !tab.ips.Add(ip) { - log.Debug("IP exceeds table limit", "ip", ip) + tab.log.Debug("IP exceeds table limit", "ip", ip) return false } if !b.ips.Add(ip) { - log.Debug("IP exceeds bucket limit", "ip", ip) + tab.log.Debug("IP exceeds bucket limit", "ip", ip) tab.ips.Remove(ip) return false } diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index 701747bb0442..bb541ab07a65 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -228,6 +228,9 @@ type Config struct { NetRestrict *netutil.Netlist // network whitelist Bootnodes []*Node // list of bootstrap nodes Unhandled chan<- ReadPacket // unhandled packets are sent on this channel + + // The options below are useful in very specific cases, like in unit tests. + Log log.Logger // if set, log messages go here } // ListenUDP returns a new table that listens for UDP packets on laddr. diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go index 54981a4f7b12..6c5c0b11743e 100644 --- a/p2p/discv5/net.go +++ b/p2p/discv5/net.go @@ -420,7 +420,6 @@ loop: // Ingress packet handling. case pkt := <-net.read: - //fmt.Println("read", pkt.ev) log.Trace("<-net.read") n := net.internNode(&pkt) prestate := n.state @@ -428,10 +427,10 @@ loop: if err := net.handle(n, pkt.ev, &pkt); err != nil { status = err.Error() } - log.Trace("", "msg", log.Lazy{Fn: func() string { - return fmt.Sprintf("<<< (%d) %v from %x@%v: %v -> %v (%v)", - net.tab.count, pkt.ev, pkt.remoteID[:8], pkt.remoteAddr, prestate, n.state, status) - }}) + if log.Enabled(log.LevelTrace) { + msg := fmt.Sprintf("<<< (%d) %v from %x@%v: %v -> %v (%v)", net.tab.count, pkt.ev, pkt.remoteID[:8], pkt.remoteAddr, prestate, n.state, status) + log.Trace("", "msg", msg) + } // TODO: persist state if n.state goes >= known, delete if it goes <= known // State transition timeouts. @@ -447,10 +446,10 @@ loop: if err := net.handle(timeout.node, timeout.ev, nil); err != nil { status = err.Error() } - log.Trace("", "msg", log.Lazy{Fn: func() string { - return fmt.Sprintf("--- (%d) %v for %x@%v: %v -> %v (%v)", - net.tab.count, timeout.ev, timeout.node.ID[:8], timeout.node.addr(), prestate, timeout.node.state, status) - }}) + if log.Enabled(log.LevelTrace) { + msg := fmt.Sprintf("--- (%d) %v for %x@%v: %v -> %v (%v)", net.tab.count, timeout.ev, timeout.node.ID[:8], timeout.node.addr(), prestate, timeout.node.state, status) + log.Trace("", "msg", msg) + } // Querying. case q := <-net.queryReq: @@ -683,15 +682,15 @@ func (net *Network) refresh(done chan<- struct{}) { return } for _, n := range seeds { - log.Debug("", "msg", log.Lazy{Fn: func() string { + if log.Enabled(log.LevelDebug) { var age string if net.db != nil { age = time.Since(net.db.lastPong(n.ID)).String() } else { age = "unknown" } - return fmt.Sprintf("seed node (age %s): %v", age, n) - }}) + log.Debug("", "msg", fmt.Sprintf("seed node (age %s): %v", age, n)) + } n = net.internNodeFromDB(n) if n.state == unknown { net.transition(n, verifyinit) diff --git a/p2p/server_test.go b/p2p/server_test.go index a22a47147b51..0affd51f583f 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -32,7 +32,7 @@ import ( ) func init() { - // log.Root().SetHandler(log.LvlFilterHandler(log.LvlError, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) + // log.SetDefault(log.LvlFilterHandler(log.LvlError, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) } type testTransport struct { diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 1ad3961fea8b..1ab534f8d07f 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -24,6 +24,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net" "os" "os/exec" @@ -155,8 +156,7 @@ func (n *ExecNode) Client() (*rpc.Client, error) { var wsAddrPattern = regexp.MustCompile(`ws://[\d.:]+`) // Start exec's the node passing the ID and service as command line arguments -// and the node config encoded as JSON in the _P2P_NODE_CONFIG environment -// variable +// and the node config encoded as JSON in an environment variable. func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { if n.Cmd != nil { return errors.New("already started") @@ -189,7 +189,7 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { cmd := n.newCmd() cmd.Stdout = os.Stdout cmd.Stderr = stderr - cmd.Env = append(os.Environ(), fmt.Sprintf("_P2P_NODE_CONFIG=%s", confData)) + cmd.Env = append(os.Environ(), envNodeConfig+"="+string(confData)) if err := cmd.Start(); err != nil { return fmt.Errorf("error starting node: %s", err) } @@ -344,25 +344,58 @@ type execNodeConfig struct { PeerAddrs map[string]string `json:"peer_addrs,omitempty"` } -// execP2PNode starts a devp2p node when the current binary is executed with +func initLogging() { + // Initialize the logging by default first. + var innerHandler slog.Handler + innerHandler = slog.NewTextHandler(os.Stderr, nil) + glogger := log.NewGlogHandler(innerHandler) + glogger.Verbosity(log.LevelInfo) + log.SetDefault(log.NewLogger(glogger)) + + confEnv := os.Getenv(envNodeConfig) + if confEnv == "" { + return + } + var conf execNodeConfig + if err := json.Unmarshal([]byte(confEnv), &conf); err != nil { + return + } + var writer = os.Stderr + if conf.Node.LogFile != "" { + logWriter, err := os.Create(conf.Node.LogFile) + if err != nil { + return + } + writer = logWriter + } + var verbosity = log.LevelInfo + if conf.Node.LogVerbosity <= log.LevelTrace && conf.Node.LogVerbosity >= log.LevelCrit { + verbosity = log.FromLegacyLevel(int(conf.Node.LogVerbosity)) + } + // Reinitialize the logger + innerHandler = log.NewTerminalHandler(writer, true) + glogger = log.NewGlogHandler(innerHandler) + glogger.Verbosity(verbosity) + log.SetDefault(log.NewLogger(glogger)) +} + +// execP2PNode starts a simulation node when the current binary is executed with // argv[0] being "p2p-node", reading the service / ID from argv[1] / argv[2] -// and the node config from the _P2P_NODE_CONFIG environment variable +// and the node config from an environment variable. func execP2PNode() { - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat())) - glogger.Verbosity(log.LvlInfo) - log.Root().SetHandler(glogger) + initLogging() // read the services from argv serviceNames := strings.Split(os.Args[1], ",") // decode the config - confEnv := os.Getenv("_P2P_NODE_CONFIG") + confEnv := os.Getenv(envNodeConfig) if confEnv == "" { - log.Crit("missing _P2P_NODE_CONFIG") + log.Crit("missing " + envNodeConfig) } var conf execNodeConfig if err := json.Unmarshal([]byte(confEnv), &conf); err != nil { - log.Crit("error decoding _P2P_NODE_CONFIG", "err", err) + log.Crit("error decoding "+envNodeConfig, "err", err) } conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey conf.Stack.Logger = log.New("node.id", conf.Node.ID.String()) @@ -477,6 +510,10 @@ func (s *snapshotService) Stop() error { return nil } +const ( + envNodeConfig = "_P2P_NODE_CONFIG" +) + // SnapshotAPI provides an RPC method to create snapshots of services type SnapshotAPI struct { services map[string]node.Service diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index 0d7cf62ca406..59ae2e8ad3df 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -21,6 +21,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "log/slog" "os" "github.com/XinFinOrg/XDPoSChain/crypto" @@ -38,7 +39,6 @@ import ( // * SimNode - An in-memory node // * ExecNode - A child process node // * DockerNode - A Docker container node -// type Node interface { // Addr returns the node's address (e.g. an Enode URL) Addr() []byte @@ -97,24 +97,39 @@ type NodeConfig struct { // function to sanction or prevent suggesting a peer Reachable func(id discover.NodeID) bool + + // LogFile is the log file name of the p2p node at runtime. + // + // The default value is empty so that the default log writer + // is the system standard output. + LogFile string + + // LogVerbosity is the log verbosity of the p2p node at runtime. + // + // The default verbosity is INFO. + LogVerbosity slog.Level } // nodeConfigJSON is used to encode and decode NodeConfig as JSON by encoding // all fields as strings type nodeConfigJSON struct { - ID string `json:"id"` - PrivateKey string `json:"private_key"` - Name string `json:"name"` - Services []string `json:"services"` + ID string `json:"id"` + PrivateKey string `json:"private_key"` + Name string `json:"name"` + Services []string `json:"services"` + LogFile string `json:"logfile"` + LogVerbosity int `json:"log_verbosity"` } // MarshalJSON implements the json.Marshaler interface by encoding the config // fields as strings func (n *NodeConfig) MarshalJSON() ([]byte, error) { confJSON := nodeConfigJSON{ - ID: n.ID.String(), - Name: n.Name, - Services: n.Services, + ID: n.ID.String(), + Name: n.Name, + Services: n.Services, + LogFile: n.LogFile, + LogVerbosity: int(n.LogVerbosity), } if n.PrivateKey != nil { confJSON.PrivateKey = hex.EncodeToString(crypto.FromECDSA(n.PrivateKey)) @@ -152,6 +167,8 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error { n.Name = confJSON.Name n.Services = confJSON.Services + n.LogFile = confJSON.LogFile + n.LogVerbosity = slog.Level(confJSON.LogVerbosity) return nil } diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go index 488abfb8208d..450ab047906e 100644 --- a/p2p/simulations/examples/ping-pong.go +++ b/p2p/simulations/examples/ping-pong.go @@ -42,7 +42,7 @@ func main() { flag.Parse() // set the log level to Trace - log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, false))) // register a single ping-pong service services := map[string]adapters.ServiceFunc{