130

I was trying to throw errors in my Golang program with log.Fatal but, log.Fatal does not also print the line where the log.Fatal was ran. Is there no way of getting access to the line number that called log.Fatal? i.e. is there a way to get the line number when throwing an error?

I was trying to google this but was unsure how. The best thing I could get was printing the stack trace, which I guess is good but might be a little too much. I also don't want to write debug.PrintStack() every time I need the line number, I am just surprised there isn't any built in function for this like log.FatalStackTrace() or something that isn't costume.

Also, the reason I do not want to make my own debugging/error handling stuff is because I don't want people to have to learn how to use my special costume handling code. I just want something standard where people can read my code later and be like

"ah ok, so its throwing an error and doing X..."

The less people have to learn about my code the better :)

3
  • 1
    see golang.org/src/pkg/log/log.go?s=4418:4472#L120 Commented Jul 17, 2014 at 17:16
  • The moment you're printing line numbers it means I will have to dive into your code, so the "The less people have to learn about my code the better" is moot here. What you should do is have clear and concise errors.
    – Wessie
    Commented Jul 17, 2014 at 19:58
  • @Wessie I disagree here. I read it as OP knows people will need to look at the code, but wants to minimise the amount of digging they have to do. Rather than following many levels deep trying to work out what some custom function does, OP wants an easy simple clear thing that returns line number so future readers don't have to try and work out what is happening there.
    – Gostega
    Commented Apr 25, 2022 at 5:36

3 Answers 3

173

You can set the Flags on either a custom Logger, or the default to include Llongfile or Lshortfile

// to change the flags on the default logger
log.SetFlags(log.LstdFlags | log.Lshortfile)
6
  • So, for this to work I only need to set that at the top of one of the package files and it will available for all my files for that package? Commented Jul 17, 2014 at 18:31
  • 5
    Yes, if you're using a custom log you can use it like var mylog = log.New(os.Stderr, "app: ", log.LstdFlags | log.Lshortfile).
    – OneOfOne
    Commented Jul 17, 2014 at 18:33
  • 1
    do I really have to create a variable? I can't just do log.SetFlags(log.LstdFlags | log.Lshortfile) at the top of my go file? I get an error: expected declaration, found 'INDENT' log when I try to do log.SetFlags(log.LstdFlags | log.Lshortfile). It just irritates me to have to create a variable for it, why can't there be a log.Fatal("string", log.Flag). But creating a new variable log did work. Is it a standard thing to create log variables and stuff? Commented Jul 17, 2014 at 19:01
  • 4
    @Pinocchio: That error is because it's not valid Go, you can't have a bare function call at the top level. Put it in init() or some other entry-point.
    – JimB
    Commented Jul 17, 2014 at 20:00
  • 7
    you have to put it in func init() {}
    – OneOfOne
    Commented Jul 18, 2014 at 2:52
119

Short version, there's nothing directly built in, however you can implement it with a minimal learning curve using 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.
        _, filename, line, _ := runtime.Caller(1)
        log.Printf("[error] %s:%d %v", filename, 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, filename, line, _ := runtime.Caller(1)

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

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

playground

4
  • 12
    While the answer already given fixes the problem neatly, your solution alerted me for the existence of something awesome — the runtime package! Lovely stuff :) golang.org/pkg/runtime Commented Jun 12, 2017 at 17:54
  • 1
    The fn variable assigned from runtime.Caller() is actually the name of the file, not a function reference. I think of fn as function, not filename.
    – sshow
    Commented Sep 25, 2019 at 22:01
  • 1
    Awesome! Thanks. This is great example of runtime package usage. Very helpful for debugging through logs.
    – 18augst
    Commented Oct 9, 2019 at 16:14
  • Awesome! This answer is a great explain why logger knows where it called.
    – Li Jinyao
    Commented Jan 26, 2021 at 9:04
8

If you need exactly a stack trace, take a look at https://github.com/ztrue/tracerr

I created this package in order to have both stack trace and source fragments to be able to debug faster and log errors with much more details.

Here is a code example:

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

And here is the output: golang error stack trace

Not the answer you're looking for? Browse other questions tagged or ask your own question.