How to debug a running Go app with VSCode

Tom Elliott
average-coder
Published in
4 min readSep 17, 2018
Debugging Go in VSCode

VSCode is a great editor for Go, providing a variety of helpful features to save you time while coding. It also includes support for debugging Go programs in-editor. While this is a very handy feature, I’ve not been making as much use of it as I might, since my testing workflow usually involves launching a group of microservices at once from the command line (sometimes in containers), rather than one-offs in an IDE.

Often, I want to be able to just connect to one of these processes I already have running to quickly inspect what is happening under the hood. Fortunately, you can do this with a little extra work and some command-line trickery.

Pre-requisites

You will need to have installed VSCode and the official Go extension. Detail on how to do this is beyond the scope of this post, but the Readme for the extension has a great set of instructions.

You will also need to install Delve, a debugger for Go. I’d recommend following the up-to-date manual installation steps for your OS. I tried using Homebrew initially and had a number of issues, the manual steps for macOS worked first time.

1. Configure your project

The first time you debug your application, VSCode will create a configuration for you automatically, which can then be edited easily.

Open your main.go file, then select the debug view and press the play button at the top of the screen. This will start up a new instance of your application in the debugger.

Feel free to play around if you’ve not used the debugger before. Set some breakpoints, step through some code. But this just gets a new instance of your application debugging, we want to be able to do this for something we’re running separately.

Running the debugger will have created a new file in your workspace at .vscode/launch.json that looks something like this:

{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "auto",
"remotePath": "",
"port": 2345,
"host": "127.0.0.1",
"program": "${fileDirname}",
"env": {},
"args": [],
"showLog": false
}
]
}

We need to make a few changes to this to make it ready for remote debugging. First off, the mode will need changing to “remote”. We can also get rid of some of the lines that just use default values.

A remotePath matching the program value is also recommended by some documentation, but I’ve found it isn’t strictly necessary for this process. We’ll include it here for safety.

${fileDirname} is a predefined variable referencing the directory containing the file you’re currently editing. For simple projects, this will also be the binary name for your application. With more complex projects, you can set this to ${workspaceFolder}, or the full path to your binary.

When you’re done, you should have something like this:

{
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"remotePath": "${workspaceFolder}",
"mode": "remote",
"port": 2345,
"host": "127.0.0.1",
"program": "${workspaceFolder}",
}
]
}

Your project is ready to go, so let’s start your application.

2. Start your application

This is the easiest step, just start your application as you normally would. You just need to have a note of where your binary can be found.

3. Find the PID and attach Delve

We now need to launch Delve in headless mode, listening on port 2345 (the default for the debug configuration in step 1).

Delve needs to know the PID of our application’s process to attach. We can use pgrep to find this. Since pgrep will return only the PID, we can use, we can evaluate that as part of our command in most shells. So we can start Delve with:

dlv attach --headless --listen=:2345 $(pgrep FILENAME) PATH

Where FILENAME is the name of our application’s binary file (the command that is running), and PATH is the path to the copy of the binary we’re currently running.

This will start a Delve server attached to our running application. You should see some output from Delve along the lines of:

API server listening at: [::]:2345

Delve is now ready for VSCode to connect!

4. Start debugging

If you go back to the debug view in VSCode and click the play button to start debugging, VSCode will now connect to your running Delve server. You can use breakpoints and step through code in the same manner as if VSCode had started a new instance itself.

The only real difference is that stopping debugging will not stop the application that it attaches to (in some circumstances it will stop Delve, though). This means that you can attach to the same process multiple times during its lifecycle.

Enjoy!

Remote debugging of existing processes is certainly a little more involved than the default workflow, but for those times you need it, may be an invaluable tool in your belt. If containers are a big part of your workflow, you could likely even adapt these steps to always have a debug port open on your test containers.

Certainly beats adding a bunch of logging lines!

--

--