Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log filename and line number #63

Closed
gust1n opened this issue Sep 22, 2014 · 119 comments
Closed

Log filename and line number #63

gust1n opened this issue Sep 22, 2014 · 119 comments
Assignees

Comments

@gust1n
Copy link

gust1n commented Sep 22, 2014

Both stdlib package log and many other logging frameworks are supporting this (e.g. https://github.com/inconshreveable/log15). Am I missing how to enable it or is it not supported?

@sirupsen
Copy link
Owner

There's currently no way of doing it. Would definitely be interested in a contribution adding this.

@gust1n
Copy link
Author

gust1n commented Sep 22, 2014

I was part of the discussion leading to this solution: inconshreveable/log15@dbf739e
Does it look good to you? Then I could start working on a PR in a couple of days. I guess the question os how to enable it, since we probably should let users decide whether to use it or not...

@sirupsen
Copy link
Owner

Hm.. it'd be nice if formatters and hooks could lazily load this if they need it, but I'm not sure if we can do that nicely. I don't want to call those functions and pass it down with the entry every time we log.

@derekdowling
Copy link

I'm interested in this as well. @sirupsen from your perspective how do you feel about adding some sort of a middleware layer that allows people to gather their own context and automatically append it to an entry's Fields?

That mechanism would allow us to add this sort of thing in quite easily. I am not sure about what other use cases this might also open up.

@sirupsen
Copy link
Owner

sirupsen commented Nov 4, 2014

I wouldn't be opposed to adding it by default and then allowing the formatters/hooks to use it appropriately. This is also useful for exception tracking etc., however, it might have a performance impact. We'll need to measure that

@c4milo
Copy link

c4milo commented Nov 11, 2014

Yes please, this feature is very much needed.

@aarongreenlee
Copy link
Collaborator

I like the idea of a middleware layer but agree with @sirupsen that this is not something I would want to occur in a production environment. If we're logging errors the existing ability to log detailed and helpful values should be sufficient; however, in a development environment quick access to filepath and line number is very valuable.

Thanks everyone for this discussion.

@sirupsen
Copy link
Owner

Yes, unless someone can prove a negligible performance impact I think this should be a hook.

@cep21
Copy link

cep21 commented Mar 13, 2015

I solve this in a performant manner by doing the following:

var log *logrus.Logger

func init() {
    log = mylog.NewLogger()
}


func NewLogger() *logrus.Logger {
    _, file, _, ok := runtime.Caller(1)
    if !ok {
        file = "unknown"
    }
    ret := logrus.New()
    inst := &logInstance{
        innerLogger: ret,
        file:        filepath.Base(file),
    }
    ret.Hooks.Add(inst)
    return ret
}

logInstance is a hook that adds "file" to the log entry. This saves me calling runtime.Caller each time I log a message while still having code that does

  log.WithField()....Info()

The disadvantage is that I only have one "log" per package that contains the filename of where it was created. This usually works enough for me since I use many packages. If not, I would give each file it's own log() variable.

@chreble
Copy link

chreble commented Jun 4, 2015

+1 it's a MUST-HAVE, and saying "completely API compatible with the standard library logger" in the README is wrong as we have that feature in the standard Go logger (SetFlags) http://golang.org/pkg/log/#SetFlags

@gonzaloserrano
Copy link

+1

@sirupsen
Copy link
Owner

Perhaps someone take a closer look at SetFlags implementation and port it to logrus? Currently all the implementations I've seen rely on hardcoding number of stack frames which is not very portable and doesn't work in some cases.

@chreble
Copy link

chreble commented Jun 15, 2015

@sirupsen, I can definitely have a look :)

@sirupsen
Copy link
Owner

@chreble awesome, let me know what you find!

@dmlyons
Copy link

dmlyons commented Jun 23, 2015

@sirupsen unfortunately, the SetFlags implementation in the standard library uses a (sort of) hardcoded number of stack frames: http://golang.org/src/log/log.go#L130. Each exported output function, such as Println, passes callDepth like this: http://golang.org/src/log/log.go#L169.

Is this such a big deal to implement in logrus?

@sirupsen
Copy link
Owner

sirupsen commented Jul 6, 2015

