Log là công cụ gỡ lỗi phổ biến nhất, nhưng nó có giới hạn cố hữu: nó chỉ cho biết cái bạn nghĩ trước là cần in. Khi gặp một bug mà bạn không đoán được nguyên nhân, thêm log rồi deploy lại rồi đợi tái hiện là vòng lặp chậm chạp. Có lúc bạn cần xem trạng thái đầy đủ tại một điểm: giá trị mọi biến, ngăn xếp lời gọi, và bước qua từng dòng để thấy logic đi đâu.

Delve (lệnh dlv) là debugger chính thức cho Go — hiểu goroutine, kiểu Go, và runtime. Quan trọng hơn, chế độ headless cho phép gỡ lỗi từ xa: server dlv chạy cạnh tiến trình (kể cả bên trong container production), còn client (hoặc IDE) kết nối qua mạng. Bài này chạy Delve thật — đặt breakpoint, xem biến, cả cục bộ lẫn qua mạng — và nêu rõ rủi ro bảo mật của debug từ xa.

Build với cờ tắt tối ưu để debug chính xác

$ go build -gcflags="all=-N -l" -o dbgbin main.go

Cờ -N tắt tối ưu và -l tắt inline. Vì sao cần: trình tối ưu của Go làm biến "biến mất" (bị gộp vào thanh ghi, loại bỏ), và dòng thực thi nhảy lung tung so với mã nguồn. Với build tối ưu, debugger báo "optimized out" khi bạn xem biến, hoặc breakpoint dừng ở dòng lạ. Tắt tối ưu để trạng thái debug khớp chính xác mã nguồn (chỉ dùng cho build debug, không phải production).

Debug cục bộ: breakpoint + xem biến

$ dlv exec dbgbin
(dlv) break tinhTong        # dừng khi vào hàm tinhTong
(dlv) continue              # chạy tới breakpoint
(dlv) print d.Items         # xem giá trị biến bất kỳ
(dlv) print gia
(dlv) next                  # bước qua từng dòng
(dlv) locals                # mọi biến cục bộ hiện tại

Các lệnh cốt lõi: break đặt điểm dừng (theo tên hàm hoặc file:line), continue chạy tới đó, print xem bất kỳ biểu thức nào, next/step bước từng dòng (next bước qua lời gọi, step vào trong), locals liệt kê mọi biến cục bộ.

Ảnh chụp đoạn mã và lệnh nền tối minh hoạ gỡ lỗi từ xa với Delve trong Go gắn debugger vào tiến trình đang chạy, vì sao có lúc log không đủ để soi lỗi log chỉ cho biết cái bạn nghĩ để in có bug cần xem trạng thái đầy đủ tại một điểm giá trị mọi biến stack bước từng dòng Delve dlv là debugger cho Go breakpoint xem biến step từ xa server dlv chạy cạnh tiến trình kể cả trong container client kết nối qua mạng soi lỗi ở môi trường không đụng tay được, build với cờ tắt tối ưu để debug chính xác go build gcflags all N l o dbgbin main.go N tắt tối ưu l tắt inline biến dòng khớp mã nguồn tối ưu làm biến biến mất dòng nhảy lung tung khi debug, debug cục bộ breakpoint cộng xem biến dlv exec dbgbin break tinhTong dừng khi vào hàm tinhTong continue chạy tới breakpoint print d.Items xem giá trị biến bất kỳ print gia next bước qua từng dòng locals mọi biến cục bộ hiện tại, từ xa headless server cộng client qua mạng trong container máy chủ chạy dlv làm server headless dlv exec dbgbin headless listen 2345 api-version 2 từ máy dev client kết nối tới server qua mạng dlv connect host 2345 hoặc gắn vào tiến trình đang chạy dlv attach pid IDE VS Code GoLand cũng nối được tới server dlv này

Hình 1: Delve trong Go — build với -gcflags="all=-N -l" để debug chính xác; debug cục bộ bằng dlv exec (break, print, next, locals); debug từ xa bằng headless server + dlv connect qua mạng, hoặc dlv attach <pid> vào tiến trình đang chạy.

Đo thật: cục bộ và từ xa

Cục bộ — breakpoint dừng đúng hàm, xem trạng thái đầy đủ:

(dlv) break tinhTong
Breakpoint 1 set at main.tinhTong() ./main.go:11
(dlv) continue
> [Breakpoint 1] main.tinhTong() ./main.go:11 (hits goroutine(1):1)
(dlv) print d.ID
1001
(dlv) print d.Items
[]string len: 3, cap: 3, ["ca-phe","banh","tra"]
(dlv) print gia
[]int len: 3, cap: 3, [45000,25000,5000]
(dlv) print len(d.Items)  -> 3   print len(gia) -> 3

Breakpoint dừng đúng main.go:11, và ta xem được mọi biến: d.ID = 1001, d.Items = mảng 3 chuỗi, gia = mảng 3 số, kể cả gọi hàm len() trong biểu thức print. Đây là trạng thái đầy đủ tại điểm dừng — thứ log không cho được trừ khi bạn đoán trước phải in đúng những biến này.

Từ xa — server headless cạnh tiến trình, client kết nối qua mạng:

# server (trong container):
API server listening at: [::]:2345
warning: Listening for remote connections
         (connections are not authenticated nor encrypted)

