Documentation
¶
Overview ¶
Package efmt attaches structured key-value data to errors.
Errors are created with New or wrapped with Wrap, passing pairs built by KV. The data can later be read back from anywhere in the error tree with Values, Value, ValuePairs or, for logging, Attrs.
Errors produced by this package work with errors.Is, errors.As and errors.Unwrap. Values are also collected through errors that were wrapped by other means, such as fmt.Errorf with %w or errors.Join.
When the same key is set at several levels, the outermost value wins: wrapping an error can override what was attached closer to its origin.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Attrs ¶ added in v1.1.0
Attrs returns the values attached to err and the errors it wraps as slog attributes. Keys are deduplicated with the same precedence as Values, and attributes are ordered by the first occurrence of their key in ValuePairs, so the output is stable.
Example ¶
package main
import (
"context"
"errors"
"log/slog"
"os"
"github.com/vasilesk/efmt"
)
var errNotFound = errors.New("not found")
func findUser(id int) error {
return efmt.Wrap(errNotFound, "user lookup failed", efmt.KV("user_id", id))
}
func main() {
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
},
}))
err := efmt.Wrap(findUser(42), "request failed", efmt.KV("request_id", "abc"))
attrs := append(efmt.Attrs(err), slog.Any("error", err))
logger.LogAttrs(context.Background(), slog.LevelError, "request failed", attrs...)
}
Output: level=ERROR msg="request failed" request_id=abc user_id=42 error="request failed: user lookup failed: not found"
func New ¶
New creates a new formatted error with the given text and optional key-value pairs.
Example ¶
package main
import (
"fmt"
"github.com/vasilesk/efmt"
)
func main() {
err := efmt.New("user is underage", efmt.KV("age", 16), efmt.KV("required_age", 18))
fmt.Println(err)
fmt.Println(efmt.Values(err))
}
Output: user is underage map[age:16 required_age:18]
func Value ¶
Value retrieves a typed value for a specific key from the error chain. As with Values, the outermost occurrence of the key is used. It returns false if the key is not found or if that value is not of type T; in the latter case inner values with the same key are not consulted.
Example ¶
package main
import (
"errors"
"fmt"
"github.com/vasilesk/efmt"
)
var errNotFound = errors.New("not found")
func findUser(id int) error {
return efmt.Wrap(errNotFound, "user lookup failed", efmt.KV("user_id", id))
}
func main() {
err := findUser(42)
if id, ok := efmt.Value[int](err, "user_id"); ok {
fmt.Println("user id:", id)
}
if _, ok := efmt.Value[string](err, "user_id"); !ok {
fmt.Println("user id is not a string")
}
}
Output: user id: 42 user id is not a string
func Values ¶
Values extracts all key-value pairs from an error chain and converts them to a map. When multiple errors contain the same key, the value that ValuePairs returns first is used, i.e. the one from the outermost error.
Example (Join) ¶
package main
import (
"errors"
"fmt"
"github.com/vasilesk/efmt"
)
func main() {
err := errors.Join(
efmt.New("first", efmt.KV("first", 1)),
fmt.Errorf("second: %w", efmt.New("inner", efmt.KV("second", 2))),
)
fmt.Println(efmt.Values(err))
}
Output: map[first:1 second:2]
Example (Override) ¶
package main
import (
"fmt"
"github.com/vasilesk/efmt"
)
func main() {
inner := efmt.New("inner", efmt.KV("stage", "parse"), efmt.KV("line", 7))
outer := efmt.Wrap(inner, "outer", efmt.KV("stage", "load"))
fmt.Println(efmt.Values(outer))
}
Output: map[line:7 stage:load]
func Wrap ¶
Wrap wraps an existing error with additional text and optional key-value pairs. It preserves the original error's context while adding new information. Returns nil if the provided error is nil.
Example ¶
package main
import (
"errors"
"fmt"
"github.com/vasilesk/efmt"
)
var errNotFound = errors.New("not found")
func findUser(id int) error {
return efmt.Wrap(errNotFound, "user lookup failed", efmt.KV("user_id", id))
}
func main() {
err := efmt.Wrap(findUser(42), "request failed", efmt.KV("request_id", "abc"))
fmt.Println(err)
fmt.Println(errors.Is(err, errNotFound))
fmt.Println(efmt.Values(err))
}
Output: request failed: user lookup failed: not found true map[request_id:abc user_id:42]
Types ¶
type KeyValue ¶
KeyValue is a single piece of structured data attached to an error.
func KV ¶
KV creates a key-value pair for attaching to errors. This is a convenience function to create structured data entries.
func ValuePairs ¶
ValuePairs extracts all key-value pairs from an error and its wrapped errors. The error tree is traversed depth-first, the same way errors.Is does it: an error comes before the errors it wraps, and errors combined with errors.Join or several %w verbs are visited in order.