mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
Delete log directory
Signed-off-by: Isabel Schöps Thiel @IsabelSchoepd <155141998+IST-Github@users.noreply.github.com>
This commit is contained in:
parent
dc1415b6b7
commit
1233054b48
7 changed files with 0 additions and 1292 deletions
369
log/format.go
369
log/format.go
|
|
@ -1,369 +0,0 @@
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
timeFormat = "2006-01-02T15:04:05-0700"
|
|
||||||
floatFormat = 'f'
|
|
||||||
termMsgJust = 40
|
|
||||||
termCtxMaxPadding = 40
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// screen.
|
|
||||||
type TerminalStringer interface {
|
|
||||||
TerminalString() string
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
return b.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *TerminalHandler) formatAttributes(buf *bytes.Buffer, r slog.Record, color string) {
|
|
||||||
// tmp is a temporary buffer we use, until bytes.Buffer.AvailableBuffer() (1.21)
|
|
||||||
// can be used.
|
|
||||||
var tmp = make([]byte, 40)
|
|
||||||
writeAttr := func(attr slog.Attr, first, last bool) {
|
|
||||||
buf.WriteByte(' ')
|
|
||||||
|
|
||||||
if color != "" {
|
|
||||||
buf.WriteString(color)
|
|
||||||
//buf.Write(appendEscapeString(buf.AvailableBuffer(), attr.Key))
|
|
||||||
buf.Write(appendEscapeString(tmp[:0], attr.Key))
|
|
||||||
buf.WriteString("\x1b[0m=")
|
|
||||||
} else {
|
|
||||||
//buf.Write(appendEscapeString(buf.AvailableBuffer(), attr.Key))
|
|
||||||
buf.Write(appendEscapeString(tmp[:0], attr.Key))
|
|
||||||
buf.WriteByte('=')
|
|
||||||
}
|
|
||||||
//val := FormatSlogValue(attr.Value, true, buf.AvailableBuffer())
|
|
||||||
val := FormatSlogValue(attr.Value, tmp[:0])
|
|
||||||
|
|
||||||
padding := h.fieldPadding[attr.Key]
|
|
||||||
|
|
||||||
length := utf8.RuneCount(val)
|
|
||||||
if padding < length && length <= termCtxMaxPadding {
|
|
||||||
padding = length
|
|
||||||
h.fieldPadding[attr.Key] = padding
|
|
||||||
}
|
|
||||||
buf.Write(val)
|
|
||||||
if !last && padding > length {
|
|
||||||
buf.Write(spaces[:padding-length])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var n = 0
|
|
||||||
var nAttrs = len(h.attrs) + r.NumAttrs()
|
|
||||||
for _, attr := range h.attrs {
|
|
||||||
writeAttr(attr, n == 0, n == nAttrs-1)
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
r.Attrs(func(attr slog.Attr) bool {
|
|
||||||
writeAttr(attr, n == 0, n == nAttrs-1)
|
|
||||||
n++
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
buf.WriteByte('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = []byte("<nil>")
|
|
||||||
} 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("<nil>")
|
|
||||||
}
|
|
||||||
switch v := value.(type) {
|
|
||||||
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 appendEscapeString(tmp, v.Error())
|
|
||||||
case TerminalStringer:
|
|
||||||
return appendEscapeString(tmp, v.TerminalString())
|
|
||||||
case fmt.Stringer:
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 n.IsInt64() {
|
|
||||||
return appendInt64(dst, n.Int64())
|
|
||||||
}
|
|
||||||
|
|
||||||
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:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 it contains spaces or equal-sign, we need to quote it.
|
|
||||||
if r == ' ' || r == '=' {
|
|
||||||
needsQuoting = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 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 needsEscaping {
|
|
||||||
return strconv.AppendQuote(dst, s)
|
|
||||||
}
|
|
||||||
// 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 {
|
|
||||||
// Allow CR/LF/TAB. This is to make multi-line messages work.
|
|
||||||
if r == '\r' || r == '\n' || r == '\t' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// We quote everything below <space> (0x20) and above~ (0x7E),
|
|
||||||
// plus equal-sign
|
|
||||||
if r < ' ' || r > '~' || r == '=' {
|
|
||||||
needsQuoting = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 golang.org/x/exp/slog/internal/buffer/buffer.go
|
|
||||||
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
|
|
||||||
}
|
|
||||||
// i < 10
|
|
||||||
bb[bp] = byte('0' + i)
|
|
||||||
b.Write(bb[bp:])
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
192
log/handler.go
192
log/handler.go
|
|
@ -1,192 +0,0 @@
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
type discardHandler struct{}
|
|
||||||
|
|
||||||
// DiscardHandler returns a no-op handler
|
|
||||||
func DiscardHandler() slog.Handler {
|
|
||||||
return &discardHandler{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *discardHandler) Handle(_ context.Context, r slog.Record) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *discardHandler) Enabled(_ context.Context, level slog.Level) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *discardHandler) WithGroup(name string) slog.Handler {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *discardHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
||||||
return &discardHandler{}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
buf []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *TerminalHandler) Enabled(_ context.Context, level slog.Level) bool {
|
|
||||||
return level >= h.lvl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *TerminalHandler) WithGroup(name string) slog.Handler {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetFieldPadding zeroes the field-padding for all attribute pairs.
|
|
||||||
func (t *TerminalHandler) ResetFieldPadding() {
|
|
||||||
t.mu.Lock()
|
|
||||||
t.fieldPadding = make(map[string]int)
|
|
||||||
t.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
type leveler struct{ minLevel slog.Level }
|
|
||||||
|
|
||||||
func (l *leveler) Level() slog.Level {
|
|
||||||
return l.minLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONHandler returns a handler which prints records in JSON format.
|
|
||||||
func JSONHandler(wr io.Writer) slog.Handler {
|
|
||||||
return slog.NewJSONHandler(wr, &slog.HandlerOptions{
|
|
||||||
ReplaceAttr: builtinReplaceJSON,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogfmtHandler returns a handler which 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 LogfmtHandler(wr io.Writer) slog.Handler {
|
|
||||||
return slog.NewTextHandler(wr, &slog.HandlerOptions{
|
|
||||||
ReplaceAttr: builtinReplaceLogfmt,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func builtinReplaceLogfmt(_ []string, attr slog.Attr) slog.Attr {
|
|
||||||
return builtinReplace(nil, attr, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case slog.LevelKey:
|
|
||||||
if l, ok := attr.Value.Any().(slog.Level); ok {
|
|
||||||
attr = slog.Any("lvl", LevelString(l))
|
|
||||||
return attr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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("<nil>")
|
|
||||||
} else {
|
|
||||||
attr.Value = slog.StringValue(v.String())
|
|
||||||
}
|
|
||||||
case *uint256.Int:
|
|
||||||
if v == nil {
|
|
||||||
attr.Value = slog.StringValue("<nil>")
|
|
||||||
} 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("<nil>")
|
|
||||||
} else {
|
|
||||||
attr.Value = slog.StringValue(v.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return attr
|
|
||||||
}
|
|
||||||
|
|
@ -1,209 +0,0 @@
|
||||||
// Copyright 2017 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// errVmoduleSyntax is returned when a user vmodule pattern is invalid.
|
|
||||||
var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N")
|
|
||||||
|
|
||||||
// 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 slog.Handler // The origin handler this wraps
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pattern contains a filter for the Vmodule option, holding a verbosity level
|
|
||||||
// and a file pattern to match.
|
|
||||||
type pattern struct {
|
|
||||||
pattern *regexp.Regexp
|
|
||||||
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 slog.Level) {
|
|
||||||
h.level.Store(int32(level))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vmodule sets the glog verbosity pattern.
|
|
||||||
//
|
|
||||||
// The syntax of the argument is a comma-separated list of pattern=N, where the
|
|
||||||
// pattern is a literal file name or "glob" pattern matching and N is a V level.
|
|
||||||
//
|
|
||||||
// For instance:
|
|
||||||
//
|
|
||||||
// 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 contains "foo"
|
|
||||||
func (h *GlogHandler) Vmodule(ruleset string) error {
|
|
||||||
var filter []pattern
|
|
||||||
for _, rule := range strings.Split(ruleset, ",") {
|
|
||||||
// Empty strings such as from a trailing comma can be ignored
|
|
||||||
if len(rule) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Ensure we have a pattern = level filter rule
|
|
||||||
parts := strings.Split(rule, "=")
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return errVmoduleSyntax
|
|
||||||
}
|
|
||||||
parts[0] = strings.TrimSpace(parts[0])
|
|
||||||
parts[1] = strings.TrimSpace(parts[1])
|
|
||||||
if len(parts[0]) == 0 || len(parts[1]) == 0 {
|
|
||||||
return errVmoduleSyntax
|
|
||||||
}
|
|
||||||
// Parse the level and if correct, assemble the filter rule
|
|
||||||
l, err := strconv.Atoi(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return errVmoduleSyntax
|
|
||||||
}
|
|
||||||
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
|
|
||||||
matcher := ".*"
|
|
||||||
for _, comp := range strings.Split(parts[0], "/") {
|
|
||||||
if comp == "*" {
|
|
||||||
matcher += "(/.*)?"
|
|
||||||
} else if comp != "" {
|
|
||||||
matcher += "/" + regexp.QuoteMeta(comp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(parts[0], ".go") {
|
|
||||||
matcher += "/[^/]+\\.go"
|
|
||||||
}
|
|
||||||
matcher = matcher + "$"
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *GlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
||||||
h.lock.RLock()
|
|
||||||
siteCache := make(map[uintptr]slog.Level)
|
|
||||||
for k, v := range h.siteCache {
|
|
||||||
siteCache[k] = v
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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) 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If no rule matched, remember to drop log the next time
|
|
||||||
if !ok {
|
|
||||||
h.siteCache[r.PC] = 0
|
|
||||||
}
|
|
||||||
h.lock.Unlock()
|
|
||||||
}
|
|
||||||
if lvl <= r.Level {
|
|
||||||
return h.origin.Handle(context.Background(), r)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
210
log/logger.go
210
log/logger.go
|
|
@ -1,210 +0,0 @@
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
const errorKey = "LOG_ERROR"
|
|
||||||
|
|
||||||
const (
|
|
||||||
legacyLevelCrit = iota
|
|
||||||
legacyLevelError
|
|
||||||
legacyLevelWarn
|
|
||||||
legacyLevelInfo
|
|
||||||
legacyLevelDebug
|
|
||||||
legacyLevelTrace
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
// convert 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 LevelTrace:
|
|
||||||
return "TRACE"
|
|
||||||
case slog.LevelDebug:
|
|
||||||
return "DEBUG"
|
|
||||||
case slog.LevelInfo:
|
|
||||||
return "INFO "
|
|
||||||
case slog.LevelWarn:
|
|
||||||
return "WARN "
|
|
||||||
case slog.LevelError:
|
|
||||||
return "ERROR"
|
|
||||||
case LevelCrit:
|
|
||||||
return "CRIT "
|
|
||||||
default:
|
|
||||||
return "unknown level"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LevelString returns a 5-character string containing the name of a Lvl.
|
|
||||||
func LevelString(l slog.Level) string {
|
|
||||||
switch l {
|
|
||||||
case LevelTrace:
|
|
||||||
return "trace"
|
|
||||||
case slog.LevelDebug:
|
|
||||||
return "debug"
|
|
||||||
case slog.LevelInfo:
|
|
||||||
return "info"
|
|
||||||
case slog.LevelWarn:
|
|
||||||
return "warn"
|
|
||||||
case slog.LevelError:
|
|
||||||
return "eror"
|
|
||||||
case LevelCrit:
|
|
||||||
return "crit"
|
|
||||||
default:
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Logger writes key/value pairs to a Handler
|
|
||||||
type Logger interface {
|
|
||||||
// With returns a new Logger that has this logger's attributes plus the given attributes
|
|
||||||
With(ctx ...interface{}) Logger
|
|
||||||
|
|
||||||
// With returns a new Logger that has this logger's attributes plus the given attributes. Identical to 'With'.
|
|
||||||
New(ctx ...interface{}) Logger
|
|
||||||
|
|
||||||
// Log logs a message at the specified level with context key/value pairs
|
|
||||||
Log(level slog.Level, msg string, ctx ...interface{})
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
type logger struct {
|
|
||||||
inner *slog.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLogger returns a logger with the specified handler set
|
|
||||||
func NewLogger(h slog.Handler) Logger {
|
|
||||||
return &logger{
|
|
||||||
slog.New(h),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
var pcs [1]uintptr
|
|
||||||
runtime.Callers(3, pcs[:])
|
|
||||||
|
|
||||||
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) Log(level slog.Level, msg string, attrs ...any) {
|
|
||||||
l.Write(level, msg, attrs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) With(ctx ...interface{}) Logger {
|
|
||||||
return &logger{l.inner.With(ctx...)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) New(ctx ...interface{}) Logger {
|
|
||||||
return l.With(ctx...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) Trace(msg string, ctx ...interface{}) {
|
|
||||||
l.Write(LevelTrace, msg, ctx...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Debug(msg string, ctx ...interface{}) {
|
|
||||||
l.Write(slog.LevelDebug, msg, ctx...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Info(msg string, ctx ...interface{}) {
|
|
||||||
l.Write(slog.LevelInfo, msg, ctx...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Warn(msg string, ctx ...any) {
|
|
||||||
l.Write(slog.LevelWarn, msg, ctx...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Error(msg string, ctx ...interface{}) {
|
|
||||||
l.Write(slog.LevelError, msg, ctx...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Crit(msg string, ctx ...interface{}) {
|
|
||||||
l.Write(LevelCrit, msg, ctx...)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 messag ..." -> "a messag..."
|
|
||||||
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 messag ..." -> "a messag..."
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkTraceLogging(b *testing.B) {
|
|
||||||
SetDefault(NewLogger(NewTerminalHandler(os.Stderr, 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 = fmt.Errorf("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 = fmt.Errorf("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=<nil> 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
116
log/root.go
116
log/root.go
|
|
@ -1,116 +0,0 @@
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
var root atomic.Value
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
root.Store(&logger{slog.New(DiscardHandler())})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.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
|
|
||||||
// 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(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(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(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(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(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(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...)
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue