Перейти к содержанию

Отладка Go в Bugsaur

Адаптер: dlv (Delve)

Поставить адаптер

go install github.com/go-delve/delve/cmd/dlv@latest
command -v dlv

Настроить

bugsaur init опознаёт проект по go.mod и пишет по профилю на каждый каталог с package main: сам корень модуля и прямые подкаталоги cmd/.

Один бинарник в корне

go-hello/
├── go.mod
├── main.go          package main
└── .env
version = 1
default = "go-hello"

[profiles.go-hello]
adapter = "dlv"
program = "."

Рабочий каталог — корень, .env читается. То же самое, что go run . из корня.

Несколько команд в cmd/

go-order/
├── go.mod
├── .env
├── configs/dev.yaml
├── migrations/
└── cmd/
    ├── go-order/main.go     package main
    ├── worker/main.go       package main
    └── shared/shared.go     package shared  ← не команда, профиля нет
version = 1
default = "go-order"

[profiles.go-order]
adapter = "dlv"
program = "cmd/go-order"

[profiles.worker]
adapter = "dlv"
program = "cmd/worker"

Рабочий каталог у обоих профилей один — корень модуля, — поэтому .env, configs/ и migrations/ видны каждой команде и cwd писать не нужно. Переключение: :DebugStart worker.

Go собирает сам Bugsaur

В отличие от остальных языков, Go-бинарники собирает Bugsaur — до старта адаптера, в фазе building, — и адаптеру отдаёт уже готовый файл. Настраивать это не нужно, оно включено по умолчанию.

Зачем. dlv debug собирает внутри запроса launch, кладёт результат в __debug_bin<случайное> и удаляет его за собой. Go не кэширует слинкованный исполняемый файл — он переиспользует его только по тому же пути. Из-за случайного имени каждый запуск платил полную линковку. Замерено на сервисе с бинарником в 74 МБ:

dlv debug своя сборка
первый запуск 14 с 25 с
повторный, код не менялся 14 с каждый раз ~2 с

Куда. <корень>/.bugsaur/build/<имя программы> — путь постоянный, на этом всё и держится. Ключ — программа, а не профиль, поэтому два профиля одного бинарника переиспользуют одну сборку. Каталог игнорирует себя сам, дописывать .gitignore не нужно.

Чем. По умолчанию — тем, что каталог адаптеров знает про dlv: go build -gcflags=all=-N -l -o {out} {program}. Профиль может перебить:

[profiles.worker]
adapter = "dlv"
program = "cmd/worker"

[profiles.worker.build]
command = "go"
args = ["build", "-tags=dev", "-gcflags=all=-N -l", "-o", "{out}", "./cmd/worker"]

command и args по отдельности, а не одной строкой: в -gcflags=all=-N -l пробел находится внутри одного аргумента, и разбор строки потребовал бы правил кавычек ровно там, где ошибка тихо ломает отладочную информацию. Подстановки: {out} — собираемый бинарник, {program} — то, что названо в program.

Когда не собирается: в режиме attach; для адаптеров, сборки которых нет в каталоге; и когда профиль сам назвал адаптеру mode или program в launch_arguments — значит, запуск ведут руками.

Если сборка не удалась, сессия падает в failed с текстом компилятора, а адаптер не поднимается вовсе.

Отладить тест

[profiles.orders-test]
adapter = "dlv"
program = "internal/orders"
# `go test` запускает бинарник из каталога пакета — без этой строки
# `testdata/` не найдётся, потому что по умолчанию cwd — корень.
cwd = "internal/orders"

[profiles.orders-test.launch_arguments]
mode = "test"

Либо пользуйтесь отладкой теста под курсором, для которой профиль не нужен:

bugsaur gui --project . --test-at internal/orders/order_test.go:31

См. Рецепты → Отладка тестов Go.

Долгая сборка: отдать Delve готовый бинарник

Если сборка долгая всегда, честнее вынести её из сессии:

go build -gcflags="all=-N -l" -o bin/api ./cmd/api
[profiles.api]
adapter = "dlv"
program = "bin/api"

[profiles.api.launch_arguments]
mode = "exec"

Тогда launch отвечает мгновенно. Цена — пересборка руками после каждой правки.

Два разных ключа mode

mode на уровне профиля — это launch / attach. mode внутри launch_arguments — режим самого Delve (debug, test, exec), и он перебивает сгенерированный debug.

Монорепа и go.work

Корнем становится ближайший модуль. Общий .env выше него — это одна строка:

# services/api/.bugsaur/config.toml
[profiles.api]
adapter = "dlv"
program = "."
env_file = "../../.env"

Маркером служит go.mod; про go.work определение не знает.

Диагностика

Симптом Вероятная причина Куда смотреть
Сессия падает с выводом компилятора не собралось почините код; текст лежит в панели Logs
launch висит минутами Delve собирает внутри запроса используйте mode = "exec" выше
В списке тысячи потоков это горутины Потоки
В тесте не находится testdata/ cwd по умолчанию — корень задайте cwd каталогом пакета
Сессия не стартует dlv не виден через PATH Адаптер не запускается