@dmlyons no it's not, I've just not needed this myself and haven't seen an implementation that hasn't failed on different platforms.

@davisford
Copy link

Has anyone resolved this in a reasonable way? I just want the filename and line number. I'd also prefer to be able to configure it to only print on specific levels (e.g. err, panic, warn)

@gonzaloserrano
Copy link

We have wrapped the stuff we most use from logrus with the file information, i don't know if you will find it useful or not but here you are https://gist.github.com/gonzaloserrano/d360c1b195f7313b6d19

@ryanwalls
Copy link

+1

1 similar comment
@ephemeralsnow
Copy link

+1

@santagada
Copy link

I will try to take a swing at it, as @dmlyons said it is pretty straightforward to implement.

@leonlee
Copy link

leonlee commented Sep 17, 2015

+1

@AkihiroSuda
Copy link

+1

@davisford
Copy link

My solution was to use https://github.com/inconshreveable/log15

@dgsb
Copy link
Collaborator

dgsb commented Nov 3, 2018

This feature is contained in v1.2.0

jaytaylor added a commit to jaytaylor/andromeda that referenced this issue Nov 18, 2018
@wesgt
Copy link

wesgt commented Nov 19, 2018

Here's how to have logrus log the filename and line number with the latest release. Basically just add log.SetReportCaller(true) when initializing.

Example:

package main

import (
	log "github.com/sirupsen/logrus"
)

func main() {
        // Add this line for logging filename and line number!
	log.SetReportCaller(true)

	log.Println("hello world")
}

Output:

INFO[0000]/Users/bob/go/src/github.com/bob/test/main.go:17 main.main() hello world

Hi all, is there a way to cut the longest file path? like just to display the

INFO[0000]/test/main.go:17 main.main() hello world

Thanks...QQ

@Jing-Li
Copy link

Jing-Li commented Nov 21, 2018

@andytsnowden ,

@Jing-Li I've been using the solution posted here: #63 (comment) in production for about 6 months now and haven't really had any issues at all. In my use-case we emit about 10m lines of logging daily and I really haven't seen logging be a "slow" point.

@andytsnowden , this helps a lot, thanks.

@Wamani
Copy link

Wamani commented Mar 23, 2019

Here's how to have logrus log the filename and line number with the latest release. Basically just add log.SetReportCaller(true) when initializing.
Example:

package main

import (
	log "github.com/sirupsen/logrus"
)

func main() {
        // Add this line for logging filename and line number!
	log.SetReportCaller(true)

	log.Println("hello world")
}

Output:

INFO[0000]/Users/bob/go/src/github.com/bob/test/main.go:17 main.main() hello world

Hi all, is there a way to cut the longest file path? like just to display the

INFO[0000]/test/main.go:17 main.main() hello world

Thanks...QQ

any idea on how to cut file name ?

@dgsb
Copy link
Collaborator

dgsb commented Mar 23, 2019

@Wamani please don't comment on closed issue, because it's kind of hard to find it back.
The basic filter on the github interface doesn't show closed issues whatever how recently they have been updated. The next time open a new one, it will be easier for maintainers to provide feedback and guidance.

