Jak sprawić, by program Golang wypisał numer wiersza błędu, który właśnie wywołał?

96

Próbowałem zgłosić błędy w moim programie Golang, log.Fatalale log.Fatalnie wypisuje również wiersza, w którym log.Fatalzostał uruchomiony. Czy nie ma możliwości uzyskania dostępu do numeru linii, który nazywa się log.Fatal? tj. czy istnieje sposób, aby uzyskać numer linii podczas zgłaszania błędu?

Próbowałem to wygooglować, ale nie byłem pewien jak. Najlepsze, co mogłem uzyskać, to wydrukowanie śladu stosu , co wydaje mi się dobre, ale może być trochę za dużo. Nie chcę też pisać za debug.PrintStack()każdym razem, gdy potrzebuję numeru wiersza, jestem po prostu zaskoczony, że nie ma żadnej wbudowanej funkcji log.FatalStackTrace()lub czegoś, co nie jest kostiumem.

Ponadto powodem, dla którego nie chcę tworzyć własnych rzeczy związanych z debugowaniem / obsługą błędów, jest to, że nie chcę, aby ludzie musieli uczyć się, jak używać mojego specjalnego kodu do obsługi kostiumów. Po prostu chcę czegoś standardowego, w którym ludzie będą mogli później przeczytać mój kod i być jak

„ah ok, więc zgłasza błąd i robi X ...”

Im mniej osób musi się uczyć o moim kodzie, tym lepiej :)

Pinokio
źródło
W momencie, gdy drukujesz numery linii, oznacza to, że będę musiał zagłębić się w Twój kod, więc kwestia „Im mniej ludzi musi się uczyć o moim kodzie, tym lepiej” jest tutaj dyskusyjna. Powinieneś mieć jasne i zwięzłe błędy.
Wessie,

Odpowiedzi:

124

Możesz ustawić flagi w niestandardowym rejestratorze lub domyślnie uwzględniać LlongfilelubLshortfile

// to change the flags on the default logger
log.SetFlags(log.LstdFlags | log.Lshortfile)
JimB
źródło
Tak więc, aby to zadziałało, wystarczy ustawić to na górze jednego z plików pakietu i będzie on dostępny dla wszystkich moich plików dla tego pakietu?
Pinokio,
4
Tak, jeśli używasz niestandardowego dziennika, możesz go używać w podobny sposób var mylog = log.New(os.Stderr, "app: ", log.LstdFlags | log.Lshortfile).
OneOfOne
czy naprawdę muszę tworzyć zmienną? Nie mogę po prostu zrobić log.SetFlags (log.LstdFlags | log.Lshortfile) na górze mojego pliku go? Pojawia się błąd: expected declaration, found 'INDENT' logkiedy próbuję to zrobić log.SetFlags(log.LstdFlags | log.Lshortfile). Po prostu irytuje mnie konieczność tworzenia dla niego zmiennej, dlaczego nie może istnieć log.Fatal("string", log.Flag). Ale utworzenie nowego dziennika zmiennych zadziałało. Czy tworzenie zmiennych dziennika i takie tam jest standardem?
Pinokio,
3
@Pinocchio: Ten błąd wynika z tego, że jest nieprawidłowy Go, nie możesz mieć samego wywołania funkcji na najwyższym poziomie. Umieść go w init () lub innym punkcie wejścia.
JimB,
5
musisz to włożyćfunc init() {}
OneOfOne
94

Krótka wersja, nie ma nic bezpośrednio wbudowanegojednak można go zaimplementować przy minimalnej krzywej uczenia się przy użyciu 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
źródło
11
Chociaż już udzielona odpowiedź rozwiązuje problem, Twoje rozwiązanie ostrzegło mnie o istnieniu czegoś niesamowitego - pakietu uruchomieniowego! Cudowne rzeczy :) golang.org/pkg/runtime
Gwyneth Llewelyn
fnZmienna przypisana od runtime.Caller()rzeczywistości jest nazwa pliku, a nie funkcja odniesienia. Myślę o fn jako funkcji, a nie nazwie pliku .
pokaż
1
Niesamowite! Dzięki. To świetny przykład runtimeużycia pakietu. Bardzo przydatne do debugowania za pomocą dzienników.
sierpnia,
2

Jeśli potrzebujesz dokładnego śladu stosu, zajrzyj na https://github.com/ztrue/tracerr

Utworzyłem ten pakiet, aby mieć zarówno ślad stosu, jak i fragmenty źródła, aby móc szybciej debugować i rejestrować błędy ze znacznie większą liczbą szczegółów.

Oto przykład kodu:

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

A oto wynik: Śledzenie stosu błędów golang

Johan
źródło