How to get relative path from runtime.Caller


(Ryze Kong) #1

I have a function which returns new error with caller file path (fn from runtime.Caller(1))

But I want to get relative path (relative to the dir in which go build, also the directory of main.go)

How to achieve this gracefully?

func NewError(e interface{}) error {
	if e != nil {
		_, fn, line, _ := runtime.Caller(1)
		return fmt.Errorf("[error] %s:%d %v", fn, line, e)
	}
	return nil
}

(Holloway) #2

Use os.Args?

It has all the arguments that starts the go program, including the go binary path (os.Args[0]).


(Ryze Kong) #3

go binary path can change if you move the binary file to somewhere else

but the fn returned by runtime.Caller will not change


(Christophe Meessen) #4

In a file relative.go define the following

package main

import (
    "runtime"
    "filepath"
)

func init() {
    initRelative()
}

var prefixPath string

func initRelative() {
    _, fileName, _, _ := runtime.Caller(0)
    prefixPath = filepath.Dir(fileName)
} 

func relative(path string) return {
    if filepath.HasPrefix(path, prefixPath) {
        return path[len(prefixPath):]
    }
    return path
}

This code assumes that the file relative.go is stored in the build root directory. You may need to modify the code to match your specific use case.


(Ryze Kong) #5

Thanks!

utils/relative.go

package utils

import (
	"path/filepath"
	"runtime"
	"strings"
)

func init() {
	initRelative()
}

var prefixPath string

func initRelative() {
	_, fileName, _, _ := runtime.Caller(0)
	prefixPath = filepath.Dir(filepath.Dir(fileName)) + string(filepath.Separator)

}

func relative(path string) string {
	return strings.TrimPrefix(path, prefixPath)
}

utils/functions.go

package utils

import (
	"fmt"
	"os"
	"runtime"
)

// NewError :
func NewError(e interface{}) error {
	if e != nil {
		_, fn, line, _ := runtime.Caller(1)
		return fmt.Errorf("[error] %s:%d %v", relative(fn), line, e)
	}
	return nil
}

(Christophe Meessen) #6

That’s better than my code. But relative.go is in the utils package and filepath.Dir(filepath.Dir(fileName)) removes only relative.go at end of fileName. So prefixPath ends with “…/utils/”. As a consequence, paths not starting with “…/utils/” won’t be changed into a relative path.

To fix that I suggest you change your initRelative function into the following.

func initRelative() {
	_, fileName, _, _ := runtime.Caller(0)
	prefixPath = fileName[:len(fileName)-len("utils/relative.go")]
}

This should set prefixPath to the root path of your project.


(Ryze Kong) #7

It will be the directory of main.go using filepath.Dir twice…