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

feat!: introduce logger choice via slog #292

Merged
merged 6 commits into from
Apr 2, 2024
Merged

feat!: introduce logger choice via slog #292

merged 6 commits into from
Apr 2, 2024

Conversation

blgm
Copy link
Member

@blgm blgm commented Jan 29, 2024

Breaking Changes

This package now accepts a *slog.Logger from the Go standard library, rather than a Lager logger. This allows the use of alternative loggers.

  • This package no longer requires you to import code.cloudfoundry.org/lager/v3.
  • The constructors New(), NewWithCustomAuth(), NewWithOptions(), and also AttachRoutes() all take a *slog.Logger
  • apiresponses.FailureResponse errors with a ValidatedStatusCode() method also take a *slog.Logger rather than a Lager logger
  • The middleware middlewares.APIVersionMiddleware has had the LoggerFactory field removed, and a new field Logger added with type *slog.Logger.

If you want to continue to use Lager, you can just convert it to a *slog.Logger, for which you will need Lager v3.0.3 for example:

logger := lager.NewLogger("a-lager-logger")
router := brokerapi.New(serviceBroker, slog.New(lager.NewHandler(logger)), credentials)

Reasons

Historically brokerapi has required use of the lager logger. In Go 1.21, structured logging was introduced into the Go standard library via the log/slog package, and slog compatability was added to lager.

brokerapi has been modified to require a slog logger to be passed rather than a lager logger. This allows users a choice of logger. Users who still want to use lager can easily do that using the lager/slog compatability.

And users who want to use slog or an slog-compatible logger can do that instead.

A key advantage is that lager is no longer a dependency of this package, which simplifies package management for apps that use brokerapi and other libraries which use lager.

Resolves #267

@cf-gitbot
Copy link
Member

We have created an issue in Pivotal Tracker to manage this:

https://www.pivotaltracker.com/story/show/186934394

The labels on this github issue will be updated when the story is started.

Copy link

@elgohr elgohr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn’t it be better to use an interface that matches the slog/Logger, instead of the concrete type?

@blgm
Copy link
Member Author

blgm commented Jan 29, 2024

Wouldn’t it be better to use an interface that matches the slog/Logger, instead of the concrete type?

Good point. That's a good pattern in general for Go. I think I was influenced by an example (that I can no longer find) that I interpreted as it being "normal" to pass a logger concrete type. But the more I think about it, the more it makes sense to take an interface that's defined in brokerapi. (One of the big issues with lager.Loger is that despite being an interface, it's defined in the lager package, so you end up being tightly coupled to lager even if you write another logger that implements the interface.)

I'll have a go at re-working this to make that change.

@blgm
Copy link
Member Author

blgm commented Jan 29, 2024

There is a snag in using the interface approach. We use the With() method which returns a *slog.Logger, so if you define the interface to be:

type Logger interface {
	With(args ...any) Logger
}

Then *slog.Logger does implement this interface because With() has the wrong return type. And the alternative:

type Logger interface {
	With(args ...any) *slog.Logger
}

Is an improvement on a concrete type, but still ties us to the concrete type. But I had been thinking about usages of With() because in this PR they are quite boilerplate-heavy. If we could refactor them away, then we might get something much nicer.

@elgohr
Copy link

elgohr commented Jan 29, 2024

I'm struggeling at the same spot for the last 30 minutes 😂
Sadly it's go, not swift (where we could just have had an interface extension as a default implementation...)

@elgohr
Copy link

elgohr commented Jan 29, 2024

I tried to turn https://github.com/pivotal-cf/brokerapi/blob/main/utils/context.go#L46 around, into a context-aware handler, so that we could have all values within the context. But in the end we would need to set the handler, which is also not going to work without slog...

@elgohr
Copy link

elgohr commented Jan 29, 2024

In the end people have a dependency to the standard library (anyways) and could provide a custom slog.Handler. So let’s keep the slog.Logger for simplicity?

@blgm
Copy link
Member Author

blgm commented Feb 6, 2024

Thanks for trying @elgohr. I also considered not using the With() method which means that you can write an interface that doesn't mention *slog.Logger. But I agree that just taking a concrete *slog.Logger is simpler for now, and if we discover a better fix later, it would not be a breaking change.

@blgm blgm force-pushed the lagerless branch 2 times, most recently from 62d6db8 to b01172f Compare February 6, 2024 22:24
@blgm blgm changed the title feat!: (HOLD FOR GO 1.22 RELEASE) introduce logger choice via slog feat!: introduce logger choice via slog Feb 7, 2024
@blgm blgm force-pushed the lagerless branch 2 times, most recently from 2bd8880 to 5e2146c Compare February 7, 2024 22:49
Historically brokerapi has required use of the
[`lager`](https://github.com/cloudfoundry/lager) logger. In Go 1.21,
structured logging was introduced into the Go standard library via the
[`log/slog`](https://pkg.go.dev/log/slog) package, and `slog`
[compatability was added](cloudfoundry/lager@4bf4955)
to `lager`.

`brokerapi` has been modified to require a `slog` logger to be passed
rather than a `lager` logger. This allows users a choice of logger.
Users who still want to use lager can easily do that using the
lager/slog compatability:
```go
logger := lager.NewLogger(name)
brokerAPI := brokerapi.New(serviceBroker, slog.New(lager.NewHandler(logger)), credentials)
```

And users who want to use `slog` or an `slog`-compatible logger can do
that instead.

A key advantage is that `lager` is no longer a dependency of this
package, which simplifies package management for apps that use brokerapi
and other libraries which use `lager`.
Externally we take a *slog.Logger, but internally we wrap this
almost everywhere
Previously some of the public API had been altered to take the
(internal) Blog logger. This is not correct, so the public API should
now consistely take a *slog.Logger, and a Blog logger now implements the
slog.Handler interface so can easily be converted to a slog.Logger.
logger := h.logger.Session(lastBindingOperationLogKey, lager.Data{
instanceIDLogKey: instanceID,
}, utils.DataForContext(req.Context(), middlewares.CorrelationIDKey, middlewares.RequestIdentityKey))
logger := h.logger.Session(req.Context(), lastBindingOperationLogKey, blog.InstanceID(instanceID))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this didn't exist before but I think we should log the bindingID here as well since it is the binding last operation

@blgm blgm merged commit d4d5791 into main Apr 2, 2024
4 checks passed
@blgm blgm deleted the lagerless branch April 2, 2024 14:56
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 this pull request may close these issues.

Replace lager.Logger with log/slog.Logger
4 participants