If you build software applications in Go, you know code doesn’t always work as expected.
This problem can then continue to compound with swearing at your computer, compiling the code over and over to see whether it will work by chance, and endlessly searching through large chunks of code to see where the error comes from.
But, you can save yourself the headache by choosing one of the many Go debugging tools. Debugging itself is just finding bugs in code, but debugging without any tools is all sweat and tears.
In this article, we will compare a few of the most popular Go debugging tools by looking at their installations, debugging processes, and pros and cons, including:
But before we move into that, it is best to discuss what debugging tools are, why we as developers need them, and the issues Go developers face that these tools can help solve.
The only prerequisite for this article is to be familiar with the Go programming language.
Debugging is the process of detecting, finding, and solving defects or problems in a software application. It’s like being the detective in a crime movie while simultaneously being the murderer.
Debugging tools help you closely spot these errors while executing the code so you can remove or fix them, helping you go step-by-step or line-by-line of your code to find them. These errors often include syntax, semantic, and run-time errors.
Using a debugging tool and pausing the execution while you follow the line of code as it executes is one reason why developers have opted to use them. It is the only way you can fully and quickly grasp what is going on in your codebase.
Using this tool, you can immediately see what happens. It is also a great learning method because you actually see how the computer interprets the code.
Let’s look more at some common debugging techniques in Go to quickly check for errors in your code.
print
This is a practice of adding extra print
statements to your code so you can see what logs to your console. With this, you can see the values of variables, how expressions execute, and check whether you’re reaching variable scopes or not.
There is a lot to see but this depends on what you need logged to your console.
However, this is a good trick if you do not use any debugging tools. One advantage of using print
is that you can see the flow of your variables and statements on the go.
But, if you build a large-scale application with a lot of functionalities, there is no better surrogate than having a logging system in your codebase.
Also, using print
can be messy, convoluting the readability of your codebase with print
typed everywhere. They can become too much if you forget to remove them as you find the errors and you cannot turn print
off.
Most IDEs have a built-in debugger that can help detect errors in your code. It is another quick way to debug your code after using print
. Debuggers are only found in IDEs, like Visual Studio Code or Eclipse.
Using a debugger helps discover breakpoints, moving line-by-line in your code watching variables flow. With these, you can find logical errors quickly.
Most developers call this technique the step-by-step execution because it moves from line to line and is a quick way to detect bugs.
But while using a debugger is still one of the most preferred techniques for debugging, using the debugger can be tedious in very large applications, which is why I personally prefer using logs, that is, writing log messages to standard output.
These tools can help find any breach of errors based on their type. This means there are certain types of error classes these tools can find, such as memory leaks by detecting memory errors.
These tools are also regarded as error monitoring tools because they mainly give the ability to visualize the error to understand what happened and where it happened.
Other common techniques for debugging include backtracking and divide-and-conquer.
Before we get into the different types of Go debugging tools, let’s discuss a few notes to keep in mind while debugging a Go application.
If you want to use a debugger like Delve, ensure you understand how it works; learn its commands, how to set breakpoints, and other basics for using the debugger. With this in my mind, you can approach bugs with those tools effectively.
You should also have a logging system to quickly log information as you code. But, don’t forget to avoid logging sensitive information, like passwords or financial information.
Avoid thinking about the worst scenario; your bug might be simple, such as having a wrong spelling somewhere or using the wrong variable name. It’s always the least expected thing.
With our understanding of debugging and debugging methods, let’s review some Go debugging tools that can be helpful when working in Go. These tools should give you better insight as to why development is a lot easier when you use them.
The first and most common debugger for Go is Delve. The goal of using Delve is to provide a simple and full-featured tool for debugging in Go.
One cool thing about Delve is that it was built from the ground up as a debugger for Go, so the actual target for this tool is the Go programming language, unlike other debuggers in IDEs that can be used for various languages.
This is extremely helpful because it knows how to tackle Go-specific features and functionalities like Goroutines, syntax expressions, data structures, and runtime.
To get Delve, you must install it on your computer.
Delve supports all platforms, including Linux, Windows, and macOS.
Installation is the same for macOS, Windows, and Linux, and all you need to do is use the go install
command:
$ go install github.com/go-delve/delve/cmd/dlv@latest
After this finishes installing, you can run go help
to check where the dlv
saved.
If on macOS, you must also install the developer tools to use Delve:
$ xcode-select --install
Below are a few of Delve’s commands to use while debugging:
--accept-multiclient Allows a headless server to accept multiple client connections. --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr --api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1) --backend string Backend selection (see 'dlv help backend'). (default "default") --build-flags string Build flags, to be passed to the compiler. For example: --build-flags="-tags=integration -mod=vendor -cover -v" --check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --disable-aslr Disables address space randomization --headless Run debug server only, in headless mode. -h, --help help for dlv --init string Init file, executed by the terminal client. -l, --listen string Debugging server listen address. (default "127.0.0.1:0") --log Enable debugging server logging. --log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true) -r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect') --wd string Working directory for running the program.
To start a debugging session in Delve, run the command below to compile your program:
$ dlv debug
The dlv debug
compiles and runs the main package in the current directory for debugging. You can also specify the package after the command, such as using dlv debug ./hello
:
# run delve $ dlv debug ./hello Type 'help' for list of commands (dlv)
To know more about using Delve, you can always use the help
command.
Delve has become more stable and mature over the years because it is was quite uncomfortable to use at its inception. The only drawback of using Go Delve is that it does not have a simple tutorial for using the commands.
To get started, you can refer to their Github for more information.
GBD is another Go debugger owned by The GNU Project. Unlike Delve, it was not built for Go.
Its current version available for download is 11.1, which was released in September 2020.
With GDB, you can inspect your compiled Go package as long as you give it the correct debugging information. And, you do not need to touch your source code to debug because GBD inspects what is happening inside your program while it executes.
GDB can do a few things while debugging your code to find bugs, including:
GDB works for all platforms, including macOS, Windows, and Linux, but they also target UNIX-like systems.
Installing GBD on macOS is pretty easy and straightforward:
brew install gdb
Installing on Windows, however, requires you to download the installer from minGW. This installs GDB for you using the manager. If you use a Linux system, you already have GDB installed.
To begin a debugging process with GBD, you must compile your program. Let’s say your code is in the hello.go
file:
go build -gcflags "-N -l" hello.go
Since GDB does not support Go fully, if after compilation the Go compiler performs optimization, it is difficult for GDB to debug properly. So, using -N -l
prevents Go from performing its default optimization.
Next, use the gdb
command to begin:
gdb hello
Then, use the command run
to run your program in GDB:
(gdb) run
When it runs, you can set a breakpoint to stop the program from running at that line:
(gdb) b 5
Our breakpoint is at the line of the hello.go
file. Other basic commands include print
, execute
, continue
, and a few others. For more debugging tips with GDB you can refer to the documentation.
If you notice, a drawback from using GDB when debugging in Go is that it does not fully support and understand the Go language like Delve does. This means you must prevent Go from optimizing your source code so GDB can perform properly.
Also, it is a little difficult to install GDB on Windows, unlike Delve where you can install it simply using the CLI. A few have even complained that the Eclipse IDE does not fully work well with GDB.
Println
PrintIn
is a default tool for debugging in Go. It is a function that prints to the console and is a variadic function, meaning it’s a function that can take an unknown number of arguments.
It provides a simple practice of adding print
statements to your code so you can see what is happening to the variables and the expressions.
Using Println
does not require installation like the others since it’s a default Go tool:
package main // Importing fmt import ( "fmt" ) func main() { const name = "Victor" fmt.Println("Hello", name) }
Using this can be very useful and save you time where you just print variables to the console to see the outcome. It is another great way to troubleshoot your code, find potential problems, and is a good logging tool overall.
PrintIn
drawbacksWhile this method of debugging can be easier, it can also be very overwhelming, from inspecting a hundred lines of code and clustering with Println
.
A better alternative is to use a logging framework that sends all the debugging messages to a log file.
We looked at a few techniques to debug your code with some popular debugging tools. Having the right tool can be a challenge, and there are many for Go.
Delve is the only debugging tool created specifically for Go, which makes it an obvious first choice.
Unfortunately, for some tools like Godebug that have suddenly been depreciated, we still hope more will be available for the Go community in the future.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.