Generally I find if you're writing code like that, your code is too high level. Okay, you've propagated errors, but what is the calling code going to do with those errors? I find large functions like that usually have enough context to handle the errors themselves. Errors, after all, are just conditions with a special name. It's not common to propagate conditions up several layers of code, so why do the same with errors?
Go doesn't have (automatic) backtraces. If you don't wrap errors with a trace or with a custom message you often have no idea where the error came from.
if err != nil {
return mherr.WrapErr(err, "optional context")
}
I just wrote a custom function that adds the stack trace to the error. I also have it setup as a code snippet in VS Code so all I have to do is type "if err" and hit tab. Yes it looks a bit verbose but it adds approximately zero extra work and makes error handling extremely easy by default.
Also, turn on log flags to add line numbers.
log.SetFlags(log.Llongfile | log.LstdFlags)