Skip to content

Add StoreOption/WithSinkWrap to expose the sink NewDefaultStore builds - #178

Open
th0114nd wants to merge 3 commits into
masterfrom
tholland/default-store-sink-wrap
Open

Add StoreOption/WithSinkWrap to expose the sink NewDefaultStore builds#178
th0114nd wants to merge 3 commits into
masterfrom
tholland/default-store-sink-wrap

Conversation

@th0114nd

@th0114nd th0114nd commented Jul 28, 2026

Copy link
Copy Markdown

Summary

  • NewDefaultStore builds its Sink (TCP statsd, or logging/null depending on Settings) entirely internally and returns only the finished Store -- there's no way to decorate that sink (e.g. to filter which stats are actually written) without reimplementing NewDefaultStore's sink-selection logic from scratch.
  • Adds a functional-options mechanism for NewDefaultStore, in the same spirit as the existing SinkOption pattern for sinks (WithLogger, WithStatsdHost, etc. in net_sink.go), but as a plain func type rather than an interface: type StoreOption func(*storeOptions). storeOptions is unexported, so the interface+apply-method indirection SinkOption uses wouldn't add any real encapsulation here -- callers still can't construct a StoreOption themselves either way.
  • NewDefaultStore(opts ...StoreOption) and WithSinkWrap(wrap func(FlushableSink) FlushableSink) StoreOption. wrap receives the sink NewDefaultStore would otherwise have used unwrapped, and its return value is what the Store is actually constructed with.
  • NewDefaultStore() (no options) is unchanged -- variadic options mean every existing call site keeps compiling and behaving identically. This is purely additive.

Motivated by a downstream service (matching) that currently duplicates this exact sink-selection logic in its own repo just to wrap the sink in a stats-blocklist filter -- see the internal statsblocklist.Sink package for context. Opened as a draft for early feedback on the API shape before I write more tests / polish docs.

Test plan

  • go build ./...
  • go vet ./...
  • go test -race ./... (128 passing, including new cases for WithSinkWrap and a zero-options NewDefaultStore() regression check)

NewDefaultStore builds its Sink (a TCP statsd sink, or a logging/null sink
depending on Settings) entirely internally and returns only the finished
Store, so a caller who wants to decorate that sink -- e.g. to filter which
stats actually get written -- has to reimplement NewDefaultStore's
sink-selection logic just to get a handle on the sink to wrap.

NewDefaultStoreWithSink(wrap func(FlushableSink) FlushableSink) Store adds
that hook: it selects the sink exactly as NewDefaultStore does, but passes
it through wrap before constructing the Store. NewDefaultStore itself now
just calls this with an identity wrap, so there's a single implementation
of the sink-selection logic instead of two.
@th0114nd th0114nd changed the title Add NewDefaultStoreWithSink to expose the sink NewDefaultStore builds Add StoreOption/WithSinkWrap to expose the sink NewDefaultStore builds Jul 28, 2026
th0114nd added 2 commits July 28, 2026 11:44
Match the existing SinkOption pattern (WithLogger, WithStatsdHost, etc. in
net_sink.go) instead of introducing a separate NewDefaultStoreWithSink
function: NewDefaultStore now takes variadic StoreOptions, and
WithSinkWrap(wrap func(FlushableSink) FlushableSink) StoreOption lets a
caller decorate the sink NewDefaultStore would otherwise have used
unwrapped.

NewDefaultStore() with no options is unchanged -- variadic options mean
every existing call site keeps compiling and behaving identically.
storeOptions is unexported either way, so the interface+apply indirection
(mirroring SinkOption) bought no extra encapsulation: nothing outside the
package could construct a StoreOption regardless, since only functions in
this package can reference storeOptions to write one. A plain
type StoreOption func(*storeOptions) is equivalent and simpler.
@th0114nd
th0114nd marked this pull request as ready for review July 28, 2026 15:51
Comment thread stats.go

// NewDefaultStore returns a Store with a TCP statsd sink, and a running flush timer.
func NewDefaultStore() Store {
func NewDefaultStore(opts ...StoreOption) Store {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think we should add sth like NewDefaultStoreWithOptions instead, and keep the backward compatibility for NewDefaultStore - especially because this is a public library.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

brainstormed a bit more - maybe it's simpler to expose sink instead? e.g. NewDefaultSink() with settings

then pseudocode integration for matching would be

sink := Wrap(stats.NewDefaultSink())
store := stats.NewStore(sink, false)
go store.Start(time.NewTicker(settings.FlushInterval()))
...

Comment thread stats_test.go
loggingSinkDisabled string
assertType func(t *testing.T, got FlushableSink)
}{
{"statsd", "true", "false", func(t *testing.T, got FlushableSink) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

i could be wrong but doesn't this setup the full sink that tries to establish a TCP connection?

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