Skip to content

feat: Add response and log functionalities#603

Open
HomayoonAlimohammadi wants to merge 2 commits intocanonical:v3from
HomayoonAlimohammadi:KU-5052/unauth-response
Open

feat: Add response and log functionalities#603
HomayoonAlimohammadi wants to merge 2 commits intocanonical:v3from
HomayoonAlimohammadi:KU-5052/unauth-response

Conversation

@HomayoonAlimohammadi
Copy link

@HomayoonAlimohammadi HomayoonAlimohammadi commented Jan 29, 2026

Context

While migrating Canonical Kubernetes from Microcluster v2 to v3, I noticed that we're moving away from the lxd/reponse and 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 new pkg/log is 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 log package to simplify logger usage with context.

Error handling improvements:

  • Added Unauthorized and ErrorResponse helper functions to response.go for generating 401 and custom error responses, respectively.

Logging utility:

  • Introduced a new microcluster/types/log package that wraps logger context handling, providing a WithLogger function and exposing the logger context key for easier logger management from external repos.

@HomayoonAlimohammadi HomayoonAlimohammadi changed the title feat(response): Add Unauthorized and ErrorResponse functions feat: Add response and log functionalities Jan 29, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 Unauthorized and ErrorResponse helpers in microcluster/rest/response/response.go for standardized 401 and arbitrary-code error responses.
  • Introduced pkg/log/log.go to expose the logger context key (CtxLogger) and a WithLogger helper that stores a slog.Logger in a context.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.

@HomayoonAlimohammadi HomayoonAlimohammadi force-pushed the KU-5052/unauth-response branch 2 times, most recently from 7c09f3b to e30187f Compare January 29, 2026 13:36
logger, ok := ctx.Value(CtxLogger).(*slog.Logger)
if !ok {
return nil, errors.New("Logger does not exist on context")
logger := slog.Default()
Copy link
Contributor

@roosterfish roosterfish Jan 30, 2026

Choose a reason for hiding this comment

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

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?

Copy link
Contributor

@roosterfish roosterfish left a comment

Choose a reason for hiding this comment

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

Thanks for adding the two additional response funcs. However I still don't see the need for the proposed logging changes.

@HomayoonAlimohammadi
Copy link
Author

Thank you for the review @roosterfish. I've reached out on MM with more context around this change.

@roosterfish
Copy link
Contributor

Thank you for the review @roosterfish. I've reached out on MM with more context around this change.

Ty for providing the additional context.
When you test your API handlers as a standalone, the passed context requires the logger to be set (which happens automatically when the handler is fired inside Microcluster).

So as part of our unit tests we also run ctx := context.WithValue(context.TODO(), log.CtxLogger, slog.Default()) to ensure we have a context with the required logger set to e.g. run the database funcs which might need to access the logger.

Deriving another default logger on the internal/log.LoggerFromContext func isn't trivial as we don't know at this point which log handler was passed when starting Microcluster.
If you want to test the API handler external from Microcluster, pass a context with the "context" key populated as shown above.
If we start returning a default logger, we might miss places where we unintentionally pass the wrong context (not the daemon's shutdown ctx), so it takes the default but instead we want the logs to be routed to the actual logger which isn't available as the context is wrong.

@HomayoonAlimohammadi
Copy link
Author

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"
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we move the CtxLogger itself into microcluster/types/log? Then we can reference it both from the internal/ and public packages.

Copy link
Contributor

Choose a reason for hiding this comment

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

My proposal was to only move the CtxLogger const, not the entire file. It looks you have now moved everything.

Copy link
Contributor

Choose a reason for hiding this comment

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

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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

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).

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see thanks.
Let's call it ContextWithLogger to be more descriptive. This would also align with the LoggerFromContext funcs.

"context"
"log/slog"

"github.com/canonical/microcluster/v3/internal/log"
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

@HomayoonAlimohammadi HomayoonAlimohammadi force-pushed the KU-5052/unauth-response branch 2 times, most recently from e0ae35a to e49501f Compare February 3, 2026 15:42
@roosterfish
Copy link
Contributor

Can you please rebase, there are now conflicts after merging in other PRs.

… handling

Signed-off-by: Homayoon Alimohammadi <homayoon.alimohammadi@canonical.com>

// CtxLogger is the name of the context value for the central logger.
const CtxLogger ctxKey = "logger"
const CtxLogger CtxKey = "logger"
Copy link
Contributor

Choose a reason for hiding this comment

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

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"
Copy link
Contributor

Choose a reason for hiding this comment

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

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>
@HomayoonAlimohammadi
Copy link
Author

@roosterfish Thank you for the review. I've updated the PR with your instructions. Please let me know if it needs further polishing.

Copy link
Contributor

@roosterfish roosterfish left a comment

Choose a reason for hiding this comment

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

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"
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe my comment here was not clear.


// 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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

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"
Copy link
Contributor

Choose a reason for hiding this comment

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

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)
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

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.

2 participants