99[ ![ Maintainability] ( https://api.codeclimate.com/v1/badges/fe1720426006f3af30b0/maintainability )] ( https://codeclimate.com/github/muonsoft/errors/maintainability )
1010[ ![ Contributor Covenant] ( https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg )] ( CODE_OF_CONDUCT.md )
1111
12- Errors package for structured logging. Adds stack trace without a pain
12+ Errors package for structured logging with native ` log/slog ` integration . Adds stack trace without a pain
1313(no confuse with ` Wrap ` /` WithMessage ` methods).
1414
15+ > ** ⚠️ Breaking Changes in v0.5.0**
16+ > Version 0.5.0 replaces the custom field system with native ` log/slog ` integration.
17+ > If you're migrating from v0.4.1, please read the [ Migration Guide] ( MIGRATION.md ) .
18+
1519## Key features
1620
1721This package is based on well known [ github.com/pkg/errors] ( https://github.com/pkg/errors ) .
@@ -22,13 +26,17 @@ Key differences and features:
2226* minimalistic API: few methods to wrap an error: ` errors.Errorf() ` , ` errors.Wrap() ` ;
2327* adds stack trace idempotently (only once in a chain);
2428* ` errors.As() ` method is based on typed parameters (aka generics);
25- * options to skip caller in a stack trace and to add error fields for structured logging;
26- * error fields are made for the statically typed logger interface;
29+ * options to skip caller in a stack trace and to add error attributes for structured logging;
30+ * ** native integration with Go's ` log/slog ` ** - error attributes use ` slog.Attr ` ;
31+ * ** supports grouped attributes** via ` slog.Group ` ;
32+ * implements ` slog.LogValuer ` for seamless slog integration;
2733* package errors can be easily marshaled into JSON with all fields in a chain.
2834
2935## Additional features
3036
3137* ` errors.IsOfType[T any](err error) ` to test for error types.
38+ * ` errors.Attrs(err error) []slog.Attr ` to extract all attributes from error chain.
39+ * ` errors.Log(ctx, logger, level, err) ` convenience function for logging with slog.
3240
3341## Installation
3442
@@ -38,6 +46,10 @@ Run the following command to install the package
3846go get -u github.com/muonsoft/errors
3947```
4048
49+ Requires Go 1.21+ for ` log/slog ` support.
50+
51+ ** Migrating from v0.4.1?** See the [ Migration Guide] ( MIGRATION.md ) for detailed instructions.
52+
4153## How to use
4254
4355### ` errors.New() ` for package-level errors
@@ -57,14 +69,14 @@ func NewNotFoundError() error {
5769}
5870```
5971
60- ### ` errors.Errorf() ` for wrapping errors with formatted message, fields and stack trace
72+ ### ` errors.Errorf() ` for wrapping errors with formatted message, attributes and stack trace
6173
6274` errors.Errorf() ` is an equivalent to standard ` fmt.Errorf() ` . It formats according to a format specifier
6375and returns the string as a value that satisfies error. You can wrap an error using ` %w ` modifier.
6476
6577` errors.Errorf() ` also records the stack trace at the point it was called. If the wrapped error
66- contains a stack trace then a new one will not be added to a chain. Also, you can pass an
67- options to set a structured fields or to skip a caller in a stack trace.
78+ contains a stack trace then a new one will not be added to a chain. Also, you can pass
79+ options to set structured attributes or to skip a caller in a stack trace.
6880Options must be specified after formatting arguments.
6981
7082``` golang
@@ -73,7 +85,7 @@ var product Product
7385err := row.Scan (&product.ID , &product.Name )
7486if err != nil {
7587 // Use errors.Errorf to wrap the library error with the message context and
76- // error fields to be used for structured logging.
88+ // error attributes to be used for structured logging.
7789 return nil , errors.Errorf (
7890 " % w: %v " , errSQLError, err.Error (),
7991 errors.String (" sql" , findSQL),
@@ -82,18 +94,18 @@ if err != nil {
8294}
8395```
8496
85- ### ` errors.Wrap() ` for wrapping errors with fields and stack trace
97+ ### ` errors.Wrap() ` for wrapping errors with attributes and stack trace
8698
8799` errors.Wrap() ` returns an error annotating err with a stack trace at the point ` errors.Wrap() ` is called.
88100If the wrapped error contains a stack trace then a new one will not be added to a chain.
89- If err is nil, Wrap returns nil. Also, you can pass an options to set a structured fields or to skip a caller
101+ If err is nil, Wrap returns nil. Also, you can pass options to set structured attributes or to skip a caller
90102in a stack trace.
91103
92104``` golang
93105data , err := service.Handle (ctx, userID, message)
94106if err != nil {
95107 // Adds a stack trace to the line that was called (if there is no stack trace in the chain already)
96- // and adds fields for structured logging.
108+ // and adds attributes for structured logging.
97109 return nil , errors.Wrap (
98110 err,
99111 errors.Int (" userID" , userID),
@@ -102,9 +114,42 @@ if err != nil {
102114}
103115```
104116
117+ ### Working with grouped attributes
118+
119+ The package supports grouped attributes via ` slog.Group ` , allowing you to organize related attributes:
120+
121+ ``` golang
122+ err := errors.Wrap (
123+ dbErr,
124+ errors.Group (" request" ,
125+ slog.String (" method" , " POST" ),
126+ slog.String (" path" , " /api/users" ),
127+ slog.Int (" status" , 500 ),
128+ ),
129+ errors.Group (" database" ,
130+ slog.String (" query" , " INSERT INTO users..." ),
131+ slog.Duration (" duration" , 150 *time.Millisecond ),
132+ ),
133+ )
134+ ```
135+
136+ You can also use ` errors.Attr() ` to pass ` slog.Attr ` directly:
137+
138+ ``` golang
139+ err := errors.Wrap (
140+ err,
141+ errors.Attr (slog.Int64 (" timestamp" , time.Now ().Unix ())),
142+ errors.Attr (slog.Group (" metadata" ,
143+ slog.String (" version" , " v1.2.3" ),
144+ slog.Bool (" production" , true ),
145+ )),
146+ )
147+ ```
148+
105149### Printing error with stack trace
106150
107- You can use formatting with ` %+v ` modifier to print errors with message, fields for logging and a stack trace.
151+ You can use formatting with ` %+v ` modifier to print errors with message, attributes and stack trace.
152+ Grouped attributes are displayed using dot notation.
108153
109154Example
110155
@@ -117,7 +162,10 @@ func main() {
117162 )
118163 err = errors.Errorf (
119164 " find product: % w" , err,
120- errors.String (" requestID" , " 24874020-cab7-4ef3-bac5-76858832f8b0" ),
165+ errors.Group (" request" ,
166+ slog.String (" id" , " 24874020-cab7-4ef3-bac5-76858832f8b0" ),
167+ slog.String (" method" , " GET" ),
168+ ),
121169 )
122170 fmt.Printf (" %+v " , err)
123171}
@@ -127,7 +175,8 @@ Output
127175
128176```
129177find product: sql error: sql: no rows in result set
130- requestID: 24874020-cab7-4ef3-bac5-76858832f8b0
178+ request.id: 24874020-cab7-4ef3-bac5-76858832f8b0
179+ request.method: GET
131180sql: SELECT id, name FROM product WHERE id = ?
132181productID: 123
133182main.main
@@ -140,7 +189,7 @@ runtime.goexit
140189
141190### Marshal error into JSON
142191
143- Wrapped errors implements ` json.Marshaler ` interface. So you can easily marshal errors into JSON .
192+ Wrapped errors implement ` json.Marshaler ` interface. Grouped attributes are marshaled as nested objects .
144193
145194Example
146195
@@ -153,7 +202,10 @@ func main() {
153202 )
154203 err = errors.Errorf (
155204 " find product: % w" , err,
156- errors.String (" requestID" , " 24874020-cab7-4ef3-bac5-76858832f8b0" ),
205+ errors.Group (" request" ,
206+ slog.String (" id" , " 24874020-cab7-4ef3-bac5-76858832f8b0" ),
207+ slog.String (" method" , " GET" ),
208+ ),
157209 )
158210 errJSON , err := json.MarshalIndent (err, " " , " \t " )
159211 if err != nil {
@@ -169,7 +221,10 @@ Output
169221{
170222 "error" : " find product: sql error: sql: no rows in result set" ,
171223 "productID" : 123 ,
172- "requestID" : " 24874020-cab7-4ef3-bac5-76858832f8b0" ,
224+ "request" : {
225+ "id" : " 24874020-cab7-4ef3-bac5-76858832f8b0" ,
226+ "method" : " GET"
227+ },
173228 "sql" : " SELECT id, name FROM product WHERE id = ?" ,
174229 "stackTrace" : [
175230 {
@@ -191,31 +246,117 @@ Output
191246}
192247```
193248
194- ### Structured logging
249+ ### Structured logging with slog
195250
196- To use structured logging, you need to use an adapter for your logging system. It can be one of the
197- built-in adapters from the ` logging ` directory, or you can implement your own adapter using ` errors.Logger ` interface .
251+ The package provides native integration with Go's ` log/slog ` . Errors implement ` slog.LogValuer ` ,
252+ so they work seamlessly with any slog logger .
198253
199- Example of using an adapter for [ Logrus ] ( https://github.com/sirupsen/logrus ) .
254+ #### Using Log convenience function
200255
201256``` golang
202257err := errors.Errorf (
203- " sql error: % w" , sql.ErrNoRows ,
204- errors.String (" sql" , " SELECT id, name FROM product WHERE id = ?" ),
205- errors.Int (" productID" , 123 ),
258+ " database query failed: % w" , dbErr,
259+ errors.String (" query" , " SELECT * FROM users WHERE id = ?" ),
260+ errors.Int (" userID" , 123 ),
261+ errors.Group (" performance" ,
262+ slog.Duration (" duration" , 250 *time.Millisecond ),
263+ slog.Int (" retries" , 3 ),
264+ ),
206265)
207- err = errors.Errorf (
208- " find product: % w" , err,
209- errors.String (" requestID" , " 24874020-cab7-4ef3-bac5-76858832f8b0" ),
266+
267+ // Log error with all attributes and stack trace
268+ errors.Log (ctx, slog.Default (), slog.LevelError , err)
269+ ```
270+
271+ #### Extracting attributes manually
272+
273+ ``` golang
274+ err := errors.Errorf (
275+ " operation failed: % w" , someErr,
276+ errors.String (" operation" , " user.create" ),
277+ errors.Int (" userID" , 123 ),
210278)
211- logger := logrus.New ()
212- logrusadapter.Log (err, logger)
279+
280+ // Extract all attributes from error chain
281+ attrs := errors.Attrs (err)
282+
283+ // Use with slog
284+ slog.Error (" request failed" , append ([]any{slog.Any (" error" , err)}, attrsToAny (attrs)...)...)
213285```
214286
215- Output
287+ #### Using slog.LogValuer
216288
289+ Errors automatically work as ` slog.LogValuer ` , so you can log them directly:
290+
291+ ``` golang
292+ err := errors.Wrap (
293+ dbErr,
294+ errors.String (" table" , " users" ),
295+ errors.Int (" id" , 123 ),
296+ )
297+
298+ // The error will automatically provide its attributes to slog
299+ slog.Error (" database error" , " error" , err)
300+ ```
301+
302+ ### Custom LoggableError types
303+
304+ You can implement ` errors.LoggableError ` interface on your custom error types to provide
305+ structured attributes:
306+
307+ ``` golang
308+ type ValidationError struct {
309+ Field string
310+ Value interface {}
311+ Message string
312+ }
313+
314+ func (e *ValidationError ) Error () string {
315+ return fmt.Sprintf (" validation failed: %s " , e.Message )
316+ }
317+
318+ // Implement errors.LoggableError
319+ func (e *ValidationError ) Attrs () []slog .Attr {
320+ return []slog.Attr {
321+ slog.String (" field" , e.Field ),
322+ slog.Any (" value" , e.Value ),
323+ slog.String (" validation_message" , e.Message ),
324+ }
325+ }
326+
327+ // Usage
328+ err := &ValidationError{
329+ Field : " email" ,
330+ Value : " invalid-email" ,
331+ Message : " must be a valid email address" ,
332+ }
333+ wrapped := errors.Wrap (err, errors.String (" operation" , " user.create" ))
334+
335+ // All attributes from ValidationError will be included
336+ attrs := errors.Attrs (wrapped)
217337```
218- ERRO[0000] find product: sql error: sql: no rows in result set productID=123 requestID=24874020-cab7-4ef3-bac5-76858832f8b0 sql="SELECT id, name FROM product WHERE id = ?" stackTrace="[{main.main /home/strider/projects/errors/var/scratch.go 12} {runtime.main /usr/local/go/src/runtime/proc.go 250} {runtime.goexit /usr/local/go/src/runtime/asm_amd64.s 1571}]"
338+
339+ ## Available attribute options
340+
341+ The package provides convenience functions for creating attributes:
342+
343+ ``` golang
344+ errors.Bool (key string , value bool )
345+ errors.Int (key string , value int )
346+ errors.Uint (key string , value uint )
347+ errors.Float (key string , value float64 )
348+ errors.String (key string , value string )
349+ errors.Stringer (key string , value fmt.Stringer )
350+ errors.Strings (key string , values []string )
351+ errors.Value (key string , value interface {})
352+ errors.Time (key string , value time.Time )
353+ errors.Duration (key string , value time.Duration )
354+ errors.JSON (key string , value json.RawMessage )
355+
356+ // New slog-specific options
357+ errors.Attr (attr slog.Attr ) // Add any slog.Attr directly
358+ errors.WithAttrs (attrs ...slog .Attr ) // Add multiple slog.Attr values
359+ errors.Group (key string , attrs ...slog .Attr ) // Create a grouped attribute
219360```
220361
221362## Contributing
0 commit comments