lmittmann / tint

🌈 slog.Handler that writes tinted (colorized) logs

Home Page:https://pkg.go.dev/github.com/lmittmann/tint

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Consideration of an alternative approach when printing structs

fgimian opened this issue · comments

Hey there, thanks so much for your work on this handler, it's exactly what I was looking for.

One major difference between slog and zerolog which is reflected both in the slog.TextHandler and the tint handler, is the handling of structs whereby both handlers either use the MarshalText or essentially %v to format structs while also adding quotes if there's a space and escaping interior quotes within the string returned.

Meanwhile, zerolog would log the JSON representation of the struct:

e.g.

2023-05-07 16:37:41 INF Getting extract callback stream entry={"isDir":false,"modTime":"2020-10-25T16:38:06+11:00","path":"WinSpy-1.0.3.7z","size":807471}

This is created used a simple marshal function as follows:

func (a ArchiveEntry) MarshalZerologObject(e *zerolog.Event) {
	e.Str("path", a.Path)
	e.Bool("isDir", a.IsDir)
	e.Uint64("size", a.Size)
	if a.ModTime != nil {
		e.Time("modTime", *a.ModTime)
	}
}

I was wondering whether it may be worth consider an option which uses MarshalJSON instead and avoids the extra escaping and addition of quotes in the resultant output to provide similar output to zerolog?

I realise this would break the convention set out by slog and thus I'd understand if you were not a fan of this idea, but as it stands right now, I still am finding zerolog more practical.

Cheers
Fotis

Just to give you an idea of how I accomplished this locally while testing:

handler.go

...
func appendValue(buf *buffer, v slog.Value, quote bool) {
...
-               if tm, ok := v.Any().(encoding.TextMarshaler); ok {
-                       data, err := tm.MarshalText()
-                       if err != nil {
-                               break
-                       }
-                       appendString(buf, string(data), quote)
-                       break
+               if jm, err := json.Marshal(v.Any()); err == nil {
+                       appendString(buf, string(jm), false)
+               } else {
+                       appendString(buf, fmt.Sprintf("%+v", v.Any()), false)
                }
-               appendString(buf, fmt.Sprint(v.Any()), quote)
...

And my example:

type Person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	tintOptions := tint.Options{
		Level:      slog.LevelDebug,
		TimeFormat: time.DateTime,
	}
	slog.SetDefault(slog.New(tintOptions.NewHandler(os.Stdout)))

	person := Person{
		Name: "John",
		Age:  30,
	}
	slog.Info("This is the person", slog.Any("person", person))
}

Resulting in the following output:

image

As compared to:

image

And slog's TextHandler looks like this:

image

Notice they've also adopted %+v too so it may be worth switching that style either way 😄

Cheers
Fotis

Yes, this would definitely break with the conventions of TextHandler. Although what you describe is the default behavior of the JSONHandler.

However, I think there is a good equivalent solution to zerolog, the slog.LogValuer.

func (p Person) LogValue() slog.Value {
	return slog.GroupValue(
		slog.String("name", p.Name),
		slog.Int("age", A.age))
}

The output format is a little different though. ..., "person", p,... would become person.name=John person.age=30

func (p Person) LogValue() slog.Value {
	return slog.GroupValue(
		slog.String("name", p.Name),
		slog.Int("age", A.age))
}

Actually, that's perfect and honestly nicer output than the JSON output as it is more human friendly. Thanks so much for the reply and tip, I wasn't aware of this method.

I'm happy to close this issue 😄
Fotis