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

funcr: slog output omits intermediate groups #273

Closed
avorima opened this issue Feb 28, 2024 · 7 comments · Fixed by #274
Closed

funcr: slog output omits intermediate groups #273

avorima opened this issue Feb 28, 2024 · 7 comments · Fixed by #274
Assignees

Comments

@avorima
Copy link

avorima commented Feb 28, 2024

I'm working on implementing a SlogSink for another logger and looked to funcr for guidance on how to handle certain cases with groups but found that it behaves unexpectedly.

I added the following lines to ExampleToSlogHandler:

slogLogger.WithGroup("a").Info("single empty group")
slogLogger.WithGroup("a").WithGroup("b").Info("chained empty groups")
slogLogger.WithGroup("a").WithGroup("b").With("str", "abc").Info("first group empty")
slogLogger.WithGroup("a").With("str", "abc").WithGroup("b").Info("last group empty")
slogLogger.WithGroup("a").With("str", "abc").WithGroup("b").WithGroup("c").With("x", 1).Info("middle group empty")

The output I got was:

"level"=0 "msg"="single empty group"
"level"=0 "msg"="chained empty groups"}
"level"=0 "msg"="first group empty" "b"={"str"="abc"}}
"level"=0 "msg"="last group empty" "a"={"str"="abc"}
"level"=0 "msg"="middle group empty" "a"={"str"="abc" "c"={"x"=1}}}

Apart from the trailing curly brackets I expected funcr to behave like a slog.TextHandler, so something like:

"level"=0 "msg"="single empty group"
"level"=0 "msg"="chained empty groups"
"level"=0 "msg"="first group empty" "a"={"b"={"str"="abc"}}
"level"=0 "msg"="last group empty" "a"={"str"="abc"}
"level"=0 "msg"="middle group empty" "a"={"str"="abc" "b"={"c"={"x"=1}}}

I'm assuming the omission of the intermediate group is causing the trailing } problem.

@thockin
Copy link
Contributor

thockin commented Feb 29, 2024

What I see is:

$ cat repro/repro.go 
package main

import (
	"fmt"
	"log/slog"

	"github.com/go-logr/logr"
	"github.com/go-logr/logr/funcr"
)

func main() {
	logger := funcr.NewJSON(
		func(arg string) { fmt.Println(arg) },
		funcr.Options{})
	slogger := slog.New(logr.ToSlogHandler(logger))

	slogger.WithGroup("a").Info("single empty group")
	slogger.WithGroup("a").WithGroup("b").Info("chained empty groups")
	slogger.WithGroup("a").WithGroup("b").With("str", "abc").Info("first group empty")
	slogger.WithGroup("a").With("str", "abc").WithGroup("b").Info("last group empty")
	slogger.WithGroup("a").With("str", "abc").WithGroup("b").WithGroup("c").With("x", 1).Info("middle group empty")
}

$ go run ./repro/
{"logger":"","level":0,"msg":"single empty group"}
{"logger":"","level":0,"msg":"chained empty groups"}}
{"logger":"","level":0,"msg":"first group empty","b":{"str":"abc"}}}
{"logger":"","level":0,"msg":"last group empty","a":{"str":"abc"}}
{"logger":"","level":0,"msg":"middle group empty","a":{"str":"abc","c":{"x":1}}}}

Oh, looking closely I do see this. huh. I think you punched a hole in my pre-render-everything approach. To my own defense, we pass slogtest.TestHandler - you should file a bug against Go to cover this case! :)

I will have to think about how to solve this.

@thockin
Copy link
Contributor

thockin commented Mar 1, 2024

I figured it out:

$ cat repro/repro.go 
package main

import (
	"fmt"
	"log/slog"

	"github.com/go-logr/logr"
	"github.com/go-logr/logr/funcr"
)

func main() {
	logger := funcr.NewJSON(
		func(arg string) { fmt.Println(arg) },
		funcr.Options{})
	slogger := slog.New(logr.ToSlogHandler(logger))

	slogger.WithGroup("a").Info("single empty group")
	slogger.WithGroup("a").WithGroup("b").Info("chained empty groups")
	slogger.WithGroup("a").WithGroup("b").With("str", "abc").Info("first group empty")
	slogger.WithGroup("a").With("str", "abc").WithGroup("b").Info("last group empty")
	slogger.WithGroup("a").With("str", "abc").WithGroup("b").WithGroup("c").With("x", 1).Info("middle group empty")

	slogger.
		With("x", 1).
		WithGroup("g1").
		With("g1x", 2).
		WithGroup("g2").
		WithGroup("g3").
		With("g3x", 1).
		WithGroup("g4").
		WithGroup("g5").
		Info("kitchen sink")
}

$ go run ./repro/
{"logger":"","level":0,"msg":"single empty group"}
{"logger":"","level":0,"msg":"chained empty groups"}
{"logger":"","level":0,"msg":"first group empty","a":{"b":{"str":"abc"}}}
{"logger":"","level":0,"msg":"last group empty","a":{"str":"abc"}}
{"logger":"","level":0,"msg":"middle group empty","a":{"str":"abc","b":{"c":{"x":1}}}}
{"logger":"","level":0,"msg":"kitchen sink","x":1,"g1":{"g1x":2,"g2":{"g3":{"g3x":1}}}}

I will have to craft a test to cover this, which I can try to do tomorrow.

@thockin
Copy link
Contributor

thockin commented Mar 1, 2024

@avorima
Copy link
Author

avorima commented Mar 1, 2024

I looked into creating a golang issue and noticed that TestRunSlogTestsOnSlogHandlerLogSink fails on go 1.22. Both on master and your PR @thockin.

slog.go:62: Unexpected error: missing group "G": a Handler should not output groups if there are no attributes 

Which is this test case: https://github.com/golang/go/blob/go1.22.0/src/testing/slogtest/slogtest.go#L161

@thockin
Copy link
Contributor

thockin commented Mar 1, 2024 via email

@thockin
Copy link
Contributor

thockin commented Mar 1, 2024

Yeah, the tescase seems broken - I have the right output. Will investigate later.

@thockin
Copy link
Contributor

thockin commented Mar 1, 2024

OK. The test fail on 1.22 is because they added a new testcase that our slog handler adapter doesn't cover.

Go should add a testcase for nested groups and trailing "empty" groups, I think. funcr itself passes slogtest right now, but it should not.

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 a pull request may close this issue.

3 participants