Debugging with GoLand – Getting Started

Posted on by Florin Pățan

Debugging is an essential part of any modern application lifecycle. It’s not only useful for finding bugs as programmers often use debuggers to see and understand what happens in a new codebase they have to work with or when learning a new language.

There are two styles of debugging which people prefer:

  • print statements: which is logging as your code executes various steps that might run
  • using a debugger such as Delve, either directly or via an IDE: this gives more control over the execution flow, more capabilities to see what the code does which may not have been included in the original print statement, or even changing values during the application runtime or going back and forward with the execution of the application.

In this series we’ll focus on the second option, using an IDE to debug an application.

As you noticed from the description above, doing so provides a lot more control and capabilities to find bugs and as such this article is broken down in several sections:

And after a look at all the above scenarios, we’ll see how GoLand handles them so that you can have the same set of features listed below regardless of where your application is running:

  • the basics of debugging
    • controlling the execution flow
    • evaluating expressions
    • watching custom values
    • changing variable values
  • working with breakpoints

The IDE also supports debugging core dumps produced on Linux and using Mozilla’s rr reversible debugger on Linux. We’ll see these features in upcoming, separate blog posts.

We’ll use a simple web server in all the above applications, but these can be applied to any kind of application, CLI tools, GUI applications, etc.

We’ll use Go Modules, but the default GOPATH using any other dependency management form can work just as well.

Create the application using the Go Modules (vgo) type, and make sure you have Go 1.11+ or newer.
If you don’t have Go 1.11 or want to use the GOPATH mode, then choose Go.

The application can be found below:

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/gorilla/mux"
)

const (
	readTimeout  = 5
	writeTimeout = 10
	idleTimeout  = 120
)

func indexHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain")
	returnStatus := http.StatusOK
	w.WriteHeader(returnStatus)
	message := fmt.Sprintf("Hello %s!", r.UserAgent())
	w.Write([]byte(message))
}

func main() {
	serverAddress := ":8080"
	l := log.New(os.Stdout, "sample-srv ", log.LstdFlags|log.Lshortfile)
	m := mux.NewRouter()

	m.HandleFunc("/", indexHandler)

	srv := &http.Server{
		Addr:         serverAddress,
		ReadTimeout:  readTimeout * time.Second,
		WriteTimeout: writeTimeout * time.Second,
		IdleTimeout:  idleTimeout * time.Second,
		Handler:      m,
	}

	l.Println("server started")
	if err := srv.ListenAndServe(); err != nil {
		panic(err)
	}
}

And we can also create a test file like this:
package main

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestIndexHandler(t *testing.T) {
	tests := []struct {
		name           string
		r              *http.Request
		w              *httptest.ResponseRecorder
		expectedStatus int
	}{
		{
			name:           "good",
			r:              httptest.NewRequest("GET", "/", nil),
			w:              httptest.NewRecorder(),
			expectedStatus: http.StatusOK,
		},
	}
	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			indexHandler(test.w, test.r)
			if test.w.Code != test.expectedStatus {
				t.Errorf("Failed to produce expected status code %d, got %d", test.expectedStatus, test.w.Code)
			}
		})
	}
}

For Go Modules enabled applications, you can invoke the Alt+Enter shortcut then Sync packages of <my project>.
For non-Go Modules enabled applications, you can invoke the Alt+Enter shortcut then go get -t <missing dependency>.

One final information that we should note is that the debugging experience is also impacted by the Go version being used to compile the target program. With each Go version released, the Go Team works to add more debugging information and improve the quality of the existing one, and this can be seen when switching from an older version such as Go 1.8 to Go 1.9 or in a more dramatic way, from Go 1.8 to Go 1.11. So, the newer the Go version you can use, the better your experience will be.

Now that all our code is in place let’s start debugging it!

Debugging an application

To debug our application, we can click on the green triangle and then select Debug ‘go build main.go’.
Alternatively, we can right-click on a folder and choose Debug | go build <project name>.

Debugging with GoLand - 1

Debugging tests

This can be done similarly to debugging an application. GoLand recognizes tests from the standard testing package, gocheck, and testify frameworks, so these actions are available for all of them straight from the editor window.