# client (dlv connect :2345):
Breakpoint 1 set at main.tinhTong() ./main.go:11
(dlv) print gia          -> [45000,25000,5000]
(dlv) print d.Items[0]   -> "ca-phe"

Server dlv lắng nghe trên :2345, client dlv connect tới nó qua mạng và đặt breakpoint, xem biến y như cục bộ. Đây chính là cách gỡ lỗi một tiến trình bên trong container hay trên máy chủ từ xa mà bạn không thể mở terminal trực tiếp — và IDE (VS Code, GoLand) cũng nối vào chính server dlv này để debug đồ họa. Ngoài exec, còn dlv attach <pid> để gắn vào một tiến trình đã đang chạy mà không cần khởi động lại.

Ảnh chụp bảng kết quả đo thật nền tối gỡ lỗi từ xa với Delve trong Go chạy bằng dlv exec cộng dlv connect Go 1.23 arm64 headless 2345, cục bộ breakpoint dừng đúng hàm xem biến dlv break tinhTong Breakpoint 1 set at main tinhTong ./main.go 11 dlv continue Breakpoint 1 main tinhTong ./main.go 11 hits goroutine 1 1 dlv print d.ID 1001 dlv print d.Items string len 3 cap 3 ca-phe banh tra dlv print gia int len 3 cap 3 45000 25000 5000 dlv print len d.Items 3 print len gia 3 xem trạng thái đầy đủ tại điểm dừng thứ log không cho, từ xa server headless cộng client kết nối qua mạng server trong container API server listening at 2345 warning listening for remote connections connections are not authenticated nor encrypted client dlv connect 2345 Breakpoint 1 set at main tinhTong ./main.go 11 dlv print gia 45000 25000 5000 dlv print d.Items 0 ca-phe debug tiến trình trong container từ máy dev qua mạng, cốt lõi Delve debugger cho Go breakpoint print biến next step stack build gcflags all N l tắt tối ưu inline để debug chính xác đo thật breakpoint dừng đúng main.go 11 print d.ID 1001 gia Items từ xa headless server 2345 client connect qua mạng hoặc attach pid container soi lỗi tiến trình trong container máy chủ không đụng tay được đánh đổi dlv từ xa không mã hóa xác thực chỉ qua mạng riêng SSH tunnel

Hình 2: Đo thật — cục bộ breakpoint dừng đúng main.go:11, print được d.ID=1001/Items/gia/len; từ xa server headless :2345 (kèm cảnh báo không mã hóa/xác thực) và client dlv connect xem biến qua mạng — debug tiến trình trong container từ máy dev.

Đánh đổi cần cân nhắc

dlv từ xa KHÔNG mã hóa và KHÔNG xác thực — rủi ro bảo mật lớn. Chú ý dòng cảnh báo thật ở trên: "connections are not authenticated nor encrypted". Bất kỳ ai kết nối được tới cổng dlv đều có toàn quyền trên tiến trình — đọc mọi biến (kể cả secret trong bộ nhớ), thậm chí gọi hàm, thay đổi trạng thái. TUYỆT ĐỐI không phơi cổng dlv ra Internet hay mạng không tin cậy. Chỉ dùng qua mạng riêng, hoặc bind localhost rồi tạo SSH tunnel từ máy dev tới container — đây là cách chuẩn để debug từ xa an toàn.

Breakpoint dừng tiến trình — cẩn thận trên production sống. Khi breakpoint được hit, toàn bộ tiến trình dừng lại cho tới khi bạn continue. Trên một dịch vụ production đang phục vụ người dùng, điều này nghĩa là mọi request đóng băng — timeout hàng loạt, có thể sập health check và bị restart. Debug từ xa trên production sống là biện pháp cuối cùng, làm trên một instance đã rút khỏi load balancer, không phải instance đang nhận traffic thật.

Build debug khác build production. Cờ -N -l tắt tối ưu làm code chậm hơn và binary lớn hơn — không dùng cho production. Nghĩa là để debug từ xa "đúng chuẩn", bạn cần build một binary debug riêng để deploy tạm, hoặc chấp nhận debug binary tối ưu với hạn chế (một số biến "optimized out"). Delve có debug được binary production, chỉ là kém chính xác hơn. Cân nhắc cái nào tùy tình huống.

Ba ý mang về

  1. Delve cho bạn xem trạng thái đầy đủ tại một điểm mà log không cho: breakpoint, print bất kỳ biến/biểu thức, next/step từng dòng, locals, stack — đo thật, breakpoint dừng đúng main.go:11 và in được d.ID=1001, d.Items, gia, cả len().
  2. Chế độ headless cho phép debug từ xa: server dlv chạy cạnh tiến trình (kể cả trong container), client dlv connect qua mạng đặt breakpoint và xem biến y như cục bộ (đo thật qua :2345); dlv attach <pid> gắn vào tiến trình đang chạy, IDE cũng nối được.
  3. Nêu rõ rủi ro nghiêm trọng: dlv từ xa không mã hóa/xác thực (ai kết nối được có toàn quyền — chỉ qua mạng riêng/SSH tunnel), breakpoint dừng cả tiến trình nên nguy hiểm trên production sống (làm trên instance đã rút khỏi LB), và build debug (-N -l) khác build production.

Phần sau ta xét gỡ lỗi sau khi sự cố đã xảy ra: core dump và phân tích sự cố trong Go — sinh core dump khi crash, và mổ xẻ nó bằng Delve để tìm nguyên nhân dù tiến trình đã chết.