[Home](/) This is my extended journey (~6 hours) of debugging a minor but annoying issue with the Goland IDE ([https://www.jetbrains.com/go/](https://www.jetbrains.com/go/)). This writeup is far more detailed than probably anyone will have use for, but I thought I'd document (almost journal?) the process I took. --- I like Google's logging package: [https://pkg.go.dev/github.com/golang/glog](https://pkg.go.dev/github.com/golang/glog) It outputs lines like: ```shell I0918 22:33:11.103850 23686 main.go:39] Starting... I0918 22:33:11.103997 23686 settings.go:60] Loaded settings from: /tmp/settings.textproto I0918 22:33:11.104048 23686 main.go:48] No saves found. Creating a new one. I0918 22:33:11.355302 23686 imguiutil.go:41] Loading fonts... I0918 22:33:11.410729 23686 main.go:195] Beginning game loop... I0918 22:33:20.347605 23686 game_saves.go:89] Wrote savefile to: "/tmp/savegame" I0918 22:33:20.347680 23686 main.go:187] Exiting game ``` This is great, but it would be even better if within Goland, I were able to click on the file and line number and jump right to that location in code. I noticed that when golang panics, the link is clickable so I knew this was possible. ```shell /home/omustardo/go/src/github.com/omustardo/omustardo.com/glog_demo/$ go run . /home/omustardo/go/src/github.com/omustardo/omustardo.com/glog_demo/main.go:16 panic: goroutine 1 [running]: main.main() /home/omustardo/go/src/github.com/omustardo/omustardo.com/glog_demo/main.go:18 +0x125 exit status 2 ``` I tested it out, and by logging anything that has a full path, Goland adds a link without any additional effort. Awesome! ``` fmt.Println("/home/omustardo/go/src/github.com/omustardo/omustardo.com/glog_demo/main.go:23") ``` So how do we get the fully qualified path and make glog use it? ### Finding the path #### Path prefix Since `glog` already had the latter half of the path, I (incorrectly as it turns out) assumed that I would need to get the prefix elsewhere. One option for this is to provide it as a flag: ```go package main import ( "flag" "github.com/golang/glog" "path/filepath" "strings" ) var buildRoot = flag.String("build_root", "", "The root directory that this code was built from. "+ "This is used in `glog` logging to write full paths, "+ "which then allows for jump to source in Goland.") func main() { // alsologtostderr is a flag for the `glog` package. It ensures logs get written to stderr instead of to local log files. flag.Set("logtostderr", "true") flag.Parse() if !strings.HasSuffix(*buildRoot, string(filepath.Separator)) { *buildRoot += string(filepath.Separator) } glog.Info("Hello " + *buildRoot) } ``` The flag needs to be set, but if you use `PWD` then bash will get the current path so you never need to think about it. ```shell $ go run . --build_root=${PWD} ``` #### Investigate panic The `panic()` in the example earlier was somehow able to print the full file path and line number, so golang binaries must somehow record information about where they are built, as well as a table of symbols and their original line numbers. I had no luck Googling this, so I went to the code. 1. ["panic()"](https://cs.opensource.google/go/go/+/refs/tags/go1.23.1:src/builtin/builtin.go;l=279;drc=1843464f014c946c1663de76249267486887626f) was a dead end because it isn't implemented there. 2. I searched the repo for "panic" and came across [a unit test](https://cs.opensource.google/go/go/+/master:src/runtime/debug/stack_test.go;l=53-71;drc=a22cb5cabe2bcc8ed02c43a66a1bd319cb28e89c) to ensure that the stack traces in panic logs look right. This test seemed to have the answer, though it was a bit hard to follow. [Here](https://cs.opensource.google/go/go/+/master:src/runtime/debug/stack_test.go;l=78-106;drc=a22cb5cabe2bcc8ed02c43a66a1bd319cb28e89c) are the relevant bits. The key to this is `runtime.GOROOT()`. Clicking on the function name [in the godoc](](https://pkg.go.dev/runtime#GOROOT)) took me to [its implementation](https://cs.opensource.google/go/go/+/refs/tags/go1.23.1:src/runtime/extern.go;l=339-345;drc=ca7d300509626e2071f3f5babc2e9c121d806fec). If the `GOROOT` environmental variable is set, it returns that value. If not, it returns the root used during `go build`. It's not clear why the build root isn't exposed directly, but the unit test uses a hack to get at it and so will we. 3. I replicated the unit test in a standalone demo, but it just stalled and never completed. There's probably something special about the `GO_RUNTIME_DEBUG_TEST_ENTRYPOINT`. ```go package main import ( "flag" "fmt" "go/build" ) func main() { flag.Set("logtostderr", "true") flag.Parse() path, err := buildPath() if err != nil { glog.Exitf("Failed to get build path: %v", err) } glog.Infof("Build Path: %q", path) } func buildPath() (string, error) { exe, err := os.Executable() if err != nil { return "", fmt.Errorf("failed to create executable: %w", err) } cmd := exec.Command(exe) cmd.Env = append(os.Environ(), "GOROOT=", "GO_RUNTIME_DEBUG_TEST_ENTRYPOINT=dumpgoroot") out, err := cmd.Output() if err != nil { return "", fmt.Errorf("failed to get command output: %w", err) } fileGoroot := string(bytes.TrimSpace(out)) filePrefix := "" if fileGoroot != "" { filePrefix = filepath.ToSlash(fileGoroot) + "/src/" } return filePrefix, nil } ``` 4. I investigated a different approach: reflection. As far as I can tell, golang doesn't support reflection on private fields of packages. There's even [a proposal](https://github.com/golang/go/issues/61796) to add it. 5. Feeling like I should have stopped at the `--build_root=${PWD}` approach, I pushed onward. Let's try to figure out how [`cmd/link` sets `defaultGoroot`](https://cs.opensource.google/go/go/+/refs/tags/go1.23.1:src/runtime/extern.go;l=334;drc=ca7d300509626e2071f3f5babc2e9c121d806fec) in the first place. I did a lot of digging through commit history, but I'm not used to this UI and I found it hard to navigate. I ended up looking for "defaultgoroot": [https://cs.opensource.google/search?q=defaultgoroot&sq=&ss=go](https://cs.opensource.google/search?q=defaultgoroot&sq=&ss=go). It is set in [the linker](https://cs.opensource.google/go/go/+/master:src/cmd/link/internal/ld/main.go;l=181;drc=d79c350916c637de911d93af689a5e4e7ab5a5bb) based on the value of `buildcfg.GOROOT`. That in turn is [set to the value](https://cs.opensource.google/go/go/+/master:src/internal/buildcfg/cfg.go;l=23;drc=b049837d97d340f0693871c64bc957eabc71d017) of the "GOROOT" environmental variable. I double checked the value of GOROOT: ```go package main import ( "flag" "github.com/golang/glog" "os" "runtime" ) var buildRoot = flag.String("build_root", "", "The root directory that this code was built from. "+ "This is used in `glog` logging to write full paths, "+ "which then allows for jump to source in Goland.") func main() { flag.Set("logtostderr", "true") flag.Parse() glog.Infof("GOROOT=%q", os.Getenv("GOROOT")) glog.Infof("runtime.GOROOT()=%q", runtime.GOROOT()) } ``` ```shell $ go run . I0919 01:21:14.925218 450629 main.go:19] GOROOT="/usr/local/go" I0919 01:21:14.925272 450629 main.go:20] runtime.GOROOT()="/usr/local/go" ``` I want paths like `/home/omustardo/go/src/github.com/omustardo/omustardo.com/glog_demo/main.go:22` so this isn't even starting with the right path! ### Reaching out At this point it had been ~4 hours of debugging. I wrote up [a stackoverflow question](https://stackoverflow.com/questions/79001751/linking-log-messages-to-lines-of-source-code-in-goland/), and then went to bed. ### Realization The following day I thought I'd ask in the golang-nuts Google Group. As I wrote up a post, I was looking at how the `glog` library gets the filename. I started at [glog.Infof](https://github.com/golang/glog/blob/97303146a4ffecf364d4300e07fca855d0062c43/glog.go#L507) and went down the chain of calls to [runtime.Caller()](https://github.com/golang/glog/blob/97303146a4ffecf364d4300e07fca855d0062c43/glog.go#L213) I realized that it might actually already have access to the full filepath, and a quick test confirmed it: ```go import "runtime" func main() { if _, file, line, ok := runtime.Caller(0); ok { fmt.Printf("%s:%d\n", file, line) } } ``` At this point I tested out printing a few combinations, and noticed that "main:23" on its own was actually linkable! So my initial assumption about having to print the full path was incorrect. I briefly thought it might be some special magic characters that Goland interprets, but that doesn't make sense because the Golang binary shouldn't do anything special for the IDE. A bit more testing and I realized that the issue was the right bracket: ```text main.go:80 // Link This is a stdout message: /home/omustardo/go/src/demo/main.go:81 // Link I0919 21:38:06.966554 13927 /home/omustardo/go/src/demo/main.go:85] // Not link ``` This was a bit shocking. Are standard `log` libraries not supported in Goland? ```go import ( "log" ) func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) log.Println("with file/line") } // 2024/09/19 21:21:09 main.go:9: with file/line // Not linkable due to the colon after the line number! ``` It seems they aren't. Programming without this feels like being hobbled, so I really hope it gets fixed. I submitted a [bug report](https://youtrack.jetbrains.com/issue/GO-17484/Jump-to-source-from-terminal-logs-is-broken-but-easily-fixable) to Jetbrains on 2024-09-19. ### Temporary fixes While it isn't fixed, I need a way to jump to file. There's no suffix configuration in [`log`](https://pkg.go.dev/log#pkg-constants) nor in [`glog`](https://pkg.go.dev/github.com/golang/glog). #### Custom logging library A bit of prompting to Claude starting with: ```text log/slog is a golang logging library Google's glog library outputs logs like: I0919 21:27:38.647434 12042 main.go:50] How can I make slog do the same thing? ``` eventually got to a custom logger: [demo_logger.go](https://www.omustardo.com/timeline/2024/goland_advice/demo_logger.go) with output that properly links to source (note the space after the line number): ```text $ go run . I0919 22:40:33.425737 18146 /home/omustardo/go/src/demo/main.go:79 ] This is an info message W0919 22:40:33.425789 18146 /home/omustardo/go/src/demo/main.go:80 ] This is a warning message E0919 22:40:33.425795 18146 /home/omustardo/go/src/demo/main.go:81 ] This is an error message ``` #### Modify `glog` Find where your local copy of the `glog` library lives. For me, having run `go mod vendor` in my project directory, the file was at `./vendor/github.com/golang/glog/internal/logsink/logsink.go`. Go to the line where it adds the closing bracket. Add a space or modify it however you want. [internal/logsink/logsink.go:245](https://github.com/golang/glog/blob/97303146a4ffecf364d4300e07fca855d0062c43/internal/logsink/logsink.go#L245).