Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add slog compatibility, move to Go 1.21 #97

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

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.

StevenACoffman and others added 2 commits April 14, 2024 20:38
Signed-off-by: Steve Coffman <steve@khanacademy.org>
@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 129
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))

@@ -54,7 +56,7 @@ func wrap(err error, callerPC uintptr) error {
}

// 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?

Signed-off-by: Steve Coffman <steve@khanacademy.org>
@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.

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.

3 participants