本文由Florin Pățan于2019年2月6日发表
调试是任何现代应用程序生命周期的重要组成部分。它不仅有助于发现错误,因为程序员经常使用调试器来查看和理解他们必须使用的新代码库或学习新语言时会发生什么。
人们更喜欢两种调试方式:
- print语句:在您的代码执行可能运行的各个步骤时记录
- 直接或通过IDE 使用Delve之类的调试器:这可以更好地控制执行流程,更多功能可以查看原始打印语句中可能未包含的代码,甚至可以在应用程序期间更改值运行时或继续执行应用程序。
在本系列中,我们将重点介绍第二个选项,使用IDE调试应用程序。
正如您从上面的描述中注意到的那样,这样做提供了更多的控制和功能来查找错误,因此本文分为几个部分:
- 调试应用程序
- 调试测试
- 调试本地计算机上正在运行的应用程序
- 调试远程计算机上正在运行的应用程序
我们在下面介绍这些场景,不论从哪里调试, 都会会提供以下功能
- 调试的基础知识
- 控制执行流程
- 评估表达式
- 看自定义值
- 改变变量值
- 使用断点
IDE还支持调试在Linux上生成的核心转储以及在Linux上使用Mozilla的rr可逆调试器。我们将在即将发布的单独博文中看到这些功能。
我们将在所有上述应用程序中使用简单的Web服务器,但这些应用程序可以应用于任何类型的应用程序,CLI工具,GUI应用程序等。
我们将使用Go Modules,但使用任何其他依赖管理表单的默认GOPATH也可以正常工作。
使用Go Modules(vgo)类型创建应用程序,并确保您使用Go 1.11+或更高版本。
如果您没有Go 1.11或想要使用GOPATH模式,请选择Go。
该应用程序可以在下面找到:
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)
}
}
我们还可以创建一个这样的测试文件:
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)
}
})
}
}
我们应该注意的最后一个信息是调试体验也受到用于编译目标程序的Go版本的影响。随着每个Go版本的发布,Go团队致力于添加更多调试信息并提高现有版本的质量,从旧版本(如Go 1.8)切换到Go 1.9或以更戏剧性的方式从转1.8到Go 1.11。因此,您可以使用的Go版本越新,您的体验就越好。
现在我们所有的代码都已到位,让我们开始调试它!
调试应用程序
要调试应用程序,我们可以单击绿色三角形,然后选择Debug'go build main.go'。
或者,我们可以右键单击文件夹并选择Debug | go build <项目名称>。
调试测试
这与调试应用程序类似, Goland识别这些测试用例是标准的测试包, ,gocheck和测试框架,所以这些测试可以从编辑器窗口直接运行
至于其它测试框架,则需要编辑配置文件,或添加额外的参数,具体取决于使用非标准包的参数。
在本地计算机上调试正在运行的应用程序
在某些情况下,您可能希望调试在IDE外部启动的应用程序。
- 其中一种情况是应用程序在本地计算机上运行。
要使用调试器运行它,请在IDE中打开项目,然后选择Attach to Process ...
如果这是您第一次使用此功能,请下载一个名为gops的小型实用程序,可从https://github.com/google/gops下载。该程序可帮助IDE找到在您的计算机上运行的Go进程。然后再次调用Attach to Process ...功能。
或者从您计算上选一个已经存在的项目,然后就可以开始下一步了。
为了确保调试能够成功, 你必须要做的是编译是为的程序添加一写特殊的flag。goland将会自动添加添加这些flag,因此仅在手动编译时,才需要你来配置。
要确保调试会话成功并且您可以毫无问题地调试应用程序,您需要做的就是使用特殊标志编译应用程序。IDE将自动为其他配置类型添加这些标志,因此仅在手动编译应用程序时才需要这些标志。
如果您使用Go 1.10或更高版本运行,则需要添加-gcflags="all=-N -l"
到go build
命令中。
go build -gcflags="all=-N -l"
如果您使用的是Go 1.9或更早版本,则需要添加-gcflags="-N -l"
到go build
命令中。
go build -gcflags="-N -l"
重要的提示!有些人也使用-ldflags="all=-w"
或-ldflags="-w"
,这取决于使用的Go版本。
但这与调试应用程序不兼容,因为它删除了Delve工作所需的必要DWARF信息。因此,goland无法调试应用程序。在支持此功能的操作系统和文件系统上使用符号链接或符号链接时,将遇到类似的问题。由于Go工具链,Delve和IDE之间不兼容,使用符号链接目前与调试应用程序不兼容。
在远程计算机上调试正在运行的应用程序
最后这种情况比较复杂,这类调试允许goland调试远程主机上运行的程序。通过远程调试,我们可以在本地或云中考虑在本地计算机远程目标或实际服务器上运行的容器。
运行远程调试,有一下几点需要注意:
- 与运行本地计算机上运行的应用程序非常相似,您必须注意用于编译应用程序的编译器标志。
- 使用相同的Go版本和主机/目标编译Delve,因为各种操作系统之间可能存在细微差别,这可能导致调试会话无法按预期工作。
- 如果使用 GOPATH,确保相同的GOPATH路径
例如:如果你的项目在github.com/JetBrains/go-sample下可用,那么goland所在机器和程序运行机器上,应用程序都在GOPATH / src / github.com / JetBrains下/ go-sample,GOPATH可以在这两台机器之间有所不同,goland可以自动映射本地和远程计算机之间的目录。
下一步,在部署应用程序时,还要在远程服务器安装相同的版本Delve。完成之后,有两种方法启动调试
远程服务器上运行命令:
dlv --listen=:2345 --headless=true --api-version=2 exec ./application
或者运行命令:
dlv --listen=:2345 --headless=true --api-version=2 attach
其中是应用程序的进程ID。
以上两步请注意防火墙开启2345端口
完成所有这些操作后,编辑配置,监听远程主机和端口,即可开始调试了。
您可以使用以下Dockerfile中的容器来调试:
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"]
请注意,在此Dockerfile中,项目名为goland-debugging,但您应更改此文件夹名称以匹配您创建的项目的名称。
运行Docker容器时,还需要为其指定--security-opt="apparmor=unconfined" --cap-add=SYS_PTRACE
参数。如果从命令行执行此操作,则这些是docker run命令的参数。如果从IDE执行此操作,则必须将这些选项放在“ 运行选项”字段中。
本文章翻译自https://blog.jetbrains.com/go/2019/02/06/debugging-with-goland-getting-started
,本文译者酌情量改动