8000 Arbitrary context call for `riverlog` middleware by brandur · Pull Request #919 · riverqueue/river · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Arbitrary context call for riverlog middleware #919

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

Merged
merged 1 commit into from
May 24, 2025

Conversation

brandur
Copy link
Contributor
@brandur brandur commented May 23, 2025

This one's inspired by #914. Our current riverlog implementation
should work pretty well for most users, but it does force them to use
slog. I'm inclined to provide additional options here because in all
honesty, slog's API is terrible, and I wouldn't want to use it myself.

Here, we add the new initializer NewMiddlewareCustomContext. Users
will have to bring their own context key like:

type CustomContextKey struct{}

New middleware takes a newContext function that should initialize
whatever it wants with an input writer and put it in context:

riverlog.NewMiddlewareCustomContext(func(ctx context.Context, w io.Writer) context.Context {
    logger := log.New(w, "", 0)
    return context.WithValue(ctx, CustomContextKey{}, logger)
}, nil),

Work functions then re-extract whatever they added to context (in
reality, they'd probably define their own shorthand helpers over what's
shown below):

func (w *CustomContextLoggingWorker) Work(ctx context.Context, job *river.Job[CustomContextLogginArgs]) error {
    logger := ctx.Value(CustomContextKey{}).(*log.Logger)
    logger.Printf("Raw log from worker")
    return nil
}

An alternative to this might be to expose more River innards that'd let
callers set their own metadata, but I'm kind of thinking that a project
like that should involve very thorough consideration, and may not even
be desirable. I think that even if we go that direction, the new
NewMiddlewareCustomContext would continue to be useful as a higher
level abstraction.

We also patch up another small riverlog problem point out in review in
that previously, we'd store a metadata value even if no logs at all were
emitted during the course of a work function. Here, we put in a check so
that metadata is left unchanged if no logging occurred. A new test case
verifies the behavior.

Fixes #914.

@brandur brandur force-pushed the brandur-free-form-context branch 2 times, most recently from b7d08d0 to 1ae432a Compare May 23, 2025 05:43
@brandur brandur requested a review from bgentry May 23, 2025 05:46
Copy link
Contributor
@bgentry bgentry left a comment

Choose a reason for hiding this comment

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

Seems like a good solution to #914. The only thing I'm not sure about is the "Arbitrary" suffix on this and I'm trying to think of a better one 🤔 Best I've come up with is NewMIddlewareConstructor or NewMiddlewareWithConstructor or NewMiddlewareCustomContext but I don't like any of those much either 🤷‍♂️

// out of work context.
type ArbitraryContextKey struct{}

type ArbitraryLogginArgs struct{}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
type ArbitraryLogginArgs struct{}
type ArbitraryLoggingArgs struct{}

Maybe we also want to keep the key unexported per typical context conventions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Struggled a bit on this one because it felt weird to not export ArbitraryContextKey if ArbitraryLogginArgs right below it is exported, and I didn't want to unexport that because all the other example tests export theirs.

It doesn't matter that much though. I made arbitraryContextKey unexported.

@@ -145,5 +181,5 @@ func (m *Middleware) Work(ctx context.Context, job *rivertype.JobRow, doInner fu
metadataUpdates[metadataKey] = json.RawMessage(allLogDataBytes)
Copy link
Contributor

Choose a reason for hiding this comment

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

Just realized we're storing a log here even if logData is empty. Do you think that's the right call, or should we skip touching that metadata if there's nothing useful to write?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, good call. Fixed that up, added a test case, changelog, and modified the commit message to mention the change.

@brandur brandur force-pushed the brandur-free-form-context branch from 1ae432a to 8224749 Compare May 24, 2025 00:51
This one's inspired by #914. Our current `riverlog` implementation
should work pretty well for most users, but it does force them to use
slog. I'm inclined to provide additional options here because in all
honesty, slog's API is terrible, and I wouldn't want to use it myself.

Here, we add the new initializer `NewMiddlewareCustomContext`. Users
will have to bring their own context key like:

    type CustomContextKey struct{}

New middleware takes a `newContext` function that should initialize
whatever it wants with an input writer and put it in context:

    riverlog.NewMiddlewareCustomContext(func(ctx context.Context, w io.Writer) context.Context {
        logger := log.New(w, "", 0)
        return context.WithValue(ctx, CustomContextKey{}, logger)
    }, nil),

Work functions then re-extract whatever they added to context (in
reality, they'd probably define their own shorthand helpers over what's
shown below):

    func (w *CustomContextLoggingWorker) Work(ctx context.Context, job *river.Job[CustomContextLogginArgs]) error {
        logger := ctx.Value(CustomContextKey{}).(*log.Logger)
        logger.Printf("Raw log from worker")
        return nil
    }

An alternative to this might be to expose more River innards that'd let
callers set their own metadata, but I'm kind of thinking that a project
like that should involve very thorough consideration, and may not even
be desirable. I think that even if we go that direction, the new
`NewMiddlewareCustomContext` would continue to be useful as a higher
level abstraction.

We also patch up another small `riverlog` problem point out in review in
that previously, we'd store a metadata value even if no logs at all were
emitted during the course of a work function. Here, we put in a check so
that metadata is left unchanged if no logging occurred. A new test case
verifies the behavior.

Fixes #914.
@brandur brandur force-pushed the brandur-free-form-context branch from 8224749 to 13b3cad Compare May 24, 2025 02:25
@brandur
Copy link
Contributor Author
brandur commented May 24, 2025

The only thing I'm not sure about is the "Arbitrary" suffix on this and I'm trying to think of a better one 🤔 Best I've come up with is NewMIddlewareConstructor or NewMiddlewareWithConstructor or NewMiddlewareCustomContext but I don't like any of those much either 🤷‍♂️

Yeah I wasn't crazy about it either but I was having a ton of trouble coming up with anything good that wasn't overly verbose. I changed it to CustomContext since that's as good as anything I came up with.

8000

@brandur
Copy link
Contributor Author
brandur commented May 24, 2025

Thanks!

@brandur brandur merged commit 5e4c094 into master May 24, 2025
10 checks passed
@brandur brandur deleted the brandur-free-form-context branch May 24, 2025 02:39
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.

Implementing our own logging as it is done for riverlog
2 participants
0