
* *: use loglevel to control what gets logged instead of output redirection This stops logrus from doing all the formatting just to discard it immediately afterwards. * logflags: replace default formatter of logrus The default formatter of logrus emits logs in two different formats depending on whether or not the output is going to a terminal. The output format for non-terminals is indented to be machine readable, but we mostly read logs ourselves and the excessive quoting makes that format unreadable. When outputting to terminals it uses ANSI escape codes unconditionally, without checking whether the terminal it is connected to actually supports colors. This commit replaces the default formatter with a much simpler formatter that always uses a more readable format, doesn't use colors and places the key-value pairs at the beginning of the line (which is a better match for how we use them). * cmd/dlv: add command line options to redirect logs Adds two options, --log-to-file and --log-to-fd, to redirect logs to a file or to a file descriptor. When one of those two options is specified the "API server listening at:" message will also be redirected to the specified file/file descriptor. This allows clients that want to use the "API server listening at:" message to do so even if they want to redirect the target's stdout to another file or device. Implements #1179, #1523
228 lines
5.1 KiB
Go
228 lines
5.1 KiB
Go
package logflags
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
var debugger = false
|
|
var gdbWire = false
|
|
var lldbServerOutput = false
|
|
var debugLineErrors = false
|
|
var rpc = false
|
|
var fnCall = false
|
|
var minidump = false
|
|
|
|
var logOut io.WriteCloser
|
|
|
|
func makeLogger(flag bool, fields logrus.Fields) *logrus.Entry {
|
|
logger := logrus.New().WithFields(fields)
|
|
logger.Logger.Formatter = &textFormatter{}
|
|
if logOut != nil {
|
|
logger.Logger.Out = logOut
|
|
}
|
|
logger.Logger.Level = logrus.DebugLevel
|
|
if !flag {
|
|
logger.Logger.Level = logrus.PanicLevel
|
|
}
|
|
return logger
|
|
}
|
|
|
|
// GdbWire returns true if the gdbserial package should log all the packets
|
|
// exchanged with the stub.
|
|
func GdbWire() bool {
|
|
return gdbWire
|
|
}
|
|
|
|
// GdbWireLogger returns a configured logger for the gdbserial wire protocol.
|
|
func GdbWireLogger() *logrus.Entry {
|
|
return makeLogger(gdbWire, logrus.Fields{"layer": "gdbconn"})
|
|
}
|
|
|
|
// Debugger returns true if the debugger package should log.
|
|
func Debugger() bool {
|
|
return debugger
|
|
}
|
|
|
|
// DebuggerLogger returns a logger for the debugger package.
|
|
func DebuggerLogger() *logrus.Entry {
|
|
return makeLogger(debugger, logrus.Fields{"layer": "debugger"})
|
|
}
|
|
|
|
// LLDBServerOutput returns true if the output of the LLDB server should be
|
|
// redirected to standard output instead of suppressed.
|
|
func LLDBServerOutput() bool {
|
|
return lldbServerOutput
|
|
}
|
|
|
|
// DebugLineErrors returns true if pkg/dwarf/line should log its recoverable
|
|
// errors.
|
|
func DebugLineErrors() bool {
|
|
return debugLineErrors
|
|
}
|
|
|
|
// RPC returns true if RPC messages should be logged.
|
|
func RPC() bool {
|
|
return rpc
|
|
}
|
|
|
|
// RPCLogger returns a logger for RPC messages.
|
|
func RPCLogger() *logrus.Entry {
|
|
return makeLogger(rpc, logrus.Fields{"layer": "rpc"})
|
|
}
|
|
|
|
// FnCall returns true if the function call protocol should be logged.
|
|
func FnCall() bool {
|
|
return fnCall
|
|
}
|
|
|
|
func FnCallLogger() *logrus.Entry {
|
|
return makeLogger(fnCall, logrus.Fields{"layer": "proc", "kind": "fncall"})
|
|
}
|
|
|
|
// Minidump returns true if the minidump loader should be logged.
|
|
func Minidump() bool {
|
|
return minidump
|
|
}
|
|
|
|
func MinidumpLogger() *logrus.Entry {
|
|
return makeLogger(minidump, logrus.Fields{"layer": "core", "kind": "minidump"})
|
|
}
|
|
|
|
// WriteAPIListeningMessage writes the "API server listening" message in headless mode.
|
|
func WriteAPIListeningMessage(addr string) {
|
|
if logOut != nil {
|
|
fmt.Fprintf(logOut, "API server listening at: %s\n", addr)
|
|
} else {
|
|
fmt.Printf("API server listening at: %s\n", addr)
|
|
}
|
|
}
|
|
|
|
var errLogstrWithoutLog = errors.New("--log-output specified without --log")
|
|
|
|
// Setup sets debugger flags based on the contents of logstr.
|
|
// If logDest is not empty logs will be redirected to the file descriptor or
|
|
// file path specified by logDest.
|
|
func Setup(logFlag bool, logstr string, logDest string) error {
|
|
if logDest != "" {
|
|
n, err := strconv.Atoi(logDest)
|
|
if err == nil {
|
|
logOut = os.NewFile(uintptr(n), "delve-logs")
|
|
} else {
|
|
fh, err := os.Create(logDest)
|
|
if err != nil {
|
|
return fmt.Errorf("could not create log file: %v", err)
|
|
}
|
|
logOut = fh
|
|
}
|
|
}
|
|
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
|
if !logFlag {
|
|
log.SetOutput(ioutil.Discard)
|
|
if logstr != "" {
|
|
return errLogstrWithoutLog
|
|
}
|
|
return nil
|
|
}
|
|
if logstr == "" {
|
|
logstr = "debugger"
|
|
}
|
|
v := strings.Split(logstr, ",")
|
|
for _, logcmd := range v {
|
|
switch logcmd {
|
|
case "debugger":
|
|
debugger = true
|
|
case "gdbwire":
|
|
gdbWire = true
|
|
case "lldbout":
|
|
lldbServerOutput = true
|
|
case "debuglineerr":
|
|
debugLineErrors = true
|
|
case "rpc":
|
|
rpc = true
|
|
case "fncall":
|
|
fnCall = true
|
|
case "minidump":
|
|
minidump = true
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Close closes the logger output.
|
|
func Close() {
|
|
if logOut != nil {
|
|
logOut.Close()
|
|
}
|
|
}
|
|
|
|
// textFormatter is a simplified version of logrus.TextFormatter that
|
|
// doesn't make logs unreadable when they are output to a text file or to a
|
|
// terminal that doesn't support colors.
|
|
type textFormatter struct {
|
|
}
|
|
|
|
func (f *textFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
|
var b *bytes.Buffer
|
|
if entry.Buffer != nil {
|
|
b = entry.Buffer
|
|
} else {
|
|
b = &bytes.Buffer{}
|
|
}
|
|
|
|
keys := make([]string, 0, len(entry.Data))
|
|
for k := range entry.Data {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
b.WriteString(entry.Time.Format(time.RFC3339))
|
|
b.WriteByte(' ')
|
|
b.WriteString(entry.Level.String())
|
|
b.WriteByte(' ')
|
|
for i, key := range keys {
|
|
b.WriteString(key)
|
|
b.WriteByte('=')
|
|
stringVal, ok := entry.Data[key].(string)
|
|
if !ok {
|
|
stringVal = fmt.Sprint(entry.Data[key])
|
|
}
|
|
if f.needsQuoting(stringVal) {
|
|
fmt.Fprintf(b, "%q", stringVal)
|
|
} else {
|
|
b.WriteString(stringVal)
|
|
}
|
|
if i != len(keys)-1 {
|
|
b.WriteByte(',')
|
|
} else {
|
|
b.WriteByte(' ')
|
|
}
|
|
}
|
|
b.WriteString(entry.Message)
|
|
b.WriteByte('\n')
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func (f *textFormatter) needsQuoting(text string) bool {
|
|
for _, ch := range text {
|
|
if !((ch >= 'a' && ch <= 'z') ||
|
|
(ch >= 'A' && ch <= 'Z') ||
|
|
(ch >= '0' && ch <= '9') ||
|
|
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|