How to get relative path from runtime.Caller

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
}
2 Likes

Use os.Args?

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

1 Like

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

but the fn returned by runtime.Caller will not change

2 Likes

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.

2 Likes

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.ToSlash(filepath.Dir(filepath.Dir(fileName))) + "/"
}

func relative(path string) string {
	return strings.TrimPrefix(filepath.ToSlash(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
}

update:

use filepath.ToSlash (cross pratform)

2 Likes

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.

2 Likes

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

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.