For other frameworks you may need to configure a custom test runner by going to Run | Edit Configurations… and specifying additional arguments either in the Go tool arguments or in Program arguments, depending on where the custom library you are using needs those arguments.

Debugging with GoLand - 2

Debugging a running application on the local machine

There are cases where you would want to debug an application that’s launched outside of the IDE.
One of such cases is when the application runs on the local machine.
To run this using the debugger, then open the project in the IDE and select Attach to Process…

If it’s the first time you are using this feature, then the IDE will ask you to download a small utility program named gops, available at https://github.com/google/gops. This program helps the IDE find Go process running on your machine. Then invoke the Attach to Process… feature again.

You’ll see a list of all Go projects running on your computer, so who knows, maybe you’ll even discover new ones. Select the one that you want to debug from the list, and the debugger will attach to the process, and you can start your debugging session.

To ensure that the debugging session is successful and you can debug the application without issues, all you need to do is to compile your application with a special flag. The IDE will add these flags automatically for the other configuration types, so these are necessary only when compiling the application manually.

If you are running with Go 1.10 or newer, you need to add -gcflags="all=-N -l" to the go build command.
If you are running with Go 1.9 or older, then you need to add -gcflags="-N -l" to the go build command.

Important note! Some people also use the -ldflags="all=-w" or -ldflags="-w" , depending on the Go version being used.
This is incompatible with debugging the application as it strips out the necessary DWARF information needed for Delve to work.
As such the application will not be able to be debugged.
A similar problem will be encountered when using symbolic links, or symlinks, on operating systems and file systems which support this feature. Due to an incompatibility between the Go toolchain, Delve, and the IDE, using symlinks is currently not compatible with debugging an application.

Debugging with GoLand - 3

Debugging a running application on a remote machine

Lastly, this case is a more complex one, at least for now. This debugging session type allows you to connect the IDE to a remote target and debug a process running there. By remote target, we can consider containers running on the local machine remote targets or actual servers either on-premise or in the cloud.

Much like with running against applications running on your local machine, you have to be careful about the compiler flags that you use to compile the application.
After that, you need to compile Delve with the same Go version and host/target as your application as there might be slight differences between the various operating systems which could cause the debugging session not to work as expected.

You should also make sure that if you are using $GOPATH, the project is compiled using the same relative path to $GOPATH. For example: if your project is available under github.com/JetBrains/go-sample, then both on the machine with the IDE and in the machine that compiles the application, the application is under $GOPATH/src/github.com/JetBrains/go-sample, $GOPATH can be different between these two machines. Then the IDE can automatically map the sources between the local and remote machine.

Then, when you deploy your application, also deploy the copy of Delve that was compiled earlier, and you have two options to launch the debugger there:

  • let the debugger run the process for you: if you choose this option, you need to run dlv --listen=:2345 --headless=true --api-version=2 exec ./application . Also note that if you use any firewall or containers, then you’ll need to expose the port 2345 in those configurations. The port number can be any random one you need, not just 2345 as long as it’s free on the host machine.
  • attaching to the process: you need to run dlv --listen=:2345 --headless=true --api-version=2 attach <pid> where <pid> is the process id of your application.

After all of this is done, the final step is to connect your IDE to the remote debugger. You can do so by going to Run | Edit Configurations… | + | Go Remote and configuring the host and port your remote debugger is listening on.

Debugging with GoLand - 4

You can use the container definition from the Dockerfile below:

FROM golang:1.11.5-alpine3.8 AS build-env

ENV CGO_ENABLED 0

# Allow Go to retreive the dependencies for the build step
RUN apk add --no-cache git

WORKDIR /goland-debugging/
ADD . /goland-debugging/

RUN go build -o /goland-debugging/srv .

# Get Delve from a GOPATH not from a Go Modules project
WORKDIR /go/src/
RUN go get github.com/go-delve/delve/cmd/dlv

# final stage
FROM alpine:3.8

WORKDIR /
COPY --from=build-env /goland-debugging/srv /
COPY --from=build-env /go/bin/dlv /

EXPOSE 8080 40000

CMD ["/dlv", "--listen=:40000", "--headless=true", "--api-version=2", "exec", "/srv"]

