Как получить стек вызовов (stacktrace) всех горутин в Go для отладки
Стандартный способ: обработчик сигнала + runtime.Stack
В Go нет единой команды как jstack, но стандартная библиотека предоставляет мощный инструмент — пакет runtime. Основная функция — runtime.Stack(), которая возвращает стек вызовов всех (или текущей) горутин в виде строки. Самый популярный подход — повесить обработчик на сигнал SIGQUIT (или Ctrl+\ на Linux/macOS) или на SIGABRT. При получении сигнала программа автоматически выводит дамп всех горутин в stderr и завершается.
package main
import (
"fmt"
"os"
"os/signal"
"runtime"
"syscall"
)
func main() {
// Канал для получения сигналов
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGQUIT, syscall.SIGABRT)
// Горутина-обработчик сигналов
go func() {
<-sigs
// Буфер для стектрейса (обычно хватает 64 КБ на горутину)
buf := make([]byte, 1<<20) // 1 MB
n := runtime.Stack(buf, true) // true = все горутины
fmt.Fprintf(os.Stderr, "=== goroutine dump ===\n%s\n", buf[:n])
os.Exit(1)
}()
// Ваш код с горутинами
fmt.Println("Запуск. Нажмите Ctrl+\\ для дампа")
select {} // блокируем main
}
Если запустить такую программу и нажать Ctrl+\, в stderr появится примерно такой вывод:
goroutine 1 [select (no cases)]:
main.main()
/home/user/main.go:23 +0x105
goroutine 5 [syscall]:
os/signal.signal_recv()
/usr/local/go/src/runtime/sigqueue.go:152 +0x2e
os/signal.loop()
/usr/local/go/src/os/signal/signal_unix.go:23 +0x22
created by os/signal.Notify
/usr/local/go/src/os/signal/signal.go:128 +0x4a
Встроенный дамп при панике
Если ваша программа паникует (например, из-за разыменования nil-указателя или дедлока), Go по умолчанию выводит стектрейс всех горутин. Это работает "из коробки". Пример:
package main
func main() {
ch := make(chan int)
go func() {
<-ch // заблокируется навсегда
}()
// main завершится, но горутина останется
// panic не будет, но если вызвать panic, увидим все
panic("test")
}
Вывод паники покажет все горутины, включая заблокированную.
Программный дамп без завершения
Если вы не хотите убивать программу, а просто хотите получить дамп в лог, используйте тот же runtime.Stack в любой момент:
func dumpGoroutines() string {
buf := make([]byte, 1<<20)
n := runtime.Stack(buf, true)
return string(buf[:n])
}
Можно вызывать эту функцию по таймеру или при определённых условиях.
Продвинутый подход: net/http/pprof
Самый удобный способ для production — подключить пакет net/http/pprof. Он добавляет эндпоинт /debug/pprof/goroutine, который отдаёт стектрейсы в формате текста или protobuf.
import (
_ "net/http/pprof"
"net/http"
)
func main() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
// ... ваш код
}
Затем выполните curl http://localhost:6060/debug/pprof/goroutine?debug=2 — получите полный дамп.
Рекомендация
- Для отладки на локальной машине — используйте
SIGQUIT(Ctrl+\). - Для production —
pprofили запись дампа в лог по расписанию. - Не забывайте про
go tool pprof— он умеет анализировать дампы и показывать "горячие" горутины.
Альтернатива: использование переменной среды GOTRACEBACK
Если вам нужно получить дамп всех горутин при краше без изменения кода, установите переменную окружения GOTRACEBACK=all. По умолчанию Go выводит стектрейс только для упавшей горутины, но с этим флагом — для всех.
GOTRACEBACK=all ./myapp
Если программа упадёт, вы увидите полный дамп. Для постоянного включения в production можно установить эту переменную в Dockerfile или systemd unit.
Дополнительный инструмент: delve (dlv)
Отладчик delve позволяет в рантайме получить дамп горутин. Подключитесь к процессу:
dlv attach <PID>
(dlv) goroutines
(dlv) goroutine 1 stack
Это даёт интерактивный доступ, но требует установки delve и прав на отладку.
Совет по утечкам
Для обнаружения утечек горутин (когда они создаются, но не завершаются) используйте пакет runtime/pprof с записью профиля в файл:
import "runtime/pprof"
f, _ := os.Create("goroutine.prof")
pprof.Lookup("goroutine").WriteTo(f, 2)
f.Close()
Затем анализируйте через go tool pprof goroutine.prof.