Four years ago, I wrote a blog post called Introduction to Go Debugging with GDB. At the time, the only option was the GNU debugger. Even the official Go documentation page on GDB doesn't set the bar very high:

GDB does not understand Go programs well. The stack management, threading, and runtime contain aspects that differ enough from the execution model GDB expects that they can confuse the debugger, even when the program is compiled with gccgo. As a consequence, although GDB can be useful in some situations, it is not a reliable debugger for Go programs, particularly heavily concurrent ones.

What Are Our Options Today?

Option 1: Godebug
godebug, published by Mailgun, has 2,614 stars on GitHub. They have a pretty impressive demo page where they combine godebug and gopherjs. In this demo, the code gets transformed twice. First, godebug inserted debugging instrumentation. Then gopherjs compiled the result to javascript. The final result is an in-browser interactive demo.

Option 2: Delve
Delve was published by Dereck Parker and has 5,588 stars on GitHub. From a UX perspective, Delve is much more like a traditional debugger, like GDB or PDB, but unlike GDB it has an intimate understanding of the Go specificities. Additionally, Delve has IDE integrations for Visual Studio Code and IntelliJ platforms.

Which One Are You Using?

Typically, I utilize a debugger far less often when I develop in Go than in Python. The reason probably comes down to the fact that for years, there was no compelling option. Println or go-spew became my first line of defense. However, in recent months I have seen myself turning to Delve far more often than I used to.

At the time of writing, Delve's documentation was a bit scarce. Last year, an article was published on GopherAcadaemy's blog that’s very helpful and covers a lot of common use cases.

Usage

A recent use case I ran into was debugging github.com/yml/keep. Keep is a command line tool that accepts multiple arguments. The following command line instructions let you retrieve credentials for a lincolnloop.com account:

$ keep read lincolnloop.com
Using profile :  yml
Reading ...

Decoding private key short ID : 6A8D785C
file path : /home/yml/.kip/passwords/lincolnloop.com
Credentials have been signed by : 6A8D785C

Name :  lincolnloop.com
Username :  yann
Notes :  LL web site

To put this in context, I wanted to start debugging my program on line 98.

$ dlv debug main.go -- read lincolnloop.com
Type 'help' for list of commands.
(dlv) break main.go:98
Breakpoint 1 set at 0x401e79 for main.main() ./main.go:98
(dlv) continue
Using profile :  yml
Reading ...

> main.main() ./main.go:98 (hits goroutine(1):1 total:1) (PC: 0x401e79)
    93:
    94:         //fmt.Println(args, "\n", conf)
    95:         if val, ok := args["read"]; ok == true && val == true {
    96:                 fmt.Printf("Reading ...\n\n")
    97:                 fname, ok := args["<file>"].(string)
=>  98:                 if !ok {
    99:                         fmt.Println("An error occured while converting <file> into string")
   100:                         os.Exit(exitCodeOk)
   101:                 }
   102:
   103:                 var accountPosition *int
(dlv) print fname
"lincolnloop.com"

As illustrated above, the command line parameters are passed after --. This will drop you at a prompt where you can set a break or trace point:

  • break main.go:98 -- break the execution at line 98 of main.go
  • trace main.go:61 -- trace the execution at line 61 of main.go

Spoiled by years of using Python interactive debuggers, pdb/ipdb, there's one feature that I really miss in Delve, tracked under #119. I often find myself in a situation where I would like to execute a function and print out its result.

Wrapping Up

Even without the feature mentioned above, Delve has drastically improved my workflow when I'm trying to better understand what's going on in my program. Do you have a tip that helped with your Go debugging workflow? Please leave a comment below!