forked from cerc-io/plugeth
cmd/geth: test for logging-output (#28373)
This PR is a bit in preparation for the slog work in #28187 . Our current test re logging mostly test the internals, but we have no real end-to-end test of the logging output. This PR introduces a simple reexec-based log tester. This also relies upon a special mode in geth, which can be made to eject a set of predefined log messages (only available if the build-tag `integrationtests` is used e.g. go run --tags=integrationtests ./cmd/geth --log.format terminal logtest While working on this, I also noticed a quirk in the setup: when geth was configured to use a file output, then two separate handlers were used (one handler for the file, one handler for the console). Using two separate handlers means that two formatters are used, thus the formatting of any/all records happened twice. This PR changes the mechanism to use two separate io.Writers instead, which is both more optimal and fixes a bug which occurs due to a global statefulness in the formatter.
This commit is contained in:
parent
d8c6ae054c
commit
58ae1df684
@ -307,6 +307,9 @@ func doTest(cmdline []string) {
|
|||||||
// Enable CKZG backend in CI.
|
// Enable CKZG backend in CI.
|
||||||
gotest.Args = append(gotest.Args, "-tags=ckzg")
|
gotest.Args = append(gotest.Args, "-tags=ckzg")
|
||||||
|
|
||||||
|
// Enable integration-tests
|
||||||
|
gotest.Args = append(gotest.Args, "-tags=integrationtests")
|
||||||
|
|
||||||
// Test a single package at a time. CI builders are slow
|
// Test a single package at a time. CI builders are slow
|
||||||
// and some tests run into timeouts under load.
|
// and some tests run into timeouts under load.
|
||||||
gotest.Args = append(gotest.Args, "-p", "1")
|
gotest.Args = append(gotest.Args, "-p", "1")
|
||||||
|
185
cmd/geth/logging_test.go
Normal file
185
cmd/geth/logging_test.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
//go:build integrationtests
|
||||||
|
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/reexec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runSelf(args ...string) ([]byte, error) {
|
||||||
|
cmd := &exec.Cmd{
|
||||||
|
Path: reexec.Self(),
|
||||||
|
Args: append([]string{"geth-test"}, args...),
|
||||||
|
}
|
||||||
|
return cmd.CombinedOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
func split(input io.Reader) []string {
|
||||||
|
var output []string
|
||||||
|
scanner := bufio.NewScanner(input)
|
||||||
|
scanner.Split(bufio.ScanLines)
|
||||||
|
for scanner.Scan() {
|
||||||
|
output = append(output, strings.TrimSpace(scanner.Text()))
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
func censor(input string, start, end int) string {
|
||||||
|
if len(input) < end {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
return input[:start] + strings.Repeat("X", end-start) + input[end:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogging(t *testing.T) {
|
||||||
|
testConsoleLogging(t, "terminal", 6, 24)
|
||||||
|
testConsoleLogging(t, "logfmt", 2, 26)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConsoleLogging(t *testing.T, format string, tStart, tEnd int) {
|
||||||
|
haveB, err := runSelf("--log.format", format, "logtest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
readFile, err := os.Open(fmt.Sprintf("testdata/logging/logtest-%v.txt", format))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wantLines := split(readFile)
|
||||||
|
haveLines := split(bytes.NewBuffer(haveB))
|
||||||
|
for i, want := range wantLines {
|
||||||
|
if i > len(haveLines)-1 {
|
||||||
|
t.Fatalf("format %v, line %d missing, want:%v", format, i, want)
|
||||||
|
}
|
||||||
|
have := haveLines[i]
|
||||||
|
for strings.Contains(have, "Unknown config environment variable") {
|
||||||
|
// This can happen on CI runs. Drop it.
|
||||||
|
haveLines = append(haveLines[:i], haveLines[i+1:]...)
|
||||||
|
have = haveLines[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Black out the timestamp
|
||||||
|
have = censor(have, tStart, tEnd)
|
||||||
|
want = censor(want, tStart, tEnd)
|
||||||
|
if have != want {
|
||||||
|
t.Logf(nicediff([]byte(have), []byte(want)))
|
||||||
|
t.Fatalf("format %v, line %d\nhave %v\nwant %v", format, i, have, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(haveLines) != len(wantLines) {
|
||||||
|
t.Errorf("format %v, want %d lines, have %d", format, len(haveLines), len(wantLines))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVmodule(t *testing.T) {
|
||||||
|
checkOutput := func(level int, want, wantNot string) {
|
||||||
|
t.Helper()
|
||||||
|
output, err := runSelf("--log.format", "terminal", "--verbosity=0", "--log.vmodule", fmt.Sprintf("logtestcmd_active.go=%d", level), "logtest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(want) > 0 && !strings.Contains(string(output), want) { // trace should be present at 5
|
||||||
|
t.Errorf("failed to find expected string ('%s') in output", want)
|
||||||
|
}
|
||||||
|
if len(wantNot) > 0 && strings.Contains(string(output), wantNot) { // trace should be present at 5
|
||||||
|
t.Errorf("string ('%s') should not be present in output", wantNot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkOutput(5, "log at level trace", "") // trace should be present at 5
|
||||||
|
checkOutput(4, "log at level debug", "log at level trace") // debug should be present at 4, but trace should be missing
|
||||||
|
checkOutput(3, "log at level info", "log at level debug") // info should be present at 3, but debug should be missing
|
||||||
|
checkOutput(2, "log at level warn", "log at level info") // warn should be present at 2, but info should be missing
|
||||||
|
checkOutput(1, "log at level error", "log at level warn") // error should be present at 1, but warn should be missing
|
||||||
|
}
|
||||||
|
|
||||||
|
func nicediff(have, want []byte) string {
|
||||||
|
var i = 0
|
||||||
|
for ; i < len(have) && i < len(want); i++ {
|
||||||
|
if want[i] != have[i] {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var end = i + 40
|
||||||
|
var start = i - 50
|
||||||
|
if start < 0 {
|
||||||
|
start = 0
|
||||||
|
}
|
||||||
|
var h, w string
|
||||||
|
if end < len(have) {
|
||||||
|
h = string(have[start:end])
|
||||||
|
} else {
|
||||||
|
h = string(have[start:])
|
||||||
|
}
|
||||||
|
if end < len(want) {
|
||||||
|
w = string(want[start:end])
|
||||||
|
} else {
|
||||||
|
w = string(want[start:])
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("have vs want:\n%q\n%q\n", h, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileOut(t *testing.T) {
|
||||||
|
var (
|
||||||
|
have, want []byte
|
||||||
|
err error
|
||||||
|
path = fmt.Sprintf("%s/test_file_out-%d", os.TempDir(), rand.Int63())
|
||||||
|
)
|
||||||
|
t.Cleanup(func() { os.Remove(path) })
|
||||||
|
if want, err = runSelf(fmt.Sprintf("--log.file=%s", path), "logtest"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if have, err = os.ReadFile(path); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(have, want) {
|
||||||
|
// show an intelligent diff
|
||||||
|
t.Logf(nicediff(have, want))
|
||||||
|
t.Errorf("file content wrong")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRotatingFileOut(t *testing.T) {
|
||||||
|
var (
|
||||||
|
have, want []byte
|
||||||
|
err error
|
||||||
|
path = fmt.Sprintf("%s/test_file_out-%d", os.TempDir(), rand.Int63())
|
||||||
|
)
|
||||||
|
t.Cleanup(func() { os.Remove(path) })
|
||||||
|
if want, err = runSelf(fmt.Sprintf("--log.file=%s", path), "--log.rotate", "logtest"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if have, err = os.ReadFile(path); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(have, want) {
|
||||||
|
// show an intelligent diff
|
||||||
|
t.Logf(nicediff(have, want))
|
||||||
|
t.Errorf("file content wrong")
|
||||||
|
}
|
||||||
|
}
|
134
cmd/geth/logtestcmd_active.go
Normal file
134
cmd/geth/logtestcmd_active.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
//go:build integrationtests
|
||||||
|
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logTestCommand = &cli.Command{
|
||||||
|
Action: logTest,
|
||||||
|
Name: "logtest",
|
||||||
|
Usage: "Print some log messages",
|
||||||
|
ArgsUsage: " ",
|
||||||
|
Description: `
|
||||||
|
This command is only meant for testing.
|
||||||
|
`}
|
||||||
|
|
||||||
|
// logTest is an entry point which spits out some logs. This is used by testing
|
||||||
|
// to verify expected outputs
|
||||||
|
func logTest(ctx *cli.Context) error {
|
||||||
|
log.ResetGlobalState()
|
||||||
|
{ // big.Int
|
||||||
|
ba, _ := new(big.Int).SetString("111222333444555678999", 10) // "111,222,333,444,555,678,999"
|
||||||
|
bb, _ := new(big.Int).SetString("-111222333444555678999", 10) // "-111,222,333,444,555,678,999"
|
||||||
|
bc, _ := new(big.Int).SetString("11122233344455567899900", 10) // "11,122,233,344,455,567,899,900"
|
||||||
|
bd, _ := new(big.Int).SetString("-11122233344455567899900", 10) // "-11,122,233,344,455,567,899,900"
|
||||||
|
log.Info("big.Int", "111,222,333,444,555,678,999", ba)
|
||||||
|
log.Info("-big.Int", "-111,222,333,444,555,678,999", bb)
|
||||||
|
log.Info("big.Int", "11,122,233,344,455,567,899,900", bc)
|
||||||
|
log.Info("-big.Int", "-11,122,233,344,455,567,899,900", bd)
|
||||||
|
}
|
||||||
|
{ //uint256
|
||||||
|
ua, _ := uint256.FromDecimal("111222333444555678999")
|
||||||
|
ub, _ := uint256.FromDecimal("11122233344455567899900")
|
||||||
|
log.Info("uint256", "111,222,333,444,555,678,999", ua)
|
||||||
|
log.Info("uint256", "11,122,233,344,455,567,899,900", ub)
|
||||||
|
}
|
||||||
|
{ // int64
|
||||||
|
log.Info("int64", "1,000,000", int64(1000000))
|
||||||
|
log.Info("int64", "-1,000,000", int64(-1000000))
|
||||||
|
log.Info("int64", "9,223,372,036,854,775,807", int64(math.MaxInt64))
|
||||||
|
log.Info("int64", "-9,223,372,036,854,775,808", int64(math.MinInt64))
|
||||||
|
}
|
||||||
|
{ // uint64
|
||||||
|
log.Info("uint64", "1,000,000", uint64(1000000))
|
||||||
|
log.Info("uint64", "18,446,744,073,709,551,615", uint64(math.MaxUint64))
|
||||||
|
}
|
||||||
|
{ // Special characters
|
||||||
|
log.Info("Special chars in value", "key", "special \r\n\t chars")
|
||||||
|
log.Info("Special chars in key", "special \n\t chars", "value")
|
||||||
|
|
||||||
|
log.Info("nospace", "nospace", "nospace")
|
||||||
|
log.Info("with space", "with nospace", "with nospace")
|
||||||
|
|
||||||
|
log.Info("Bash escapes in value", "key", "\u001b[1G\u001b[K\u001b[1A")
|
||||||
|
log.Info("Bash escapes in key", "\u001b[1G\u001b[K\u001b[1A", "value")
|
||||||
|
|
||||||
|
log.Info("Bash escapes in message \u001b[1G\u001b[K\u001b[1A end", "key", "value")
|
||||||
|
|
||||||
|
colored := fmt.Sprintf("\u001B[%dmColored\u001B[0m[", 35)
|
||||||
|
log.Info(colored, colored, colored)
|
||||||
|
}
|
||||||
|
{ // Custom Stringer() - type
|
||||||
|
log.Info("Custom Stringer value", "2562047h47m16.854s", common.PrettyDuration(time.Duration(9223372036854775807)))
|
||||||
|
}
|
||||||
|
{ // Lazy eval
|
||||||
|
log.Info("Lazy evaluation of value", "key", log.Lazy{Fn: func() interface{} { return "lazy value" }})
|
||||||
|
}
|
||||||
|
{ // Multi-line message
|
||||||
|
log.Info("A message with wonky \U0001F4A9 characters")
|
||||||
|
log.Info("A multiline message \nINFO [10-18|14:11:31.106] with wonky characters \U0001F4A9")
|
||||||
|
log.Info("A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above")
|
||||||
|
}
|
||||||
|
{ // Miscellaneous json-quirks
|
||||||
|
// This will check if the json output uses strings or json-booleans to represent bool values
|
||||||
|
log.Info("boolean", "true", true, "false", false)
|
||||||
|
// Handling of duplicate keys.
|
||||||
|
// This is actually ill-handled by the current handler: the format.go
|
||||||
|
// uses a global 'fieldPadding' map and mixes up the two keys. If 'alpha'
|
||||||
|
// is shorter than beta, it sometimes causes erroneous padding -- and what's more
|
||||||
|
// it causes _different_ padding in multi-handler context, e.g. both file-
|
||||||
|
// and console output, making the two mismatch.
|
||||||
|
log.Info("repeated-key 1", "foo", "alpha", "foo", "beta")
|
||||||
|
log.Info("repeated-key 2", "xx", "short", "xx", "longer")
|
||||||
|
}
|
||||||
|
{ // loglevels
|
||||||
|
log.Debug("log at level debug")
|
||||||
|
log.Trace("log at level trace")
|
||||||
|
log.Info("log at level info")
|
||||||
|
log.Warn("log at level warn")
|
||||||
|
log.Error("log at level error")
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// The current log formatter has a global map of paddings, storing the
|
||||||
|
// longest seen padding per key in a map. This results in a statefulness
|
||||||
|
// which has some odd side-effects. Demonstrated here:
|
||||||
|
log.Info("test", "bar", "short", "a", "aligned left")
|
||||||
|
log.Info("test", "bar", "a long message", "a", 1)
|
||||||
|
log.Info("test", "bar", "short", "a", "aligned right")
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// This sequence of logs should be output with alignment, so each field becoems a column.
|
||||||
|
log.Info("The following logs should align so that the key-fields make 5 columns")
|
||||||
|
log.Info("Inserted known block", "number", 1_012, "hash", common.HexToHash("0x1234"), "txs", 200, "gas", 1_123_123, "other", "first")
|
||||||
|
log.Info("Inserted new block", "number", 1, "hash", common.HexToHash("0x1235"), "txs", 2, "gas", 1_123, "other", "second")
|
||||||
|
log.Info("Inserted known block", "number", 99, "hash", common.HexToHash("0x12322"), "txs", 10, "gas", 1, "other", "third")
|
||||||
|
log.Warn("Inserted known block", "number", 1_012, "hash", common.HexToHash("0x1234"), "txs", 200, "gas", 99, "other", "fourth")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
23
cmd/geth/logtestcmd_inactive.go
Normal file
23
cmd/geth/logtestcmd_inactive.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
//go:build !integrationtests
|
||||||
|
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
var logTestCommand *cli.Command
|
@ -234,6 +234,9 @@ func init() {
|
|||||||
// See verkle.go
|
// See verkle.go
|
||||||
verkleCommand,
|
verkleCommand,
|
||||||
}
|
}
|
||||||
|
if logTestCommand != nil {
|
||||||
|
app.Commands = append(app.Commands, logTestCommand)
|
||||||
|
}
|
||||||
sort.Sort(cli.CommandsByName(app.Commands))
|
sort.Sort(cli.CommandsByName(app.Commands))
|
||||||
|
|
||||||
app.Flags = flags.Merge(
|
app.Flags = flags.Merge(
|
||||||
|
39
cmd/geth/testdata/logging/logtest-logfmt.txt
vendored
Normal file
39
cmd/geth/testdata/logging/logtest-logfmt.txt
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=big.Int 111,222,333,444,555,678,999=111,222,333,444,555,678,999
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=-big.Int -111,222,333,444,555,678,999=-111,222,333,444,555,678,999
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=big.Int 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=-big.Int -11,122,233,344,455,567,899,900=-11,122,233,344,455,567,899,900
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=uint256 111,222,333,444,555,678,999=111,222,333,444,555,678,999
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=uint256 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=int64 1,000,000=1,000,000
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=int64 -1,000,000=-1,000,000
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=int64 9,223,372,036,854,775,807=9,223,372,036,854,775,807
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=int64 -9,223,372,036,854,775,808=-9,223,372,036,854,775,808
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=uint64 1,000,000=1,000,000
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=uint64 18,446,744,073,709,551,615=18,446,744,073,709,551,615
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="Special chars in value" key="special \r\n\t chars"
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="Special chars in key" "special \n\t chars"=value
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=nospace nospace=nospace
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="with space" "with nospace"="with nospace"
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="Bash escapes in value" key="\x1b[1G\x1b[K\x1b[1A"
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="Bash escapes in key" "\x1b[1G\x1b[K\x1b[1A"=value
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m["
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="Custom Stringer value" 2562047h47m16.854s=2562047h47m16.854s
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="Lazy evaluation of value" key="lazy value"
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="A message with wonky 💩 characters"
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above"
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=boolean true=true false=false
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="repeated-key 1" foo=alpha foo=beta
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="repeated-key 2" xx=short xx=longer
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="log at level info"
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=warn msg="log at level warn"
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=eror msg="log at level error"
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=test bar=short a="aligned left"
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=test bar="a long message" a=1
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg=test bar=short a="aligned right"
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="The following logs should align so that the key-fields make 5 columns"
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=1,123,123 other=first
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="Inserted new block" number=1 hash=0x0000000000000000000000000000000000000000000000000000000000001235 txs=2 gas=1123 other=second
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=info msg="Inserted known block" number=99 hash=0x0000000000000000000000000000000000000000000000000000000000012322 txs=10 gas=1 other=third
|
||||||
|
t=2023-10-20T12:56:08+0200 lvl=warn msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=99 other=fourth
|
40
cmd/geth/testdata/logging/logtest-terminal.txt
vendored
Normal file
40
cmd/geth/testdata/logging/logtest-terminal.txt
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
INFO [10-20|12:56:42.532] big.Int 111,222,333,444,555,678,999=111,222,333,444,555,678,999
|
||||||
|
INFO [10-20|12:56:42.532] -big.Int -111,222,333,444,555,678,999=-111,222,333,444,555,678,999
|
||||||
|
INFO [10-20|12:56:42.532] big.Int 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
|
||||||
|
INFO [10-20|12:56:42.532] -big.Int -11,122,233,344,455,567,899,900=-11,122,233,344,455,567,899,900
|
||||||
|
INFO [10-20|12:56:42.532] uint256 111,222,333,444,555,678,999=111,222,333,444,555,678,999
|
||||||
|
INFO [10-20|12:56:42.532] uint256 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
|
||||||
|
INFO [10-20|12:56:42.532] int64 1,000,000=1,000,000
|
||||||
|
INFO [10-20|12:56:42.532] int64 -1,000,000=-1,000,000
|
||||||
|
INFO [10-20|12:56:42.532] int64 9,223,372,036,854,775,807=9,223,372,036,854,775,807
|
||||||
|
INFO [10-20|12:56:42.532] int64 -9,223,372,036,854,775,808=-9,223,372,036,854,775,808
|
||||||
|
INFO [10-20|12:56:42.532] uint64 1,000,000=1,000,000
|
||||||
|
INFO [10-20|12:56:42.532] uint64 18,446,744,073,709,551,615=18,446,744,073,709,551,615
|
||||||
|
INFO [10-20|12:56:42.532] Special chars in value key="special \r\n\t chars"
|
||||||
|
INFO [10-20|12:56:42.532] Special chars in key "special \n\t chars"=value
|
||||||
|
INFO [10-20|12:56:42.532] nospace nospace=nospace
|
||||||
|
INFO [10-20|12:56:42.532] with space "with nospace"="with nospace"
|
||||||
|
INFO [10-20|12:56:42.532] Bash escapes in value key="\x1b[1G\x1b[K\x1b[1A"
|
||||||
|
INFO [10-20|12:56:42.532] Bash escapes in key "\x1b[1G\x1b[K\x1b[1A"=value
|
||||||
|
INFO [10-20|12:56:42.532] "Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value
|
||||||
|
INFO [10-20|12:56:42.532] "\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m["
|
||||||
|
INFO [10-20|12:56:42.532] Custom Stringer value 2562047h47m16.854s=2562047h47m16.854s
|
||||||
|
INFO [10-20|12:56:42.532] Lazy evaluation of value key="lazy value"
|
||||||
|
INFO [10-20|12:56:42.532] "A message with wonky 💩 characters"
|
||||||
|
INFO [10-20|12:56:42.532] "A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"
|
||||||
|
INFO [10-20|12:56:42.532] A multiline message
|
||||||
|
LALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above
|
||||||
|
INFO [10-20|12:56:42.532] boolean true=true false=false
|
||||||
|
INFO [10-20|12:56:42.532] repeated-key 1 foo=alpha foo=beta
|
||||||
|
INFO [10-20|12:56:42.532] repeated-key 2 xx=short xx=longer
|
||||||
|
INFO [10-20|12:56:42.532] log at level info
|
||||||
|
WARN [10-20|12:56:42.532] log at level warn
|
||||||
|
ERROR[10-20|12:56:42.532] log at level error
|
||||||
|
INFO [10-20|12:56:42.532] test bar=short a="aligned left"
|
||||||
|
INFO [10-20|12:56:42.532] test bar="a long message" a=1
|
||||||
|
INFO [10-20|12:56:42.532] test bar=short a="aligned right"
|
||||||
|
INFO [10-20|12:56:42.532] The following logs should align so that the key-fields make 5 columns
|
||||||
|
INFO [10-20|12:56:42.532] Inserted known block number=1012 hash=000000..001234 txs=200 gas=1,123,123 other=first
|
||||||
|
INFO [10-20|12:56:42.532] Inserted new block number=1 hash=000000..001235 txs=2 gas=1123 other=second
|
||||||
|
INFO [10-20|12:56:42.532] Inserted known block number=99 hash=000000..012322 txs=10 gas=1 other=third
|
||||||
|
WARN [10-20|12:56:42.532] Inserted known block number=1012 hash=000000..001234 txs=200 gas=99 other=fourth
|
@ -218,8 +218,7 @@ func Setup(ctx *cli.Context) error {
|
|||||||
return fmt.Errorf("unknown log format: %v", ctx.String(logFormatFlag.Name))
|
return fmt.Errorf("unknown log format: %v", ctx.String(logFormatFlag.Name))
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
stdHandler = log.StreamHandler(output, logfmt)
|
ostream = log.StreamHandler(output, logfmt)
|
||||||
ostream = stdHandler
|
|
||||||
logFile = ctx.String(logFileFlag.Name)
|
logFile = ctx.String(logFileFlag.Name)
|
||||||
rotation = ctx.Bool(logRotateFlag.Name)
|
rotation = ctx.Bool(logRotateFlag.Name)
|
||||||
)
|
)
|
||||||
@ -242,20 +241,21 @@ func Setup(ctx *cli.Context) error {
|
|||||||
} else {
|
} else {
|
||||||
context = append(context, "location", filepath.Join(os.TempDir(), "geth-lumberjack.log"))
|
context = append(context, "location", filepath.Join(os.TempDir(), "geth-lumberjack.log"))
|
||||||
}
|
}
|
||||||
ostream = log.MultiHandler(log.StreamHandler(&lumberjack.Logger{
|
lumberWriter := &lumberjack.Logger{
|
||||||
Filename: logFile,
|
Filename: logFile,
|
||||||
MaxSize: ctx.Int(logMaxSizeMBsFlag.Name),
|
MaxSize: ctx.Int(logMaxSizeMBsFlag.Name),
|
||||||
MaxBackups: ctx.Int(logMaxBackupsFlag.Name),
|
MaxBackups: ctx.Int(logMaxBackupsFlag.Name),
|
||||||
MaxAge: ctx.Int(logMaxAgeFlag.Name),
|
MaxAge: ctx.Int(logMaxAgeFlag.Name),
|
||||||
Compress: ctx.Bool(logCompressFlag.Name),
|
Compress: ctx.Bool(logCompressFlag.Name),
|
||||||
}, logfmt), stdHandler)
|
|
||||||
} else if logFile != "" {
|
|
||||||
if logOutputStream, err := log.FileHandler(logFile, logfmt); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
ostream = log.MultiHandler(logOutputStream, stdHandler)
|
|
||||||
context = append(context, "location", logFile)
|
|
||||||
}
|
}
|
||||||
|
ostream = log.StreamHandler(io.MultiWriter(output, lumberWriter), logfmt)
|
||||||
|
} else if logFile != "" {
|
||||||
|
f, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ostream = log.StreamHandler(io.MultiWriter(output, f), logfmt)
|
||||||
|
context = append(context, "location", logFile)
|
||||||
}
|
}
|
||||||
glogger.SetHandler(ostream)
|
glogger.SetHandler(ostream)
|
||||||
|
|
||||||
|
@ -24,6 +24,14 @@ const (
|
|||||||
termCtxMaxPadding = 40
|
termCtxMaxPadding = 40
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ResetGlobalState resets the fieldPadding, which is useful for producing
|
||||||
|
// predictable output.
|
||||||
|
func ResetGlobalState() {
|
||||||
|
fieldPaddingLock.Lock()
|
||||||
|
fieldPadding = make(map[string]int)
|
||||||
|
fieldPaddingLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// locationTrims are trimmed for display to avoid unwieldy log lines.
|
// locationTrims are trimmed for display to avoid unwieldy log lines.
|
||||||
var locationTrims = []string{
|
var locationTrims = []string{
|
||||||
"github.com/ethereum/go-ethereum/",
|
"github.com/ethereum/go-ethereum/",
|
||||||
|
@ -1,105 +1,10 @@
|
|||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrettyInt64(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
n int64
|
|
||||||
s string
|
|
||||||
}{
|
|
||||||
{0, "0"},
|
|
||||||
{10, "10"},
|
|
||||||
{-10, "-10"},
|
|
||||||
{100, "100"},
|
|
||||||
{-100, "-100"},
|
|
||||||
{1000, "1000"},
|
|
||||||
{-1000, "-1000"},
|
|
||||||
{10000, "10000"},
|
|
||||||
{-10000, "-10000"},
|
|
||||||
{99999, "99999"},
|
|
||||||
{-99999, "-99999"},
|
|
||||||
{100000, "100,000"},
|
|
||||||
{-100000, "-100,000"},
|
|
||||||
{1000000, "1,000,000"},
|
|
||||||
{-1000000, "-1,000,000"},
|
|
||||||
{math.MaxInt64, "9,223,372,036,854,775,807"},
|
|
||||||
{math.MinInt64, "-9,223,372,036,854,775,808"},
|
|
||||||
}
|
|
||||||
for i, tt := range tests {
|
|
||||||
if have := FormatLogfmtInt64(tt.n); have != tt.s {
|
|
||||||
t.Errorf("test %d: format mismatch: have %s, want %s", i, have, tt.s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrettyUint64(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
n uint64
|
|
||||||
s string
|
|
||||||
}{
|
|
||||||
{0, "0"},
|
|
||||||
{10, "10"},
|
|
||||||
{100, "100"},
|
|
||||||
{1000, "1000"},
|
|
||||||
{10000, "10000"},
|
|
||||||
{99999, "99999"},
|
|
||||||
{100000, "100,000"},
|
|
||||||
{1000000, "1,000,000"},
|
|
||||||
{math.MaxUint64, "18,446,744,073,709,551,615"},
|
|
||||||
}
|
|
||||||
for i, tt := range tests {
|
|
||||||
if have := FormatLogfmtUint64(tt.n); have != tt.s {
|
|
||||||
t.Errorf("test %d: format mismatch: have %s, want %s", i, have, tt.s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrettyBigInt(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
int string
|
|
||||||
s string
|
|
||||||
}{
|
|
||||||
{"111222333444555678999", "111,222,333,444,555,678,999"},
|
|
||||||
{"-111222333444555678999", "-111,222,333,444,555,678,999"},
|
|
||||||
{"11122233344455567899900", "11,122,233,344,455,567,899,900"},
|
|
||||||
{"-11122233344455567899900", "-11,122,233,344,455,567,899,900"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
v, _ := new(big.Int).SetString(tt.int, 10)
|
|
||||||
if have := formatLogfmtBigInt(v); have != tt.s {
|
|
||||||
t.Errorf("invalid output %s, want %s", have, tt.s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrettyUint256(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
int string
|
|
||||||
s string
|
|
||||||
}{
|
|
||||||
{"111222333444555678999", "111,222,333,444,555,678,999"},
|
|
||||||
{"11122233344455567899900", "11,122,233,344,455,567,899,900"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
v := new(uint256.Int)
|
|
||||||
v.SetFromDecimal(tt.int)
|
|
||||||
if have := formatLogfmtUint256(v); have != tt.s {
|
|
||||||
t.Errorf("invalid output %s, want %s", have, tt.s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sink string
|
var sink string
|
||||||
|
|
||||||
func BenchmarkPrettyInt64Logfmt(b *testing.B) {
|
func BenchmarkPrettyInt64Logfmt(b *testing.B) {
|
||||||
@ -115,47 +20,3 @@ func BenchmarkPrettyUint64Logfmt(b *testing.B) {
|
|||||||
sink = FormatLogfmtUint64(rand.Uint64())
|
sink = FormatLogfmtUint64(rand.Uint64())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSanitation(t *testing.T) {
|
|
||||||
msg := "\u001b[1G\u001b[K\u001b[1A"
|
|
||||||
msg2 := "\u001b \u0000"
|
|
||||||
msg3 := "NiceMessage"
|
|
||||||
msg4 := "Space Message"
|
|
||||||
msg5 := "Enter\nMessage"
|
|
||||||
|
|
||||||
for i, tt := range []struct {
|
|
||||||
msg string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
msg: msg,
|
|
||||||
want: fmt.Sprintf("] %q %q=%q\n", msg, msg, msg),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
msg: msg2,
|
|
||||||
want: fmt.Sprintf("] %q %q=%q\n", msg2, msg2, msg2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
msg: msg3,
|
|
||||||
want: fmt.Sprintf("] %s %s=%s\n", msg3, msg3, msg3),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
msg: msg4,
|
|
||||||
want: fmt.Sprintf("] %s %q=%q\n", msg4, msg4, msg4),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
msg: msg5,
|
|
||||||
want: fmt.Sprintf("] %s %q=%q\n", msg5, msg5, msg5),
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
var (
|
|
||||||
logger = New()
|
|
||||||
out = new(strings.Builder)
|
|
||||||
)
|
|
||||||
logger.SetHandler(LvlFilterHandler(LvlInfo, StreamHandler(out, TerminalFormat(false))))
|
|
||||||
logger.Info(tt.msg, tt.msg, tt.msg)
|
|
||||||
if have := out.String()[24:]; tt.want != have {
|
|
||||||
t.Fatalf("test %d: want / have: \n%v\n%v", i, tt.want, have)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user