terminal/logflags: Added SetLoggerFactory(LoggerFactory) (#3257)

* terminal/logflags: Added `SetLoggerFactory(LoggerFactory)`

This change will enable people who want to embed Delve into their applications to adjust the logging better to their needs.

* terminal/logflags: Added `SetLoggerFactory(LoggerFactory)`

Added changes from code review.

* terminal/logflags: Added `SetLoggerFactory(LoggerFactory)`

Reworked requested changes.

* terminal/logflags: Added `SetLoggerFactory(LoggerFactory)`

Reworked requested changes.
This commit is contained in:
Gregor Noczinski 2023-02-14 18:46:35 +01:00 committed by GitHub
parent 7c835342d3
commit 260229b979
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 243 additions and 42 deletions

@ -29,17 +29,20 @@ var minidump = false
var logOut io.WriteCloser var logOut io.WriteCloser
func makeLogger(flag bool, fields logrus.Fields) *logrus.Entry { func makeLogger(flag bool, fields Fields) Logger {
logger := logrus.New().WithFields(fields) if lf := loggerFactory; lf != nil {
logger.Logger.Formatter = &textFormatter{} return lf(flag, fields, logOut)
}
logger := logrus.New().WithFields(logrus.Fields(fields))
logger.Logger.Formatter = DefaultFormatter()
if logOut != nil { if logOut != nil {
logger.Logger.Out = logOut logger.Logger.Out = logOut
} }
logger.Logger.Level = logrus.DebugLevel logger.Logger.Level = logrus.ErrorLevel
if !flag { if flag {
logger.Logger.Level = logrus.ErrorLevel logger.Logger.Level = logrus.DebugLevel
} }
return logger return &logrusLogger{logger}
} }
// Any returns true if any logging is enabled. // Any returns true if any logging is enabled.
@ -54,8 +57,8 @@ func GdbWire() bool {
} }
// GdbWireLogger returns a configured logger for the gdbserial wire protocol. // GdbWireLogger returns a configured logger for the gdbserial wire protocol.
func GdbWireLogger() *logrus.Entry { func GdbWireLogger() Logger {
return makeLogger(gdbWire, logrus.Fields{"layer": "gdbconn"}) return makeLogger(gdbWire, Fields{"layer": "gdbconn"})
} }
// Debugger returns true if the debugger package should log. // Debugger returns true if the debugger package should log.
@ -64,8 +67,8 @@ func Debugger() bool {
} }
// DebuggerLogger returns a logger for the debugger package. // DebuggerLogger returns a logger for the debugger package.
func DebuggerLogger() *logrus.Entry { func DebuggerLogger() Logger {
return makeLogger(debugger, logrus.Fields{"layer": "debugger"}) return makeLogger(debugger, Fields{"layer": "debugger"})
} }
// LLDBServerOutput returns true if the output of the LLDB server should be // LLDBServerOutput returns true if the output of the LLDB server should be
@ -80,14 +83,24 @@ func DebugLineErrors() bool {
return debugLineErrors return debugLineErrors
} }
// DebugLineLogger returns a logger for the dwarf/line package.
func DebugLineLogger() Logger {
return makeLogger(debugLineErrors, Fields{"layer": "dwarf-line"})
}
// RPC returns true if RPC messages should be logged. // RPC returns true if RPC messages should be logged.
func RPC() bool { func RPC() bool {
return rpc return rpc
} }
// RPCLogger returns a logger for RPC messages. // RPCLogger returns a logger for RPC messages.
func RPCLogger() *logrus.Entry { func RPCLogger() Logger {
return makeLogger(rpc, logrus.Fields{"layer": "rpc"}) return rpcLogger(rpc)
}
// rpcLogger returns a logger for RPC messages set to a specific minimal log level.
func rpcLogger(flag bool) Logger {
return makeLogger(flag, Fields{"layer": "rpc"})
} }
// DAP returns true if dap package should log. // DAP returns true if dap package should log.
@ -96,8 +109,8 @@ func DAP() bool {
} }
// DAPLogger returns a logger for dap package. // DAPLogger returns a logger for dap package.
func DAPLogger() *logrus.Entry { func DAPLogger() Logger {
return makeLogger(dap, logrus.Fields{"layer": "dap"}) return makeLogger(dap, Fields{"layer": "dap"})
} }
// FnCall returns true if the function call protocol should be logged. // FnCall returns true if the function call protocol should be logged.
@ -105,8 +118,8 @@ func FnCall() bool {
return fnCall return fnCall
} }
func FnCallLogger() *logrus.Entry { func FnCallLogger() Logger {
return makeLogger(fnCall, logrus.Fields{"layer": "proc", "kind": "fncall"}) return makeLogger(fnCall, Fields{"layer": "proc", "kind": "fncall"})
} }
// Minidump returns true if the minidump loader should be logged. // Minidump returns true if the minidump loader should be logged.
@ -114,8 +127,8 @@ func Minidump() bool {
return minidump return minidump
} }
func MinidumpLogger() *logrus.Entry { func MinidumpLogger() Logger {
return makeLogger(minidump, logrus.Fields{"layer": "core", "kind": "minidump"}) return makeLogger(minidump, Fields{"layer": "core", "kind": "minidump"})
} }
// WriteDAPListeningMessage writes the "DAP server listening" message in dap mode. // WriteDAPListeningMessage writes the "DAP server listening" message in dap mode.
@ -139,8 +152,7 @@ func writeListeningMessage(server string, addr net.Addr) {
if tcpAddr == nil || tcpAddr.IP.IsLoopback() { if tcpAddr == nil || tcpAddr.IP.IsLoopback() {
return return
} }
logger := RPCLogger() logger := rpcLogger(true)
logger.Logger.Level = logrus.WarnLevel
logger.Warnln("Listening for remote connections (connections are not authenticated nor encrypted)") logger.Warnln("Listening for remote connections (connections are not authenticated nor encrypted)")
} }
@ -217,12 +229,17 @@ func Close() {
} }
} }
// textFormatter is a simplified version of logrus.TextFormatter that // DefaultFormatter provides a simplified version of logrus.TextFormatter that
// doesn't make logs unreadable when they are output to a text file or to a // doesn't make logs unreadable when they are output to a text file or to a
// terminal that doesn't support colors. // terminal that doesn't support colors.
type textFormatter struct { func DefaultFormatter() logrus.Formatter {
return textFormatterInstance
} }
type textFormatter struct{}
var textFormatterInstance = &textFormatter{}
func (f *textFormatter) Format(entry *logrus.Entry) ([]byte, error) { func (f *textFormatter) Format(entry *logrus.Entry) ([]byte, error) {
var b *bytes.Buffer var b *bytes.Buffer
if entry.Buffer != nil { if entry.Buffer != nil {

@ -0,0 +1,117 @@
package logflags
import (
"bytes"
"io"
"reflect"
"testing"
"github.com/sirupsen/logrus"
)
func TestMakeLogger_usingLoggerFactory(t *testing.T) {
if loggerFactory != nil {
t.Fatalf("expected loggerFactory to be nil; but was <%v>", loggerFactory)
}
defer func() {
loggerFactory = nil
}()
if logOut != nil {
t.Fatalf("expected logOut to be nil; but was <%v>", logOut)
}
logOut = &bufferWriter{}
defer func() {
logOut = nil
}()
expectedLogger := &logrusLogger{}
SetLoggerFactory(func(flag bool, fields Fields, out io.Writer) Logger {
if flag != true {
t.Fatalf("expected flag to be <%v>; but was <%v>", true, flag)
}
if len(fields) != 1 || fields["foo"] != "bar" {
t.Fatalf("expected fields to be {'foo':'bar'}; but was <%v>", fields)
}
if out != logOut {
t.Fatalf("expected out to be <%v>; but was <%v>", logOut, out)
}
return expectedLogger
})
actual := makeLogger(true, Fields{"foo": "bar"})
if actual != expectedLogger {
t.Fatalf("expected actual to <%v>; but was <%v>", expectedLogger, actual)
}
}
func TestMakeLogger_usingDefaultBehavior(t *testing.T) {
if loggerFactory != nil {
t.Fatalf("expected loggerFactory to be nil; but was <%v>", loggerFactory)
}
if logOut != nil {
t.Fatalf("expected logOut to be nil; but was <%v>", logOut)
}
logOut = &bufferWriter{}
defer func() {
logOut = nil
}()
actual := makeLogger(false, Fields{"foo": "bar"})
actualEntry, expectedType := actual.(*logrusLogger)
if !expectedType {
t.Fatalf("expected actual to be of type <%v>; but was <%v>", reflect.TypeOf((*logrus.Entry)(nil)), reflect.TypeOf(actualEntry))
}
if actualEntry.Entry.Logger.Level != logrus.ErrorLevel {
t.Fatalf("expected actualEntry.Entry.Logger.Level to be <%v>; but was <%v>", logrus.ErrorLevel, actualEntry.Logger.Level)
}
if actualEntry.Entry.Logger.Out != logOut {
t.Fatalf("expected actualEntry.Entry.Logger.Out to be <%v>; but was <%v>", logOut, actualEntry.Logger.Out)
}
if actualEntry.Entry.Logger.Formatter != textFormatterInstance {
t.Fatalf("expected actualEntry.Entry.Logger.Formatter to be <%v>; but was <%v>", textFormatterInstance, actualEntry.Logger.Formatter)
}
if len(actualEntry.Entry.Data) != 1 || actualEntry.Entry.Data["foo"] != "bar" {
t.Fatalf("expected actualEntry.Entry.Data to be {'foo':'bar'}; but was <%v>", actualEntry.Data)
}
}
func TestMakeLogger_usingDefaultBehaviorAndFlagged(t *testing.T) {
if loggerFactory != nil {
t.Fatalf("expected loggerFactory to be nil; but was <%v>", loggerFactory)
}
if logOut != nil {
t.Fatalf("expected logOut to be nil; but was <%v>", logOut)
}
logOut = &bufferWriter{}
defer func() {
logOut = nil
}()
actual := makeLogger(true, Fields{"foo": "bar"})
actualEntry, expectedType := actual.(*logrusLogger)
if !expectedType {
t.Fatalf("expected actual to be of type <%v>; but was <%v>", reflect.TypeOf((*logrus.Entry)(nil)), reflect.TypeOf(actualEntry))
}
if actualEntry.Entry.Logger.Level != logrus.DebugLevel {
t.Fatalf("expected actualEntry.Entry.Logger.Level to be <%v>; but was <%v>", logrus.DebugLevel, actualEntry.Logger.Level)
}
if actualEntry.Entry.Logger.Out != logOut {
t.Fatalf("expected actualEntry.Entry.Logger.Out to be <%v>; but was <%v>", logOut, actualEntry.Logger.Out)
}
if actualEntry.Entry.Logger.Formatter != textFormatterInstance {
t.Fatalf("expected actualEntry.Entry.Logger.Formatter to be <%v>; but was <%v>", textFormatterInstance, actualEntry.Logger.Formatter)
}
if len(actualEntry.Entry.Data) != 1 || actualEntry.Entry.Data["foo"] != "bar" {
t.Fatalf("expected actualEntry.Entry.Data to be {'foo':'bar'}; but was <%v>", actualEntry.Data)
}
}
type bufferWriter struct {
bytes.Buffer
}
func (bw bufferWriter) Close() error {
return nil
}

77
pkg/logflags/logger.go Normal file

@ -0,0 +1,77 @@
package logflags
import (
"github.com/sirupsen/logrus"
"io"
)
// Logger represents a generic interface for logging inside of
// Delve codebase.
type Logger interface {
// WithField returns a new Logger enriched with the given field.
WithField(key string, value interface{}) Logger
// WithFields returns a new Logger enriched with the given fields.
WithFields(fields Fields) Logger
// WithError returns a new Logger enriched with the given error.
WithError(err error) Logger
Debugf(format string, args ...interface{})
Infof(format string, args ...interface{})
Printf(format string, args ...interface{})
Warnf(format string, args ...interface{})
Warningf(format string, args ...interface{})
Errorf(format string, args ...interface{})
Fatalf(format string, args ...interface{})
Panicf(format string, args ...interface{})
Debug(args ...interface{})
Info(args ...interface{})
Print(args ...interface{})
Warn(args ...interface{})
Warning(args ...interface{})
Error(args ...interface{})
Fatal(args ...interface{})
Panic(args ...interface{})
Debugln(args ...interface{})
Infoln(args ...interface{})
Println(args ...interface{})
Warnln(args ...interface{})
Warningln(args ...interface{})
Errorln(args ...interface{})
Fatalln(args ...interface{})
Panicln(args ...interface{})
}
// LoggerFactory is used to create new Logger instances.
// SetLoggerFactory can be used to configure it.
//
// The given parameters fields and out can be both be nil.
type LoggerFactory func(flag bool, fields Fields, out io.Writer) Logger
var loggerFactory LoggerFactory
// SetLoggerFactory will ensure that every Logger created by this package, will be now created
// by the given LoggerFactory. Default behavior will be a logrus based Logger instance using DefaultFormatter.
func SetLoggerFactory(lf LoggerFactory) {
loggerFactory = lf
}
// Fields type wraps many fields for Logger
type Fields map[string]interface{}
type logrusLogger struct {
*logrus.Entry
}
func (l *logrusLogger) WithField(key string, value interface{}) Logger {
return &logrusLogger{l.Entry.WithField(key, value)}
}
func (l *logrusLogger) WithFields(fields Fields) Logger {
return &logrusLogger{l.Entry.WithFields(logrus.Fields(fields))}
}
func (l *logrusLogger) WithError(err error) Logger {
return &logrusLogger{l.Entry.WithError(err)}
}

@ -34,7 +34,6 @@ import (
"github.com/go-delve/delve/pkg/logflags" "github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/proc/debuginfod" "github.com/go-delve/delve/pkg/proc/debuginfod"
"github.com/hashicorp/golang-lru/simplelru" "github.com/hashicorp/golang-lru/simplelru"
"github.com/sirupsen/logrus"
) )
const ( const (
@ -111,7 +110,7 @@ type BinaryInfo struct {
// Go 1.17 register ABI is enabled. // Go 1.17 register ABI is enabled.
regabi bool regabi bool
logger *logrus.Entry logger logflags.Logger
} }
var ( var (
@ -944,7 +943,7 @@ func (image *Image) Close() error {
return err2 return err2
} }
func (image *Image) setLoadError(logger *logrus.Entry, fmtstr string, args ...interface{}) { func (image *Image) setLoadError(logger logflags.Logger, fmtstr string, args ...interface{}) {
image.loadErrMu.Lock() image.loadErrMu.Lock()
image.loadErr = fmt.Errorf(fmtstr, args...) image.loadErr = fmt.Errorf(fmtstr, args...)
image.loadErrMu.Unlock() image.loadErrMu.Unlock()
@ -1586,7 +1585,7 @@ func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync.
} }
} }
func getSymbol(image *Image, logger *logrus.Entry, exe *elf.File, name string) *elf.Symbol { func getSymbol(image *Image, logger logflags.Logger, exe *elf.File, name string) *elf.Symbol {
symbols, err := exe.Symbols() symbols, err := exe.Symbols()
if err != nil { if err != nil {
image.setLoadError(logger, "could not parse ELF symbols: %v", err) image.setLoadError(logger, "could not parse ELF symbols: %v", err)
@ -2069,11 +2068,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB
if hasLineInfo && lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) { if hasLineInfo && lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) {
var logfn func(string, ...interface{}) var logfn func(string, ...interface{})
if logflags.DebugLineErrors() { if logflags.DebugLineErrors() {
logger := logrus.New().WithFields(logrus.Fields{"layer": "dwarf-line"}) logfn = logflags.DebugLineLogger().Printf
logger.Logger.Level = logrus.DebugLevel
logfn = func(fmt string, args ...interface{}) {
logger.Printf(fmt, args)
}
} }
cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), image.debugLineStr, logfn, image.StaticBase, bi.GOOS == "windows", bi.Arch.PtrSize()) cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), image.debugLineStr, logfn, image.StaticBase, bi.GOOS == "windows", bi.Arch.PtrSize())
} }

@ -17,7 +17,6 @@ import (
"github.com/go-delve/delve/pkg/logflags" "github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
"github.com/sirupsen/logrus"
) )
type gdbConn struct { type gdbConn struct {
@ -49,7 +48,7 @@ type gdbConn struct {
useXcmd bool // forces writeMemory to use the 'X' command useXcmd bool // forces writeMemory to use the 'X' command
log *logrus.Entry log logflags.Logger
} }
var ErrTooManyAttempts = errors.New("too many transmit attempts") var ErrTooManyAttempts = errors.New("too many transmit attempts")

@ -43,8 +43,6 @@ import (
"github.com/go-delve/delve/service/debugger" "github.com/go-delve/delve/service/debugger"
"github.com/go-delve/delve/service/internal/sameuser" "github.com/go-delve/delve/service/internal/sameuser"
"github.com/google/go-dap" "github.com/google/go-dap"
"github.com/sirupsen/logrus"
) )
// Server implements a DAP server that can accept a single client for // Server implements a DAP server that can accept a single client for
@ -167,7 +165,7 @@ type Config struct {
*service.Config *service.Config
// log is used for structured logging. // log is used for structured logging.
log *logrus.Entry log logflags.Logger
// StopTriggered is closed when the server is Stop()-ed. // StopTriggered is closed when the server is Stop()-ed.
// Can be used to safeguard against duplicate shutdown sequences. // Can be used to safeguard against duplicate shutdown sequences.
StopTriggered chan struct{} StopTriggered chan struct{}

@ -31,7 +31,6 @@ import (
"github.com/go-delve/delve/pkg/proc/gdbserial" "github.com/go-delve/delve/pkg/proc/gdbserial"
"github.com/go-delve/delve/pkg/proc/native" "github.com/go-delve/delve/pkg/proc/native"
"github.com/go-delve/delve/service/api" "github.com/go-delve/delve/service/api"
"github.com/sirupsen/logrus"
) )
var ( var (
@ -70,7 +69,7 @@ type Debugger struct {
targetMutex sync.Mutex targetMutex sync.Mutex
target *proc.TargetGroup target *proc.TargetGroup
log *logrus.Entry log logflags.Logger
running bool running bool
runningMutex sync.Mutex runningMutex sync.Mutex

@ -25,7 +25,6 @@ import (
"github.com/go-delve/delve/service/internal/sameuser" "github.com/go-delve/delve/service/internal/sameuser"
"github.com/go-delve/delve/service/rpc1" "github.com/go-delve/delve/service/rpc1"
"github.com/go-delve/delve/service/rpc2" "github.com/go-delve/delve/service/rpc2"
"github.com/sirupsen/logrus"
) )
// ServerImpl implements a JSON-RPC server that can switch between two // ServerImpl implements a JSON-RPC server that can switch between two
@ -45,7 +44,7 @@ type ServerImpl struct {
s2 *rpc2.RPCServer s2 *rpc2.RPCServer
// maps of served methods, one for each supported API. // maps of served methods, one for each supported API.
methodMaps []map[string]*methodType methodMaps []map[string]*methodType
log *logrus.Entry log logflags.Logger
} }
type RPCCallback struct { type RPCCallback struct {
@ -216,7 +215,7 @@ func isExportedOrBuiltinType(t reflect.Type) bool {
// //
// func (rcvr ReceiverType) Method(in InputType, out *ReplyType) error // func (rcvr ReceiverType) Method(in InputType, out *ReplyType) error
// func (rcvr ReceiverType) Method(in InputType, cb service.RPCCallback) // func (rcvr ReceiverType) Method(in InputType, cb service.RPCCallback)
func suitableMethods(rcvr interface{}, methods map[string]*methodType, log *logrus.Entry) { func suitableMethods(rcvr interface{}, methods map[string]*methodType, log logflags.Logger) {
typ := reflect.TypeOf(rcvr) typ := reflect.TypeOf(rcvr)
rcvrv := reflect.ValueOf(rcvr) rcvrv := reflect.ValueOf(rcvr)
sname := reflect.Indirect(rcvrv).Type().Name() sname := reflect.Indirect(rcvrv).Type().Name()