Please note that in this Dockerfile, the project is named goland-debugging, but you should change this folder name to match the name of the project you created.
When running the Docker container, you also need to specify --security-opt="apparmor=unconfined" --cap-add=SYS_PTRACE arguments to it. If you do it from the command line, these are arguments to the docker run command. If you do it from the IDE, these options must be placed in the Run options field.

Debugging with GoLand - 5

That’s it for today. In the next article in the series, we’ll learn how to use the various features that are available to us during one of the debugging scenarios described above. Please let us know your feedback in the comments section below, on Twitter, or open an issue on our issue tracker.

Comments below can no longer be edited.

20 Responses to Debugging with GoLand – Getting Started

  1. Brendan says:

    April 17, 2019

    Would it be possible to add manual directory mapping for the local and remote machine? With the addition of go modules, it is no longer necessary to keep src files in $GOPATH. It would be nice if we could do a find and replace on the path so the source maps match and we can set a breakpoint.

    • Florin Pățan says:

      April 18, 2019

      Hi Brendan,

      The automatic mapping works for Go Modules also.

      In case you have issues with this, please make sure you are using the latest IDE version, and if it’s still present, please report it on our issue tracker: https://youtrack.jetbrains.com/issues/Go

      Thank you.

  2. Jim says:

    July 9, 2019

    I’m trying to debug an application with GoLand 2019.1. The app has a startup script and I can connect to it once I start it up by using “Attach to Process”, but I can’t debug application startup, since the code that I need to set a breakpoint on has already run once by time the I connect to it. What can I do?

    • Florin Pățan says:

      July 10, 2019

      In your case, it would probably be better to launch the application via “Run | Debug” option and configure all the settings required in the Run Configuration as that will launch the application directly via the debugger and allow you to set breakpoints in the initialization part.

      If this is not an option for you, please describe your use-case and I’ll try and find a workaround it.

      • greg says:

        February 28, 2020

        Hello! I have the same need: the Go binary is launched by a script with command line parameters, and I’d like to debug the Go program startup in this situation.

        • Florin Pățan says:

          February 28, 2020

          As above, why can you not set the script parameters in the Run Configuration?

  3. Eric says:

    August 30, 2019

    Text above references the “go build command”.. I’m having trouble finding such a field in my run configuration….

    • Florin Pățan says:

      August 30, 2019

      Hi Eric,

      Where is the “go build” field referenced?
      And what problems are you currently facing?

  4. Artem says:

    September 30, 2019

    Hi, I need to run a custom command as I am using a Buffalo framework. So I need to substitute “go test” with “buffalo test” command. Is there any possible way to do it?
    Thanks

  5. SC says:

    October 30, 2019

    I can’t figure out how to imported & packages inside breakpoint expressions and watch expressions when debugging golang… it looks like golang doesn’t support using fully qualified names as a way to avoid imports, and adding an import to the expression doesn’t seem to work either.

    • Florin Pățan says:

      October 30, 2019

      Hi SC,

      Can you provide the steps of what you are trying to do, what you expect to happen, and what actually happens?

      Thank you.

  6. Serge Zarembsky says:

    December 30, 2019

    Hi SC,
    My go module runs as a Windows service under SYSTEM account. Attempt to attach Debugger results in Access Denied error. The goland IDE itself runs as Admin, but it does not help. Is there a workaround for this kind of issue?

    • Anton Rumiantsev says:

      December 31, 2019

      Hi,
      Try to create a run configuration with “Run with elevated priviliges” on
      https://1drv.ms/u/s!Ag6SLoTV7YOMgw9Kgnquzs2ixdJY?e=dQzOJk

      It’s not really needed to run GoLand as admin

      If this doesn’t help, give please more details on how did you launch the service

  7. Rene Baron says:

    February 24, 2020

    Dear SC,
    Have newest versions of Goland (2019.3.2), Go (1.13.4) installed on an updated Windows10 x64 on an i386 workstation.

    When following your manual I am facing the following issues:

    1. “you can invoke the Alt+Enter shortcut then Sync packages of .”
    –> Alt+Enter does not react and I cannot find anything that looks like “Sync packages”

    2. When starting the Debugger with Debug go build myApp I am geeting the following error:

    GOROOT=C:\Go #gosetup
    GOPATH=C:\Users\reneb\go #gosetup
    C:\Go\bin\go.exe build -o C:\Users\reneb\AppData\Local\Temp\___go_build_Test2.exe -gcflags “all=-N -l” Test2 #gosetup
    go: finding github.com/gorilla/mux v1.7.4
    go: downloading github.com/gorilla/mux v1.7.4
    go: extracting github.com/gorilla/mux v1.7.4
    “C:\Program Files\JetBrains\GoLand 2019.3.1\plugins\go\lib\dlv\windows\dlv.exe” –listen=localhost:52814 –headless=true –api-version=2 –check-go-version=false exec C:\Users\reneb\AppData\Local\Temp\___go_build_Test2.exe — #gosetup
    API server listening at: 127.0.0.1:52814
    unsupported architecture of windows/386 – only windows/amd64 is supported

    Debugger finished with exit code 1

    Any ideas on this?

    • Florin Pățan says:

      February 24, 2020

      Hi Rene,

      Let’s take this in reverse order:

      2. The error message:

      > unsupported architecture of windows/386 – only windows/amd64 is supported

      This tells me that the Go version installed is for i386 (32bit) instead of amd64(64 bit). In your case, since you already have Windows x64, you can go ahead and replace Go with the am64 version.

      1. From this

      > you can invoke the Alt+Enter shortcut then Sync packages of

      As far as I can tell, your application uses Go Modules. To check the features are enabled, go to Settings/Preferences | Go | Go Modules and see if the checkbox Enable Go Modules integration.

      Please let me know if you need further assistance,
      Florin

      • Rene Baron says:

        February 24, 2020

        Hi Florin,
        You made my day: Probleme solved!!
        Issue solved for me:
        Solution: Uninstall the i386/32Bit Go version and Install the newest amd64/64-bit version!

        Problem was due to 32Bit Go installation rather than 64Bit!
        (I have accidently installed the i386 = 32-Bit Go Version when upgrading go to the newest 1.13.4 version. The dumb person I am, I have taken i386 for Intel (I am running on Intel Processors) and amd64 for AMD Processors assuming that both are 64Bit per default – so – shame on me! ).

        So for me – having a 64Bit machine – the problem is solved for me.
        However it seems that 32-Bit is not or no longer supported as mentioned by other Issues here already.

        Thank you so much for this!

  8. Semyon says:

    March 12, 2020

    it’s not work with docker-compose

    • Florin Pățan says:

      March 12, 2020

      You need to make the correct settings for docker-compose, for the debugger to work inside the Docker environment. They are mentioned in the last section of the article.

      I’m currently working on an article to describe the Docker Compose workflow, and it should be published soon. If you can’t manage to get this working, either ping me @dlsniper on Gophers Slack in the #goland channel or open an issue on our tracker and I’ll be happy to help you until the article is out.

  9. Andrea Feltner says:

    April 15, 2020

    Hi,
    I’m able to connect using docker build/run, but if I try to attach when running from docker-compose (from GOLAND), it just says connected…but never actually triggers the application to run. It just sits there. I had this issue on another project, and I ended up pulling version RUN go get github.com/go-delve/delve/cmd/dlv@v1.3.2. I now try that one, or the latest delve and can’t seem to get it to run. I’m nto sure what I’ve done wrong. Here are some of my settings in my docker file:
    FROM golang:1.14 as builder

    RUN go get github.com/go-delve/delve/cmd/dlv

    RUN go build -gcflags=”all=-N -l” -a -ldflags “-X main.Version=${VERSION}” -o mysvc .

    CMD [“/dlv”, “–listen=:40001”, “–headless=true”, “–log”, “–api-version=2”, “exec”, “./mysvc]

    Any ideas?

    • Florin Pățan says:

      April 15, 2020

      Hi Andrea,

      First, I would try to pull Delve at 1.4.0. It probably won’t solve the issue, but it would improve the debugging quality of life in general (in case pulling from HEAD is not an option).

      Then, the configuration looks ok. Please try this repository/branch https://github.com/dlsniper/dockerdev/tree/compose-debug and see if the debugging works there. If it does then compare it with the docker-compose file you have for the project.

      Hope it helps. If not, let me know

Subscribe

Subscribe for updates