-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
510 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,90 @@ | ||
# errlog | ||
|
||
[![Test](https://github.com/ichizero/errlog/actions/workflows/test.yml/badge.svg)](https://github.com/ichizero/errlog/actions/workflows/test.yml) | ||
[![Go Reference](https://pkg.go.dev/badge/github.com/ichizero/errlog.svg)](https://pkg.go.dev/github.com/ichizero/errlog) | ||
[![Codecov](https://codecov.io/gh/ichizero/errlog/branch/main/graph/badge.svg)](https://codecov.io/gh/ichizero/errlog) | ||
[![Go Report Card](https://goreportcard.com/badge/github.com/ichizero/errlog)](https://goreportcard.com/report/github.com/ichizero/errlog) | ||
|
||
`errlog` is a error logging package based on [log/slog](https://pkg.go.dev/log/slog) standard library. | ||
It provides error logging with stack trace and source location. | ||
It does not require any third-party package. | ||
|
||
## 🚀 Installation | ||
|
||
```bash | ||
go get github.com/ichizero/errlog | ||
``` | ||
|
||
## 🧐 Usage | ||
|
||
### Initialize logger | ||
`errlog.NewHandler` wraps `slog.Handler`, so you can provide `*slog.JSONHandler`, `*slog.TextHandler`, | ||
or any other handler. | ||
|
||
```go | ||
h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}) | ||
hErr := errlog.NewHandler(h, &errlog.HandlerOptions{OverrideSource: true, SuppressStackTrace: false}) | ||
slog.SetDefault(slog.New(hErr)) | ||
``` | ||
|
||
### Logging error with stack trace | ||
|
||
#### With errlog.Err | ||
`errlog.Err` wraps error with stack trace and returns `slog.Attr` with key `error`. | ||
|
||
```go | ||
err := errors.New("test error") | ||
slog.ErrorContext(ctx, "test", errlog.Err(err)) | ||
``` | ||
|
||
#### With custom error | ||
|
||
`errlog.NewHandler` outputs stack trace with the error that implements `errlog.StackTrace` interface, | ||
so you can provide custom error with stack trace. | ||
|
||
```go | ||
type yourCustomError struct { | ||
err error | ||
stack []uintptr | ||
} | ||
|
||
func (e yourCustomError) Stack() []uintptr { | ||
return e.stack | ||
} | ||
``` | ||
|
||
If so, you can log stack trace without using `errlog.Err`. | ||
|
||
```go | ||
err := newYourCustomError("error") | ||
slog.ErrorContext(ctx, "test", slog.Any("error", err)) | ||
``` | ||
|
||
#### Example usage | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"log/slog" | ||
"os" | ||
|
||
"github.com/ichizero/errlog" | ||
) | ||
|
||
func main() { | ||
h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}) | ||
hErr := errlog.NewHandler(h, &errlog.HandlerOptions{OverrideSource: true, SuppressStackTrace: false}) | ||
slog.SetDefault(slog.New(hErr)) | ||
|
||
ctx := context.Background() | ||
|
||
err := errors.New("test error") | ||
slog.ErrorContext(ctx, "test", errlog.Err(err)) | ||
|
||
err = errlog.WrapError(err) | ||
slog.ErrorContext(ctx, "test", slog.Any("error", err)) | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package errlog | ||
|
||
import ( | ||
"log/slog" | ||
) | ||
|
||
const ( | ||
ErrorKey = "error" | ||
StackTraceKey = "stack_trace" | ||
) | ||
|
||
// Err returns an attribute that contains the given error. | ||
// If the error does not implement the StackTracer interface, it will be wrapped with the stack trace. | ||
func Err(err error) slog.Attr { | ||
if _, ok := err.(StackTracer); !ok { | ||
err = wrapError(err, 1) | ||
} | ||
return slog.Any(ErrorKey, err) | ||
} | ||
|
||
// WrapError wraps the given error with a stack trace. | ||
func WrapError(err error) error { | ||
return wrapError(err, 1) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package errlog_test | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"log/slog" | ||
"testing" | ||
"testing/slogtest" | ||
|
||
"github.com/ichizero/errlog" | ||
) | ||
|
||
func TestRun(t *testing.T) { | ||
t.Parallel() | ||
|
||
var buf bytes.Buffer | ||
|
||
newHandler := func(*testing.T) slog.Handler { | ||
buf.Reset() | ||
|
||
h := slog.NewJSONHandler(&buf, &slog.HandlerOptions{AddSource: true}) | ||
return errlog.NewHandler(h, &errlog.HandlerOptions{OverrideSource: true, SuppressStackTrace: false}) | ||
} | ||
|
||
result := func(t *testing.T) map[string]any { | ||
t.Helper() | ||
|
||
m := map[string]any{} | ||
if err := json.Unmarshal(buf.Bytes(), &m); err != nil { | ||
t.Fatal(err) | ||
} | ||
return m | ||
} | ||
|
||
slogtest.Run(t, newHandler, result) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package errlog_test | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"log/slog" | ||
"os" | ||
|
||
"github.com/ichizero/errlog" | ||
) | ||
|
||
func Example() { | ||
h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}) | ||
hErr := errlog.NewHandler(h, &errlog.HandlerOptions{OverrideSource: true, SuppressStackTrace: false}) | ||
slog.SetDefault(slog.New(hErr)) | ||
|
||
ctx := context.Background() | ||
|
||
err := errors.New("test error") | ||
slog.ErrorContext(ctx, "test", errlog.Err(err)) | ||
|
||
err = errlog.WrapError(err) | ||
slog.ErrorContext(ctx, "test", slog.Any("error", err)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package errlog | ||
|
||
import ( | ||
"context" | ||
"log/slog" | ||
) | ||
|
||
// Handler is a slog.Handler that adds error and stack trace information to log records. | ||
type Handler struct { | ||
base slog.Handler | ||
opts HandlerOptions | ||
} | ||
|
||
var _ slog.Handler = (*Handler)(nil) | ||
|
||
// HandlerOptions contains options for the Handler. | ||
type HandlerOptions struct { | ||
// SuppressStackTrace suppresses the stack trace from being added to log records. | ||
SuppressStackTrace bool | ||
// OverrideSource overrides the source location of the log record with the source location of the error. | ||
OverrideSource bool | ||
// StackTraceFormatter is a function that formats the stack trace. | ||
StackTraceFormatter func(stack []uintptr) string | ||
} | ||
|
||
// NewHandler returns a new Handler that wraps the given base slog.handler. | ||
func NewHandler(base slog.Handler, opts *HandlerOptions) *Handler { | ||
if opts == nil { | ||
opts = &HandlerOptions{} | ||
} | ||
|
||
return &Handler{ | ||
base: base, | ||
opts: *opts, | ||
} | ||
} | ||
|
||
// Enabled is a thin wrapper around the base handler's Enabled method. | ||
func (h *Handler) Enabled(ctx context.Context, level slog.Level) bool { | ||
return h.base.Enabled(ctx, level) | ||
} | ||
|
||
// WithAttrs is a thin wrapper around the base handler's WithAttrs method. | ||
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler { | ||
return &Handler{base: h.base.WithAttrs(attrs)} | ||
} | ||
|
||
// WithGroup is a thin wrapper around the base handler's WithGroup method. | ||
func (h *Handler) WithGroup(name string) slog.Handler { | ||
return &Handler{base: h.base.WithGroup(name)} | ||
} | ||
|
||
// Handle adds error and stack trace information to the log record. | ||
func (h *Handler) Handle(ctx context.Context, r slog.Record) error { | ||
r.Attrs(func(a slog.Attr) bool { | ||
const stopIter = false | ||
|
||
if a.Key != ErrorKey { | ||
return true | ||
} | ||
|
||
err, ok := a.Value.Any().(error) | ||
if !ok { | ||
return stopIter | ||
} | ||
|
||
a.Value = slog.StringValue(err.Error()) | ||
|
||
stack := make([]uintptr, 0) | ||
if str, ok := err.(StackTracer); ok { | ||
stack = str.Stack() | ||
} | ||
|
||
if len(stack) == 0 { | ||
return stopIter | ||
} | ||
|
||
if h.opts.OverrideSource { | ||
r.PC = stack[0] | ||
} | ||
|
||
if !h.opts.SuppressStackTrace { | ||
if h.opts.StackTraceFormatter != nil { | ||
r.AddAttrs(slog.String(StackTraceKey, h.opts.StackTraceFormatter(stack))) | ||
return stopIter | ||
} | ||
r.AddAttrs(slog.String(StackTraceKey, formatStack(stack))) | ||
} | ||
return stopIter | ||
}) | ||
return h.base.Handle(ctx, r) | ||
} |
Oops, something went wrong.