feat: Add response and log functionalities#603
feat: Add response and log functionalities#603HomayoonAlimohammadi wants to merge 2 commits intocanonical:v3from
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds reusable helpers for HTTP error responses and introduces a public logging utility package so external consumers can integrate with Microcluster’s logging/context mechanism.
Changes:
- Added
UnauthorizedandErrorResponsehelpers inmicrocluster/rest/response/response.gofor standardized 401 and arbitrary-code error responses. - Introduced
pkg/log/log.goto expose the logger context key (CtxLogger) and aWithLoggerhelper that stores aslog.Loggerin acontext.Context.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| pkg/log/log.go | New public logging helper package that wraps the internal logger context key and provides WithLogger for attaching a logger to a context. |
| microcluster/rest/response/response.go | Extends the HTTP response helpers with 401 and generic error-response constructors and wires in errors.New for message-based error responses. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
7c09f3b to
e30187f
Compare
e30187f to
1af6e75
Compare
internal/log/logger.go
Outdated
| logger, ok := ctx.Value(CtxLogger).(*slog.Logger) | ||
| if !ok { | ||
| return nil, errors.New("Logger does not exist on context") | ||
| logger := slog.Default() |
There was a problem hiding this comment.
But this is already caught by the outer microcluster.LoggerFromContext isn't it? If this internal LoggerFromContext returns an error, then the outer one is running slog.New.
I don't understand why we have to replicate this here too?
roosterfish
left a comment
There was a problem hiding this comment.
Thanks for adding the two additional response funcs. However I still don't see the need for the proposed logging changes.
|
Thank you for the review @roosterfish. I've reached out on MM with more context around this change. |
Ty for providing the additional context. So as part of our unit tests we also run Deriving another default logger on the |
1af6e75 to
d7202ad
Compare
|
Thank you for all the information @roosterfish! I've updated the PR with what we've discussed |
|
|
||
| // CtxLogger is the name of the context value for the central logger. | ||
| const CtxLogger ctxKey = "logger" | ||
| const CtxLogger CtxKey = "logger" |
There was a problem hiding this comment.
Can we move the CtxLogger itself into microcluster/types/log? Then we can reference it both from the internal/ and public packages.
There was a problem hiding this comment.
My proposal was to only move the CtxLogger const, not the entire file. It looks you have now moved everything.
There was a problem hiding this comment.
If we just move CtxLogger const, we can still reference it from anywhere inside internal/ but you can also make use of it when you try to derive a new context including a logger.
|
|
||
| // WithLogger returns a new context with the given logger. | ||
| // If no logger is provided, the default logger is used. | ||
| func WithLogger(ctx context.Context, logger ...*slog.Logger) context.Context { |
There was a problem hiding this comment.
I don't understand the complexity of this func. Why does it have to be variadic and accept multiple instances of the logger if only ever the first instance is used?
If you wanted to add a helper func that takes care of populating the context value, you can just do something like return context.WithValue(ctx, CtxLogger, slog.Default()).
Please also ensure this helper is then used instead, for example here https://github.com/canonical/microcluster/blob/v3/internal/db/db_test.go#L655 (and the other places).
There was a problem hiding this comment.
The reason behind this variadic arg is to enable users to do:
ctx := types.WithLogger(context.Background())
// instead of
ctx := types.WithLogger(context.Background(), slog.Default())to set a default logger to the context.
and they're also able to do the following to set a custom logger:
ctx := types.WithLogger(context.Background(), myCustomLogger)I've seen this pattern in multiple places and found it interesting. If you don't like it I can change it.
There was a problem hiding this comment.
I see thanks.
Let's call it ContextWithLogger to be more descriptive. This would also align with the LoggerFromContext funcs.
microcluster/types/log.go
Outdated
| "context" | ||
| "log/slog" | ||
|
|
||
| "github.com/canonical/microcluster/v3/internal/log" |
There was a problem hiding this comment.
From microcluster/types we should not start importing from internal/ to prevent import loops in the future.
Therefore I mentioned to move the CtxLogger into microcluster/types/log directly.
e0ae35a to
e49501f
Compare
|
Can you please rebase, there are now conflicts after merging in other PRs. |
c429936 to
c250354
Compare
… handling Signed-off-by: Homayoon Alimohammadi <homayoon.alimohammadi@canonical.com>
c250354 to
e81370e
Compare
|
|
||
| // CtxLogger is the name of the context value for the central logger. | ||
| const CtxLogger ctxKey = "logger" | ||
| const CtxLogger CtxKey = "logger" |
There was a problem hiding this comment.
My proposal was to only move the CtxLogger const, not the entire file. It looks you have now moved everything.
|
|
||
| // CtxLogger is the name of the context value for the central logger. | ||
| const CtxLogger ctxKey = "logger" | ||
| const CtxLogger CtxKey = "logger" |
There was a problem hiding this comment.
If we just move CtxLogger const, we can still reference it from anywhere inside internal/ but you can also make use of it when you try to derive a new context including a logger.
Signed-off-by: Homayoon Alimohammadi <homayoon.alimohammadi@canonical.com>
e81370e to
dde6ba3
Compare
|
@roosterfish Thank you for the review. I've updated the PR with your instructions. Please let me know if it needs further polishing. |
roosterfish
left a comment
There was a problem hiding this comment.
The current logger changes feel too invasive to me.
In one of the recent comments I mentioned the approach we take for logging.
I understand you want to have an easy way to derive a context with the logger set to mimic what is done internally by Microcluster so you can test e.g. API handlers as standalone (as we do in our unit tests).
The easiest (which already works today) is to derive a context on your own using context.WithValue(context.TODO(), log.CtxLogger, slog.Default()).
But together we identified that log.CtxLogger is in the internal/ package. To overcome this limitation my suggestion was to move this constant into microcluster/types/log instead so you can access it too. It can even be a just a file like log.go (no need for an extra log/ package).
But this should not require this amount of changes. Only the places where we currently use log.CtxLogger need to be updated to point to the micrcluster/types package which would then contain the constant.
| ) | ||
|
|
||
| // CtxLogger is the name of the context value for the central logger. | ||
| const CtxLogger ctxKey = "logger" |
There was a problem hiding this comment.
Let's move this const (and type ctxKey) entirely into microcluster/types. There is no need to define another const here once it's moved.
|
|
||
| // WithLogger returns a new context with the given logger. | ||
| // If no logger is provided, the default logger is used. | ||
| func WithLogger(ctx context.Context, logger ...*slog.Logger) context.Context { |
There was a problem hiding this comment.
I see thanks.
Let's call it ContextWithLogger to be more descriptive. This would also align with the LoggerFromContext funcs.
| internalClient "github.com/canonical/microcluster/v3/internal/rest/client" | ||
| "github.com/canonical/microcluster/v3/internal/sys" | ||
| "github.com/canonical/microcluster/v3/microcluster/types" | ||
| mclog "github.com/canonical/microcluster/v3/microcluster/types/log" |
There was a problem hiding this comment.
We tend to use micro prefix if there is a collision. I would suggest microLog.
| // We always expect the logger to be present. | ||
| func (d *Daemon) log() *slog.Logger { | ||
| return d.shutdownCtx.Value(internalLog.CtxLogger).(*slog.Logger) //nolint:revive | ||
| l, err := internalLog.LoggerFromContext(d.shutdownCtx) |
There was a problem hiding this comment.
I am a bit surprised to see those changes.
At this stage there already is a logger set on the d.shutdownCtx. See the comment on the func.
Context
While migrating Canonical Kubernetes from Microcluster v2 to v3, I noticed that we're moving away from the
lxd/reponseand it's now baked into Microcluster itself. These two functions were used in Canonical Kubernetes but are not available in Microcluster. Also, the logging package is internal to Microcluster which makes testing on the k8s snap side impossible. The newpkg/logis supposed to surface the logging functionality.Overview
This pull request introduces new functionality for error handling in HTTP responses and adds a new logging utility package. The most important changes are the addition of helper functions for standardized error responses and the creation of a
logpackage to simplify logger usage with context.Error handling improvements:
UnauthorizedandErrorResponsehelper functions toresponse.gofor generating 401 and custom error responses, respectively.Logging utility:
microcluster/types/logpackage that wraps logger context handling, providing aWithLoggerfunction and exposing the logger context key for easier logger management from external repos.