That said if you want to customiza the output of the caller field you can give a specific formatting function in the CallerPrettyfier field of the JSONFormatter (https://godoc.org/github.com/sirupsen/logrus#JSONFormatter) or of the TextFormatter (https://godoc.org/github.com/sirupsen/logrus#TextFormatter).

You also have an example in the logrus repository on how to use that with the JSONFormatter here
https://github.com/sirupsen/logrus/blob/master/example_custom_caller_test.go

@miguelmota
Copy link

@wesgt @Wamani

Here's how to cut the longest file path:

package main

import (
	"fmt"
	"os"
	"runtime"
	"strings"

	"github.com/sirupsen/logrus"
)

func main() {
	log := logrus.New()
	log.SetReportCaller(true)
	log.Formatter = &logrus.TextFormatter{
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			repopath := fmt.Sprintf("%s/src/github.com/bob", os.Getenv("GOPATH"))
			filename := strings.Replace(f.File, repopath, "", -1)
			return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}

	log.Println("hello world")
}

Output:

INFO[0000]/test/main.go:23 main.main() hello world

@Wamani
Copy link

Wamani commented Apr 4, 2019

@wesgt @Wamani

Here's how to cut the longest file path:

package main

import (
	"fmt"
	"os"
	"runtime"
	"strings"

	"github.com/sirupsen/logrus"
)

func main() {
	log := logrus.New()
	log.SetReportCaller(true)
	log.Formatter = &logrus.TextFormatter{
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			repopath := fmt.Sprintf("%s/src/github.com/bob", os.Getenv("GOPATH"))
			filename := strings.Replace(f.File, repopath, "", -1)
			return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}

	log.Println("hello world")
}

Output:

INFO[0000]/test/main.go:23 main.main() hello world

thanks it works

@art-frela
Copy link

package main

import (
	"fmt"
	"os"
	"runtime"
	"strings"

	"github.com/sirupsen/logrus"
)

func main() {
	log := logrus.New()
	log.SetReportCaller(true)
	log.Formatter = &logrus.TextFormatter{
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			repopath := fmt.Sprintf("%s/src/github.com/bob", os.Getenv("GOPATH"))
			filename := strings.Replace(f.File, repopath, "", -1)
			return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}

	log.Println("hello world")
}

Thank a lot of!
I am using path.Base, this is more universal, I think ;-)

package main

import (
	"fmt"
	"path"
	"runtime"

	"github.com/sirupsen/logrus"
)

func main() {
	log := logrus.New()
	log.SetReportCaller(true)
	log.Formatter = &logrus.TextFormatter{
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			filename := path.Base(f.File)
			return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}

	log.Println("hello world")
}
`

@miguelmota
Copy link

@art-frela great suggestion on using path.Base!

@saurabh-hirani
Copy link

Worth mentioning that if you are using logrus JSONFormatter and you need filename and line support, there is a handy example in the codebase which adds funcname and filename. - https://github.com/sirupsen/logrus/blob/master/example_custom_caller_test.go

l.Formatter = &logrus.JSONFormatter{
		DisableTimestamp: true,
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			s := strings.Split(f.Function, ".")
			funcname := s[len(s)-1]
			_, filename := path.Split(f.File)
			return funcname, filename
		},
	}

A slight modification to it can return line number also

return funcname, filename + ":" + strconv.Itoa(f.Line)

@bruceadowns
Copy link

Worth mentioning that if you are using logrus JSONFormatter and you need filename and line support, there is a handy example in the codebase which adds funcname and filename. - https://github.com/sirupsen/logrus/blob/master/example_custom_caller_test.go

My code ended up looking a little like this:

logrus.SetReportCaller(true)
formatter = &logrus.JSONFormatter{
    CallerPrettyfier: func(f *runtime.Frame) (string, string) {
        s := strings.Split(f.Function, ".")
        funcName := s[len(s)-1]
        return funcName, fmt.Sprintf("%s:%d", path.Base(f.File), f.Line)
    },
}

Which logs source file name, function name, and line number.

@chibby0ne
Copy link

chibby0ne commented Feb 28, 2020

I can't reproduce this in logrus 1.4.2 using Go 1.14, I tried @art-frela snippet, and I can't reproduce it. This is my output:

INFO[0000]entry.go:359 github.com/sirupsen/logrus.(*Entry).Logln() hello world

I would have expected to see something like

INFO[0000]/test/main.go:23 main.main() hello world

Any ideas?

@dgsb
Copy link
Collaborator

dgsb commented Feb 28, 2020

related to #1096

@chibby0ne
Copy link

Yep it's Go 1.14 thanks @dgsb I thought I was going crazy for the last hour 👍

@huongtx498
Copy link

formatter := runtime.Formatter{ChildFormatter: &log.TextFormatter{
	FullTimestamp: true,
}}
formatter.Line = true
log.SetFormatter(&formatter)
log.SetOutput(os.Stdout)
log.SetLevel(log.InfoLevel)
log.WithFields(log.Fields{
	"file": "main.go",
}).Info("Server is running...")

Note: import ( runtime "github.com/banzaicloud/logrus-runtime-formatter"
log "github.com/sirupsen/logrus")

@kuchaguangjie
Copy link

package main

import (
	"fmt"
	"os"
	"runtime"
	"strings"

	"github.com/sirupsen/logrus"
)

func main() {
	log := logrus.New()
	log.SetReportCaller(true)
	log.Formatter = &logrus.TextFormatter{
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			repopath := fmt.Sprintf("%s/src/github.com/bob", os.Getenv("GOPATH"))
			filename := strings.Replace(f.File, repopath, "", -1)
			return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}

	log.Println("hello world")
}

Thank a lot of!
I am using path.Base, this is more universal, I think ;-)

package main

import (
	"fmt"
	"path"
	"runtime"

	"github.com/sirupsen/logrus"
)

func main() {
	log := logrus.New()
	log.SetReportCaller(true)
	log.Formatter = &logrus.TextFormatter{
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			filename := path.Base(f.File)
			return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}

	log.Println("hello world")
}
`

This lose coloring on console.

@shinebayar-g
Copy link

shinebayar-g commented Nov 27, 2022

Just for reference I came up with this to show filename:line. It's little bit cleaner than f.Function IMO.

log.SetReportCaller(true)
log.SetFormatter(&log.TextFormatter{
    FullTimestamp: true,
    CallerPrettyfier: func(f *runtime.Frame) (second string, first string) {
        _, b, _, _ := runtime.Caller(0)
        basepath := filepath.Dir(b)
        rel, err := filepath.Rel(basepath, f.File)
        if err != nil {
            log.Error("Couldn't determine file path\n", err)
        }
        return "", fmt.Sprintf("%s:%d", rel, f.Line)
    },
})
INFO[2022-11-27T03:16:16-08:00]main.go:34 Hello from main.go
INFO[2022-11-27T03:16:16-08:00]database/setup.go:31 Connected to database.
INFO[2022-11-27T03:16:16-08:00]database/setup.go:32 Running migrations.
INFO[2022-11-27T03:16:16-08:00]scheduler/task.go:17 Doing something.

@flyzhang007
Copy link

flyzhang007 commented Nov 27, 2022 via email

@shinebayar-g
Copy link

package main

import (
	"fmt"
	"os"
	"runtime"
	"strings"

	"github.com/sirupsen/logrus"
)

func main() {
	log := logrus.New()
	log.SetReportCaller(true)
	log.Formatter = &logrus.TextFormatter{
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			repopath := fmt.Sprintf("%s/src/github.com/bob", os.Getenv("GOPATH"))
			filename := strings.Replace(f.File, repopath, "", -1)
			return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}

	log.Println("hello world")
}

Thank a lot of!
I am using path.Base, this is more universal, I think ;-)

package main

import (
	"fmt"
	"path"
	"runtime"

	"github.com/sirupsen/logrus"
)

func main() {
	log := logrus.New()
	log.SetReportCaller(true)
	log.Formatter = &logrus.TextFormatter{
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			filename := path.Base(f.File)
			return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}

	log.Println("hello world")
}
`

This lose coloring on console.

You're right.. Using &log.TextFormatter loses all the colors..

log.SetFormatter(&log.TextFormatter{
    FullTimestamp: true,
})

@nimrodshn
Copy link

Is there a way to do this with the file and line number added as separate fields?

@flyzhang007
Copy link

flyzhang007 commented Apr 18, 2023 via email

@SharkFourSix
Copy link

SharkFourSix commented Jul 1, 2023

Is there a way to do this with the file and line number added as separate fields?

I think the best way would be to create an instance per package and pass a custom string (package name or tag) to the formatter. At least that way you know which package the error took place in. It just adds some context instead of a spontaneous error message.

@kuchaguangjie
Copy link

After set log.SetReportCaller(true)

The log looks like this:

time="2024-02-06T13:12:16+08:00" level=info msg="Server exit, bye." func=github.com/xxx/xxx/util/main_util.gracefulShutdown file="/home/eric/go/src/gitlab.com/xxx/xxx/util/main_util/main_util.go:108"

2 problems:

  • It contains func and file part, we only want file part usually.
  • and call log.SetFormatter(..) as suggested above, doesn't make the file part shorter.

@flyzhang007
Copy link

flyzhang007 commented Feb 6, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests