9
9
"io"
10
10
"os"
11
11
"path/filepath"
12
+ "runtime/debug"
12
13
"strconv"
13
14
"strings"
14
15
"time"
@@ -50,7 +51,7 @@ func c(w io.Writer, attrs ...color.Attribute) *color.Color {
50
51
// for extra lines in a log so if we did it here, the fields would be indented
51
52
// twice in test logs. So the Stderr logger indents all the fields itself.
52
53
func Fmt (w io.Writer , ent slog.SinkEntry ) string {
53
- var ents string
54
+ ents := c ( w , color . Reset ). Sprint ( "" )
54
55
ts := ent .Time .Format (TimeFormat )
55
56
ents += ts + " "
56
57
@@ -64,7 +65,8 @@ func Fmt(w io.Writer, ent slog.SinkEntry) string {
64
65
ents += fmt .Sprintf ("%v\t " , loggerName )
65
66
}
66
67
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>\t %v" , hpath , ent .Line , hfn )
68
70
loc = c (w , color .FgCyan ).Sprint (loc )
69
71
ents += fmt .Sprintf ("%v\t " , loc )
70
72
@@ -127,7 +129,7 @@ func Fmt(w io.Writer, ent slog.SinkEntry) string {
127
129
lines := strings .Split (multilineVal , "\n " )
128
130
for i , line := range lines [1 :] {
129
131
if line != "" {
130
- lines [i + 1 ] = strings .Repeat (" " , len (multilineKey )+ 4 ) + line
132
+ lines [i + 1 ] = c ( w , color . Reset ). Sprint ( "" ) + strings .Repeat (" " , len (multilineKey )+ 4 ) + line
131
133
}
132
134
}
133
135
multilineVal = strings .Join (lines , "\n " )
@@ -194,3 +196,64 @@ func quoteKey(key string) string {
194
196
// Replace spaces in the map keys with underscores.
195
197
return strings .ReplaceAll (key , " " , "_" )
196
198
}
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