Comment obtenir un programme Golang pour imprimer le numéro de ligne de l'erreur qu'il vient d'appeler?

94

J'essayais de lancer des erreurs dans mon programme Golang avec log.Fatalmais, log.Fataln'imprime pas également la ligne où le a log.Fatalété exécuté. N'existe-t-il aucun moyen d'accéder au numéro de ligne qui a appelé log.Fatal? ie y a-t-il un moyen d'obtenir le numéro de ligne lors du lancement d'une erreur?

J'essayais de google mais je ne savais pas comment. La meilleure chose que j'ai pu obtenir a été d' imprimer la trace de la pile , ce qui, je suppose, est bon mais peut-être un peu trop. Je ne veux pas non plus écrire debug.PrintStack()chaque fois que j'ai besoin du numéro de ligne, je suis juste surpris qu'il n'y ait pas de fonction intégrée pour cela log.FatalStackTrace()ou quelque chose qui n'est pas un costume.

Aussi, la raison pour laquelle je ne veux pas faire mes propres trucs de débogage / gestion des erreurs est que je ne veux pas que les gens doivent apprendre à utiliser mon code spécial de gestion des costumes. Je veux juste quelque chose de standard où les gens peuvent lire mon code plus tard et être comme

"ah ok, donc il lance une erreur et fait X ..."

Moins les gens doivent apprendre mon code, mieux c'est :)

Pinocchio
la source
Au moment où vous imprimez les numéros de ligne, cela signifie que je devrai plonger dans votre code, donc le "Moins les gens doivent apprendre mon code, mieux c'est" est sans objet ici. Ce que vous devez faire, c'est avoir des erreurs claires et concises.
Wessie

Réponses:

122

Vous pouvez définir les indicateurs sur un enregistreur personnalisé ou la valeur par défaut pour inclure LlongfileouLshortfile

// to change the flags on the default logger
log.SetFlags(log.LstdFlags | log.Lshortfile)
JimB
la source
Donc, pour que cela fonctionne, je n'ai besoin que de le définir en haut de l'un des fichiers du package et il sera disponible pour tous mes fichiers pour ce package?
Pinocchio
4
Oui, si vous utilisez un journal personnalisé, vous pouvez l'utiliser comme var mylog = log.New(os.Stderr, "app: ", log.LstdFlags | log.Lshortfile).
OneOfOne
dois-je vraiment créer une variable? Je ne peux pas simplement faire log.SetFlags (log.LstdFlags | log.Lshortfile) en haut de mon fichier go? J'obtiens une erreur: expected declaration, found 'INDENT' logquand j'essaye de faire log.SetFlags(log.LstdFlags | log.Lshortfile). Cela m'irrite juste d'avoir à créer une variable pour cela, pourquoi n'y a-t-il pas de fichier log.Fatal("string", log.Flag). Mais la création d'un nouveau journal de variables a fonctionné. Est-ce une chose standard pour créer des variables de journal et autres?
Pinocchio
3
@Pinocchio: Cette erreur est due au fait que ce n'est pas valide Go, vous ne pouvez pas avoir un appel de fonction nu au niveau supérieur. Mettez-le dans init () ou un autre point d'entrée.
JimB
5
vous devez le mettre dansfunc init() {}
OneOfOne
94

Version courte, il n'y a rien directement intégré, mais vous pouvez l'implémenter avec une courbe d'apprentissage minimale en utilisant 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
la source
11
Alors que la réponse déjà donnée résout le problème parfaitement, votre solution m'a alerté de l'existence de quelque chose de génial - le package d'exécution! Lovely stuff :) golang.org/pkg/runtime
Gwyneth Llewelyn
La fnvariable attribuée à partir de runtime.Caller()est en fait le nom du fichier, pas une référence de fonction. Je considère fn comme une fonction, pas comme un nom de fichier .
sshow le
1
Impressionnant! Merci. C'est un excellent exemple d' runtimeutilisation de package. Très utile pour le débogage via les journaux.
18
1

Si vous avez besoin exactement d'une trace de pile, jetez un œil à https://github.com/ztrue/tracerr

J'ai créé ce package afin d'avoir à la fois la trace de la pile et les fragments source pour pouvoir déboguer plus rapidement et enregistrer les erreurs avec beaucoup plus de détails.

Voici un exemple de code:

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)
}

Et voici la sortie: trace de pile d'erreurs de Golang

Johan
la source