Skip to content

Commit

Permalink
write error and re-export structured
Browse files Browse the repository at this point in the history
  • Loading branch information
Greg Weber committed Jan 8, 2025
1 parent a1c6da5 commit a4ce0fd
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 50 deletions.
23 changes: 23 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
package errors

import (
"io"
"log"

"github.com/gregwebs/errors/errwrap"
"github.com/gregwebs/errors/slogerr"
"github.com/gregwebs/errors/stackfmt"
)

Expand All @@ -51,3 +55,22 @@ func GetStackTracer(origErr error) stackfmt.StackTracer {
func IsNil(err error) bool {
return isNil(err)
}

// HandleFmtWriteError handles (rare) errors when writing to fmt.State.
// It defaults to printing the errors.
func HandleFmtWriteError(handler func(err error)) {
handleFmtWriteError = handler
errwrap.HandleFmtWriteError(handler)
stackfmt.HandleFmtWriteError(handler)
slogerr.HandleFmtWriteError(handler)
}

var handleFmtWriteError = func(err error) {
log.Println(err)
}

func writeString(w io.Writer, s string) {
if _, err := io.WriteString(w, s); err != nil {
handleFmtWriteError(err)
}
}
28 changes: 5 additions & 23 deletions errstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package errors
import (
stderrors "errors"
"fmt"
"io"
"log"

"github.com/gregwebs/errors/stackfmt"
)
Expand Down Expand Up @@ -44,13 +42,13 @@ func (f *fundamental) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
writeStringErrstack(s, f.Error())
writeString(s, f.Error())
f.StackTrace().Format(s, verb)
return
}
fallthrough
case 's':
writeStringErrstack(s, f.Error())
writeString(s, f.Error())
case 'q':
fmt.Fprintf(s, "%q", f.Error())
}
Expand Down Expand Up @@ -221,10 +219,10 @@ func formatErrorUnwrap(err error, s fmt.State, verb rune) {
if uwErr := err.(errorUnwrap); uwErr != nil {
formatterPlusV(s, verb, uwErr.Unwrap())
if msg := uwErr.ErrorNoUnwrap(); msg != "" {
writeStringErrstack(s, "\n"+msg)
writeString(s, "\n"+msg)
}
} else {
writeStringErrstack(s, err.Error())
writeString(s, err.Error())
}
if stackTracer, ok := err.(stackfmt.StackTracer); ok {
stackTracer.StackTrace().Format(s, verb)
Expand All @@ -235,24 +233,8 @@ func formatErrorUnwrap(err error, s fmt.State, verb rune) {
}
fallthrough
case 's':
writeStringErrstack(s, err.Error())
writeString(s, err.Error())
case 'q':
fmt.Fprintf(s, "%q", err.Error())
}
}

// HandleWriteErrorErrstack handles (rare) errors when writing to fmt.State.
// It defaults to printing the errors.
func HandleWriteErrorErrstack(handler func(err error)) {
handleWriteErrorErrstack = handler
}

var handleWriteErrorErrstack = func(err error) {
log.Println(err)
}

