*: Use structured logging

Implements structured logging via Logrus. This gives us a logger per
boundry that we care about, allowing for easier parsing of logs if users
have more than one log option enabled. Also, cleans up a lot of
conditionals in the code by simply silencing the logger at creation as
opposed to conditionally logging everywhere.
This commit is contained in:
Derek Parker 2018-06-14 11:12:11 -07:00 committed by Alessandro Arzilli
parent 774b5c7ce2
commit a208c89719
13 changed files with 123 additions and 153 deletions

@ -364,7 +364,7 @@ func traceCmd(cmd *cobra.Command, args []string) {
APIVersion: 2, APIVersion: 2,
WorkingDir: WorkingDir, WorkingDir: WorkingDir,
Backend: Backend, Backend: Backend,
}, logflags.RPC()) })
if err := server.Run(); err != nil { if err := server.Run(); err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
return 1 return 1
@ -510,7 +510,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
Foreground: Headless, Foreground: Headless,
DisconnectChan: disconnectChan, DisconnectChan: disconnectChan,
}, logflags.RPC()) })
default: default:
fmt.Printf("Unknown API version: %d\n", APIVersion) fmt.Printf("Unknown API version: %d\n", APIVersion)
return 1 return 1
@ -535,8 +535,6 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
var status int var status int
if Headless { if Headless {
// Print listener address
fmt.Printf("API server listening at: %s\n", listener.Addr())
ch := make(chan os.Signal, 1) ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT) signal.Notify(ch, syscall.SIGINT)
select { select {

@ -80,13 +80,13 @@ func TestBuild(t *testing.T) {
buildtestdir := filepath.Join(fixtures, "buildtest") buildtestdir := filepath.Join(fixtures, "buildtest")
cmd := exec.Command(dlvbin, "debug", "--headless=true", "--listen="+listenAddr, "--api-version=2", "--backend="+testBackend) cmd := exec.Command(dlvbin, "debug", "--headless=true", "--listen="+listenAddr, "--api-version=2", "--backend="+testBackend, "--log", "--log-output=debugger,rpc")
cmd.Dir = buildtestdir cmd.Dir = buildtestdir
stdout, err := cmd.StdoutPipe() stderr, err := cmd.StderrPipe()
assertNoError(err, t, "stdout pipe") assertNoError(err, t, "stderr pipe")
cmd.Start() cmd.Start()
scan := bufio.NewScanner(stdout) scan := bufio.NewScanner(stderr)
// wait for the debugger to start // wait for the debugger to start
scan.Scan() scan.Scan()
go func() { go func() {

@ -27,14 +27,13 @@ type DebugLineInfo struct {
Instructions []byte Instructions []byte
Lookup map[string]*FileEntry Lookup map[string]*FileEntry
Logf func(string, ...interface{})
// stateMachineCache[pc] is a state machine stopped at pc // stateMachineCache[pc] is a state machine stopped at pc
stateMachineCache map[uint64]*StateMachine stateMachineCache map[uint64]*StateMachine
// lastMachineCache[pc] is a state machine stopped at an address after pc // lastMachineCache[pc] is a state machine stopped at an address after pc
lastMachineCache map[uint64]*StateMachine lastMachineCache map[uint64]*StateMachine
// logSuppressedErrors enables logging of otherwise suppressed errors
logSuppressedErrors bool
} }
type FileEntry struct { type FileEntry struct {
@ -47,7 +46,7 @@ type FileEntry struct {
type DebugLines []*DebugLineInfo type DebugLines []*DebugLineInfo
// ParseAll parses all debug_line segments found in data // ParseAll parses all debug_line segments found in data
func ParseAll(data []byte) DebugLines { func ParseAll(data []byte, logfn func(string, ...interface{})) DebugLines {
var ( var (
lines = make(DebugLines, 0) lines = make(DebugLines, 0)
buf = bytes.NewBuffer(data) buf = bytes.NewBuffer(data)
@ -55,7 +54,7 @@ func ParseAll(data []byte) DebugLines {
// We have to parse multiple file name tables here. // We have to parse multiple file name tables here.
for buf.Len() > 0 { for buf.Len() > 0 {
lines = append(lines, Parse("", buf)) lines = append(lines, Parse("", buf, logfn))
} }
return lines return lines
@ -63,8 +62,9 @@ func ParseAll(data []byte) DebugLines {
// Parse parses a single debug_line segment from buf. Compdir is the // Parse parses a single debug_line segment from buf. Compdir is the
// DW_AT_comp_dir attribute of the associated compile unit. // DW_AT_comp_dir attribute of the associated compile unit.
func Parse(compdir string, buf *bytes.Buffer) *DebugLineInfo { func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{})) *DebugLineInfo {
dbl := new(DebugLineInfo) dbl := new(DebugLineInfo)
dbl.Logf = logfn
dbl.Lookup = make(map[string]*FileEntry) dbl.Lookup = make(map[string]*FileEntry)
if compdir != "" { if compdir != "" {
dbl.IncludeDirs = append(dbl.IncludeDirs, compdir) dbl.IncludeDirs = append(dbl.IncludeDirs, compdir)
@ -146,8 +146,3 @@ func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool)
return entry return entry
} }
// LogSuppressedErrors enables or disables logging of suppressed errors
func (dbl *DebugLineInfo) LogSuppressedErrors(v bool) {
dbl.logSuppressedErrors = v
}

@ -67,7 +67,7 @@ const (
func testDebugLinePrologueParser(p string, t *testing.T) { func testDebugLinePrologueParser(p string, t *testing.T) {
data := grabDebugLineSection(p, t) data := grabDebugLineSection(p, t)
debugLines := ParseAll(data) debugLines := ParseAll(data, nil)
mainFileFound := false mainFileFound := false
@ -164,7 +164,7 @@ func BenchmarkLineParser(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = ParseAll(data) _ = ParseAll(data, nil)
} }
} }
@ -179,7 +179,7 @@ func loadBenchmarkData(tb testing.TB) DebugLines {
tb.Fatal("Could not read test data", err) tb.Fatal("Could not read test data", err)
} }
return ParseAll(data) return ParseAll(data, nil)
} }
func BenchmarkStateMachine(b *testing.B) { func BenchmarkStateMachine(b *testing.B) {

@ -5,7 +5,6 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"log"
"github.com/derekparker/delve/pkg/dwarf/util" "github.com/derekparker/delve/pkg/dwarf/util"
) )
@ -125,8 +124,8 @@ func (lineInfo *DebugLineInfo) AllPCsForFileLine(f string, l int) (pcs []uint64)
for { for {
if err := sm.next(); err != nil { if err := sm.next(); err != nil {
if lineInfo.logSuppressedErrors { if lineInfo.Logf != nil {
log.Printf("AllPCsForFileLine error: %v", err) lineInfo.Logf("AllPCsForFileLine error: %v", err)
} }
break break
} }
@ -154,8 +153,8 @@ func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64, excludeFile stri
for { for {
if err := sm.next(); err != nil { if err := sm.next(); err != nil {
if lineInfo.logSuppressedErrors { if lineInfo.Logf != nil {
log.Printf("AllPCsBetween error: %v", err) lineInfo.Logf("AllPCsBetween error: %v", err)
} }
break break
} }
@ -228,8 +227,8 @@ func (lineInfo *DebugLineInfo) PCToLine(basePC, pc uint64) (string, int) {
func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) { func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) {
if !sm.started { if !sm.started {
if err := sm.next(); err != nil { if err := sm.next(); err != nil {
if sm.dbl.logSuppressedErrors { if sm.dbl.Logf != nil {
log.Printf("PCToLine error: %v", err) sm.dbl.Logf("PCToLine error: %v", err)
} }
return "", 0, false return "", 0, false
} }
@ -247,8 +246,8 @@ func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) {
} }
} }
if err := sm.next(); err != nil { if err := sm.next(); err != nil {
if sm.dbl.logSuppressedErrors { if sm.dbl.Logf != nil {
log.Printf("PCToLine error: %v", err) sm.dbl.Logf("PCToLine error: %v", err)
} }
break break
} }
@ -276,8 +275,8 @@ func (lineInfo *DebugLineInfo) LineToPC(filename string, lineno int) uint64 {
for { for {
if err := sm.next(); err != nil { if err := sm.next(); err != nil {
if lineInfo.logSuppressedErrors { if lineInfo.Logf != nil {
log.Printf("LineToPC error: %v", err) lineInfo.Logf("LineToPC error: %v", err)
} }
break break
} }
@ -311,8 +310,8 @@ func (lineInfo *DebugLineInfo) PrologueEndPC(start, end uint64) (pc uint64, file
} }
} }
if err := sm.next(); err != nil { if err := sm.next(); err != nil {
if lineInfo.logSuppressedErrors { if lineInfo.Logf != nil {
log.Printf("PrologueEnd error: %v", err) lineInfo.Logf("PrologueEnd error: %v", err)
} }
return 0, "", 0, false return 0, "", 0, false
} }

@ -68,6 +68,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"go/ast" "go/ast"
"io/ioutil"
"net" "net"
"os" "os"
"os/exec" "os/exec"
@ -81,6 +82,7 @@ import (
"github.com/derekparker/delve/pkg/logflags" "github.com/derekparker/delve/pkg/logflags"
"github.com/derekparker/delve/pkg/proc" "github.com/derekparker/delve/pkg/proc"
"github.com/sirupsen/logrus"
) )
const ( const (
@ -166,11 +168,17 @@ type gdbRegister struct {
// Detach. // Detach.
// Use Listen, Dial or Connect to complete connection. // Use Listen, Dial or Connect to complete connection.
func New(process *os.Process) *Process { func New(process *os.Process) *Process {
logger := logrus.New().WithFields(logrus.Fields{"layer": "gdbconn"})
logger.Level = logrus.DebugLevel
if !logflags.GdbWire() {
logger.Logger.Out = ioutil.Discard
}
p := &Process{ p := &Process{
conn: gdbConn{ conn: gdbConn{
maxTransmitAttempts: maxTransmitAttempts, maxTransmitAttempts: maxTransmitAttempts,
inbuf: make([]byte, 0, initialInputBufferSize), inbuf: make([]byte, 0, initialInputBufferSize),
direction: proc.Forward, direction: proc.Forward,
log: logger,
}, },
threads: make(map[int]*Thread), threads: make(map[int]*Thread),
bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH), bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH),

@ -18,6 +18,7 @@ import (
"github.com/derekparker/delve/pkg/logflags" "github.com/derekparker/delve/pkg/logflags"
"github.com/derekparker/delve/pkg/proc" "github.com/derekparker/delve/pkg/proc"
"github.com/sirupsen/logrus"
) )
type gdbConn struct { type gdbConn struct {
@ -43,6 +44,8 @@ type gdbConn struct {
maxTransmitAttempts int // maximum number of transmit or receive attempts when bad checksums are read maxTransmitAttempts int // maximum number of transmit or receive attempts when bad checksums are read
threadSuffixSupported bool // thread suffix supported by stub threadSuffixSupported bool // thread suffix supported by stub
isDebugserver bool // true if the stub is debugserver isDebugserver bool // true if the stub is debugserver
log *logrus.Entry
} }
const ( const (
@ -634,7 +637,7 @@ func (conn *gdbConn) parseStopPacket(resp []byte, threadID string, tu *threadUpd
sp.sig = uint8(sig) sp.sig = uint8(sig)
if logflags.GdbWire() && gdbWireFullStopPacket { if logflags.GdbWire() && gdbWireFullStopPacket {
fmt.Fprintf(os.Stderr, "full stop packet: %s\n", string(resp)) conn.log.Debug("full stop packet: %s\n", string(resp))
} }
buf := resp[3:] buf := resp[3:]
@ -705,9 +708,7 @@ const ctrlC = 0x03 // the ASCII character for ^C
// executes a ctrl-C on the line // executes a ctrl-C on the line
func (conn *gdbConn) sendCtrlC() error { func (conn *gdbConn) sendCtrlC() error {
if logflags.GdbWire() { conn.log.Debug("<- interrupt")
fmt.Println("<- interrupt")
}
_, err := conn.conn.Write([]byte{ctrlC}) _, err := conn.conn.Write([]byte{ctrlC})
return err return err
} }
@ -994,9 +995,9 @@ func (conn *gdbConn) send(cmd []byte) error {
for { for {
if logflags.GdbWire() { if logflags.GdbWire() {
if len(cmd) > gdbWireMaxLen { if len(cmd) > gdbWireMaxLen {
fmt.Printf("<- %s...\n", string(cmd[:gdbWireMaxLen])) conn.log.Debugf("<- %s...\n", string(cmd[:gdbWireMaxLen]))
} else { } else {
fmt.Printf("<- %s\n", string(cmd)) conn.log.Debugf("<- %s\n", string(cmd))
} }
} }
_, err := conn.conn.Write(cmd) _, err := conn.conn.Write(cmd)
@ -1045,9 +1046,9 @@ func (conn *gdbConn) recv(cmd []byte, context string, binary bool) (resp []byte,
partial = true partial = true
} }
if !partial { if !partial {
fmt.Printf("-> %s%s\n", string(resp), string(conn.inbuf[:2])) conn.log.Debugf("-> %s%s\n", string(resp), string(conn.inbuf[:2]))
} else { } else {
fmt.Printf("-> %s...\n", string(out)) conn.log.Debugf("-> %s...\n", string(out))
} }
} }
@ -1098,9 +1099,7 @@ func (conn *gdbConn) readack() bool {
if err != nil { if err != nil {
return false return false
} }
if logflags.GdbWire() { conn.log.Debugf("-> %s\n", string(b))
fmt.Printf("-> %s\n", string(b))
}
return b == '+' return b == '+'
} }
@ -1110,9 +1109,7 @@ func (conn *gdbConn) sendack(c byte) {
panic(fmt.Errorf("sendack(%c)", c)) panic(fmt.Errorf("sendack(%c)", c))
} }
conn.conn.Write([]byte{c}) conn.conn.Write([]byte{c})
if logflags.GdbWire() { conn.log.Debugf("<- %s\n", string(c))
fmt.Printf("<- %s\n", string(c))
}
} }
// escapeXor is the value mandated by the specification to escape characters // escapeXor is the value mandated by the specification to escape characters

@ -24,6 +24,7 @@ import (
"github.com/derekparker/delve/pkg/dwarf/reader" "github.com/derekparker/delve/pkg/dwarf/reader"
"github.com/derekparker/delve/pkg/goversion" "github.com/derekparker/delve/pkg/goversion"
"github.com/derekparker/delve/pkg/logflags" "github.com/derekparker/delve/pkg/logflags"
"github.com/sirupsen/logrus"
) )
// The kind field in runtime._type is a reflect.Kind value plus // The kind field in runtime._type is a reflect.Kind value plus
@ -220,8 +221,15 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou
} }
lineInfoOffset, _ := entry.Val(dwarf.AttrStmtList).(int64) lineInfoOffset, _ := entry.Val(dwarf.AttrStmtList).(int64)
if lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) { if lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) {
cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:])) var logfn func(string, ...interface{})
cu.lineInfo.LogSuppressedErrors(logflags.DebugLineErrors()) if logflags.DebugLineErrors() {
logger := logrus.New().WithFields(logrus.Fields{"layer": "dwarf-line"})
logger.Level = logrus.DebugLevel
logfn = func(fmt string, args ...interface{}) {
logger.Printf(fmt, args)
}
}
cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn)
} }
cu.producer, _ = entry.Val(dwarf.AttrProducer).(string) cu.producer, _ = entry.Val(dwarf.AttrProducer).(string)
if cu.isgo && cu.producer != "" { if cu.isgo && cu.producer != "" {

@ -113,7 +113,7 @@ func withTestTerminalBuildFlags(name string, t testing.TB, buildFlags test.Build
Listener: listener, Listener: listener,
ProcessArgs: []string{test.BuildFixture(name, buildFlags).Path}, ProcessArgs: []string{test.BuildFixture(name, buildFlags).Path},
Backend: testBackend, Backend: testBackend,
}, false) })
if err := server.Run(); err != nil { if err := server.Run(); err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -4,7 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"go/parser" "go/parser"
"log" "io/ioutil"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
@ -18,6 +18,7 @@ import (
"github.com/derekparker/delve/pkg/proc/gdbserial" "github.com/derekparker/delve/pkg/proc/gdbserial"
"github.com/derekparker/delve/pkg/proc/native" "github.com/derekparker/delve/pkg/proc/native"
"github.com/derekparker/delve/service/api" "github.com/derekparker/delve/service/api"
"github.com/sirupsen/logrus"
) )
// Debugger service. // Debugger service.
@ -35,6 +36,7 @@ type Debugger struct {
// TODO(DO NOT MERGE WITHOUT) rename to targetMutex // TODO(DO NOT MERGE WITHOUT) rename to targetMutex
processMutex sync.Mutex processMutex sync.Mutex
target proc.Process target proc.Process
log *logrus.Entry
} }
// Config provides the configuration to start a Debugger. // Config provides the configuration to start a Debugger.
@ -63,17 +65,21 @@ type Config struct {
// New creates a new Debugger. ProcessArgs specify the commandline arguments for the // New creates a new Debugger. ProcessArgs specify the commandline arguments for the
// new process. // new process.
func New(config *Config, processArgs []string) (*Debugger, error) { func New(config *Config, processArgs []string) (*Debugger, error) {
logger := logrus.New().WithFields(logrus.Fields{"layer": "debugger"})
logger.Level = logrus.DebugLevel
if !logflags.Debugger() {
logger.Logger.Out = ioutil.Discard
}
d := &Debugger{ d := &Debugger{
config: config, config: config,
processArgs: processArgs, processArgs: processArgs,
log: logger,
} }
// Create the process by either attaching or launching. // Create the process by either attaching or launching.
switch { switch {
case d.config.AttachPid > 0: case d.config.AttachPid > 0:
if logflags.Debugger() { d.log.Infof("attaching to pid %d", d.config.AttachPid)
log.Printf("attaching to pid %d", d.config.AttachPid)
}
path := "" path := ""
if len(d.processArgs) > 0 { if len(d.processArgs) > 0 {
path = d.processArgs[0] path = d.processArgs[0]
@ -89,14 +95,10 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
var err error var err error
switch d.config.Backend { switch d.config.Backend {
case "rr": case "rr":
if logflags.Debugger() { d.log.Infof("opening trace %s", d.config.CoreFile)
log.Printf("opening trace %s", d.config.CoreFile)
}
p, err = gdbserial.Replay(d.config.CoreFile, false) p, err = gdbserial.Replay(d.config.CoreFile, false)
default: default:
if logflags.Debugger() { d.log.Infof("opening core file %s (executable %s)", d.config.CoreFile, d.processArgs[0])
log.Printf("opening core file %s (executable %s)", d.config.CoreFile, d.processArgs[0])
}
p, err = core.OpenCore(d.config.CoreFile, d.processArgs[0]) p, err = core.OpenCore(d.config.CoreFile, d.processArgs[0])
} }
if err != nil { if err != nil {
@ -105,9 +107,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
d.target = p d.target = p
default: default:
if logflags.Debugger() { d.log.Infof("launching process with args: %v", d.processArgs)
log.Printf("launching process with args: %v", d.processArgs)
}
p, err := d.Launch(d.processArgs, d.config.WorkingDir) p, err := d.Launch(d.processArgs, d.config.WorkingDir)
if err != nil { if err != nil {
if err != proc.NotExecutableErr && err != proc.UnsupportedLinuxArchErr && err != proc.UnsupportedWindowsArchErr && err != proc.UnsupportedDarwinArchErr { if err != proc.NotExecutableErr && err != proc.UnsupportedLinuxArchErr && err != proc.UnsupportedWindowsArchErr && err != proc.UnsupportedDarwinArchErr {
@ -367,9 +367,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
return nil, err return nil, err
} }
createdBp = api.ConvertBreakpoint(bp) createdBp = api.ConvertBreakpoint(bp)
if logflags.Debugger() { d.log.Infof("created breakpoint: %#v", createdBp)
log.Printf("created breakpoint: %#v", createdBp)
}
return createdBp, nil return createdBp, nil
} }
@ -417,9 +415,7 @@ func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint
return nil, fmt.Errorf("Can't clear breakpoint @%x: %s", requestedBp.Addr, err) return nil, fmt.Errorf("Can't clear breakpoint @%x: %s", requestedBp.Addr, err)
} }
clearedBp = api.ConvertBreakpoint(bp) clearedBp = api.ConvertBreakpoint(bp)
if logflags.Debugger() { d.log.Infof("cleared breakpoint: %#v", clearedBp)
log.Printf("cleared breakpoint: %#v", clearedBp)
}
return clearedBp, err return clearedBp, err
} }
@ -517,9 +513,7 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
if command.Name == api.Halt { if command.Name == api.Halt {
// RequestManualStop does not invoke any ptrace syscalls, so it's safe to // RequestManualStop does not invoke any ptrace syscalls, so it's safe to
// access the process directly. // access the process directly.
if logflags.Debugger() { d.log.Debug("halting")
log.Print("halting")
}
err = d.target.RequestManualStop() err = d.target.RequestManualStop()
} }
@ -530,14 +524,10 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
switch command.Name { switch command.Name {
case api.Continue: case api.Continue:
if logflags.Debugger() { d.log.Debug("continuing")
log.Print("continuing")
}
err = proc.Continue(d.target) err = proc.Continue(d.target)
case api.Rewind: case api.Rewind:
if logflags.Debugger() { d.log.Debug("rewinding")
log.Print("rewinding")
}
if err := d.target.Direction(proc.Backward); err != nil { if err := d.target.Direction(proc.Backward); err != nil {
return nil, err return nil, err
} }
@ -546,35 +536,23 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
}() }()
err = proc.Continue(d.target) err = proc.Continue(d.target)
case api.Next: case api.Next:
if logflags.Debugger() { d.log.Debug("nexting")
log.Print("nexting")
}
err = proc.Next(d.target) err = proc.Next(d.target)
case api.Step: case api.Step:
if logflags.Debugger() { d.log.Debug("stepping")
log.Print("stepping")
}
err = proc.Step(d.target) err = proc.Step(d.target)
case api.StepInstruction: case api.StepInstruction:
if logflags.Debugger() { d.log.Debug("single stepping")
log.Print("single stepping")
}
err = d.target.StepInstruction() err = d.target.StepInstruction()
case api.StepOut: case api.StepOut:
if logflags.Debugger() { d.log.Debug("step out")
log.Print("step out")
}
err = proc.StepOut(d.target) err = proc.StepOut(d.target)
case api.SwitchThread: case api.SwitchThread:
if logflags.Debugger() { d.log.Debugf("switching to thread %d", command.ThreadID)
log.Printf("switching to thread %d", command.ThreadID)
}
err = d.target.SwitchThread(command.ThreadID) err = d.target.SwitchThread(command.ThreadID)
withBreakpointInfo = false withBreakpointInfo = false
case api.SwitchGoroutine: case api.SwitchGoroutine:
if logflags.Debugger() { d.log.Debugf("switching to goroutine %d", command.GoroutineID)
log.Printf("switching to goroutine %d", command.GoroutineID)
}
err = d.target.SwitchGoroutine(command.GoroutineID) err = d.target.SwitchGoroutine(command.GoroutineID)
withBreakpointInfo = false withBreakpointInfo = false
case api.Halt: case api.Halt:

@ -6,7 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log" "io/ioutil"
"net" "net"
"net/rpc" "net/rpc"
"net/rpc/jsonrpc" "net/rpc/jsonrpc"
@ -16,12 +16,14 @@ import (
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
"github.com/derekparker/delve/pkg/logflags"
"github.com/derekparker/delve/pkg/version" "github.com/derekparker/delve/pkg/version"
"github.com/derekparker/delve/service" "github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api" "github.com/derekparker/delve/service/api"
"github.com/derekparker/delve/service/debugger" "github.com/derekparker/delve/service/debugger"
"github.com/derekparker/delve/service/rpc1" "github.com/derekparker/delve/service/rpc1"
"github.com/derekparker/delve/service/rpc2" "github.com/derekparker/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
@ -41,7 +43,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 bool log *logrus.Entry
} }
type RPCCallback struct { type RPCCallback struct {
@ -65,17 +67,24 @@ type methodType struct {
} }
// NewServer creates a new RPCServer. // NewServer creates a new RPCServer.
func NewServer(config *service.Config, logEnabled bool) *ServerImpl { func NewServer(config *service.Config) *ServerImpl {
logger := logrus.New().WithFields(logrus.Fields{"layer": "rpc"})
logger.Level = logrus.DebugLevel
if !logflags.RPC() {
logger.Logger.Out = ioutil.Discard
}
if config.APIVersion < 2 { if config.APIVersion < 2 {
if logEnabled { logger.Info("Using API v1")
log.Printf("Using API v1") }
} if config.Foreground {
// Print listener address
logger.Infof("API server listening at: %s", config.Listener.Addr())
} }
return &ServerImpl{ return &ServerImpl{
config: config, config: config,
listener: config.Listener, listener: config.Listener,
stopChan: make(chan struct{}), stopChan: make(chan struct{}),
log: logEnabled, log: logger,
} }
} }
@ -183,14 +192,12 @@ func isExportedOrBuiltinType(t reflect.Type) bool {
// two signatures: // two signatures:
// 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, logEnabled bool) { func suitableMethods(rcvr interface{}, methods map[string]*methodType, log *logrus.Entry) {
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()
if sname == "" { if sname == "" {
if logEnabled { log.Debugf("rpc.Register: no service name for type %s", typ)
log.Printf("rpc.Register: no service name for type %s", typ)
}
return return
} }
for m := 0; m < typ.NumMethod(); m++ { for m := 0; m < typ.NumMethod(); m++ {
@ -203,17 +210,13 @@ func suitableMethods(rcvr interface{}, methods map[string]*methodType, logEnable
} }
// Method needs three ins: (receive, *args, *reply) or (receiver, *args, *RPCCallback) // Method needs three ins: (receive, *args, *reply) or (receiver, *args, *RPCCallback)
if mtype.NumIn() != 3 { if mtype.NumIn() != 3 {
if logEnabled { log.Warn("method", mname, "has wrong number of ins:", mtype.NumIn())
log.Println("method", mname, "has wrong number of ins:", mtype.NumIn())
}
continue continue
} }
// First arg need not be a pointer. // First arg need not be a pointer.
argType := mtype.In(1) argType := mtype.In(1)
if !isExportedOrBuiltinType(argType) { if !isExportedOrBuiltinType(argType) {
if logEnabled { log.Warn(mname, "argument type not exported:", argType)
log.Println(mname, "argument type not exported:", argType)
}
continue continue
} }
@ -223,39 +226,29 @@ func suitableMethods(rcvr interface{}, methods map[string]*methodType, logEnable
if synchronous { if synchronous {
// Second arg must be a pointer. // Second arg must be a pointer.
if replyType.Kind() != reflect.Ptr { if replyType.Kind() != reflect.Ptr {
if logEnabled { log.Warn("method", mname, "reply type not a pointer:", replyType)
log.Println("method", mname, "reply type not a pointer:", replyType)
}
continue continue
} }
// Reply type must be exported. // Reply type must be exported.
if !isExportedOrBuiltinType(replyType) { if !isExportedOrBuiltinType(replyType) {
if logEnabled { log.Warn("method", mname, "reply type not exported:", replyType)
log.Println("method", mname, "reply type not exported:", replyType)
}
continue continue
} }
// Method needs one out. // Method needs one out.
if mtype.NumOut() != 1 { if mtype.NumOut() != 1 {
if logEnabled { log.Warn("method", mname, "has wrong number of outs:", mtype.NumOut())
log.Println("method", mname, "has wrong number of outs:", mtype.NumOut())
}
continue continue
} }
// The return type of the method must be error. // The return type of the method must be error.
if returnType := mtype.Out(0); returnType != typeOfError { if returnType := mtype.Out(0); returnType != typeOfError {
if logEnabled { log.Warn("method", mname, "returns", returnType.String(), "not error")
log.Println("method", mname, "returns", returnType.String(), "not error")
}
continue continue
} }
} else { } else {
// Method needs zero outs. // Method needs zero outs.
if mtype.NumOut() != 0 { if mtype.NumOut() != 0 {
if logEnabled { log.Warn("method", mname, "has wrong number of outs:", mtype.NumOut())
log.Println("method", mname, "has wrong number of outs:", mtype.NumOut())
}
continue continue
} }
} }
@ -273,18 +266,14 @@ func (s *ServerImpl) serveJSONCodec(conn io.ReadWriteCloser) {
err := codec.ReadRequestHeader(&req) err := codec.ReadRequestHeader(&req)
if err != nil { if err != nil {
if err != io.EOF { if err != io.EOF {
if s.log { s.log.Error("rpc:", err)
log.Println("rpc:", err)
}
} }
break break
} }
mtype, ok := s.methodMaps[s.config.APIVersion-1][req.ServiceMethod] mtype, ok := s.methodMaps[s.config.APIVersion-1][req.ServiceMethod]
if !ok { if !ok {
if s.log { s.log.Errorf("rpc: can't find method %s", req.ServiceMethod)
log.Printf("rpc: can't find method %s", req.ServiceMethod)
}
continue continue
} }
@ -307,9 +296,9 @@ func (s *ServerImpl) serveJSONCodec(conn io.ReadWriteCloser) {
} }
if mtype.Synchronous { if mtype.Synchronous {
if s.log { if logflags.RPC() {
argvbytes, _ := json.Marshal(argv.Interface()) argvbytes, _ := json.Marshal(argv.Interface())
log.Printf("-> %s(%T%s)\n", req.ServiceMethod, argv.Interface(), argvbytes) s.log.Debugf("-> %s(%T%s)\n", req.ServiceMethod, argv.Interface(), argvbytes)
} }
replyv = reflect.New(mtype.ReplyType.Elem()) replyv = reflect.New(mtype.ReplyType.Elem())
function := mtype.method.Func function := mtype.method.Func
@ -330,15 +319,15 @@ func (s *ServerImpl) serveJSONCodec(conn io.ReadWriteCloser) {
errmsg = errInter.(error).Error() errmsg = errInter.(error).Error()
} }
resp = rpc.Response{} resp = rpc.Response{}
if s.log { if logflags.RPC() {
replyvbytes, _ := json.Marshal(replyv.Interface()) replyvbytes, _ := json.Marshal(replyv.Interface())
log.Printf("<- %T%s error: %q\n", replyv.Interface(), replyvbytes, errmsg) s.log.Debugf("<- %T%s error: %q\n", replyv.Interface(), replyvbytes, errmsg)
} }
s.sendResponse(sending, &req, &resp, replyv.Interface(), codec, errmsg) s.sendResponse(sending, &req, &resp, replyv.Interface(), codec, errmsg)
} else { } else {
if s.log { if logflags.RPC() {
argvbytes, _ := json.Marshal(argv.Interface()) argvbytes, _ := json.Marshal(argv.Interface())
log.Printf("(async %d) -> %s(%T%s)\n", req.Seq, req.ServiceMethod, argv.Interface(), argvbytes) s.log.Debugf("(async %d) -> %s(%T%s)\n", req.Seq, req.ServiceMethod, argv.Interface(), argvbytes)
} }
function := mtype.method.Func function := mtype.method.Func
ctl := &RPCCallback{s, sending, codec, req} ctl := &RPCCallback{s, sending, codec, req}
@ -374,9 +363,7 @@ func (s *ServerImpl) sendResponse(sending *sync.Mutex, req *rpc.Request, resp *r
defer sending.Unlock() defer sending.Unlock()
err := codec.WriteResponse(resp, reply) err := codec.WriteResponse(resp, reply)
if err != nil { if err != nil {
if s.log { s.log.Error("writing response:", err)
log.Println("rpc: writing response:", err)
}
} }
} }
@ -386,9 +373,9 @@ func (cb *RPCCallback) Return(out interface{}, err error) {
errmsg = err.Error() errmsg = err.Error()
} }
var resp rpc.Response var resp rpc.Response
if cb.s.log { if logflags.RPC() {
outbytes, _ := json.Marshal(out) outbytes, _ := json.Marshal(out)
log.Printf("(async %d) <- %T%s error: %q", cb.req.Seq, out, outbytes, errmsg) cb.s.log.Debugf("(async %d) <- %T%s error: %q", cb.req.Seq, out, outbytes, errmsg)
} }
cb.s.sendResponse(cb.sending, &cb.req, &resp, out, cb.codec, errmsg) cb.s.sendResponse(cb.sending, &cb.req, &resp, out, cb.codec, errmsg)
} }

@ -33,7 +33,7 @@ func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) {
Listener: listener, Listener: listener,
ProcessArgs: []string{protest.BuildFixture(name, 0).Path}, ProcessArgs: []string{protest.BuildFixture(name, 0).Path},
Backend: testBackend, Backend: testBackend,
}, false) })
if err := server.Run(); err != nil { if err := server.Run(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -61,7 +61,7 @@ func Test1RunWithInvalidPath(t *testing.T) {
Listener: listener, Listener: listener,
ProcessArgs: []string{"invalid_path"}, ProcessArgs: []string{"invalid_path"},
Backend: testBackend, Backend: testBackend,
}, false) })
if err := server.Run(); err == nil { if err := server.Run(); err == nil {
t.Fatal("Expected Run to return error for invalid program path") t.Fatal("Expected Run to return error for invalid program path")
} }
@ -150,7 +150,7 @@ func Test1Restart_attachPid(t *testing.T) {
Listener: nil, Listener: nil,
AttachPid: 999, AttachPid: 999,
Backend: testBackend, Backend: testBackend,
}, false) })
if err := server.Restart(); err == nil { if err := server.Restart(); err == nil {
t.Fatal("expected error on restart after attaching to pid but got none") t.Fatal("expected error on restart after attaching to pid but got none")
} }

@ -51,7 +51,7 @@ func withTestClient2(name string, t *testing.T, fn func(c service.Client)) {
Listener: listener, Listener: listener,
ProcessArgs: []string{protest.BuildFixture(name, 0).Path}, ProcessArgs: []string{protest.BuildFixture(name, 0).Path},
Backend: testBackend, Backend: testBackend,
}, false) })
if err := server.Run(); err != nil { if err := server.Run(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -84,7 +84,7 @@ func TestRunWithInvalidPath(t *testing.T) {
ProcessArgs: []string{"invalid_path"}, ProcessArgs: []string{"invalid_path"},
APIVersion: 2, APIVersion: 2,
Backend: testBackend, Backend: testBackend,
}, false) })
if err := server.Run(); err == nil { if err := server.Run(); err == nil {
t.Fatal("Expected Run to return error for invalid program path") t.Fatal("Expected Run to return error for invalid program path")
} }
@ -175,7 +175,7 @@ func TestRestart_attachPid(t *testing.T) {
AttachPid: 999, AttachPid: 999,
APIVersion: 2, APIVersion: 2,
Backend: testBackend, Backend: testBackend,
}, false) })
if err := server.Restart(); err == nil { if err := server.Restart(); err == nil {
t.Fatal("expected error on restart after attaching to pid but got none") t.Fatal("expected error on restart after attaching to pid but got none")
} }