oops, a Go error type that carries context and source fragments
🔥 Error handling library with context, assertion, stack trace and source fragments
At a glance
- What is it?
- oops is a zero-dependency Go library that returns an error type carrying stack traces, source code fragments, attributes and user info, so you can attach context where the failure happens and hand it to whichever logger you already use. It is v1, MIT licensed, and explicitly not a logging library.
- Who is it for?
- oops fits Go services that want context on every error without adopting a logging framework, especially teams already using zap, zerolog, logrus or slog who need one error type across layers. It does not fit codebases that keep errors as plain values with fmt.Errorf, since the builder chain and the OopsError type change how errors are created and inspected, and it does not replace a logger.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 21 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
An error type that holds context, not a logger that swallows it
oops is for Go developers who want errors to carry more than a message. The library returns an error type that satisfies the built-in error interface and, in the documentation's words, is designed as a drop-in replacement for it, adding stack traces, source code fragments, structured attributes and debugging hints. The positioning is careful and worth repeating, because it is the first thing to get wrong when evaluating the library. The README carries an explicit warning that this is not a logging library, and that oops should complement an existing logging toolchain such as zap, zerolog, logrus, slog or go-sentry. The stated design principle is separation of concerns, with error handling and logging treated as separate jobs. So the value proposition is not better log lines, it is a richer error object that a logger can render. The package is on pkg.go.dev under github.com/samber/oops, requires Go 1.21 or newer, and the README states it has no dependencies outside the Go standard library, which is what makes it cheap to add to a service that already has its own logging stack.
The builder chain: In, Tags, Code, User, With
Errors are constructed through a fluent builder, and the quick start shows the shape of it:
import "github.com/samber/oops"
func main() {
// Simple error with context
err := oops.
In("user-service").
Tags("database", "postgres").
Code("network_failure").
User("user-123", "email", "[email protected]").
With("path", "/hello/world").
Errorf("failed to fetch user: %s", "connection timeout")Each link adds a different kind of context: In names the service or module, Tags adds categorical labels, Code sets a machine-readable code, User attaches user identity information, and With adds arbitrary key and value attributes. The builder table in the README lists the full method set, and the shape of it tells you how the library wants to be used. Construction comes in pairs, .New and .Errorf for a fresh error, .Wrap and .Wrapf for an existing one, .Recover and .Recoverf to turn a panic into an error, and .Assert and .Assertf, which panic when a condition is false and can be chained. There is also .Join, which takes several errors. Because the returned object satisfies the error interface, you can compose it with native errors, and the wrap example in the README shows a second builder adding a trace identifier and a product id to an error that already came from elsewhere:
if err != nil {
return oops.
Trace("req-123").
With("product_id", "456").
Wrapf(err, "user operation failed")
}That composition is the reason to care about the library. A service can attach its own context at each layer while keeping one error type all the way up.
Installing it and running the first error
Installation is a single module fetch, and the README pairs it with an optional agent skill for code assistants:
go get github.com/samber/oops
# AI Agent Skill:
npx skills add https://github.com/samber/cc-skills-golang --skill golang-samber-oopsThere is no init step, no configuration file and no global logger to set up, which is worth saying because most error libraries in this space want one. Your first real use is the code above, placed in the package where the failure happens, so the stack trace and source fragment point at the right line. The library also points to a Go playground link for trying the API without setting up a module, which is the fastest way to see what the rendered error looks like. The versioning promise is explicit: the library is v1, follows SemVer strictly, and no breaking changes will be made to the APIs before v2.0.0. That is a real constraint for a team that adopts it, since it means the method set you start with is the method set you will have for the life of the v1 line, and your upgrade cost is low as long as you stay on v1.
Stack traces, source fragments and the files that hold them
The repository layout shows how the pieces are separated, and the file names are a decent map of the feature list. builder.go holds the fluent builder, error.go the error type, helpers.go and utils.go the small utilities, kv.go the key and value handling, context.go the context support, and sources.go the source code fragment capture. Two stack trace files sit next to each other, stacktrace.go and stacktrace_cleanpath.go, the second of which suggests a cleaned variant of the path handling. A recovery/ directory and an oops_n.go file exist alongside the main oops.go entry point. The examples/ directory is the part worth reading first, because it shows the library in the shapes you will actually use: separate directories for log, logrus, zap, zerolog, slog, panic, segfault, and sources. The examples/panic and examples/segfault directories in particular tell you the library has an opinion about what to do with an unrecoverable fault, while examples/sources is where you would look to see the source fragment feature in action. A benchmarks/ directory sits at the top level, and the README claims a lightweight footprint, but the published numbers in the README are not present, so any performance comparison would have to come from running the benchmark suite yourself.
Separation of concerns, and the loggers it refuses to replace
The README is unusually firm about the boundary, and that is a design decision worth weighing rather than a disclaimer to skim. Error handling and logging are separate jobs: oops builds the error, and something else decides how it is rendered, where it goes, and at what level. The library's own feature list names logger integration as a headline item, listing zap, zerolog, logrus, slog and go-sentry among the tools it works with, and the topic tags on the repository name the same set. The mechanics are visible in the source layout, with a loggers/ directory and one example directory per logger, so the integration is not a single generic hook but per-logger work. The practical consequence for a team is that a stack trace and source fragment arrive at the logging layer as data attached to an error, and what appears in your logs depends on your logger's formatter. The README does not document a shared formatter contract across those integrations, so if you rely on a particular rendering of the source fragment, verify it in the example directory for the logger you use instead of assuming it is uniform.
Building, testing and auditing the library itself
If you want to read the code before trusting it, the Makefile gives you the full workflow. Building is a single package build, and the test target runs the race detector across every module in the workspace:
build:
go build -v ./...
test:
go test -race -v ${MODULES} ./...There are watch variants for both tests and benchmarks that shell out to reflex, plus a bench target that runs with benchmem and a count of 3. The coverage target writes a cover.out profile and renders cover.html. A tools target installs the helper binaries, including golangci-lint, gotest, go-mod-outdated, nancy and benchstat, and then tidies the module. The lint target runs golangci-lint with a 60 second timeout, and there is a .golangci.yml in the repository root for that configuration. The audit target pipes the module list through nancy sleuth for known vulnerabilities, and there is a .trivyignore file for the container scanning side. The module file itself notes that test dependencies are removed on release, and it lists ulid, samber/lo, testify, the OpenTelemetry trace API and goleak in the require block, with otel, yaml and golang.org/x/text as indirect. A go.work file is present, so the repository is set up for multi-module development.
Where oops is the wrong tool
Several limits are visible. First, this is a library that changes how you write errors, not a drop-in at the type level. The documentation calls it a drop-in replacement for the error interface, and it does satisfy that interface, but adopting it means building errors through the builder rather than through fmt.Errorf or errors.New, and that is a code change across every layer that returns errors, not a single import. Second, the source fragment and stack trace features carry a cost that the README does not quantify: capturing a stack means walking frames, and a service that creates errors in a hot loop should measure that with the benchmarks/ directory before enabling it everywhere. Third, the assertion helpers panic, which is correct for an invariant but a different failure model from returning an error, and mixing the two in one codebase needs a convention. Fourth, if your team wants error reporting to own transport, retries and alerting, this library stops short of that. It gives you a better error value. Everything you would build on top of it, from structured logging to tracing spans to metrics, is still your work, and the go.opentelemetry.io/otel/trace dependency appears in the module file as a test-time entry rather than as a tracing integration in the library.
Editorial conclusion
oops fits Go services that want context on every error without adopting a logging framework, especially teams already using zap, zerolog, logrus or slog who need one error type across layers. It does not fit codebases that keep errors as plain values with fmt.Errorf, since the builder chain and the OopsError type change how errors are created and inspected, and it does not replace a logger. Before adopting, read the builder table in the README for the exact method set, check that your Go toolchain is 1.21 or newer, and look at the examples/log, examples/slog and examples/zap directories to see how the error type reaches your logger.
Frequently asked questions
How do I install oops?
Run go get github.com/samber/oops. The library requires Go 1.21 or newer and has no dependencies outside the Go standard library, so there is no other setup step.
Is oops a logging library?
No. The README carries an explicit warning that oops is not a logging library and should complement an existing toolchain such as zap, zerolog, logrus, slog or go-sentry. It builds richer error values, and separate directories under examples/ show the integration with each logger.
How do I add context to an error in oops?
Use the builder chain, for example oops.In("user-service").Tags("database", "postgres").Code("network_failure").With("path", "/hello/world").Errorf(...). The method set in the README also includes Wrap, Wrapf, Recover, Recoverf, Assert, Assertf and Join.
Will oops break my existing code when it updates?
The library is v1, follows SemVer strictly, and the README states that no breaking changes will be made to the APIs before v2.0.0, so upgrades within v1 are expected to be non-breaking.
Does oops capture stack traces and source code?
Yes. The feature list names out-of-the-box stacktraces and source code fragments, and the repository holds stacktrace.go, stacktrace_cleanpath.go and sources.go, with an examples/sources directory showing the feature in use.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/samber-oops)Was this useful?