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

proposal: log/slog: invoke LogValue inside slog.Value.String #62636

Closed
pohly opened this issue Sep 14, 2023 · 5 comments
Closed

proposal: log/slog: invoke LogValue inside slog.Value.String #62636

pohly opened this issue Sep 14, 2023 · 5 comments
Labels
Milestone

Comments

@pohly
Copy link

pohly commented Sep 14, 2023

slog.Value implements fmt.Stringer. The result of String depends on the kind of the value (int, group, etc.). When the kind is "any" and the type implements slog.LogValuer, that type's LogValue method is currently not called.

I'm a bit on the edge whether it should be called, with a slight tendency towards "yes". My understanding is that String is meant to return a pretty-printed version of the value for users or developers. Same with LogValue. Therefore using LogValue in String makes sense to me.

Example (playground:

package main

import (
	"fmt"
	"log/slog"
)

var _ slog.LogValuer = coordinatesValuer{}

type coordinatesValuer struct {
	x, y int
}

func (c coordinatesValuer) LogValue() slog.Value {
	return slog.GroupValue(slog.Attr{Key: "X", Value: slog.IntValue(c.x)}, slog.Attr{Key: "Y", Value: slog.IntValue(c.y)})
}

func main() {
	fmt.Println("Some values", slog.GroupValue(slog.Attr{Key: "X", Value: slog.IntValue(1)}, slog.Attr{Key: "Y", Value: slog.IntValue(2)}), slog.IntValue(42))
	fmt.Println("A slog value with a LogValuer", slog.AnyValue(coordinatesValuer{1, 2}))
}

Current output:

Some values [X=1 Y=2] 42
A slog value with a LogValuer {1 2}

Expected (?) output:

Some values [X=1 Y=2] 42
A slog value with a LogValuer [X=1 Y=2]
@pohly pohly added the Proposal label Sep 14, 2023
@gopherbot gopherbot added this to the Proposal milestone Sep 14, 2023
@pohly
Copy link
Author

pohly commented Sep 14, 2023

cc @jba

@pohly
Copy link
Author

pohly commented Sep 14, 2023

A related question is whether slog.GroupValue should invoke resolve (aka call LogValue) its attributes when String is called. The text and JSON handlers resolve, String doesn't.

Example (playground:

package main

import (
	"fmt"
	"log/slog"
	"os"
)

var _ slog.LogValuer = coordinatesValuer{}

type coordinatesValuer struct {
	x, y int
}

func (c coordinatesValuer) LogValue() slog.Value {
	return slog.GroupValue(slog.Attr{Key: "X", Value: slog.IntValue(c.x)}, slog.Attr{Key: "Y", Value: slog.IntValue(c.y)})
}

func main() {
	jsonHandler := slog.NewJSONHandler(os.Stdout, nil)
	textHandler := slog.NewTextHandler(os.Stdout, nil)
	coordinates := coordinatesValuer{1, 2}
	coordinatesAttr := slog.Any("coordinates", coordinates)
	coordinatesGroup := slog.Group("group", coordinatesAttr)
	slog.New(jsonHandler).Info("JSON", "value", coordinates, coordinatesGroup)
	slog.New(textHandler).Info("Text", "value", coordinates, coordinatesGroup)
	fmt.Println(coordinatesGroup)
}

Output:

{"time":"2009-11-10T23:00:00Z","level":"INFO","msg":"JSON","value":{"X":1,"Y":2},"group":{"coordinates":{"X":1,"Y":2}}}
time=2009-11-10T23:00:00.000Z level=INFO msg=Text value.X=1 value.Y=2 group.coordinates.X=1 group.coordinates.Y=2
group=[coordinates={1 2}]

@jba
Copy link
Contributor

jba commented Sep 14, 2023

I can think of two cases to call Value.String:

  1. You're in your Handler.Handle method and you want to output the value in a format that you don't care a lot about. In that case, you should have already resolved the value. (You can't process groups correctly if you don't.)

  2. You're debugging. In that case, you want String to do as little as possible, so you can find the bug. So it shouldn't resolve.

So I lean towards "no." Are there other cases I haven't thought of?

Same answer for GroupValue.

@jba
Copy link
Contributor

jba commented Sep 14, 2023

Anyway, unless we think this is a real bug, we can't change it.

@pohly
Copy link
Author

pohly commented Sep 15, 2023

You're in your Handler.Handle method and you want to output the value in a format that you don't care a lot about. In that case, you should have already resolved the value. (You can't process groups correctly if you don't.)

Correct.

You're debugging. In that case, you want String to do as little as possible, so you can find the bug. So it shouldn't resolve.

While debugging, I want useful output. {1 2} is less useful than [X=1 Y=2], in particular when I am debugging code where I don't know the structures. I can't quite nail it anymore, but I think there were cases where String included less information than available.

Anyway, this is subjective. I'm okay with leaving it as-is.

@pohly pohly closed this as completed Sep 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Incoming
Development

No branches or pull requests

3 participants