func writeStringErrstack(w io.Writer, s string) {
if _, err := io.WriteString(w, s); err != nil {
handleWriteErrorErrstack(err)
}
}
16 changes: 8 additions & 8 deletions errwrap/wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func formatErrorUnwrapStack(err ErrorUnwrap, s fmt.State, verb rune) {
if s.Flag('+') {
formatterPlusV(s, verb, err.Unwrap())
if msg := err.ErrorNoUnwrap(); msg != "" {
writeStringErrwrap(s, "\n"+msg)
writeString(s, "\n"+msg)
}

if stackTracer, ok := err.(stackTraceFormatter); ok {
Expand All @@ -190,7 +190,7 @@ func formatErrorUnwrapStack(err ErrorUnwrap, s fmt.State, verb rune) {
}
fallthrough
case 's':
writeStringErrwrap(s, err.Error())
writeString(s, err.Error())
case 'q':
fmt.Fprintf(s, "%q", err.Error())
}
Expand All @@ -215,19 +215,19 @@ type stackTraceFormatter interface {
FormatStackTrace(s fmt.State, verb rune)
}

// HandleWriteErrorErrwrap handles (rare) errors when writing to fmt.State.
// HandleFmtWriteError handles (rare) errors when writing to fmt.State.
// It defaults to printing the errors.
func HandleWriteErrorErrWrap(handler func(err error)) {
handleWriteErrorErrwrap = handler
func HandleFmtWriteError(handler func(err error)) {
handleFmtWriteError = handler
}

var handleWriteErrorErrwrap = func(err error) {
var handleFmtWriteError = func(err error) {
log.Println(err)
}

func writeStringErrwrap(w io.Writer, s string) {
func writeString(w io.Writer, s string) {
if _, err := io.WriteString(w, s); err != nil {
handleWriteErrorErrwrap(err)
handleFmtWriteError(err)
}
}

Expand Down
23 changes: 14 additions & 9 deletions slogerr/structured.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ func (se structuredErr) Format(s fmt.State, verb rune) {
se.stack.Format(s, verb)
}
}
writeStringSlogerr(s, "\n"+joinZero(" ", se.msg, structureAsText(se.Record)))
writeString(s, "\n"+joinZero(" ", se.msg, structureAsText(se.Record)))
return
}
fallthrough
case 's', 'q':
writeStringSlogerr(s, se.Error())
writeString(s, se.Error())
}
}

Expand All @@ -94,10 +94,11 @@ func (se structuredErr) FormatStackTrace(s fmt.State, verb rune) {
// The slog Record can be retrieved with SlogRecord.
// Structured errors are more often created by wrapping existing errors with Wraps.
func New(msg string, args ...interface{}) StructuredError {
return wrapsSkip(stderrors.New(""), msg, 1, args...)
return WrapsSkip(stderrors.New(""), msg, 1, args...)
}

func wrapsSkip(err error, msg string, skip int, args ...interface{}) StructuredError {
// Same as Wraps but allows specifying the number of stack frames to skip.
func WrapsSkip(err error, msg string, skip int, args ...interface{}) StructuredError {
if err == nil {
return nil
}
Expand Down Expand Up @@ -137,7 +138,7 @@ func wrapsSkip(err error, msg string, skip int, args ...interface{}) StructuredE
// Accepts as args any valid slog args. These will generate an slog Record
// Also accepts []slog.Attr as a single argument to avoid having to cast that argument.
func Wraps(err error, msg string, args ...interface{}) StructuredError {
return wrapsSkip(err, msg, 1, args...)
return WrapsSkip(err, msg, 1, args...)
}

// SlogRecord traverses the error chain, calling Unwrap(), to look for slog Records
Expand Down Expand Up @@ -383,15 +384,19 @@ func walkUnwrapLevel(err error, visitor func(error, int) bool, stack int) bool {
return false
}

// HandleWriteErrorSlogerr handles (rare) errors when writing to fmt.State.
// HandleFmtWriteError handles (rare) errors when writing to fmt.State.
// It defaults to printing the errors.
var HandleWriteErrorSlogerr = func(err error) {
func HandleFmtWriteError(handler func(err error)) {
handleWriteError = handler
}

var handleWriteError = func(err error) {
log.Println(err)
}

func writeStringSlogerr(w io.Writer, s string) {
func writeString(w io.Writer, s string) {
if _, err := io.WriteString(w, s); err != nil {
HandleWriteErrorSlogerr(err)
handleWriteError(err)
}
}

Expand Down
39 changes: 39 additions & 0 deletions slogerr_export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package errors

import (
stderrors "errors"
"log/slog"

"github.com/gregwebs/errors/slogerr"
)

// SlogRecord traverses the error chain, calling Unwrap(), to look for slog Records
// All records will be merged and mesage strings are joined together
// The message string may contain some of the structured information
// This depends on defining ErrorNoUnwrap from the interface ErrorNotUnwrapped
//
// if record := errors.SlogRecord(errIn); record != nil {
// record.Add(logArgs...)
// if err := slog.Default().Handler().Handle(ctx, *record); err != nil {
// slog.ErrorContext(ctx, fmt.Sprintf("%+v", err))
// }
// }
func SlogRecord(inputErr error) *slog.Record {
return slogerr.SlogRecord(inputErr)
}

// Slog creates an error that instead of generating a format string generates a structured slog Record.
// Accepts as args any valid slog args.
// Also accepts `[]slog.Attr` as a single argument to avoid having to cast that argument.
// The slog Record can be retrieved with SlogRecord.
// Structured errors are more often created by wrapping existing errors with Wraps.
func Slog(msg string, args ...interface{}) slogerr.StructuredError {
return slogerr.WrapsSkip(stderrors.New(""), msg, 1, args...)
}

// Wraps ends with an "s" to indicate it is Structured.
// Accepts as args any valid slog args. These will generate an slog Record
// Also accepts []slog.Attr as a single argument to avoid having to cast that argument.
func Wraps(err error, msg string, args ...interface{}) slogerr.StructuredError {
return slogerr.WrapsSkip(err, msg, 1, args...)
}
20 changes: 10 additions & 10 deletions stackfmt/stackfmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,22 @@ func (f Frame) Format(s fmt.State, verb rune) {
pc := f.pc()
fn := runtime.FuncForPC(pc)
if fn == nil {
writeStringStackfmt(s, "unknown")
writeString(s, "unknown")
} else {
file, _ := fn.FileLine(pc)
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
}
default:
writeStringStackfmt(s, path.Base(f.file()))
writeString(s, path.Base(f.file()))
}
case 'd':
fmt.Fprintf(s, "%d", f.line())
case 'n':
name := runtime.FuncForPC(f.pc()).Name()
writeStringStackfmt(s, funcname(name))
writeString(s, funcname(name))
case 'v':
f.Format(s, 's')
writeStringStackfmt(s, ":")
writeString(s, ":")
f.Format(s, 'd')
}
}
Expand Down Expand Up @@ -165,18 +165,18 @@ func funcname(name string) string {
return name[i+1:]
}

// HandleWriteErrorStackfmt handles (rare) errors when writing to fmt.State.
// HandleFmtWriteError handles (rare) errors when writing to fmt.State.
// It defaults to printing the errors.
func HandleWriteErrorStackfmt(handler func(err error)) {
handleWriteErrorStackfmt = handler
func HandleFmtWriteError(handler func(err error)) {
handleWriteError = handler
}

var handleWriteErrorStackfmt = func(err error) {
var handleWriteError = func(err error) {
log.Println(err)
}

func writeStringStackfmt(w io.Writer, s string) {
func writeString(w io.Writer, s string) {
if _, err := io.WriteString(w, s); err != nil {
handleWriteErrorStackfmt(err)
handleWriteError(err)
}
}

0 comments on commit a4ce0fd

Please sign in to comment.