I have some code mostly copied from https://github.com/go-errors/errors which generates a Stack Trace

When I run it in VSCode debugger the output is exactly as expected. File names and function names are correct.

Debug Output

C:/work/projects/go/src/errtest/customerrors.go:17 (0x4d149f)
    NewDbError: dbe.stacktrace = NewStackTrace()
C:/work/projects/go/src/errtest/errortest04a.go:6 (0x4d156f)
    errorTest04a: err := NewDbError("This is a db error")
C:/work/projects/go/src/errtest/errortest04.go:4 (0x4d1507)
    errorTest04: errorTest04a()
C:/work/projects/go/src/errtest/main.go:4 (0x4d16b7)
    main: errorTest04()
C:/Go/src/runtime/proc.go:200 (0x431bc1)
    main: fn()
C:/Go/src/runtime/asm_amd64.s:1337 (0x458bc1)
    goexit: BYTE    $0x90   // NOP

When I build and run the same code however the output is not correct.

Build Output

C:/work/projects/go/src/errtest/customerrors.go:17 (0x4a5dbe)
        errorTest04a: dbe.stacktrace = NewStackTrace()
C:/work/projects/go/src/errtest/customerrors.go:17 (0x4a5db9)
        NewDbError: dbe.stacktrace = NewStackTrace()
C:/work/projects/go/src/errtest/errortest04.go:4 (0x4a5e88)
        main: errorTest04a()
C:/work/projects/go/src/errtest/errortest04.go:4 (0x4a5e83)
        errorTest04: errorTest04a()
C:/Go/src/runtime/proc.go:200 (0x42d5ac)
        main: fn()
C:/Go/src/runtime/asm_amd64.s:1337 (0x452911)
        goexit: BYTE    $0x90   // NOP

Question:

Why does the code generate different output depending on whether run in Debug or build?

NewDbErrordbe.stacktrace = NewStackTrace()customerrors.go:17
errorTest04abut in the file

The second line in the Build output shows what appears as the first line in the Debug output

main

It seems pretty weird that the same code generates different output.

End Question

I was thinking perhaps the compile removed file names but they are there, just in an incorrect order and with the function names out of order too.

If anyone can shed some light it would be appreciated. Thanks

Here is the code I have used

main.go

package main

func main() {
    errorTest04()
}

errortest04.go

package main

func errorTest04() {
    errorTest04a()
}

errortest04a.go package main

import "fmt"

func errorTest04a() {
    err := NewDbError("This is a db error")

    fmt.Print(string(err.stacktrace.Stack()))
}

customerorrs.go

package main

// DbError is a custom error that holds a dberror
type DbError struct {
    stacktrace *StackTrace

    errmsg string
}

func (e *DbError) Error() string {
    return e.errmsg
}

// NewDbError returns a new DbError
func NewDbError(e string) DbError {
    dbe := DbError{errmsg: e}
    dbe.stacktrace = NewStackTrace()
    return dbe
}
package main

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "runtime"
    "strings"
)

const maxStackDepth = 50

// StackTrace holds the stack data
type StackTrace struct {
    callers    []uintptr
    callerslen int
    frames     []StackFrame
}

// Stack - returns a formatted Stack Trace
func (st *StackTrace) Stack() []byte {
    buf := bytes.Buffer{}

    for _, frame := range st.frames {
        buf.WriteString(frame.String())
    }

    return buf.Bytes()
}

// NewStackTrace creates a new stack object
func NewStackTrace() *StackTrace {
    st := StackTrace{}
    st.callers = make([]uintptr, maxStackDepth)
    st.callerslen = runtime.Callers(2, st.callers[:])

    st.frames = make([]StackFrame, st.callerslen)

    for i := range st.frames {
        st.frames[i] = newStackFrame(st.callers[i])
    }

    return &st
}

// StackFrame - details of a caller. Lifted from https://github.com/go-errors/errors/blob/master/stackframe.go
type StackFrame struct {
    // The path to the file containing this ProgramCounter
    File string
    // The LineNumber in that file
    LineNumber int
    // The Name of the function that contains this ProgramCounter
    FnName string
    // The Package that contains this function
    Package string
    // The underlying ProgramCounter
    ProgramCounter uintptr
}

func newStackFrame(pc uintptr) StackFrame {

    frame := StackFrame{ProgramCounter: pc}
    if frame.Func() == nil {
        return frame
    }

    frame.Package, frame.FnName = packageAndName(frame.Func())

    // pc -1 because the program counters we use are usually return addresses,
    // and we want to show the line that corresponds to the function call
    // frame.File, frame.LineNumber = frame.Func().FileLine(pc)
    frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
    return frame
}

// Func returns the function that contained this frame.
func (frame *StackFrame) Func() *runtime.Func {
    if frame.ProgramCounter == 0 {
        return nil
    }
    return runtime.FuncForPC(frame.ProgramCounter)
}

// SourceLine gets the line of code (from File and Line) of the original source if possible.
func (frame *StackFrame) SourceLine() (string, error) {
    data, err := ioutil.ReadFile(frame.File)
    // _ replace with err

    if err != nil {
        return "", err
    }

    lines := bytes.Split(data, []byte{'
'})
    if frame.LineNumber <= 0 || frame.LineNumber >= len(lines) {
        return "???", nil
    }
    // -1 because line-numbers are 1 based, but our array is 0 based
    return string(bytes.Trim(lines[frame.LineNumber-1], " \t")), nil
}

// String returns the stackframe formatted in the same way as go does
// in runtime/debug.Stack()
func (frame *StackFrame) String() string {
    str := fmt.Sprintf("%s:%d (0x%x)
", frame.File, frame.LineNumber, frame.ProgramCounter)

    source, err := frame.SourceLine()
    if err != nil {
        return str
    }

    return str + fmt.Sprintf("\t%s: %s
", frame.FnName, source)
}

func packageAndName(fn *runtime.Func) (string, string) {
    name := fn.Name()
    pkg := ""

    // The name includes the path name to the package, which is unnecessary
    // since the file name is already included.  Plus, it has center dots.
    // That is, we see
    //  runtime/debug.*T·ptrmethod
    // and want
    //  *T.ptrmethod
    // Since the package path might contains dots (e.g. code.google.com/...),
    // we first remove the path prefix if there is one.
    if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
        pkg += name[:lastslash] + "/"
        name = name[lastslash+1:]
    }
    if period := strings.Index(name, "."); period >= 0 {
        pkg += name[:period]
        name = name[period+1:]
    }

    name = strings.Replace(name, "·", ".", -1)
    return pkg, name
}