mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 01:41:36 +00:00
Merge remote-tracking branch 'origin/master' into fix/freezer-incorrect-fsync-ordering
This commit is contained in:
commit
b269abadc9
7 changed files with 266 additions and 85 deletions
|
|
@ -115,7 +115,7 @@ var (
|
|||
Name: "trace.noreturndata",
|
||||
Aliases: []string{"noreturndata"},
|
||||
Value: true,
|
||||
Usage: "enable return data output",
|
||||
Usage: "disable return data output",
|
||||
Category: traceCategory,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1899,7 +1899,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
cfg.StatelessSelfValidation = ctx.Bool(VMStatelessSelfValidationFlag.Name)
|
||||
}
|
||||
// Auto-enable StatelessSelfValidation when witness stats are enabled
|
||||
if ctx.Bool(VMWitnessStatsFlag.Name) {
|
||||
if cfg.EnableWitnessStats {
|
||||
cfg.StatelessSelfValidation = true
|
||||
}
|
||||
|
||||
|
|
|
|||
98
eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/eip7702_deauth.json
vendored
Normal file
98
eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/eip7702_deauth.json
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
"genesis": {
|
||||
"blobGasUsed": "0",
|
||||
"difficulty": "0",
|
||||
"excessBlobGas": "0",
|
||||
"extraData": "0x",
|
||||
"gasLimit": "11511229",
|
||||
"hash": "0x455b93a512baa4ed5e117508b184a6bb03904b94d665ce38931728eca9cdd8fe",
|
||||
"miner": "0x71562b71999873db5b286df957af199ec94617f7",
|
||||
"mixHash": "0x042877c4fab9f022d29590ae83bad89d6181afb1d6e107619911ea52e5901364",
|
||||
"nonce": "0x0000000000000000",
|
||||
"number": "1",
|
||||
"parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"stateRoot": "0xc8688ad6433e6b9f4edeb82360d2b99c8e919f493a01cacbe7c4a97184f5d043",
|
||||
"timestamp": "1775654796",
|
||||
"alloc": {
|
||||
"0x71562b71999873db5b286df957af199ec94617f7": {
|
||||
"balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffdb64910c3bf7",
|
||||
"nonce": "1"
|
||||
},
|
||||
"0xe85a1c0e9d5b1c9b417c6c1b34c22cd77f623f50": {
|
||||
"balance": "0x0",
|
||||
"code": "0xef0100d313d93607c016a85e63e557a11ca5ab0b53ad83",
|
||||
"codeHash": "0x9eea9f41ed2b35e6234d1e1c14e88c1136f85d56ed1f32a7efc0096d998dad3d",
|
||||
"nonce": "1"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"chainId": 1337,
|
||||
"homesteadBlock": 0,
|
||||
"eip150Block": 0,
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0,
|
||||
"byzantiumBlock": 0,
|
||||
"constantinopleBlock": 0,
|
||||
"petersburgBlock": 0,
|
||||
"istanbulBlock": 0,
|
||||
"muirGlacierBlock": 0,
|
||||
"berlinBlock": 0,
|
||||
"londonBlock": 0,
|
||||
"arrowGlacierBlock": 0,
|
||||
"grayGlacierBlock": 0,
|
||||
"shanghaiTime": 0,
|
||||
"cancunTime": 0,
|
||||
"pragueTime": 0,
|
||||
"terminalTotalDifficulty": 0,
|
||||
"blobSchedule": {
|
||||
"cancun": {
|
||||
"target": 3,
|
||||
"max": 6,
|
||||
"baseFeeUpdateFraction": 3338477
|
||||
},
|
||||
"prague": {
|
||||
"target": 6,
|
||||
"max": 9,
|
||||
"baseFeeUpdateFraction": 5007716
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"context": {
|
||||
"number": "2",
|
||||
"difficulty": "0",
|
||||
"timestamp": "1775654797",
|
||||
"gasLimit": "11522469",
|
||||
"miner": "0x71562b71999873db5b286df957af199ec94617f7",
|
||||
"baseFeePerGas": "766499147"
|
||||
},
|
||||
"input": "0x04f8cd82053901843b9aca008477359400830186a09471562b71999873db5b286df957af199ec94617f78080c0f85ef85c8205399400000000000000000000000000000000000000000101a011fc0271f2566e7ebe5ddbff6d48ea97a19afa248452a392781096b7e3b89177a0020107ecefe99c90429b416fe4d1eead5a7fa253761e85cd7cdc7df6e5032d7f80a098495fb16c904f0b67b49afe868b28b0159c8df07522bed99ef6ff2cc2ac2935a048857a9c385d91735a9fdccabc66de7a5ea1897f523a5b9a352e281642a76e6b",
|
||||
"tracerConfig": {
|
||||
"diffMode": true
|
||||
},
|
||||
"result": {
|
||||
"post": {
|
||||
"0x71562b71999873db5b286df957af199ec94617f7": {
|
||||
"balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffc1bd12c85eb7",
|
||||
"nonce": 2
|
||||
},
|
||||
"0xe85a1c0e9d5b1c9b417c6c1b34c22cd77f623f50": {
|
||||
"code": "0x",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
|
||||
"nonce": 2
|
||||
}
|
||||
},
|
||||
"pre": {
|
||||
"0x71562b71999873db5b286df957af199ec94617f7": {
|
||||
"balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffdb64910c3bf7",
|
||||
"nonce": 1
|
||||
},
|
||||
"0xe85a1c0e9d5b1c9b417c6c1b34c22cd77f623f50": {
|
||||
"balance": "0x0",
|
||||
"code": "0xef0100d313d93607c016a85e63e557a11ca5ab0b53ad83",
|
||||
"codeHash": "0x9eea9f41ed2b35e6234d1e1c14e88c1136f85d56ed1f32a7efc0096d998dad3d",
|
||||
"nonce": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,14 +16,14 @@ var _ = (*accountMarshaling)(nil)
|
|||
func (a account) MarshalJSON() ([]byte, error) {
|
||||
type account struct {
|
||||
Balance *hexutil.Big `json:"balance,omitempty"`
|
||||
Code hexutil.Bytes `json:"code,omitempty"`
|
||||
Code *hexutil.Bytes `json:"code,omitempty"`
|
||||
CodeHash *common.Hash `json:"codeHash,omitempty"`
|
||||
Nonce uint64 `json:"nonce,omitempty"`
|
||||
Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
|
||||
}
|
||||
var enc account
|
||||
enc.Balance = (*hexutil.Big)(a.Balance)
|
||||
enc.Code = a.Code
|
||||
enc.Code = (*hexutil.Bytes)(a.Code)
|
||||
enc.CodeHash = a.CodeHash
|
||||
enc.Nonce = a.Nonce
|
||||
enc.Storage = a.Storage
|
||||
|
|
@ -47,7 +47,7 @@ func (a *account) UnmarshalJSON(input []byte) error {
|
|||
a.Balance = (*big.Int)(dec.Balance)
|
||||
}
|
||||
if dec.Code != nil {
|
||||
a.Code = *dec.Code
|
||||
a.Code = (*[]byte)(dec.Code)
|
||||
}
|
||||
if dec.CodeHash != nil {
|
||||
a.CodeHash = dec.CodeHash
|
||||
|
|
|
|||
|
|
@ -44,21 +44,24 @@ func init() {
|
|||
type stateMap = map[common.Address]*account
|
||||
|
||||
type account struct {
|
||||
Balance *big.Int `json:"balance,omitempty"`
|
||||
Code []byte `json:"code,omitempty"`
|
||||
Balance *big.Int `json:"balance,omitempty"`
|
||||
// Code is a pointer so omitempty can omit unchanged code (nil) while
|
||||
// still emitting "0x" when code is cleared (e.g. EIP-7702 deauth).
|
||||
Code *[]byte `json:"code,omitempty"`
|
||||
CodeHash *common.Hash `json:"codeHash,omitempty"`
|
||||
Nonce uint64 `json:"nonce,omitempty"`
|
||||
Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
|
||||
empty bool
|
||||
|
||||
empty bool
|
||||
}
|
||||
|
||||
func (a *account) exists() bool {
|
||||
return a.Nonce > 0 || len(a.Code) > 0 || len(a.Storage) > 0 || (a.Balance != nil && a.Balance.Sign() != 0)
|
||||
return a.Nonce > 0 || (a.Code != nil && len(*a.Code) > 0) || len(a.Storage) > 0 || (a.Balance != nil && a.Balance.Sign() != 0)
|
||||
}
|
||||
|
||||
type accountMarshaling struct {
|
||||
Balance *hexutil.Big
|
||||
Code hexutil.Bytes
|
||||
Code *hexutil.Bytes
|
||||
}
|
||||
|
||||
type prestateTracer struct {
|
||||
|
|
@ -266,24 +269,28 @@ func (t *prestateTracer) processDiffState() {
|
|||
modified = true
|
||||
postAccount.Nonce = newNonce
|
||||
}
|
||||
prevCodeHash := common.Hash{}
|
||||
// Empty code hashes are excluded from the prestate, so default
|
||||
// to EmptyCodeHash to match what GetCodeHash returns for codeless accounts.
|
||||
prevCodeHash := types.EmptyCodeHash
|
||||
if t.pre[addr].CodeHash != nil {
|
||||
prevCodeHash = *t.pre[addr].CodeHash
|
||||
}
|
||||
// Empty code hashes are excluded from the prestate. Normalize
|
||||
// the empty code hash to a zero hash to make it comparable.
|
||||
if newCodeHash == types.EmptyCodeHash {
|
||||
newCodeHash = common.Hash{}
|
||||
}
|
||||
if newCodeHash != prevCodeHash {
|
||||
modified = true
|
||||
postAccount.CodeHash = &newCodeHash
|
||||
}
|
||||
if !t.config.DisableCode {
|
||||
newCode := t.env.StateDB.GetCode(addr)
|
||||
if !bytes.Equal(newCode, t.pre[addr].Code) {
|
||||
var prevCode []byte
|
||||
if t.pre[addr].Code != nil {
|
||||
prevCode = *t.pre[addr].Code
|
||||
}
|
||||
if !bytes.Equal(newCode, prevCode) {
|
||||
modified = true
|
||||
postAccount.Code = newCode
|
||||
if newCode == nil {
|
||||
newCode = []byte{}
|
||||
}
|
||||
postAccount.Code = &newCode
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -323,10 +330,13 @@ func (t *prestateTracer) lookupAccount(addr common.Address) {
|
|||
return
|
||||
}
|
||||
|
||||
code := t.env.StateDB.GetCode(addr)
|
||||
acc := &account{
|
||||
Balance: t.env.StateDB.GetBalance(addr).ToBig(),
|
||||
Nonce: t.env.StateDB.GetNonce(addr),
|
||||
Code: t.env.StateDB.GetCode(addr),
|
||||
}
|
||||
if len(code) > 0 {
|
||||
acc.Code = &code
|
||||
}
|
||||
codeHash := t.env.StateDB.GetCodeHash(addr)
|
||||
// If the code is empty, we don't need to store it in the prestate.
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ package log
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
|
@ -38,22 +36,22 @@ var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N")
|
|||
// matches; and requesting backtraces at certain positions.
|
||||
type GlogHandler struct {
|
||||
origin slog.Handler // The origin handler this wraps
|
||||
lock sync.Mutex // synchronizes writes to config
|
||||
config atomic.Pointer[glogConfig]
|
||||
}
|
||||
|
||||
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]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
|
||||
type glogConfig struct {
|
||||
patterns []pattern
|
||||
cache sync.Map
|
||||
level slog.Level
|
||||
}
|
||||
|
||||
// NewGlogHandler creates a new log handler with filtering functionality similar
|
||||
// to Google's glog logger. The returned handler implements Handler.
|
||||
func NewGlogHandler(h slog.Handler) *GlogHandler {
|
||||
return &GlogHandler{
|
||||
origin: h,
|
||||
}
|
||||
func NewGlogHandler(origin slog.Handler) *GlogHandler {
|
||||
h := &GlogHandler{origin: origin}
|
||||
h.config.Store(new(glogConfig))
|
||||
return h
|
||||
}
|
||||
|
||||
// pattern contains a filter for the Vmodule option, holding a verbosity level
|
||||
|
|
@ -66,7 +64,12 @@ type pattern struct {
|
|||
// Verbosity sets the glog verbosity ceiling. The verbosity of individual packages
|
||||
// and source files can be raised using Vmodule.
|
||||
func (h *GlogHandler) Verbosity(level slog.Level) {
|
||||
h.level.Store(int32(level))
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
cfg := h.config.Load()
|
||||
newcfg := &glogConfig{level: level, patterns: cfg.patterns}
|
||||
h.config.Store(newcfg)
|
||||
}
|
||||
|
||||
// Vmodule sets the glog verbosity pattern.
|
||||
|
|
@ -128,13 +131,13 @@ func (h *GlogHandler) Vmodule(ruleset string) error {
|
|||
re, _ := regexp.Compile(matcher)
|
||||
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]slog.Level)
|
||||
h.override.Store(len(filter) != 0)
|
||||
cfg := h.config.Load()
|
||||
newcfg := &glogConfig{level: cfg.level, patterns: filter}
|
||||
h.config.Store(newcfg)
|
||||
h.lock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -142,30 +145,9 @@ func (h *GlogHandler) Vmodule(ruleset string) error {
|
|||
// 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,
|
||||
}
|
||||
|
||||
res.level.Store(h.level.Load())
|
||||
res.override.Store(h.override.Load())
|
||||
return &res
|
||||
// fast-track skipping logging if vmodule is not enabled or level too low
|
||||
cfg := h.config.Load()
|
||||
return len(cfg.patterns) > 0 || cfg.level <= lvl
|
||||
}
|
||||
|
||||
// WithGroup implements slog.Handler, returning a new Handler with the given
|
||||
|
|
@ -178,37 +160,70 @@ func (h *GlogHandler) WithGroup(name string) slog.Handler {
|
|||
|
||||
// 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 slog.Level(h.level.Load()) <= r.Level {
|
||||
return h.origin.Handle(context.Background(), r)
|
||||
}
|
||||
func (h *GlogHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||
return h.handle(ctx, r, h.origin)
|
||||
}
|
||||
|
||||
// Check callsite cache for previously calculated log levels
|
||||
h.lock.RLock()
|
||||
lvl, ok := h.siteCache[r.PC]
|
||||
h.lock.RUnlock()
|
||||
|
||||
// If we didn't cache the callsite yet, calculate it
|
||||
if !ok {
|
||||
h.lock.Lock()
|
||||
func (h *GlogHandler) handle(ctx context.Context, r slog.Record, origin slog.Handler) error {
|
||||
cfg := h.config.Load()
|
||||
|
||||
var lvl slog.Level
|
||||
cachedLvl, ok := cfg.cache.Load(r.PC)
|
||||
if ok {
|
||||
// Fast path: cache hit
|
||||
lvl = cachedLvl.(slog.Level)
|
||||
} else {
|
||||
// Resolve the callsite file.
|
||||
fs := runtime.CallersFrames([]uintptr{r.PC})
|
||||
frame, _ := fs.Next()
|
||||
|
||||
for _, rule := range h.patterns {
|
||||
if rule.pattern.MatchString(fmt.Sprintf("+%s", frame.File)) {
|
||||
h.siteCache[r.PC], lvl, ok = rule.level, rule.level, true
|
||||
file := frame.File
|
||||
// Match against patterns and cache the level applied at this callsite.
|
||||
lvl = cfg.level // default: use global level
|
||||
for _, rule := range cfg.patterns {
|
||||
if rule.pattern.MatchString("+" + file) {
|
||||
lvl = rule.level
|
||||
}
|
||||
}
|
||||
// If no rule matched, remember to drop log the next time
|
||||
if !ok {
|
||||
h.siteCache[r.PC] = 0
|
||||
}
|
||||
h.lock.Unlock()
|
||||
cfg.cache.Store(r.PC, lvl)
|
||||
}
|
||||
|
||||
// Handle the message.
|
||||
if lvl <= r.Level {
|
||||
return h.origin.Handle(context.Background(), r)
|
||||
return origin.Handle(ctx, r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithAttrs implements slog.Handler, returning a new Handler whose attributes
|
||||
// consist of both the receiver's attributes and the arguments.
|
||||
//
|
||||
// Note the handler created here will still listen to Verbosity and Vmodule settings
|
||||
// done on the original handler.
|
||||
func (h *GlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
return &glogWithAttrs{base: h, origin: h.origin.WithAttrs(attrs)}
|
||||
}
|
||||
|
||||
type glogWithAttrs struct {
|
||||
base *GlogHandler
|
||||
origin slog.Handler
|
||||
}
|
||||
|
||||
func (wh *glogWithAttrs) Enabled(ctx context.Context, lvl slog.Level) bool {
|
||||
return wh.base.Enabled(ctx, lvl)
|
||||
}
|
||||
|
||||
func (wh *glogWithAttrs) Handle(ctx context.Context, r slog.Record) error {
|
||||
return wh.base.handle(ctx, r, wh.origin)
|
||||
}
|
||||
|
||||
func (wh *glogWithAttrs) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
return &glogWithAttrs{base: wh.base, origin: wh.origin.WithAttrs(attrs)}
|
||||
}
|
||||
|
||||
// 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 (wh *glogWithAttrs) WithGroup(name string) slog.Handler {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,64 @@ func TestLoggingWithVmodule(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestLoggingWithVmoduleDowngrade checks that vmodule can be downgraded.
|
||||
func TestLoggingWithVmoduleDowngrade(t *testing.T) {
|
||||
out := new(bytes.Buffer)
|
||||
glog := NewGlogHandler(NewTerminalHandlerWithLevel(out, LevelTrace, false))
|
||||
glog.Verbosity(LevelTrace) // Allow all logs globally
|
||||
logger := NewLogger(glog)
|
||||
|
||||
// This should appear (global level allows it)
|
||||
logger.Info("before vmodule downgrade, this should be logged")
|
||||
if !bytes.Contains(out.Bytes(), []byte("before vmodule downgrade")) {
|
||||
t.Fatal("expected 'before vmodule downgrade' to be logged")
|
||||
}
|
||||
out.Reset()
|
||||
|
||||
// Downgrade this file to only allow Warn and above
|
||||
glog.Vmodule("logger_test.go=2")
|
||||
|
||||
// Info should now be filtered out
|
||||
logger.Info("after vmodule downgrade, this should be filtered")
|
||||
if bytes.Contains(out.Bytes(), []byte("after vmodule downgrade, this should be filtered")) {
|
||||
t.Fatal("expected 'after vmodule downgrade, this should be filtered' to NOT be logged after vmodule downgrade")
|
||||
}
|
||||
|
||||
// Warn should still appear
|
||||
logger.Warn("after vmodule downgrade, this should be logged")
|
||||
if !bytes.Contains(out.Bytes(), []byte("after vmodule downgrade, this should be logged")) {
|
||||
t.Fatal("expected 'should appear' to be logged")
|
||||
}
|
||||
}
|
||||
|
||||
// TestWithAttrsVerbosityChange checks that verbosity changes affect child loggers.
|
||||
func TestWithAttrsVerbosityChange(t *testing.T) {
|
||||
out := new(bytes.Buffer)
|
||||
glog := NewGlogHandler(NewTerminalHandlerWithLevel(out, LevelTrace, false))
|
||||
glog.Verbosity(LevelInfo)
|
||||
|
||||
// Create a child logger with an extra attribute.
|
||||
child := slog.New(glog.WithAttrs([]slog.Attr{slog.String("peer", "foo")}))
|
||||
|
||||
// Debug should be filtered at Info level.
|
||||
child.Debug("this should be filtered")
|
||||
if bytes.Contains(out.Bytes(), []byte("this should be filtered")) {
|
||||
t.Fatal("expected debug message to be filtered at Info level")
|
||||
}
|
||||
|
||||
// Change verbosity on the parent to allow Debug.
|
||||
glog.Verbosity(LevelDebug)
|
||||
|
||||
// Child should pick up the new level and include its attributes.
|
||||
child.Debug("this should be logged")
|
||||
if !bytes.Contains(out.Bytes(), []byte("this should be logged")) {
|
||||
t.Fatal("expected child logger to pick up verbosity change")
|
||||
}
|
||||
if !bytes.Contains(out.Bytes(), []byte("peer=foo")) {
|
||||
t.Fatal("expected child logger to include WithAttrs attributes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerminalHandlerWithAttrs(t *testing.T) {
|
||||
out := new(bytes.Buffer)
|
||||
glog := NewGlogHandler(NewTerminalHandlerWithLevel(out, LevelTrace, false).WithAttrs([]slog.Attr{slog.String("baz", "bat")}))
|
||||
|
|
|
|||
Loading…
Reference in a new issue