Skip to content

Comments

Add slog compatibility#97

Merged
abhinav merged 6 commits intobracesdev:mainfrom
StevenACoffman:main
Jun 2, 2025
Merged

Add slog compatibility#97
abhinav merged 6 commits intobracesdev:mainfrom
StevenACoffman:main

Conversation

@StevenACoffman
Copy link
Contributor

Go 1.21 adds the log/slog package and this PR adds convenience and compatibility with that package. Merging this PR would require dropping Go 1.20 support, which may not (yet!) be desired. If so, this PR can wait until that is desired.

Each major Go release is supported until there are two newer major releases. Go 1.22 was released on February 6, 2024
so Go 1.20 is no longer supported.

@abhinav
Copy link
Contributor

abhinav commented Apr 15, 2024

Hey, @StevenACoffman!
We're willing to drop support for Go 1.20 at this point. We tend to follow the upstream support policy for most of our projects (current and last minor Go are supported) with the addendum that we're willing to keep support for older versions around if it's not too difficult. (Sometimes folks lag behind on upgrades.) But we're happy to bump to minimum supported version when the need arises.

Copy link
Contributor

@abhinav abhinav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prior comment notwithstanding, I think we can do this without bumping the Go version and just moving the two new functions into a Go 1.21 tagged file.

errtrace.go Outdated
Comment on lines 127 to 160
func ErrAttr(err error) slog.Attr {
return slog.Any("error", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking out loud:
The number of times "err" appears in this expression is quite high:

errtrace.ErrAttr(err)

What if we named this LogAttr?

slog.Default().Error("great sadness", errtrace.LogAttr(err))


// Format writes the return trace for given error to the writer.
// The output takes a fromat similar to the following:
// The output takes a format similar to the following:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks

errtrace.go Outdated
//
// slog.Default().Error("msg here", errtrace.ErrAttr(err))
func ErrAttr(err error) slog.Attr {
return slog.Any("error", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The convention on log/slog seems to be to use "err" as the key for errors, not "error". This matches what Zap and Zerolog do as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, Zap uses "error" as the key: https://github.com/uber-go/zap/blob/master/error.go#L34

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation also shows that "error" is the key in Zerolog https://github.com/rs/zerolog?tab=readme-ov-file#error-logging

package main

import (
	"errors"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
)

func main() {
	zerolog.TimeFieldFormat = zerolog.TimeFormatUnix

	err := errors.New("seems we have an error here")
	log.Error().Err(err).Msg("")
}

// Output: {"level":"error","error":"seems we have an error here","time":1609085256}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In slog, the various proposals (and usage by the primary author jba) show using "error" as the key: golang/go#59364

func ErrAttr(err error) slog.Attr {
	return slog.Any("error", err)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, Zap uses "error" as the key: https://github.com/uber-go/zap/blob/master/error.go#L34

Well, that's embarrassing for me. 😆

image

I don't know why I assumed "err". Will revert that change.

@abhinav
Copy link
Contributor

abhinav commented Apr 15, 2024

I've renamed the field and the function just to see how it looks.
What do you think? We can revert it if it doesn't look good.

@StevenACoffman
Copy link
Contributor Author

StevenACoffman commented Apr 15, 2024

I would rather not use "err" as the key, but I don't feel strongly, and otherwise looks good to me! Thanks for a wonderful library.

Zap uses "error" as the key: https://github.com/uber-go/zap/blob/master/error.go#L34

// Error is shorthand for the common idiom NamedError("error", err).
func Error(err error) Field {
	return NamedError("error", err)
}

Zerolog has documentation that also shows that "error" is the key in Zerolog https://github.com/rs/zerolog?tab=readme-ov-file#error-logging

package main

import (
	"errors"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
)

func main() {
	zerolog.TimeFieldFormat = zerolog.TimeFormatUnix

	err := errors.New("seems we have an error here")
	log.Error().Err(err).Msg("")
}

// Output: {"level":"error","error":"seems we have an error here","time":1609085256}

In slog, the various proposals (and usage by the primary author jba) show using "error" as the key: golang/go#59364

func ErrAttr(err error) slog.Attr {
	return slog.Any("error", err)
}

Maybe there is some other examples that you have seen where that is not the case?

@abhinav
Copy link
Contributor

abhinav commented Apr 15, 2024

Oops. Yeah, "error" is good.
I was just having a bad case of memory corruption.

@StevenACoffman
Copy link
Contributor Author

@abhinav is there anything remaining to get this merged?

@abhinav
Copy link
Contributor

abhinav commented May 5, 2024

Hey @StevenACoffman, apologies, I had an update typed out but I never sent it.
I was discussing this with @prashantv earlier, and the short of it is:
I was having trouble deciding on whether the logged value should be a structured object or a string.

After mulling it over a bit, I don't think that the value should be structured: the majority use case will want to consume a string. For cases where folks want a structured object logged for errors, the logger APIs should provide enough control/overrides for how specific values are encoded—e.g. "encode errors using this function instead."
So I think we should keep it a string—keep this PR as-is.

But I want to confirm my thinking with others—you, @prashantv, @akshayjshah.

Outside of that, if we agree on the approach, what remains before merging this PR is:

  • dropping the go directive in go.mod back to 1.20 (since we're using a build tag)
  • adding a test for this functionality

@prashantv
Copy link
Contributor

After mulling it over a bit, I don't think that the value should be structured: the majority use case will want to consume a string. For cases where folks want a structured object logged for errors, the logger APIs should provide enough control/overrides for how specific values are encoded—e.g. "encode errors using this function instead." So I think we should keep it a string—keep this PR as-is.

But I want to confirm my thinking with others—you, @prashantv, @akshayjshah.

+1 on using string. Should we do something similar to zap here, errorVerbose for the formatted string? Or is there an existing slog idiom that we want to follow?

@abhinav
Copy link
Contributor

abhinav commented May 6, 2024

Should we do something similar to zap here, errorVerbose for the formatted string?

I think not. In hindsight, that wasn't the most ideal choice we could've made there.
The error should be a single field, and if it has multiple pieces of information, the encoder should be responsible for turning it into a group of values.

@prashantv
Copy link
Contributor

I think not. In hindsight, that wasn't the most ideal choice we could've made there. The error should be a single field, and if it has multiple pieces of information, the encoder should be responsible for turning it into a group of values.

Flexibility (at the logger level) is probably the right answer, but I've found the message-only errors useful when dealing with logs in aggregate (e.g., looking at a distribution of errors across log messages in a timeframe) where additional stacks/traces get quite noisy. On the flip side, once I've drilled into a specific log, then I find the additional information useful to understand where it came from.

@cbandy
Copy link
Contributor

cbandy commented Nov 14, 2024

📝 Go 1.21 since #111.

Co-authored-by: Abhinav Gupta <mail@abhinavg.net>
Co-authored-by: Chris Bandy <bandy.chris@gmail.com>
Signed-off-by: Steve Coffman <steve@khanacademy.org>
@cbandy
Copy link
Contributor

cbandy commented Jan 25, 2025

I rebased and squashed all these down over here: main...cbandy:errtrace:pr-97

@StevenACoffman StevenACoffman force-pushed the main branch 2 times, most recently from a857d47 to c729dda Compare June 1, 2025 17:47
@StevenACoffman
Copy link
Contributor Author

@abhinav @prashantv I have force pushed @cbandy rebased squashed commit to my branch.

abhinav added 3 commits June 1, 2025 11:05
Writing to a strings.Builder can never fail
so we don't need the impossible error path.
Use FormatString instead, which already does this.
@abhinav abhinav changed the title Add slog compatibility, move to Go 1.21 Add slog compatibility Jun 1, 2025
Copy link
Contributor

@abhinav abhinav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. I've added a couple test cases.

@abhinav
Copy link
Contributor

abhinav commented Jun 1, 2025

Fixing lint breakage here: #125

@abhinav
Copy link
Contributor

abhinav commented Jun 1, 2025

Will give it a day to see if @prashantv has any thoughts, but good to merge otherwise

Copy link
Contributor

@prashantv prashantv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will drop LogAttr and merge.

errtrace.go Outdated
// When serialized with a slog-based logger,
// this will report an error return trace if the error has one,
// otherwise the original error message will be logged as-is.
func LogAttr(err error) slog.Attr {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the implementation of LogAttr is completely independent of errtrace, I'm not sure if it's valuable to add it here. I'll drop this for now, and we can always bring it back if we think there's value in this being part of the errtrace interface.


// LogValue implements the [slog.LogValuer] interface.
func (e *errTrace) LogValue() slog.Value {
return slog.StringValue(FormatString(e))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no changes needed, but wanted to consider whether this should return a string value, or a group with multiple fields for message, trace, etc.

I think a string makes sense to start, and allows callers to choose how they want the error encoded, e.g., in their equivalent of the LogAttr function.

@abhinav abhinav merged commit cc0b57f into bracesdev:main Jun 2, 2025
15 of 16 checks passed
@abhinav
Copy link
Contributor

abhinav commented Jun 2, 2025

Merged! Thanks for your patience, @StevenACoffman

prashantv added a commit that referenced this pull request Jun 2, 2025
Follow-up to #97.

We dropped the `LogAttr` function, so the changelog needs to be updated.
prashantv added a commit that referenced this pull request Jun 2, 2025
Follow-up to #97.

We dropped the `LogAttr` function, so the changelog needs to be updated.
@StevenACoffman
Copy link
Contributor Author

Wohoo! Thanks!

}
```

- Add `LogAttr` function to log any error with log/slog with the key "error".

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this changelog entry was left behind after the LogAttr func was removed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We realized this after merging too, and fixed it in #126.
Thanks for catching either way!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants