Como você faz com que um programa Golang imprima o número da linha do erro que acabou de chamar?

94

Estava tentando lançar erros no meu programa Golang com log.Fatalmas, log.Fataltambém não imprime a linha onde o log.Fatalfoi executado. Não há como obter acesso ao número da linha que chamou log.Fatal? ou seja, existe uma maneira de obter o número da linha ao lançar um erro?

Eu estava tentando pesquisar no Google, mas não sabia como. A melhor coisa que consegui foi imprimir o rastreamento da pilha , o que acho bom, mas pode ser um pouco demais. Eu também não quero escrever debug.PrintStack()sempre que preciso do número da linha, estou apenas surpreso que não haja nenhuma função embutida para este tipo log.FatalStackTrace()ou algo que não seja fantasia.

Além disso, o motivo pelo qual não quero fazer minhas próprias coisas de depuração / tratamento de erros é porque não quero que as pessoas tenham que aprender como usar meu código especial de tratamento de fantasias. Eu só quero algo padrão onde as pessoas possam ler meu código mais tarde e ser como

"ah ok, então é lançar um erro e fazer X ..."

Quanto menos pessoas aprenderem sobre meu código, melhor :)

Pinóquio
fonte
No momento em que você estiver imprimindo números de linha, isso significa que terei que mergulhar em seu código, então o "Quanto menos pessoas tiverem que aprender sobre meu código, melhor" é discutível aqui. O que você deve fazer é apresentar erros claros e concisos.
Wessie

Respostas:

122

Você pode definir os sinalizadores em um registrador personalizado ou o padrão para incluir LlongfileouLshortfile

// to change the flags on the default logger
log.SetFlags(log.LstdFlags | log.Lshortfile)
JimB
fonte
Então, para que isso funcione, eu só preciso definir isso no topo de um dos arquivos do pacote e ele estará disponível para todos os meus arquivos desse pacote?
Pinóquio
4
Sim, se você estiver usando um log personalizado, pode usá-lo como var mylog = log.New(os.Stderr, "app: ", log.LstdFlags | log.Lshortfile).
OneOfOne
eu realmente tenho que criar uma variável? Não posso simplesmente fazer log.SetFlags (log.LstdFlags | log.Lshortfile) na parte superior do meu arquivo go? Recebo um erro: expected declaration, found 'INDENT' logquando tento fazer log.SetFlags(log.LstdFlags | log.Lshortfile). Só me irrita ter que criar uma variável para isso, por que não pode haver um log.Fatal("string", log.Flag). Mas criar um novo log de variável funcionou. É uma coisa padrão criar variáveis ​​de log e outras coisas?
Pinóquio
3
@Pinocchio: Esse erro é porque não é válido, Go, você não pode ter uma chamada de função simples no nível superior. Coloque-o em init () ou em algum outro ponto de entrada.
JimB
5
você tem que colocá-lo emfunc init() {}
OneOfOne
94

Versão curta, não há nada embutido diretamente, no entanto, você pode implementá-lo com uma curva de aprendizado mínima usando runtime.Caller

func HandleError(err error) (b bool) {
    if err != nil {
        // notice that we're using 1, so it will actually log where
        // the error happened, 0 = this function, we don't want that.
        _, fn, line, _ := runtime.Caller(1)
        log.Printf("[error] %s:%d %v", fn, line, err)
        b = true
    }
    return
}

//this logs the function name as well.
func FancyHandleError(err error) (b bool) {
    if err != nil {
        // notice that we're using 1, so it will actually log the where
        // the error happened, 0 = this function, we don't want that.
        pc, fn, line, _ := runtime.Caller(1)

        log.Printf("[error] in %s[%s:%d] %v", runtime.FuncForPC(pc).Name(), fn, line, err)
        b = true
    }
    return
}

func main() {
    if FancyHandleError(fmt.Errorf("it's the end of the world")) {
        log.Print("stuff")
    }
}

playground

OneOfOne
fonte
11
Embora a resposta já dada resolva o problema perfeitamente, sua solução me alertou para a existência de algo incrível - o pacote runtime! Coisas adoráveis ​​:) golang.org/pkg/runtime
Gwyneth Llewelyn
A fnvariável atribuída de runtime.Caller()é, na verdade, o nome do arquivo, não uma referência de função. Eu penso em fn como uma função, não como um nome de arquivo .
sshow
1
Impressionante! Obrigado. Este é um ótimo exemplo de runtimeuso de pacote. Muito útil para depuração por meio de logs.
agosto,
1

Se você precisa exatamente de um rastreamento de pilha, dê uma olhada em https://github.com/ztrue/tracerr

Eu criei este pacote para ter rastreamento de pilha e fragmentos de origem para poder depurar mais rápido e registrar erros com muito mais detalhes.

Aqui está um exemplo de código:

package main

import (
    "io/ioutil"
    "github.com/ztrue/tracerr"
)

func main() {
    if err := read(); err != nil {
        tracerr.PrintSourceColor(err)
    }
}

func read() error {
    return readNonExistent()
}

func readNonExistent() error {
    _, err := ioutil.ReadFile("/tmp/non_existent_file")
    // Add stack trace to existing error, no matter if it's nil.
    return tracerr.Wrap(err)
}

E aqui está a saída: rastreamento de pilha de erro golang

João
fonte