| name | serr-structured-error-wrapper |
| description | Serr allows us to enrich our Go application errors with location, etc, and bubble them up to the caller where we only have to log once |
SErr - Structured Error Package for Go
SErr enriches errors with contextual attributes and metadata at each stack frame as errors bubble up to the caller. Rather than logging at every level, wrap errors with attributes throughout the call stack, then extract all accumulated context at the top level for structured logging.
Key Philosophy
- Errors bubble up with accumulated context without logging at intermediate frames
- Attributes are added at each function call level (location, function name, custom fields)
- All metadata can be extracted in structured format for logging systems
- Supports both machine-readable attribute maps and human-readable string representations
Import
import "github.com/rohanthewiz/serr"
Error Creation
New - Create a new structured error
err := serr.New("something went wrong")
err := serr.New("database error", "table", "users", "operation", "insert")
NewF - Create new error with formatted message
err := serr.NewF("failed to read file: %s", filename)
err := serr.NewF("item %d not found in %s", itemID, collection)
F - Create error with formatted message
err := serr.F("failed to process item %d: %s", itemID, reason)
NewSErr - Create with concrete SErr type
se := serr.NewSErr("my error", "att1", "val1", "att2", "val2")
Error Wrapping
Wrap - Wrap existing error with attributes
err := serr.Wrap(dbErr, "failed to save user")
err := serr.Wrap(dbErr, "table", "users", "user_id", userID)
WrapF - Wrap with formatted message
err := serr.WrapF(baseErr, "processing item %d failed with code %s", itemID, code)
WrapAsSErr - Wrap returning concrete SErr
se := serr.WrapAsSErr(err, "context", "additional info")
Attribute Access
Fields - Get all fields as string slice
fields := se.Fields()
UserFields - Get caller-supplied fields only, in order
Fields minus the reserved keys serr itself writes (location/function
frame context, msg wrap messages, and the user-message fields). Use it
to format an error for user-facing output, where frame context would be
noise:
err := serr.New("wrong number of parameters", "want", "1", "got", "0")
flds := serr.SErrFromErr(err).UserFields()
FieldsMap - Get attributes as map
mapFields := se.FieldsMap()
if val, ok := mapFields["user_id"]; ok {
fmt.Println("User:", val)
}
FieldsMapOfAny - Get attributes with any value types
se.AppendAttributes("count", 123, "enabled", true)
mapAny := se.FieldsMapOfAny()
count := mapAny["count"].(int)
FieldsMapOfSliceOfAny - Get attributes as map of slices
mapSlice := se.FieldsMapOfSliceOfAny()
locations := mapSlice["location"]
GetAttribute - Get single attribute value
if val, ok := se.GetAttribute("key"); ok {
}
Adding Attributes
AppendKeyValPairs - Add string key-value pairs
se.AppendKeyValPairs("key1", "val1", "key2", "val2")
AppendAttributes - Add any type key-value pairs
se.AppendAttributes("count", 42, "ratio", 3.14, "active", true)
String Formatting
StringFromErr - Get enriched string representation
errStr := serr.StringFromErr(err)
FieldsAsString - Format fields as key[value] pairs
str := se.FieldsAsString()
FieldsAsCustomString - Format with custom separators
str := se.FieldsAsCustomString(", ", " -> ")
User-Facing Messages
SetUserMsg - Set user-displayable message with severity
se.SetUserMsg("Your payment could not be processed", serr.Severity.Error)
UserMsg - Get user message and severity
msg, severity := se.UserMsg()
msg, severity := serr.UserMsg(err)
msg, severity := serr.UserMsgFromErr(err, "An unexpected error occurred")
Unwrapping and Core Error
GetError - Get the wrapped underlying error
coreErr := se.GetError()
Unwrap - Standard Go 1.13+ unwrapping
unwrapped := errors.Unwrap(se)
Duplicate Key Handling
When a key is repeated across wraps, values are concatenated with arrows showing the call order:
se1 := serr.NewSErr("error", "status", "initial")
se2 := serr.WrapAsSErr(se1, "status", "updated")
fields := se2.FieldsMap()
Converting errors to SErr
NewSerrNoContext / SErrFromErr - Build SErr without frame context
se := serr.NewSerrNoContext(err)
se := serr.SErrFromErr(err)
AppendAttributesToErr - Append attributes to an error if it is an SErr
serr.AppendAttributesToErr(err, "key", "value", "count", 42)
Nil Error Handling
Wrap safely handles nil errors:
result := serr.Wrap(nil, "some message")
Common Patterns
Multi-Layer Error Wrapping
func getUser(id string) (*User, error) {
user, err := db.Query(...)
if err != nil {
return nil, serr.Wrap(err, "user_id", id, "operation", "query")
}
return user, nil
}
func processUser(id string) error {
user, err := getUser(id)
if err != nil {
return serr.Wrap(err, "action", "process_user")
}
return nil
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
err := processUser(r.URL.Query().Get("id"))
if err != nil {
logger.LogErr(err, "Request failed")
}
}
Structured Logging Integration
import "github.com/rohanthewiz/logger"
if err != nil {
logger.LogErr(err, "Operation failed")
}
User Error Messages
func handlePayment(amount float64) error {
err := paymentService.Process(amount)
if err != nil {
se := serr.WrapAsSErr(err, "amount", fmt.Sprintf("%.2f", amount))
se.SetUserMsg("Payment could not be processed. Please try again.", serr.Severity.Error)
return se
}
return nil
}
if err != nil {
msg, sev := serr.UserMsg(err)
sendResponse(msg, sev)
}
Utility Functions
FunctionLoc - Get file location
loc := serr.FunctionLoc()
FunctionName - Get function name
name := serr.FunctionName()
Clone - Copy an SErr
clone := se.Clone()