-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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
log/slog: change constructors to NewXXXHandler(io.Writer, *HandlerOptions) #59339
Comments
Link to original slog proposal: #56345. |
@dsnet, I'd love your thoughts. I was partly inspired by your design of encoding/json v2. |
The way the v2 "encodingjson" prototype handles options is not something we particularly like and is subject to potential change. In terms of API aesthetics, I personally like variadic options similar to how The main reason why we didn't use variadic options for Given that |
One objection I've often heard with this approach is that the presence of |
To this point, I feel like chainable methods on a configuration struct flow well into To very quickly sketch that style of API, limited to // Opening a new logger configuration
func New() *Config
// Discrete configuration tasks
func (*Config) AddSource(bool) *Config
func (*Config) Ref(Leveler) *Config
func (*Config) Replace(func([]string, Attr) Attr) *Config
func (*Config) Writer(io.Writer) *Config
// Closing a configuration and producing a Logger
func (*Config) JSON() Logger
func (*Config) Text() Logger Building a logger might look like: // very fast to get up and running (if a default writer / ref of os.Stderr, INFO is sane enough)
log := slog.New().JSON()
// more detailed configuration, the per-line method calling survives "go fmt'ing"
log := slog.New().
Replace(quirkyTimestamp).
Ref(slog.DEBUG).
Writer(os.Stdout).
JSON() |
Another solution could be to just name the constructors different things. func NewJSONHandler(w io.Writer) *JSONHandler
func NewJSONHandlerWithOptions(w io.Writer, opts HandlerOptions) *JSONHandler My thought is the current model is effectively two constructors already. You just replace: slog.HandlerOptions{}.NewJSONHandler(os.Stdout) With: slog.NewJSONHandlerWithOptions(os.Stdout, HandlerOptions{}) |
@travbale, to be precise, you'll actually be replacing: slog.HandlerOptions{}.NewJSONHandler(os.Stdout) with slog.NewJSONHandlerWithOptions(os.Stdout, slog.HandlerOptions{}) which is 17 characters longer unless we have #12854, in which case, it reduces down to: slog.NewJSONHandlerWithOptions(os.Stdout, {}) which is 2 characters shorter. |
@dsnet, Yep it would certainly is a bit more verbose so its not one to one. I know functional options could be done which is a pattern I like as well. Any implementation using functional options would increase the API surface area and would probably add more characters as well given each would be prefixed with |
There is one example of I prefer the terser "Opt" (in other words, |
Here's a example of the Opt form: https://pkg.go.dev/golang.org/x/net/http2#Transport.RoundTripOpt. |
I personally think |
Here are some reasons why I think that functional options are inferior to option structs:
Their advantages are:
|
Here's another idea: keep the Problems:
I sometimes wish that Go had a two-dot variadic argument that meant "zero or one". |
@AndrewHarrisSPU, I think your fluent-config design is elegant. But it isn't idiomatic Go. In particular, people would expect It also has one of the problems of the current design: the built-in handlers have special status with respect to the config/option type, because they're the only ones with constructors that are methods. That seems mildly unfair to third-party handlers. |
How about func NewJSONHandler(w io.Writer, opts HandlerOptions) *JSONHandler
func NewDefaultJSONHandler(w io.Writer) *JSONHandler and then if we ever get the improved type inference rules we can deprecate |
This proposal has been added to the active column of the proposals project |
I'd be +1 on a variadic approach, similar to |
Passing |
@mrhov, can you elaborate on that? Why can't you wrap the handlers now? |
Here's another idea, mentioned at the end of the top post:
Yes, the tried-and-true, old-fashioned way. Its great advantages are that there is only one function, and it has precedent on its side. Almost anywhere you find an Options struct in the standard library, you will find a constructor that takes a pointer to it:
I personally have no problem with passing |
func NewJSONHandler(w io.Writer, opts HandlerOptions) *JSONHandler seems preferable. Passing This is roughly equivalent to @ianthehat suggestion modulo constructor names. |
I said above that no one ever uses a variadic argument for a single optional value. Not true; here's an example from the standard library: https://pkg.go.dev/go/doc#NewFromFiles. |
On balance, I think the good old-fashioned way is the best choice. I'll update the proposal. |
I had a brainfart. I can also wrap it now but I have to proxy all methods. Embedding it and just overwriting handle method was what I had in mind and that also won't work with new New* method. Sorry for noise. |
I don't think that But I think it is reasonable to accept a |
Based on the discussion above, this proposal seems like a likely accept. |
Change https://go.dev/cl/486415 mentions this issue: |
No change in consensus, so accepted. 🎉 |
The pattern of constructors for zapslog.Handler was previously: type HandlerOptions struct{ ... } func (*HandlerOptions) New(zapcore.Core) *Handler func NewHandler(zapcore.Core) *Handler This was modeled after similar constructors in slog for JSON and Text handlers. slog has since dropped those constructors in favor of simple: func NewJSONHandler(io.Writer, *HandlerOptions) *JSONHandler func NewTextHandler(io.Writer, *HandlerOptions) *TextHandler This change similarly drops the options.New method and default no-argument constructor in favor of: func NewHandler(zapcore.Core, *HandlerOptions) *Handler As with slog's JSON or Text handlers, the first argument is the destination: an io.Writer for JSON and Text, a Zap core for us. Refs golang/go#59339
The pattern of constructors for zapslog.Handler was previously: type HandlerOptions struct{ ... } func (*HandlerOptions) New(zapcore.Core) *Handler func NewHandler(zapcore.Core) *Handler This was modeled after similar constructors in slog for JSON and Text handlers. slog has since dropped those constructors in favor of simple: func NewJSONHandler(io.Writer, *HandlerOptions) *JSONHandler func NewTextHandler(io.Writer, *HandlerOptions) *TextHandler This change similarly drops the options.New method and default no-argument constructor in favor of: func NewHandler(zapcore.Core, *HandlerOptions) *Handler As with slog's JSON or Text handlers, the first argument is the destination: an io.Writer for JSON and Text, a Zap core for us. Refs golang/go#59339
There is now one constructor function for each built-in handler, with signature NewXXXHandler(io.Writer, *HandlerOptions) *XXXHandler Fixes golang#59339. Change-Id: Ia02183c5ce0dc15c64e33ad05fd69bca09df2d2d Reviewed-on: https://go-review.googlesource.com/c/go/+/486415 Reviewed-by: Alan Donovan <adonovan@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Jonathan Amsterdam <jba@google.com>
There is now one constructor function for each built-in handler, with signature NewXXXHandler(io.Writer, *HandlerOptions) *XXXHandler Fixes golang#59339. Change-Id: Ia02183c5ce0dc15c64e33ad05fd69bca09df2d2d Reviewed-on: https://go-review.googlesource.com/c/go/+/486415 Reviewed-by: Alan Donovan <adonovan@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Jonathan Amsterdam <jba@google.com>
There are two ways to constructors
The former cannot set any options, the latter is a method on the options.
I believe this is a workaround for not having function overloads, if we had them it would have been written
The trouble is, the method construction form is not discoverable. Our documentation tools do a good job of recognizing that free functions are constructors and showing them with the type the construct, but the method form is not that, it is a method on what is an unrelated type. If you go to pkgsite trying to work out how to make a
slog.JSONHandler
, it is not easy to even know that it is possible to set options on it.It also blesses the two handlers in that package as special, when the
HandlerOptions
is a generally useful struct that can be accepted by many external handler implementations.The downside of the transformed form is that you always have to have
slog.HandlerOptions{}
even to get the defaults.Maybe that is okay, we don't expect that new handler construction will be very common, if it is not we could use the pointer form, so that it is
Either way I think the methods on
slog.HandlerOptions
should be removed in favor of free functions.The text was updated successfully, but these errors were encountered: