Traceback em Go: Como Explorar a Pilha de Execução
Muitas linguagens de programação fornecem algum tipo de “stack trace” ou “backtrace”. Python usa o módulo traceback
, C no Linux oferece a função backtrace()
em execinfo.h
(não faz parte do padrão do C) e Java inclui Thread.dumpStack()
ou e.printStackTrace()
para exceções.
Stack Trace em Go
O Go fornece a função runtime.Caller
para obter informações sobre a pilha de execução. O exemplo a seguir exibe o arquivo, a linha e a função que chamaram info()
. Observe que o argumento 1
passado para runtime.Caller
indica que queremos informações sobre a função que chamou info()
:
func info() {
count := 1
for {
pc, filePath, lineNumber, ok := runtime.Caller(count)
if !ok {
return
}
funcName := runtime.FuncForPC(pc).Name()
fmt.Printf("%d %s:%d %s\n",
count,
filePath,
lineNumber,
funcName)
count++
}
}
Você pode usar esses dados em um sistema de log para rastrear a origem de um erro. O package log
padrão do Go já captura parte dessas informações, mas você pode querer personalizar a saída ou persistir os dados em um banco de dados. Em projetos com transações financeiras, por exemplo, é comum registrar cada transação com um ID único end-to-end que acompanha todas as etapas da transação e incluir alguns dados sobre a execução do programa torna muito mais fácil rastrear problemas e fazer auditorias.
Se você quiser imprimir a pilha de execução inteira, utilize um loop que chama runtime.Caller
sucessivamente até não haver mais informações disponíveis. O exemplo a seguir ilustra essa abordagem:
func traceback() {
i := 1
for {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
fmt.Printf("File: %s, Line: %d\n", file, line)
i++
}
}
Assim, você obtém um “stack trace” personalizado em Go. Para mais detalhes sobre runtime.Caller
, consulte a documentação oficial em: https://pkg.go.dev/runtime#Caller
runtime.CallersFrames
Outra forma de obter mais detalhes do programa é usar a função runtime.CallersFrames
, que retorna um iterador para explorar a pilha de execução. O exemplo a seguir exibe o arquivo, a linha e a função de cada frame da pilha de execução:
pcs := make([]uintptr, 10)
n := runtime.Callers(0, pcs)
pcs = pcs[:n]
frames := runtime.CallersFrames(pcs)
for {
frame, ok := frames.Next()
if !ok {
break
}
fmt.Printf(
"%s:%d %s\n",
frame.File,
frame.Line,
frame.Function,
)
}
Log
Apenas para deixar registrado, você pode muito facilmente fazer com que o log
do Go imprima a linha atual e o arquivo em que foi chamado, para isso basta usar o método SetFlags
do log
na inicialização do seu programa:
log.SetFlags(log.LstdFlags | log.Lshortfile)
-trimpath
O Go armazena o caminho completo dos arquivos do seu sistema e das dependências importadas. Se você compilar o programa na sua máquina, o caminho refletirá o diretório local, possivelmente expondo seu nome de usuário e outras informações irrelevantes ou sensíveis. Esses dados podem poluir seus registros de log. Para remover esses caminhos, use o parâmetro -trimpath
ao compilar. Assim, o Go reduzirá a exposição de detalhes sensíveis:
go build -trimpath main.go
Conclusão
Obter a pilha de execução em Go é simples e ajuda a rastrear a origem de erros em projetos grandes. Esse recurso é uma ferramenta valiosa para auditoria e diagnóstico. Use runtime.Caller
, a função de traceback personalizada ou runtime.CallersFrames
para coletar informações detalhadas, e utilize parâmetros como -trimpath
para proteger dados sensíveis.
Vídeo com a demonstração do código: