Skip to content

Commit 69ae538

Browse files
committed
entryhuman: Log the caller's relative module path and function
Instead of just their file's basename.
1 parent 2f71a40 commit 69ae538

File tree

1 file changed

+64
-1
lines changed

1 file changed

+64
-1
lines changed

internal/entryhuman/entry.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"os"
1111
"path/filepath"
12+
"runtime/debug"
1213
"strconv"
1314
"strings"
1415
"time"
@@ -64,7 +65,8 @@ func Fmt(w io.Writer, ent slog.SinkEntry) string {
6465
ents += fmt.Sprintf("%v\t", loggerName)
6566
}
6667

67-
loc := fmt.Sprintf("<%v:%v>", filepath.Base(ent.File), ent.Line)
68+
hpath, hfn := humanPathAndFunc(ent.File, ent.Func)
69+
loc := fmt.Sprintf("<%v:%v> %v", hpath, ent.Line, hfn)
6870
loc = c(w, color.FgCyan).Sprint(loc)
6971
ents += fmt.Sprintf("%v\t", loc)
7072

@@ -194,3 +196,64 @@ func quoteKey(key string) string {
194196
// Replace spaces in the map keys with underscores.
195197
return strings.ReplaceAll(key, " ", "_")
196198
}
199+
200+
var mainPackagePath string
201+
var mainModulePath string
202+
203+
func init() {
204+
// Unfortunately does not work for tests yet :(
205+
// See https://github.com/golang/go/issues/33976
206+
bi, ok := debug.ReadBuildInfo()
207+
if !ok {
208+
return
209+
}
210+
mainPackagePath = bi.Path
211+
mainModulePath = bi.Main.Path
212+
}
213+
214+
// humanPathAndFunc takes the absolute path to a file and an absolute module path to a
215+
// function in that file and returns the module path to the file. It also returns
216+
// the path to the function stripped of its module prefix.
217+
//
218+
// If the file is in the main Go module then its path is returned
219+
// relative to the main Go module's root.
220+
//
221+
// fn is from https://pkg.go.dev/runtime#Func.Name
222+
func humanPathAndFunc(filename, fn string) (hpath, hfn string) {
223+
// pkgDir is the dir of the pkg.
224+
// e.g. cdr.dev/slog/internal
225+
// base is the package name and the function name separated by a period.
226+
// e.g. entryhuman.humanPathAndFunc
227+
// There can be multiple periods when methods of types are involved.
228+
pkgDir, base := filepath.Split(fn)
229+
s := strings.Split(base, ".")
230+
pkg := s[0]
231+
hfn = strings.Join(s[1:], ".")
232+
233+
if pkg == "main" {
234+
// This happens with go build main.go
235+
if mainPackagePath == "command-line-arguments" {
236+
// Without a real mainPath, we can't find the path to the file
237+
// relative to the module. So we just return the base.
238+
return filepath.Base(filename), hfn
239+
}
240+
// Go doesn't return the full main path in runtime.Func.Name()
241+
// It just returns the path "main"
242+
// Only runtime.ReadBuildInfo returns it so we have to check and replace.
243+
pkgDir = mainPackagePath
244+
// pkg main isn't reflected on the disk so we should not add it
245+
// into the pkgpath.
246+
pkg = ""
247+
}
248+
249+
hpath = filepath.Join(pkgDir, pkg, filepath.Base(filename))
250+
251+
if mainModulePath != "" {
252+
relhpath, err := filepath.Rel(mainModulePath, hpath)
253+
if err == nil {
254+
hpath = "./" + relhpath
255+
}
256+
}
257+
258+
return hpath, hfn
259+
}

0 commit comments

Comments
 (0)