Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ defer eg.Release() // Return to pool when done
eg.Add(err1)
eg.Add(err2)

if eg.HasErrors() {
return eg.Error()
if err := eg.Join(); err != nil {
return err
}
```

Expand Down Expand Up @@ -184,6 +184,11 @@ err := ewrap.New("error occurred",
zerologLogger := zerolog.New(os.Stdout)
err := ewrap.New("error occurred",
ewrap.WithLogger(adapters.NewZerologAdapter(zerologLogger)))

// Slog logger (Go 1.21+)
slogLogger := slog.New(slog.NewTextHandler(os.Stdout, nil))
err := ewrap.New("error occurred",
ewrap.WithLogger(adapters.NewSlogAdapter(slogLogger)))
```

## Error Formatting
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/features/error-groups.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ defer eg.Release() // Don't forget to release it back to the pool
eg.Add(ewrap.New("validation failed for email"))
eg.Add(ewrap.New("validation failed for password"))

// Check if there are any errors
if eg.HasErrors() {
fmt.Printf("Encountered errors: %v\n", eg.Error())
// Aggregate errors using errors.Join
if err := eg.Join(); err != nil {
fmt.Printf("Encountered errors: %v\n", err)
}
```

Expand Down
18 changes: 18 additions & 0 deletions docs/docs/features/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,24 @@ func setupZerolog() error {
}
```

### Slog Integration (Go 1.21+)

```go
import (
"log/slog"
"os"
"github.com/hyp3rd/ewrap/adapters"
)

func setupSlogLogger() error {
slogLogger := slog.New(slog.NewTextHandler(os.Stdout, nil))
logger := adapters.NewSlogAdapter(slogLogger)

return ewrap.New("operation failed",
ewrap.WithLogger(logger))
}
```

## Advanced Logging Patterns

### Contextual Logging
Expand Down
14 changes: 11 additions & 3 deletions error_group.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package ewrap

import (
"errors"
"fmt"
"log/slog"
"slices"
"strings"
"sync"
)
Expand Down Expand Up @@ -145,10 +147,16 @@ func (eg *ErrorGroup) Errors() []error {
eg.mu.RLock()
defer eg.mu.RUnlock()

result := make([]error, len(eg.errors))
copy(result, eg.errors)
return slices.Clone(eg.errors)
}

// Join aggregates all errors in the group using errors.Join.
// It returns nil if the group is empty.
func (eg *ErrorGroup) Join() error {
eg.mu.RLock()
defer eg.mu.RUnlock()

return result
return errors.Join(eg.errors...)
}

// Clear removes all errors from the group while preserving capacity.
Expand Down
23 changes: 23 additions & 0 deletions error_group_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ewrap

import (
"errors"
"fmt"
"sync"
"testing"
Expand Down Expand Up @@ -103,3 +104,25 @@ func BenchmarkErrorGroupPool(b *testing.B) {
}
})
}

func TestErrorGroupJoin(t *testing.T) {
eg := NewErrorGroup()
err1 := fmt.Errorf("first")
err2 := fmt.Errorf("second")

eg.Add(err1)
eg.Add(err2)

joined := eg.Join()
if joined == nil {
t.Fatal("expected joined error")
}
if !errors.Is(joined, err1) || !errors.Is(joined, err2) {
t.Fatalf("joined error does not contain original errors")
}

eg.Clear()
if eg.Join() != nil {
t.Fatal("expected nil when joining empty group")
}
}
6 changes: 2 additions & 4 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,8 @@ func Wrap(err error, msg string, opts ...Option) *Error {
wrappedErr.mu.RLock()

stack = wrappedErr.stack
// Create a new metadata map with the existing values
metadata = make(map[string]any, len(wrappedErr.metadata))

maps.Copy(metadata, wrappedErr.metadata)
// Clone metadata map using maps.Clone for simplicity
metadata = maps.Clone(wrappedErr.metadata)

wrappedErr.mu.RUnlock()
} else {
Expand Down
30 changes: 30 additions & 0 deletions pkg/ewrap/adapters/slog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//go:build go1.21

package adapters

import "log/slog"

// SlogAdapter adapts slog.Logger to the ewrap.Logger interface.
type SlogAdapter struct {
logger *slog.Logger
}

// NewSlogAdapter creates a new slog logger adapter.
func NewSlogAdapter(logger *slog.Logger) *SlogAdapter {
return &SlogAdapter{logger: logger}
}

// Error logs an error message with optional key-value pairs.
func (s *SlogAdapter) Error(msg string, keysAndValues ...any) {
s.logger.Error(msg, keysAndValues...)
}

// Debug logs a debug message with optional key-value pairs.
func (s *SlogAdapter) Debug(msg string, keysAndValues ...any) {
s.logger.Debug(msg, keysAndValues...)
}

// Info logs an info message with optional key-value pairs.
func (s *SlogAdapter) Info(msg string, keysAndValues ...any) {
s.logger.Info(msg, keysAndValues...)
}
34 changes: 34 additions & 0 deletions pkg/ewrap/adapters/slog_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//go:build go1.21

package adapters

import (
"bytes"
"log/slog"
"testing"

"github.com/stretchr/testify/assert"
)

func TestSlogAdapter(t *testing.T) {
var buf bytes.Buffer
handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})
logger := slog.New(handler)
adapter := NewSlogAdapter(logger)

t.Run("LogLevels", func(t *testing.T) {
buf.Reset()
adapter.Error("error message", "key1", "value1")
output := buf.String()
assert.Contains(t, output, "error message")
assert.Contains(t, output, "key1")
assert.Contains(t, output, "value1")

buf.Reset()
adapter.Debug("debug message", "key2", "value2")
output = buf.String()
assert.Contains(t, output, "debug message")
assert.Contains(t, output, "key2")
assert.Contains(t, output, "value2")
})
}