You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm integration river into a project and would love to be able to have some kind of a middleware pattern whereby I can inject metadata such as trace IDs, correlation IDs, etc...
I'm looking for something akin to the ClientMiddleware available in Sidekiq, which allows for access to the job object before persistence. The equivalent for ServerMiddleware is more straightforward, as wrapping the Worker interface is easily done!
I think for the time being I'll create a smaller Client interface for river that I propagate through my application which I can then use to wrap the Insert operations with custom logic that have access to the context in a uniform manner.
It might be nice for that to be something that can be configured directly on a *river.Client as a middleware stack?
Would be great to hear thoughts about how others are approaching this problem, and views around this from the maintainers as well! Thank you 😄
I've ended up with the following interface for the time being to enable instrumenting enqueues and propagating tracing information
package riverutil
import (
"context""github.com/jackc/pgx/v5""github.com/riverqueue/river""github.com/riverqueue/river/rivertype"
)
// Ensure *river.Client satisfies this interface at compile time.var_EnqueueClient[pgx.Tx] = (*river.Client[pgx.Tx])(nil)
// EnqueueClient defines the subset of the *river.Client interface which is// available when initialised without a `*pgxpool.Pool`.typeEnqueueClient[TTxany] interface {
InsertTx(context.Context, TTx, river.JobArgs, *river.InsertOpts) (*rivertype.JobRow, error)
InsertManyTx(context.Context, TTx, []river.InsertManyParams) (int64, error)
}
And the current implementation for instrumented enqueues (doesn't create any new spans, but propagates the SpanContext through, so the work can know where the job came from and correlate through):
package olly
import (
"context""fmt""github.com/jackc/pgx/v5""github.com/CGA1123/riverplayground/riverutil""github.com/riverqueue/river""github.com/riverqueue/river/riverdriver/riverpgxv5""github.com/riverqueue/river/rivertype""go.opentelemetry.io/otel""go.opentelemetry.io/otel/propagation"
)
typeenqueueClientstruct {
river*river.Client[pgx.Tx]
}
func (ec*enqueueClient) InsertTx(ctx context.Context, tx pgx.Tx, j river.JobArgs, opts*river.InsertOpts) (*rivertype.JobRow, error) {
opts=propagateRiverTrace(ctx, j, opts)
row, err:=ec.river.InsertTx(ctx, tx, j, opts)
iferr!=nil {
returnrow, err
}
returnrow, err
}
func (ec*enqueueClient) InsertManyTx(ctx context.Context, tx pgx.Tx, jobs []river.InsertManyParams) (int64, error) {
count, err:=ec.river.InsertManyTx(ctx, tx, jobs)
iferr!=nil {
returncount, err
}
for_, j:=rangejobs {
j.InsertOpts=propagateRiverTrace(ctx, j.Args, j.InsertOpts)
}
returncount, err
}
funcpropagateRiverTrace(ctx context.Context, j river.JobArgs, opts*river.InsertOpts) *river.InsertOpts {
ifopts==nil {
opts=&river.InsertOpts{}
}
vartags []stringifargsWithOpts, ok:=j.(river.JobArgsWithInsertOpts); ok {
tags=argsWithOpts.InsertOpts().Tags
}
c:=propagation.MapCarrier(map[string]string{})
otel.GetTextMapPropagator().Inject(ctx, c)
ollyTags:=make([]string, 0, len(c))
fork, v:=rangec {
ollyTags=append(ollyTags, fmt.Sprintf("%s%s:%s", riverTagPrefix, k, v))
}
// This replicates the behaviour of `river.insertParamsFromArgsAndOptions`ifopts.Tags==nil {
opts.Tags=append(tags, ollyTags...)
} else {
opts.Tags=append(opts.Tags, ollyTags...)
}
returnopts
}
// EnqueueClient returns a wrapped `*river.Client` with a reduced set of// methods exposed.//// It includes additional observability and propagates relevant metadata at// enqueue-time.//// This function will panic if `river.NewClient` returns an error.funcEnqueueClient(ctx context.Context) riverutil.EnqueueClient[pgx.Tx] {
c, err:=river.NewClient(riverpgxv5.New(nil), &river.Config{})
iferr!=nil {
panic(err)
}
return&enqueueClient{river: c}
}
Activity
CGA1123 commentedon Jan 18, 2024
I've ended up with the following interface for the time being to enable instrumenting enqueues and propagating tracing information
And the current implementation for instrumented enqueues (doesn't create any new spans, but propagates the SpanContext through, so the work can know where the job came from and correlate through):