¿Cómo se consigue que un programa Golang imprima el número de línea del error que acaba de llamar?

94

Estaba tratando de arrojar errores en mi programa Golang con log.Fatal, pero log.Fataltampoco imprime la línea donde log.Fatalse ejecutó. ¿No hay forma de acceder al número de línea que llamó log.Fatal? es decir, ¿hay alguna forma de obtener el número de línea cuando se produce un error?

Estaba tratando de buscar en Google esto, pero no estaba seguro de cómo. Lo mejor que pude conseguir fue imprimir el seguimiento de la pila , que supongo que es bueno, pero puede que sea demasiado. Tampoco quiero escribir debug.PrintStack()cada vez que necesito el número de línea, solo me sorprende que no haya ninguna función incorporada para esto log.FatalStackTrace()o algo que no sea un disfraz.

Además, la razón por la que no quiero hacer mi propia depuración / manejo de errores es porque no quiero que la gente tenga que aprender a usar mi código especial de manejo de vestuario. Solo quiero algo estándar donde la gente pueda leer mi código más tarde y ser como

"Ah, está bien, entonces está arrojando un error y haciendo X ..."

Cuanta menos gente tenga que aprender sobre mi código, mejor :)

Pinocho
fuente
En el momento en que esté imprimiendo números de línea, significa que tendré que sumergirme en su código, por lo que el "Cuanto menos gente tenga que aprender sobre mi código, mejor" es discutible aquí. Lo que debes hacer es tener errores claros y concisos.
Wessie

Respuestas:

122

Puede configurar los indicadores en un registrador personalizado o en el predeterminado para incluir LlongfileoLshortfile

// to change the flags on the default logger
log.SetFlags(log.LstdFlags | log.Lshortfile)
JimB
fuente
Entonces, para que esto funcione, solo necesito configurarlo en la parte superior de uno de los archivos del paquete y estará disponible para todos mis archivos para ese paquete.
Pinocho
4
Sí, si está usando un registro personalizado, puede usarlo como var mylog = log.New(os.Stderr, "app: ", log.LstdFlags | log.Lshortfile).
OneOfOne
¿De verdad tengo que crear una variable? ¿No puedo simplemente hacer log.SetFlags (log.LstdFlags | log.Lshortfile) en la parte superior de mi archivo go? Me sale un error: expected declaration, found 'INDENT' logcuando intento hacerlo log.SetFlags(log.LstdFlags | log.Lshortfile). Simplemente me irrita tener que crear una variable para él, ¿por qué no puede haber un log.Fatal("string", log.Flag). Pero la creación de un nuevo registro de variables funcionó. ¿Es algo estándar crear variables de registro y esas cosas?
Pinocho
3
@Pinocchio: Ese error se debe a que no es válido Go, no puede tener una llamada de función simple en el nivel superior. Ponlo en init () o en algún otro punto de entrada.
JimB
5
tienes que ponerlo enfunc init() {}
OneOfOne
94

Version corta, no hay nada directamente incorporado, sin embargo, puede implementarlo con una curva de aprendizaje mínima utilizando 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

Uno de uno
fuente
11
Si bien la respuesta ya dada soluciona el problema perfectamente, su solución me alertó de la existencia de algo asombroso: ¡el paquete de tiempo de ejecución! Cosas preciosas :) golang.org/pkg/runtime
Gwyneth Llewelyn
La fnvariable asignada desde runtime.Caller()es en realidad el nombre del archivo, no una referencia de función. Pienso en fn como una función, no como un nombre de archivo .
sshow
1
¡Increíble! Gracias. Este es un gran ejemplo de runtimeuso de paquetes. Muy útil para depurar registros.
18 de agosto
1

Si necesita exactamente un seguimiento de pila, eche un vistazo a https://github.com/ztrue/tracerr

Creé este paquete para tener tanto el seguimiento de la pila como los fragmentos de origen para poder depurar más rápido y registrar errores con muchos más detalles.

Aquí hay un ejemplo 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)
}

Y aquí está la salida: seguimiento de la pila de errores de golang

Johan
fuente