Skip to content

proposal: log/slog: structured error type #74592

Closed as not planned
Closed as not planned
@urandom

Description

@urandom

Proposal Details

I propose that log/slog introduces a function to create an error from a string message and zero or more slog.Attr arguments. The type, implementing the error interface, would also implement the LogValuer one as well, in order to render the error as an attribute group when logged.

Rationale

An error, that acts as an attribute group when used with slog can currently be created by a third party. There are no known problems that should prevent this from happening. The only reason this error should exist in the standard library is adoption.

As a third-party dependency, one's own code and errors can benefit from such a structured error representation. However, the errors produced by dependencies would not. They would also have to depend on such a third-party module as well.

If this functionality would be introduced into the standard library, the above scenario would not necessarily be different. However, it is more likely that more modules would embrace it as it is provided by the language itself. I believe adoption would be significantly faster because of this.

Finally, such an addition would not be unprecedented. Such a function should be as familiar as the way we currently create formatted errors, through the fmt.Errorf function.

Language change

I proposal the following code, or one that achieves similar results to be added to the log/slog package:

package slog

import (
	"strings"
)

const CauseKey = "cause"

type serror struct {
	msg   string
	err   error
	attrs []Attr
}

// Error implements error.
func (s serror) Error() string {
	var b strings.Builder

	_, _ = b.WriteString(s.msg)

	if s.err != nil {
		_ = b.WriteByte(' ')
		_, _ = b.WriteString(CauseKey + "=[" + s.err.Error() + "]")
	}

	for _, attr := range s.attrs {
		_ = b.WriteByte(' ')
		_, _ = b.WriteString(attr.String())
	}

	return b.String()
}

func (s serror) LogValue() Value {
	size := len(s.attrs) + 1
	if s.err != nil {
		size++
	}

	attrs := make([]Attr, 0, size)
	attrs = append(attrs, String(MessageKey, s.msg))

	if s.err != nil {
		attrs = append(attrs, Any(CauseKey, s.err))
	}

	attrs = append(attrs, s.attrs...)

	return GroupValue(attrs...)
}

func (e serror) Unwrap() error {
	return e.err
}

func Error(msg string, attrs ...Attr) error {
	return serror{msg: msg, attrs: attrs}
}

func WrapError(msg string, err error, attrs ...Attr) error {
	return serror{msg: msg, err: err, attrs: attrs}
}

In this example, I've introduced two functions, Error to create an initial error, and WrapError to wrap an existing one.

Alternatively, a slog.Error attribute function may be introduced instead of the WrapError, to allow wrapping one or more errors. This would be similar to fmt.Errorf, and like it, would require different types to implement the different Unwrap interfaces.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions