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 slog for internal logs, beside of logr #4658

Open
pgillich opened this issue Oct 22, 2023 · 14 comments
Open

Support slog for internal logs, beside of logr #4658

pgillich opened this issue Oct 22, 2023 · 14 comments
Labels
documentation Provides helpful information enhancement New feature or request

Comments

@pgillich
Copy link

pgillich commented Oct 22, 2023

Problem Statement

The log/slog package already released in Go 1.21. But it's unable to set for otel.SetLogger, because it expects logr.Logger.

Proposed Solution

OTEL uses only a few log functions (Error, Warn, Info, Debug), so a new interface can be defined, for example:

type LogAdapterer interface {
	Info(msg string, keysAndValues ...interface{})
	Error(err error, msg string, keysAndValues ...interface{})
	Debug(msg string, keysAndValues ...interface{})
	Warn(msg string, keysAndValues ...interface{})
}

In order to keep backward compatibility, a new function should be introduced, which sets this kind of logger, for example:

func SetLoggerAdapter(logger LogAdapterer)

The original otel.SetLogger is kept for backward compatibility (but marked as deprecated), which calls otel.SetLoggerAdapter with the exported logr adapter.

The Error, Warn, Info and Debug functions are kept in internal/global package, but it calls the configured logger adapter, so other code part should not be changed.

A new adapter should be created for log/slog package, which can be passed to otel.SetLoggerAdapter.

Alternatives

Another alternatives don't keep backward compatibility.

Prior Art

N/A

Additional Context

N/A

@pgillich pgillich added the enhancement New feature or request label Oct 22, 2023
@pgillich
Copy link
Author

If you don't mind, I can make a pull request for it.

@MrAlias
Copy link
Contributor

MrAlias commented Oct 22, 2023

Why not just wrap the slog.Logger into a logr.Logger?

This seems like it will bloat the API. Given we still need to support Go 1.20 I would like to think this through before we add and deprecate something in a stable API.

@MrAlias
Copy link
Contributor

MrAlias commented Oct 22, 2023

@Tadimsky
Copy link

Tadimsky commented Nov 3, 2023

logr.Logger is not an interface but a struct and so you can't pass an alternate implementation.

It would make more sense for OTel to define its own logger interface that it uses (that can match the logr.Logger methods) so that we can wrap whatever logger we want in that interface and provide it to the library.

@pellared
Copy link
Member

pellared commented Nov 3, 2023

See https://github.com/go-logr/logr/blob/master/slogr/example/main.go to check how to plug a slog handler as logr logger.

More: https://pkg.go.dev/github.com/go-logr/logr

@Tadimsky
Copy link

Tadimsky commented Nov 3, 2023

Yeah, but this is forcing anyone using the opentelemetry package into taking a dependency on logr and creating a logr implementation.
What most libraries do is define an interface that they expect - see this for example: https://github.com/temporalio/sdk-go/blob/master/log/logger.go#L29-L34

@pellared
Copy link
Member

pellared commented Nov 3, 2023

Correct, we decided to use logr as the logging abstraction instead of creating our own. logr was created for this purpose.

slog was not available when we were building OTel internal logs, but https://pkg.go.dev/github.com/go-logr/logr/slogr offers interoperability.

More: https://github.com/go-logr/logr#background

slog is supported via https://pkg.go.dev/github.com/go-logr/logr/slogr. We may want to describe it somewhere in the documentation. E.g. here: https://pkg.go.dev/go.opentelemetry.io/otel#SetLogger. We could add an example once we drop support for Go 1.20.

@pellared pellared added the documentation Provides helpful information label Nov 3, 2023
@Tadimsky
Copy link

Tadimsky commented Nov 3, 2023

Ok, that makes sense - I was not particularly interested in slog myself, but just a more generic interface that didn't require taking on another dependency on a different logging library.
I see that logr is relatively light weight - I was just surprised that we needed to take a dependency on it to provide the 4 methods that are actually needed for the logger in OTel.

@pgillich
Copy link
Author

pgillich commented Nov 3, 2023

Using slog as a logr backend (logr.LogSink) is a good tradeoff nowadays.

For long term, the app and library developers must choose between the 2 logging interfaces: logr vs slog. I cannot predict which will be the winner after 2 years. I'm using Go for 6 years and the famous log library was changed in each 2 years.

I accept @MrAlias opinion, the API should not deprecate a call, because it introduces a long-term confusing situation. So, if the app which is using an slog-based logging solution (the backend of slog can be zerolog or logrus), the https://pkg.go.dev/github.com/go-logr/logr/slogr can be used to set custom logr logger as internal logger of OpenTelemetry library. So the call chain can be, for example: OpenTelemetry --> logr --> slog --> logrus.

I don't know exactly, what is the learning lesson of this story. Maybe if the Go std lib developers had introduced the slog earlier (or they had integrated logr), we wouldn't be in this situation now.

@pellared
Copy link
Member

pellared commented Nov 4, 2023

Maybe if the Go std lib developers had introduced the slog earlier (or they had integrated logr), we wouldn't be in this situation now.

This is correct. We added the internal logger in https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.3.0 (released in Dec 10, 2021). The slog proposal golang/go#56345 was created in Oct 20, 2022.

Side note: OTel was driving one of the slog design decisions: https://go.googlesource.com/proposal/+/master/design/56345-structured-logging.md#levels

@jpopadak
Copy link

jpopadak commented Nov 10, 2023

I have an issue with using logr. Using go-logr/zapr to wrap our Uber Zap logger, it prints / gives incorrect log levels. This is because otel uses 0, 1, 4, and 8 as log levels, this causes negative levels within Zap from the logr to Zap translator causing nothing to get logged.

As a result, I have to have otel -> logr -> zapr -> custom zap.Core -> zap, or otel -> custom logr -> zap. If this used an interface with the appropriate methods, I could have a simple shim without needing zapr anywhere and leave the leveling up to the given users and their impls.

@pellared
Copy link
Member

pellared commented Nov 10, 2023

I have an issue with using logr. Using go-logr/zapr to wrap our Uber Zap logger, it prints / gives incorrect log levels. This is because otel uses 0, 1, 4, and 8 as log levels, this causes negative levels within Zap from the logr to Zap translator causing nothing to get logged.

This is a known thing. See: https://github.com/go-logr/zapr#increasing-verbosity

As a result, I have to have otel -> logr -> zapr -> custom zap.Core -> zap, or otel -> custom logr -> zap. If this used an interface with the appropriate methods, I could have a simple shim without needing zapr anywhere and leave the leveling up to the given users and their impls.

You can also write a custom logr.LogSink. The interface wouldn't be probably much different. Take notice that even https://pkg.go.dev/log/slog#Level uses similar logging levels.

Thanks to logr we give the user the flexibly too choose between existing logr sinks or creating your own. What would be the difference between otel -> custom otel log adapter -> zap and otel -> custom logr -> zap?

@jpopadak
Copy link

jpopadak commented Nov 13, 2023

What would be the difference between otel -> custom otel log adapter -> zap and otel -> custom logr -> zap?

Having interfaces, one does not have to deal with translating levels. Instead, it is more obvious what each function does, and keeps the deps cleaner.

I ended up writing a custom logr.LogSink, but then I had to code dive to find the levels being used. Also, nervous about otel-go changing the levels in the future (unlikely, but possible).

@pellared
Copy link
Member

I had to code dive to find the levels being used. Also, nervous about otel-go changing the levels in the future (unlikely, but possible).

I think we should document the logging levels in use in https://pkg.go.dev/go.opentelemetry.io/otel#SetLogger

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Provides helpful information enhancement New feature or request
Projects
Status: Low priority
Development

No branches or pull requests

5 participants