- Notifications
You must be signed in to change notification settings - Fork40
palantir/stacktrace
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Look at Palantir, such a Java shop. I can't believe they want stack traces intheir Go code.
This is difficult to debug:
Inverse tachyon pulse failed
This gives the full story and is easier to debug:
Failed to register for villain discovery --- at github.com/palantir/shield/agent/discovery.go:265 (ShieldAgent.reallyRegister) --- --- at github.com/palantir/shield/connector/impl.go:89 (Connector.Register) ---Caused by: Failed to load S.H.I.E.L.D. config from /opt/shield/conf/shield.yaml --- at github.com/palantir/shield/connector/config.go:44 (withShieldConfig) ---Caused by: There isn't enough time (4 picoseconds required) --- at github.com/palantir/shield/axiom/pseudo/resource.go:46 (PseudoResource.Adjust) --- --- at github.com/palantir/shield/axiom/pseudo/growth.go:110 (reciprocatingPseudo.growDown) --- --- at github.com/palantir/shield/axiom/pseudo/growth.go:121 (reciprocatingPseudo.verify) ---Caused by: Inverse tachyon pulse failed --- at github.com/palantir/shield/metaphysic/tachyon.go:72 (TryPulse) ---
Note that stack traces arenot designed to be user-visible. We have found themto be valuable in log files of server applications. Nobody wants to see these inCLI output or a web interface or a return value from library code.
The intent isnot that we capture the exact state of the stack when an errorhappens, including every function call. For a library that does that, seegithub.com/go-errors/errors. The intenthere is to attach relevant contextual information (messages, variables) atstrategic places along the call stack, keeping stack traces compact andmaximally useful.
func WriteAll(baseDir string, entities []Entity) error { err := os.MkdirAll(baseDir, 0755) if err != nil { returnstacktrace.Propagate(err, "Failed to create base directory") } for _, ent := range entities { path := filepath.Join(baseDir, fileNameForEntity(ent)) err = Write(path, ent) if err != nil { returnstacktrace.Propagate(err, "Failed to write %v to %s", ent, path) } } return nil}
Propagate wraps an error to include line number information. This is going to beyour most common stacktrace call.
As in all of these functions, themsg
andvals
work likefmt.Errorf
.
The message passed to Propagate should describe the action that failed,resulting incause
. The canonical call looks like this:
result, err := process(arg)if err != nil { return nil,stacktrace.Propagate(err, "Failed to process %v", arg)}
To write the message, ask yourself "what does this call do?" What doesprocess(arg)
do? It processes
Pay attention that the message is not redundant with the one inerr
. In theWriteAll
example above, any error fromos.MkdirAll
will already contain thepath it failed to create, so it would be redundant to include it again in ourmessage. However, the error fromos.MkdirAll
will not identify that path ascorresponding to the "base directory" so we propagate with that information.
If it is not possible to add any useful contextual information beyond what isalready included in an error,msg
can be an empty string:
func Something() error { mutex.Lock() defer mutex.Unlock() err := reallySomething() returnstacktrace.Propagate(err, "")}
The purpose of""
as opposed to a separate function is to make you feel alittle guilty every time you do this.
This example also illustrates the behavior of Propagate whencause
is nil– it returns nil as well. There is no need to checkif err != nil
.
NewError is a drop-in replacement forfmt.Errorf
that includes line numberinformation. The canonical call looks like this:
if !IsOkay(arg) { returnstacktrace.NewError("Expected %v to be okay", arg)}
Occasionally it can be useful to propagate an error code while unwinding thestack. For example, a RESTful API may use the error code to set the HTTP statuscode.
The typestacktrace.ErrorCode
is a typedef for uint16. You name the set oferror codes relevant to your application.
const ( EcodeManifestNotFound = stacktrace.ErrorCode(iota) EcodeBadInput EcodeTimeout)
The special valuestacktrace.NoCode
is equal tomath.MaxUint16
, so avoidusing that. NoCode is the error code of errors with no code explicitly attached.
An ordinarystacktrace.Propagate
preserves the error code of an error.
PropagateWithCode and NewErrorWithCode are analogous to Propagate and NewErrorbut also attach an error code.
_, err := os.Stat(manifestPath)if os.IsNotExist(err) { returnstacktrace.PropagateWithCode(err, EcodeManifestNotFound, "")}
The error code mechanism can be useful by itself even where stack traces withline numbers are not required. NewMessageWithCode returns an error that printsjust likefmt.Errorf
with no line number, but including a code.
ttl := req.URL.Query().Get("ttl")if ttl == "" { return 0,stacktrace.NewMessageWithCode(EcodeBadInput, "Missing ttl query parameter")}
GetCode extracts the error code from an error.
for i := 0; i < attempts; i++ { err := Do() ifstacktrace.GetCode(err) != EcodeTimeout { return err } // try a few more times}return stacktrace.NewError("timed out after %d attempts", attempts)
GetCode returns the special valuestacktrace.NoCode
iferr
is nil or ifthere is no error code attached toerr
.
Stacktrace is released by Palantir Technologies, Inc. under the Apache 2.0License. See the includedLICENSE file for details.
We welcome contributions of backward-compatible changes to this library.
- Write your code
- Add tests for new functionality
- Run
go test
and verify that the tests pass - Fill out theIndividual orCorporate Contributor License Agreement and send it toopensource@palantir.com
- Submit a pull request
About
Stack traces for Go errors
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors4
Uh oh!
There was an error while loading.Please reload this page.