通过Delve调试
#
本指南假定您已熟悉在Docker容器内运行Nakama。更多信息,请参阅通过Docker Compose安装Nakama文档。
main.go文件
#
我们在本指南中使用一个示例性main.go
文件,此文件注册单个测试RPC函数,以便可以通过Delve进行调试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| package main
import (
"context"
"database/sql"
"fmt"
"github.com/heroiclabs/nakama-common/runtime"
)
func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
logger.Debug("GO SERVER RUNTIME CODE LOADED")
initializer.RegisterRpc("RpcTest", RpcTest)
return nil
}
func RpcTest(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
logger.Debug("RpcTest RPC called")
payloadExists := false
if payload != "" {
payloadExists = true
}
return fmt.Sprintf("{ \"payloadExists\": %v }", payloadExists), nil
}
|
创建Dockerfile
#
为了以可通过dlv
进行调试的方式构建服务器运行时代码,我们需要制作一个Dockerfile,以确保在没有优化的情况下构建运行时代码和Nakama二进制都。
创建新的Dockerfile并调用它Dockerfile
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| FROM registry.heroiclabs.com/heroiclabs/nakama-pluginbuilder:3.22.0 AS builder
ENV GO111MODULE on
ENV CGO_ENABLED 1
WORKDIR /backend
COPY go.mod .
COPY vendor/ vendor/
COPY *.go .
RUN apt-get update && \
apt-get -y upgrade && \
apt-get install -y --no-install-recommends gcc libc6-dev
RUN go build --trimpath --gcflags "all=-N -l" --mod=vendor --buildmode=plugin -o ./backend.so
RUN go install github.com/go-delve/delve/cmd/dlv@latest
FROM registry.heroiclabs.com/heroiclabs/nakama-dsym:3.22.0
COPY --from=builder /go/bin/dlv /nakama
COPY --from=builder /backend/backend.so /nakama/data/modules/
COPY local.yml /nakama/data/
ENTRYPOINT [ "/bin/bash" ]
|
此处有几个注意事项。在builder
步骤中,我们安装了一些包,因此可以使用--gcflags "all=-N -l"
标志构建Go运行时插件,有效禁用所得插件文件中的优化。
我们还使用RUN go install github.com/go-delve/delve/cmd/dlv@latest
安装dlv
。我们将在下一步将其复制到docker映像中。
在最终Docker步骤中,我们使用nakama-dsym
映像而不是标准nakama
映像。此映像提供的Nakama二进制文件中优化已禁用,因此适合与dlv
一起运行,就像插件一样。
然后正常复制dlv
二进制文件,以及服务器运行时插件和local.yml
配置。
这里的另一个修改是我们覆盖了默认ENTRYPOINT
,这样Nakama就不会尝试自动启动。我们可以docker exec
到容器内,并自行运行dlv
。
添加Docker Compose文件
#
我们将用于调试的docker-compose.yml
文件与通过Docker Compose安装Nakama文档中的标准文件相似,但我们做了一些调整。此处我们确保构建自己的Dockerfile
映像,移除nakama
服务器上的entrypoint
属性,确保在运行容器时,Nakama不启动。我们还设置了在容器内成功运行dlv
所需的一些值。这些属性是security_opt
、stdin_open
和tty
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| version: '3'
services:
postgres:
command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all
environment:
- POSTGRES_DB=nakama
- POSTGRES_PASSWORD=localdb
expose:
- "8080"
- "5432"
image: postgres:12.2-alpine
ports:
- "5432:5432"
- "8080:8080"
volumes:
- data:/var/lib/postgresql/data
nakama:
build:
context: .
dockerfile: Dockerfile
depends_on:
- postgres
expose:
- "7349"
- "7350"
- "7351"
- "2345"
healthcheck:
test: ["CMD", "/nakama/nakama", "healthcheck"]
interval: 10s
timeout: 5s
retries: 5
links:
- "postgres:db"
ports:
- "7349:7349"
- "7350:7350"
- "7351:7351"
- "2345:2345"
restart: unless-stopped
security_opt:
- "seccomp:unconfined"
stdin_open: true
tty: true
volumes:
data:
|
Exec’ing到Docker中
#
完成Docker配置文件后,我们现在将启动Nakama和Postgres容器:
伴随着我们容器的运行,打开一个新的终端窗口,并exec
到Nakama docker容器中(其中,server-nakama-1
是Nakama容器的名称)。
1
| docker exec -it server-nakama-1 /bin/bash
|
这会将您放到Nakama容器中的bash
壳内。
手动迁移Nakama数据库
#
从这里开始,我们将首先确保迁移Postgres数据库以便与Nakama结合使用。
1
| /nakama/nakama migrate up --database.address postgres:localdb@postgres:5432/nakama
|
通过Delve运行Nakama
#
迁移数据库后可以通过Delve运行Nakama。注意Nakama二进制文件路径和Nakama配置标志之间额外的--
。 包含它很重要,否则dlv将不能正确地将那些标志传给Nakama二进制文件。
1
| ./dlv --log --log-output=debugger exec /nakama/nakama -- --config /nakama/data/local.yml --database.address postgres:localdb@postgres:5432/nakama
|
此处使用dlv exec
命令执行预编译的Nakama二进制文件,并开始调试会话。将显示dlv
命令行界面:
通过Delve调试自定义服务器运行时代码
#
Delve正等待用户输入告诉它如何继续。此时,如果键入continue
,Delve将继续执行Nakama,可以看到标准的Nakama服务器输出日志。然而,由于Nakama在启动完成后进入等待终止信号的状态,如果这样做,将无法调试运行时代码,因为无法插入Delve。
应该在Nakama进入这个等待阶段之前将断点设置到某个点,但之后我们的自定义服务器运行时插件已经加载。
为此,我们在main.go
的181行设置断点。
1
| (dlv) break main.go:181
|
您应该看到类似于以下确认断点的输出:
1
2
| 2022-06-30T12:29:48Z info layer=debugger created breakpoint: &api.Breakpoint{ID:1, Name:"", Addr:0x1f266cd, Addrs:[]uint64{0x1f266cd}, File:"github.com/heroiclabs/nakama/v3/main.go", Line:181, FunctionName:"main.main", Cond:"", HitCond:"", Tracepoint:false, TraceReturn:false, Goroutine:false, Stacktrace:0, Variables:[]string(nil), LoadArgs:(*api.LoadConfig)(nil), LoadLocals:(*api.LoadConfig)(nil), WatchExpr:"", WatchType:0x0, VerboseDescr:[]string(nil), HitCount:map[string]uint64{}, TotalHitCount:0x0, Disabled:false, UserData:interface {}(nil)}
Breakpoint 1 set at 0x1f266cd for main.main() github.com/heroiclabs./v3/main.go:181
|
现在,继续执行Nakama直到到达断点。
1
2
| (dlv) continue
> main.main() github.com/heroiclabs./v3/main.go:181 (hits goroutine(1):1 total:1) (PC: 0x1f266cd)
|
此时,应该加载我们的自定义服务器运行时代码。我们使用libraries
命令和查找插件.so
文件来确认这一点。
1
2
3
4
5
6
7
| (dlv) libraries
0. 0x7f8099127000 /lib/x86_64-linux-gnu/libdl.so.2
1. 0x7f8099106000 /lib/x86_64-linux-gnu/libpthread.so.0
2. 0x7f8098f45000 /lib/x86_64-linux-gnu/libc.so.6
3. 0x7f8099131000 /lib64/ld-linux-x86-64.so.2
4. 0x7f8062758000 /lib/x86_64-linux-gnu/libnss_files.so.2
5. 0x7f806177c000 /nakama/data/modules/backend.so
|
如上所述,自定义服务器运行时代码作为/nakama/data/modules/backend.so
列出。
对于本例,自定义服务器运行时有一个RpcTest
函数,它记录传送到Nakama服务器控制台的消息,并返回一个JSON响应,该响应的值指示是否提供了有效负载。
验证函数的存在:
1
2
| (dlv) funcs RpcTest
heroiclabs.com/nakama-server-sandbox.RpcTest
|
可在此函数上设置断点:
1
2
3
| (dlv) break RpcTest
2022-06-30T12:37:16Z info layer=debugger created breakpoint: &api.Breakpoint{ID:2, Name:"", Addr:0x7f395d54970a, Addrs:[]uint64{0x7f395d54970a}, File:"heroiclabs.com/nakama-server-sandbox/main.go", Line:14, FunctionName:"heroiclabs.com/nakama-server-sandbox.RpcTest", Cond:"", HitCond:"", Tracepoint:false, TraceReturn:false, Goroutine:false, Stacktrace:0, Variables:[]string(nil), LoadArgs:(*api.LoadConfig)(nil), LoadLocals:(*api.LoadConfig)(nil), WatchExpr:"", WatchType:0x0, VerboseDescr:[]string(nil), HitCount:map[string]uint64{}, TotalHitCount:0x0, Disabled:false, UserData:interface {}(nil)}
Breakpoint 2 set at 0x7f395d54970a for heroiclabs.com/nakama-server-sandbox.RpcTest() heroiclabs.com.-server-sandbox/main.go:14
|
然后继续执行:
现在转到Nakama控制台并从用户触发此Rpc将使我们能够调试此函数。
一旦我们到达断点,Delve将让我们重新获得控制,并允许检查各种情况:
1
2
| > heroiclabs.com/nakama-server-sandbox.RpcTest() heroiclabs.com.-server-sandbox/main.go:14 (hits goroutine(276):1 total:1) (PC: 0x7f395d54970a)
(dlv)
|
在此,首先可以检查我们已使用args
命令传递给RPC的参数。
1
2
3
4
5
6
7
8
| (dlv) args
ctx = context.Context(*context.valueCtx) 0xbeef000000000008
logger = github.com/heroiclabs/nakama-common/runtime.Logger(*github.com/heroiclabs/nakama/v3/server.RuntimeGoLogger) 0xbeef000000000108
db = ("*database/sql.DB")(0xc0004781a0)
nk = github.com/heroiclabs/nakama-common/runtime.NakamaModule(*github.com/heroiclabs/nakama/v3/server.RuntimeGoNakamaModule) 0xbeef000000000308
payload = "{\"hello\":\"world\"}"
~r0 = ""
~r1 = error nil
|
从上面我们可以看到,除了典型的RPC参数(上下文、记录器、数据库等),我们还看到传递给RPC的JSON有效负载的值。
接下来可以执行函数,查看对记录器的调用执行,然后检查局部变量。这可以使用next
和locals
命令来完成。
1
2
3
4
| (dlv) next
2022-06-30T13:18:38Z debug layer=debugger nexting
{"level":"debug","ts":"2022-06-30T13:18:38.878Z","caller":"nakama-server-sandbox/main.go:17","msg":"RpcTest RPC called","runtime":"go","rpc_id":"RpcTest"}
> heroiclabs.com/nakama-server-sandbox.RpcTest() heroiclabs.com.-server-sandbox/main.go:19 (PC: 0x7f819f58d830)
|
通过调用next
继续执行,直到到达第21行,然后使用locals
检查局部变量。
1
2
3
4
5
| (dlv) next
2022-06-30T13:18:46Z debug layer=debugger nexting
> heroiclabs.com/nakama-server-sandbox.RpcTest() heroiclabs.com.-server-sandbox/main.go:21 (PC: 0x7f819f58d835)
(dlv) locals
payloadExists = false
|
我们使用next
继续执行,观察payloadExists
值变化。
1
2
3
4
5
| (dlv) next
2022-06-30T13:22:57Z debug layer=debugger nexting
> heroiclabs.com/nakama-server-sandbox.RpcTest() heroiclabs.com.-server-sandbox/main.go:25 (PC: 0x7f819f58d84d)
(dlv) locals
payloadExists = true
|
可以看到,payloadExists
的值发生了变化,因为if
语句求值为true
,因此该变量的值发生变化。
我们调用continue
,完全继续执行,让RPC返回。
现在,您应该在Nakama控制台API Explorer中看到来自RPC的适当响应。
1
2
3
| {
"payloadExists": true
}
|
进一步调试
#
上面演示了一个非常基本的示例,即逐步执行自定义服务器运行时RPC,检查参数与局部变量,并继续执行。
Delve是一个功能强大的调试器,如果您想进一步了解可用的命令,请查看Delve CLI官方文档。