@@ -52,7 +52,12 @@ type APIOption func(o *apiOpts) error
52
52
// err is the error that caused the retry.
53
53
type RetryCallback func (err error )
54
54
55
- // TODO(bwplotka): Add "too old sample" handling one day.
55
+ // MessageFilter is a function that filters or modifies the message before each write attempt.
56
+ // It receives the attempt number (0 = first attempt, 1+ = retries) and the message to be sent.
57
+ // It returns a potentially modified message, or an error if the message should not be sent.
58
+ // This can be used for age-based filtering, deduplication, or other application-level logic.
59
+ type MessageFilter func (attempt int , msg any ) (filtered any , err error )
60
+
56
61
type apiOpts struct {
57
62
logger * slog.Logger
58
63
client * http.Client
@@ -169,6 +174,7 @@ type WriteOption func(o *writeOpts)
169
174
170
175
type writeOpts struct {
171
176
retryCallback RetryCallback
177
+ filterFunc MessageFilter
172
178
}
173
179
174
180
// WithWriteRetryCallback sets a retry callback for this Write request.
@@ -179,6 +185,16 @@ func WithWriteRetryCallback(callback RetryCallback) WriteOption {
179
185
}
180
186
}
181
187
188
+ // WithWriteFilter sets a filter function for this Write request.
189
+ // The filter is invoked before each write attempt (including the initial attempt).
190
+ // This allows filtering out old samples, deduplication, or other application-level logic.
191
+ // If the filter returns an error, the Write operation will stop and return that error.
192
+ func WithWriteFilter (filter MessageFilter ) WriteOption {
193
+ return func (o * writeOpts ) {
194
+ o .filterFunc = filter
195
+ }
196
+ }
197
+
182
198
type vtProtoEnabled interface {
183
199
SizeVT () int
184
200
MarshalToSizedBufferVT (dAtA []byte ) (int , error )
@@ -205,63 +221,79 @@ func (r *API) Write(ctx context.Context, msgType WriteMessageType, msg any, opts
205
221
opt (& writeOpts )
206
222
}
207
223
208
- buf := r .bufPool .Get ().(* []byte )
209
-
210
224
if err := msgType .Validate (); err != nil {
211
225
return WriteResponseStats {}, err
212
226
}
213
227
214
- // Encode the payload.
215
- switch m := msg .(type ) {
216
- case vtProtoEnabled :
217
- // Use optimized vtprotobuf if supported.
218
- size := m .SizeVT ()
219
- if cap (* buf ) < size {
220
- * buf = make ([]byte , size )
221
- } else {
222
- * buf = (* buf )[:size ]
223
- }
228
+ // Since we retry writes we need to track the total amount of accepted data
229
+ // across the various attempts.
230
+ accumulatedStats := WriteResponseStats {}
224
231
225
- if _ , err := m .MarshalToSizedBufferVT (* buf ); err != nil {
226
- return WriteResponseStats {}, fmt .Errorf ("encoding request %w" , err )
227
- }
228
- case gogoProtoEnabled :
229
- // Gogo proto if supported.
230
- size := m .Size ()
231
- if cap (* buf ) < size {
232
- * buf = make ([]byte , size )
233
- } else {
234
- * buf = (* buf )[:size ]
232
+ b := backoff .New (ctx , r .opts .backoff )
233
+ for {
234
+ // Apply filter if provided.
235
+ currentMsg := msg
236
+ if writeOpts .filterFunc != nil {
237
+ filteredMsg , err := writeOpts .filterFunc (b .NumRetries (), msg )
238
+ if err != nil {
239
+ // Filter returned error, likely no data left to send.
240
+ return accumulatedStats , err
241
+ }
242
+ currentMsg = filteredMsg
235
243
}
236
244
237
- if _ , err := m .MarshalToSizedBuffer (* buf ); err != nil {
238
- return WriteResponseStats {}, fmt .Errorf ("encoding request %w" , err )
245
+ // Encode the payload.
246
+ buf := r .bufPool .Get ().(* []byte )
247
+ switch m := currentMsg .(type ) {
248
+ case vtProtoEnabled :
249
+ // Use optimized vtprotobuf if supported.
250
+ size := m .SizeVT ()
251
+ if cap (* buf ) < size {
252
+ * buf = make ([]byte , size )
253
+ } else {
254
+ * buf = (* buf )[:size ]
255
+ }
256
+
257
+ if _ , err := m .MarshalToSizedBufferVT (* buf ); err != nil {
258
+ r .bufPool .Put (buf )
259
+ return WriteResponseStats {}, fmt .Errorf ("encoding request %w" , err )
260
+ }
261
+ case gogoProtoEnabled :
262
+ // Gogo proto if supported.
263
+ size := m .Size ()
264
+ if cap (* buf ) < size {
265
+ * buf = make ([]byte , size )
266
+ } else {
267
+ * buf = (* buf )[:size ]
268
+ }
269
+
270
+ if _ , err := m .MarshalToSizedBuffer (* buf ); err != nil {
271
+ r .bufPool .Put (buf )
272
+ return WriteResponseStats {}, fmt .Errorf ("encoding request %w" , err )
273
+ }
274
+ case proto.Message :
275
+ // Generic proto.
276
+ * buf , err = (proto.MarshalOptions {}).MarshalAppend (* buf , m )
277
+ if err != nil {
278
+ r .bufPool .Put (buf )
279
+ return WriteResponseStats {}, fmt .Errorf ("encoding request %w" , err )
280
+ }
281
+ default :
282
+ r .bufPool .Put (buf )
283
+ return WriteResponseStats {}, fmt .Errorf ("unknown message type %T" , m )
239
284
}
240
- case proto. Message :
241
- // Generic proto.
242
- * buf , err = (proto. MarshalOptions {}). MarshalAppend ( * buf , m )
285
+
286
+ comprBuf := r . bufPool . Get ().( * [] byte )
287
+ payload , err := compressPayload ( comprBuf , r . opts . compression , * buf )
243
288
if err != nil {
244
- return WriteResponseStats {}, fmt .Errorf ("encoding request %w" , err )
289
+ r .bufPool .Put (buf )
290
+ r .bufPool .Put (comprBuf )
291
+ return WriteResponseStats {}, fmt .Errorf ("compressing %w" , err )
245
292
}
246
- default :
247
- return WriteResponseStats {}, fmt .Errorf ("unknown message type %T" , m )
248
- }
293
+ r .bufPool .Put (buf )
249
294
250
- comprBuf := r .bufPool .Get ().(* []byte )
251
- payload , err := compressPayload (comprBuf , r .opts .compression , * buf )
252
- if err != nil {
253
- return WriteResponseStats {}, fmt .Errorf ("compressing %w" , err )
254
- }
255
- r .bufPool .Put (buf )
256
- defer r .bufPool .Put (comprBuf )
257
-
258
- // Since we retry writes we need to track the total amount of accepted data
259
- // across the various attempts.
260
- accumulatedStats := WriteResponseStats {}
261
-
262
- b := backoff .New (ctx , r .opts .backoff )
263
- for {
264
295
rs , err := r .attemptWrite (ctx , r .opts .compression , msgType , payload , b .NumRetries ())
296
+ r .bufPool .Put (comprBuf )
265
297
accumulatedStats .Add (rs )
266
298
if err == nil {
267
299
// Check the case mentioned in PRW 2.0.
0 commit comments