本文由Florin P??an于2019年2月6日發(fā)表
調(diào)試是任何現(xiàn)代應(yīng)用程序生命周期的重要組成部分浪听。它不僅有助于發(fā)現(xiàn)錯(cuò)誤窒所,因?yàn)槌绦騿T經(jīng)常使用調(diào)試器來(lái)查看和理解他們必須使用的新代碼庫(kù)或?qū)W習(xí)新語(yǔ)言時(shí)會(huì)發(fā)生什么。
人們更喜歡兩種調(diào)試方式:
- print語(yǔ)句:在您的代碼執(zhí)行可能運(yùn)行的各個(gè)步驟時(shí)記錄
- 直接或通過(guò)IDE 使用Delve之類的調(diào)試器:這可以更好地控制執(zhí)行流程磅轻,更多功能可以查看原始打印語(yǔ)句中可能未包含的代碼珍逸,甚至可以在應(yīng)用程序期間更改值運(yùn)行時(shí)或繼續(xù)執(zhí)行應(yīng)用程序。
在本系列中聋溜,我們將重點(diǎn)介紹第二個(gè)選項(xiàng)谆膳,使用IDE調(diào)試應(yīng)用程序。
正如您從上面的描述中注意到的那樣撮躁,這樣做提供了更多的控制和功能來(lái)查找錯(cuò)誤漱病,因此本文分為幾個(gè)部分:
- 調(diào)試應(yīng)用程序
- 調(diào)試測(cè)試
- 調(diào)試本地計(jì)算機(jī)上正在運(yùn)行的應(yīng)用程序
- 調(diào)試遠(yuǎn)程計(jì)算機(jī)上正在運(yùn)行的應(yīng)用程序
我們?cè)谙旅娼榻B這些場(chǎng)景,不論從哪里調(diào)試, 都會(huì)會(huì)提供以下功能
- 調(diào)試的基礎(chǔ)知識(shí)
- 控制執(zhí)行流程
- 評(píng)估表達(dá)式
- 看自定義值
- 改變變量值
- 使用斷點(diǎn)
IDE還支持調(diào)試在Linux上生成的核心轉(zhuǎn)儲(chǔ)以及在Linux上使用Mozilla的rr可逆調(diào)試器杨帽。我們將在即將發(fā)布的單獨(dú)博文中看到這些功能漓穿。
我們將在所有上述應(yīng)用程序中使用簡(jiǎn)單的Web服務(wù)器,但這些應(yīng)用程序可以應(yīng)用于任何類型的應(yīng)用程序注盈,CLI工具晃危,GUI應(yīng)用程序等。
我們將使用Go Modules老客,但使用任何其他依賴管理表單的默認(rèn)GOPATH也可以正常工作僚饭。
使用Go Modules(vgo)類型創(chuàng)建應(yīng)用程序,并確保您使用Go 1.11+或更高版本胧砰。
如果您沒(méi)有Go 1.11或想要使用GOPATH模式鳍鸵,請(qǐng)選擇Go。
該應(yīng)用程序可以在下面找到:
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)
}
}
我們還可以創(chuàng)建一個(gè)這樣的測(cè)試文件:
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)
}
})
}
}
我們應(yīng)該注意的最后一個(gè)信息是調(diào)試體驗(yàn)也受到用于編譯目標(biāo)程序的Go版本的影響朴则。隨著每個(gè)Go版本的發(fā)布权纤,Go團(tuán)隊(duì)致力于添加更多調(diào)試信息并提高現(xiàn)有版本的質(zhì)量,從舊版本(如Go 1.8)切換到Go 1.9或以更戲劇性的方式從轉(zhuǎn)1.8到Go 1.11乌妒。因此汹想,您可以使用的Go版本越新,您的體驗(yàn)就越好撤蚊。
現(xiàn)在我們所有的代碼都已到位古掏,讓我們開始調(diào)試它!
調(diào)試應(yīng)用程序
要調(diào)試應(yīng)用程序侦啸,我們可以單擊綠色三角形槽唾,然后選擇Debug'go build main.go'。
或者光涂,我們可以右鍵單擊文件夾并選擇Debug | go build <項(xiàng)目名稱>庞萍。
調(diào)試測(cè)試
這與調(diào)試應(yīng)用程序類似, Goland識(shí)別這些測(cè)試用例是標(biāo)準(zhǔn)的測(cè)試包, 忘闻,gocheck和測(cè)試框架钝计,所以這些測(cè)試可以從編輯器窗口直接運(yùn)行
至于其它測(cè)試框架,則需要編輯配置文件齐佳,或添加額外的參數(shù)私恬,具體取決于使用非標(biāo)準(zhǔn)包的參數(shù)。
在本地計(jì)算機(jī)上調(diào)試正在運(yùn)行的應(yīng)用程序
在某些情況下炼吴,您可能希望調(diào)試在IDE外部啟動(dòng)的應(yīng)用程序本鸣。
- 其中一種情況是應(yīng)用程序在本地計(jì)算機(jī)上運(yùn)行。
要使用調(diào)試器運(yùn)行它硅蹦,請(qǐng)?jiān)贗DE中打開項(xiàng)目荣德,然后選擇Attach to Process ...
如果這是您第一次使用此功能闷煤,請(qǐng)下載一個(gè)名為gops的小型實(shí)用程序,可從https://github.com/google/gops下載命爬。該程序可幫助IDE找到在您的計(jì)算機(jī)上運(yùn)行的Go進(jìn)程曹傀。然后再次調(diào)用Attach to Process ...功能。
或者從您計(jì)算上選一個(gè)已經(jīng)存在的項(xiàng)目饲宛,然后就可以開始下一步了。
為了確保調(diào)試能夠成功嗜价, 你必須要做的是編譯是為的程序添加一寫特殊的flag艇抠。goland將會(huì)自動(dòng)添加添加這些flag,因此僅在手動(dòng)編譯時(shí)久锥,才需要你來(lái)配置家淤。
要確保調(diào)試會(huì)話成功并且您可以毫無(wú)問(wèn)題地調(diào)試應(yīng)用程序,您需要做的就是使用特殊標(biāo)志編譯應(yīng)用程序瑟由。IDE將自動(dòng)為其他配置類型添加這些標(biāo)志絮重,因此僅在手動(dòng)編譯應(yīng)用程序時(shí)才需要這些標(biāo)志。
如果您使用Go 1.10或更高版本運(yùn)行歹苦,則需要添加-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版本蚪腋。
但這與調(diào)試應(yīng)用程序不兼容丰歌,因?yàn)樗鼊h除了Delve工作所需的必要DWARF信息。因此屉凯,goland無(wú)法調(diào)試應(yīng)用程序立帖。在支持此功能的操作系統(tǒng)和文件系統(tǒng)上使用符號(hào)鏈接或符號(hào)鏈接時(shí),將遇到類似的問(wèn)題悠砚。由于Go工具鏈晓勇,Delve和IDE之間不兼容,使用符號(hào)鏈接目前與調(diào)試應(yīng)用程序不兼容哩簿。
在遠(yuǎn)程計(jì)算機(jī)上調(diào)試正在運(yùn)行的應(yīng)用程序
最后這種情況比較復(fù)雜宵蕉,這類調(diào)試允許goland調(diào)試遠(yuǎn)程主機(jī)上運(yùn)行的程序。通過(guò)遠(yuǎn)程調(diào)試节榜,我們可以在本地或云中考慮在本地計(jì)算機(jī)遠(yuǎn)程目標(biāo)或?qū)嶋H服務(wù)器上運(yùn)行的容器羡玛。
運(yùn)行遠(yuǎn)程調(diào)試,有一下幾點(diǎn)需要注意:
- 與運(yùn)行本地計(jì)算機(jī)上運(yùn)行的應(yīng)用程序非常相似宗苍,您必須注意用于編譯應(yīng)用程序的編譯器標(biāo)志稼稿。
- 使用相同的Go版本和主機(jī)/目標(biāo)編譯Delve薄榛,因?yàn)楦鞣N操作系統(tǒng)之間可能存在細(xì)微差別,這可能導(dǎo)致調(diào)試會(huì)話無(wú)法按預(yù)期工作让歼。
- 如果使用 GOPATH敞恋,確保相同的GOPATH路徑
例如:如果你的項(xiàng)目在github.com/JetBrains/go-sample下可用,那么goland所在機(jī)器和程序運(yùn)行機(jī)器上谋右,應(yīng)用程序都在GOPATH / src / github.com / JetBrains下/ go-sample硬猫,GOPATH可以在這兩臺(tái)機(jī)器之間有所不同,goland可以自動(dòng)映射本地和遠(yuǎn)程計(jì)算機(jī)之間的目錄改执。
下一步啸蜜,在部署應(yīng)用程序時(shí),還要在遠(yuǎn)程服務(wù)器安裝相同的版本Delve辈挂。完成之后衬横,有兩種方法啟動(dòng)調(diào)試
遠(yuǎn)程服務(wù)器上運(yùn)行命令:
dlv --listen=:2345 --headless=true --api-version=2 exec ./application
或者運(yùn)行命令:
dlv --listen=:2345 --headless=true --api-version=2 attach <pid>
其中<PID>是應(yīng)用程序的進(jìn)程ID。
以上兩步請(qǐng)注意防火墻開啟2345端口
完成所有這些操作后终蒂,編輯配置蜂林,監(jiān)聽(tīng)遠(yuǎn)程主機(jī)和端口,即可開始調(diào)試了拇泣。
您可以使用以下Dockerfile中的容器來(lái)調(diào)試:
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"]
請(qǐng)注意噪叙,在此Dockerfile中,項(xiàng)目名為goland-debugging挫酿,但您應(yīng)更改此文件夾名稱以匹配您創(chuàng)建的項(xiàng)目的名稱构眯。
運(yùn)行Docker容器時(shí),還需要為其指定--security-opt="apparmor=unconfined" --cap-add=SYS_PTRACE
參數(shù)早龟。如果從命令行執(zhí)行此操作惫霸,則這些是docker run命令的參數(shù)。如果從IDE執(zhí)行此操作葱弟,則必須將這些選項(xiàng)放在“ 運(yùn)行選項(xiàng)”字段中壹店。
本文章翻譯自https://blog.jetbrains.com/go/2019/02/06/debugging-with-goland-getting-started
,本文譯者酌情量改動(dòng)