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

support key/value checking for arbitrary functions #22

Open
pohly opened this issue Nov 22, 2023 · 12 comments
Open

support key/value checking for arbitrary functions #22

pohly opened this issue Nov 22, 2023 · 12 comments

Comments

@pohly
Copy link

pohly commented Nov 22, 2023

The slog.Logger API is just one of possible many other high-level APIs which accept key/value pairs. Another example is go-logr/logr.Logger and some custom wrapper functions in Kubernetes.

It would be great if sloglint could also warn about incorrect parameters in those other methods.

Disclaimer: I'm the maintainer of logcheck, a linter which does similar checking for go-logr APIs, and of logging in Kubernetes. Long-term sloglint could replace or supplant logcheck.

@pohly
Copy link
Author

pohly commented Nov 22, 2023

go vet handles wrappers around fmt.Printf and fmt.Print by detecting calls inside wrappers: https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf#hdr-Analyzer_printf

Perhaps something similar can be done here?

Alternatively, support special comment tags?

@tmzane
Copy link
Member

tmzane commented Nov 23, 2023

Hi, did you mean loggercheck? I decided to focus on slog-specific checks to not overlap with this linter too much. Sure, some sloglint checks can be applied to different logging libraries, but it seems a bit late by now to make it a generic logger checker.

Speaking about checking slog wrappers, that would be a great addition and I have plans to implement it later. Detecting calls inside wrappers seems smart, I like that it requires zero configuration, but I also worry about false positives. I'll take a look at go vet's implementation, thank you for the suggestion. We could also implement it as a list of custom functions in the linter config, like I did here. Would such API work for you?

@pohly
Copy link
Author

pohly commented Nov 23, 2023

Hi, did you mean loggercheck?

No, https://github.com/kubernetes-sigs/logtools/tree/main/logcheck. Some of its functionality is specific to the migration to structured, contextual logging in Kubernetes, therefore I have not tried to make it more general or get it included in golangci-lint.

make it a generic logger checker

The intention wasn't to add any special support for other loggers. But when wrappers can be marked or detected, then other logging libraries can enable sloglint.

For example, there's https://github.com/kubernetes/klog/blob/2086216a5034ba7c4e3a4fec7d89c5d0434eb1a0/klog.go#L1461-L1467

That could become:

func (v Verbose) InfoS(msg string, keysAndValues ...interface{}) {
	if false {
		slog.Info(msg, keysAndValues...)
	}
	if v.enabled {
		logging.infoS(v.logger, logging.filter, 0, msg, keysAndValues...)
	}
}

Then sloglint will warn about InfoS.

@pohly
Copy link
Author

pohly commented Nov 23, 2023

I'll take a look at go vet's implementation

It's here: https://cs.opensource.google/go/x/tools/+/refs/tags/v0.14.0:go/analysis/passes/printf/printf.go

We could also implement it as a list of custom functions in the linter config, like I did here. Would such API work for you?

It would work for Kubernetes, but the list would be long and everyone else using klog would have to copy it into their own golangci-lint config. Automatic detection would be nicer 😁

@pohly
Copy link
Author

pohly commented Nov 23, 2023

Another example where I would like to use it:

func HandleErrorWithContext(ctx context.Context, err error, msg string, keysAndValues ...any) {... }

@tmzane
Copy link
Member

tmzane commented Nov 25, 2023

So, klog wraps slog with key-value style arguments, and you'd like to perform slog checks on it, did I get it right?

I'll work on the wrappers support and let you know 🤝

@pohly
Copy link
Author

pohly commented Nov 25, 2023

klog can be used as wrapper around slog - it's a bit more flexible than that, though. But the net effect is the same: the same rules as for slog key/value pairs also apply to it. So yes, your understanding is correct.

@tstraley
Copy link

tstraley commented Mar 7, 2024

I'd like to express interest in this use-case, as well.

We have our own log package that wraps slog, but would like to apply this linter against our code. We can do so today by cloning this code and modifying slogFuncs in sloglint.go to match our funcs, but that becomes a headache especially when wanting to use golangci-lint to run all of our linters.

I will note that loggercheck has a way to add custom rules as a list of additional function/method signatures eg.

rules:
      - k8s.io/klog/v2.InfoS
      - (github.com/go-logr/logr.Logger).Error
      - (*go.uber.org/zap.SugaredLogger).With

But loggercheck doesn't work with Attrs, just key-value pairs, which is why we like sloglint. :)

@tmzane
Copy link
Member

tmzane commented Mar 8, 2024

@tstraley Could you provide an example of the wrappers you use? I'm currently researching the possibility to automate detection of slog wrappers, without the need to specify custom rules first. It'd help a lot, thanks.

@pedantic79
Copy link

@tmzane The wrapper my team uses looks like this:

type Logger struct {
	*slog.Logger
}

This is mostly to provide helper functions for custom levels.

// Critical logs at LevelCritical.
func (logger *Logger) Critical(msg string, args ...any) {
	logHelper(logger, context.Background(), LevelCritical, msg, args...)
}

// CriticalContext logs at LevelCritical with the given context.
func (logger *Logger) CriticalContext(ctx context.Context, msg string, args ...any) {
	logHelper(logger, ctx, LevelCritical, msg, args...)
}

Where logHelper is similar to the log/slog's internal func (l *Logger) log(ctx context.Context, level Level, msg string, args ...any)

We also have overrides of With() and WithGroup() methods that return a wrapped *Logger instead of a *slog.Logger and some helper methods to handle common logging practices in our code.

Specifically this, where we use to do log.Error(err)

func (logger *Logger) LogErr(level slog.Level, err error)

@pohly
Copy link
Author

pohly commented Jun 29, 2024

@tmzane: gentle ping regarding this... it would be great to get this working because in Kubernetes we start to have more APIs which accept log parameters and currently have no linter for those. Our own "logtools/logcheck" doesn't support it either yet.

@tmzane
Copy link
Member

tmzane commented Jul 15, 2024

Hey, sorry for the late reply, I'm kinda busy these days.

I've actually got something close to working regarding this feature, just need a bit more work to handle edge cases. I'm going to get back to it as soon as I have some free time. Will let you know when there is something ready for testing.

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

No branches or pull requests

4 participants