Delete log directory

Signed-off-by: Isabel Schöps Thiel @IsabelSchoepd <155141998+IST-Github@users.noreply.github.com>
This commit is contained in:
Isabel Schöps Thiel @IsabelSchoepd 2024-01-04 04:30:59 +01:00 committed by GitHub
parent dc1415b6b7
commit 1233054b48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 0 additions and 1292 deletions

View file

@ -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:])
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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...)
}