@@ -38,12 +38,12 @@ pub fn disable_rate_limiting(disabled: bool) {
38
38
39
39
#[ doc( hidden) ]
40
40
pub struct RateLimiter {
41
- period_ms : u32 ,
42
- events_per_period : u32 ,
43
41
state : Mutex < RateLimiterState > ,
44
42
}
45
43
46
44
struct RateLimiterState {
45
+ period_ms : u32 ,
46
+ events_per_period : u32 ,
47
47
start : Option < Instant > ,
48
48
events : u32 ,
49
49
missed : u64 ,
@@ -59,9 +59,9 @@ impl RateLimiter {
59
59
60
60
pub const fn new ( period_ms : u32 , events_per_period : u32 ) -> Self {
61
61
Self {
62
- period_ms,
63
- events_per_period,
64
62
state : Mutex :: new ( RateLimiterState {
63
+ period_ms,
64
+ events_per_period,
65
65
start : None ,
66
66
events : 0 ,
67
67
missed : 0 ,
@@ -74,31 +74,110 @@ impl RateLimiter {
74
74
/// `missed_events` is `Some(n)` if there were any missed events or if this
75
75
/// event is the last one before rate limiting kicks in.
76
76
pub fn event ( & self ) -> Result < Option < u64 > , RateLimited > {
77
+ self . event_with_config ( None , None )
78
+ }
79
+
80
+ /// Returns `Ok(missed_events)` if this event should be logged.
81
+ /// Optionally reconfigures the rate limiter if new parameters are provided.
82
+ ///
83
+ /// `missed_events` is `Some(n)` if there were any missed events or if this
84
+ /// event is the last one before rate limiting kicks in.
85
+ pub fn event_with_config (
86
+ & self ,
87
+ period_ms : Option < u32 > ,
88
+ events_per_period : Option < u32 > ,
89
+ ) -> Result < Option < u64 > , RateLimited > {
77
90
if DISABLE_RATE_LIMITING . load ( Ordering :: Relaxed ) {
78
91
return Ok ( None ) ;
79
92
}
93
+
80
94
let mut state = self . state . try_lock ( ) . ok_or ( RateLimited ) ?;
95
+
96
+ // Reconfigure if new parameters are provided
97
+ let mut reset_state = false ;
98
+ if let Some ( new_period) = period_ms {
99
+ if state. period_ms != new_period {
100
+ state. period_ms = new_period;
101
+ reset_state = true ;
102
+ }
103
+ }
104
+ if let Some ( new_events_per_period) = events_per_period {
105
+ if state. events_per_period != new_events_per_period {
106
+ state. events_per_period = new_events_per_period;
107
+ reset_state = true ;
108
+ }
109
+ }
110
+
111
+ // Reset state when parameters change
112
+ if reset_state {
113
+ state. start = None ;
114
+ state. events = 0 ;
115
+ state. missed = 0 ;
116
+ }
117
+
81
118
let now = Instant :: now ( ) ;
119
+ let period_ms = state. period_ms ;
82
120
let start = state. start . get_or_insert ( now) ;
83
121
let elapsed = now. duration_since ( * start) ;
84
- if elapsed. as_millis ( ) > self . period_ms as u128 {
122
+ if elapsed. as_millis ( ) > period_ms as u128 {
85
123
* start = now;
86
124
state. events = 0 ;
87
125
}
88
- if state. events >= self . events_per_period {
126
+ if state. events >= state . events_per_period {
89
127
state. missed += 1 ;
90
128
return Err ( RateLimited ) ;
91
129
}
92
130
state. events += 1 ;
93
131
let missed = std:: mem:: take ( & mut state. missed ) ;
94
- let missed = ( missed != 0 || state. events == self . events_per_period ) . then_some ( missed) ;
132
+ let missed = ( missed != 0 || state. events == state . events_per_period ) . then_some ( missed) ;
95
133
Ok ( missed)
96
134
}
97
135
}
98
136
99
137
/// As [`tracing::error!`], but rate limited.
138
+ ///
139
+ /// Can be called with optional parameters to customize rate limiting:
140
+ /// - `period: <ms>` - rate limiting period in milliseconds
141
+ /// - `limit: <count>` - maximum events per period
142
+ ///
143
+ /// Examples:
144
+ /// ```
145
+ /// use tracelimit::error_ratelimited;
146
+ /// error_ratelimited!("simple message");
147
+ /// error_ratelimited!(period: 1000, limit: 5, "custom rate limit");
148
+ /// error_ratelimited!(period: 10000, "custom period only");
149
+ /// error_ratelimited!(limit: 50, "custom limit only");
150
+ /// ```
100
151
#[ macro_export]
101
152
macro_rules! error_ratelimited {
153
+ // With both period and limit
154
+ ( period: $period: expr, limit: $limit: expr, $( $rest: tt) * ) => {
155
+ {
156
+ static RATE_LIMITER : $crate:: RateLimiter = $crate:: RateLimiter :: new_default( ) ;
157
+ if let Ok ( missed_events) = RATE_LIMITER . event_with_config( Some ( $period) , Some ( $limit) ) {
158
+ $crate:: tracing:: error!( dropped_ratelimited = missed_events, $( $rest) * ) ;
159
+ }
160
+ }
161
+ } ;
162
+ // With period only
163
+ ( period: $period: expr, $( $rest: tt) * ) => {
164
+ {
165
+ static RATE_LIMITER : $crate:: RateLimiter = $crate:: RateLimiter :: new_default( ) ;
166
+ if let Ok ( missed_events) = RATE_LIMITER . event_with_config( Some ( $period) , None ) {
167
+ $crate:: tracing:: error!( dropped_ratelimited = missed_events, $( $rest) * ) ;
168
+ }
169
+ }
170
+ } ;
171
+ // With limit only
172
+ ( limit: $limit: expr, $( $rest: tt) * ) => {
173
+ {
174
+ static RATE_LIMITER : $crate:: RateLimiter = $crate:: RateLimiter :: new_default( ) ;
175
+ if let Ok ( missed_events) = RATE_LIMITER . event_with_config( None , Some ( $limit) ) {
176
+ $crate:: tracing:: error!( dropped_ratelimited = missed_events, $( $rest) * ) ;
177
+ }
178
+ }
179
+ } ;
180
+ // Default case (no custom parameters)
102
181
( $( $rest: tt) * ) => {
103
182
{
104
183
static RATE_LIMITER : $crate:: RateLimiter = $crate:: RateLimiter :: new_default( ) ;
@@ -110,8 +189,49 @@ macro_rules! error_ratelimited {
110
189
}
111
190
112
191
/// As [`tracing::warn!`], but rate limited.
192
+ ///
193
+ /// Can be called with optional parameters to customize rate limiting:
194
+ /// - `period: <ms>` - rate limiting period in milliseconds
195
+ /// - `limit: <count>` - maximum events per period
196
+ ///
197
+ /// Examples:
198
+ /// ```
199
+ /// use tracelimit::warn_ratelimited;
200
+ /// warn_ratelimited!("simple message");
201
+ /// warn_ratelimited!(period: 1000, limit: 5, "custom rate limit");
202
+ /// warn_ratelimited!(period: 10000, "custom period only");
203
+ /// warn_ratelimited!(limit: 50, "custom limit only");
204
+ /// ```
113
205
#[ macro_export]
114
206
macro_rules! warn_ratelimited {
207
+ // With both period and limit
208
+ ( period: $period: expr, limit: $limit: expr, $( $rest: tt) * ) => {
209
+ {
210
+ static RATE_LIMITER : $crate:: RateLimiter = $crate:: RateLimiter :: new_default( ) ;
211
+ if let Ok ( missed_events) = RATE_LIMITER . event_with_config( Some ( $period) , Some ( $limit) ) {
212
+ $crate:: tracing:: warn!( dropped_ratelimited = missed_events, $( $rest) * ) ;
213
+ }
214
+ }
215
+ } ;
216
+ // With period only
217
+ ( period: $period: expr, $( $rest: tt) * ) => {
218
+ {
219
+ static RATE_LIMITER : $crate:: RateLimiter = $crate:: RateLimiter :: new_default( ) ;
220
+ if let Ok ( missed_events) = RATE_LIMITER . event_with_config( Some ( $period) , None ) {
221
+ $crate:: tracing:: warn!( dropped_ratelimited = missed_events, $( $rest) * ) ;
222
+ }
223
+ }
224
+ } ;
225
+ // With limit only
226
+ ( limit: $limit: expr, $( $rest: tt) * ) => {
227
+ {
228
+ static RATE_LIMITER : $crate:: RateLimiter = $crate:: RateLimiter :: new_default( ) ;
229
+ if let Ok ( missed_events) = RATE_LIMITER . event_with_config( None , Some ( $limit) ) {
230
+ $crate:: tracing:: warn!( dropped_ratelimited = missed_events, $( $rest) * ) ;
231
+ }
232
+ }
233
+ } ;
234
+ // Default case (no custom parameters)
115
235
( $( $rest: tt) * ) => {
116
236
{
117
237
static RATE_LIMITER : $crate:: RateLimiter = $crate:: RateLimiter :: new_default( ) ;
@@ -123,8 +243,49 @@ macro_rules! warn_ratelimited {
123
243
}
124
244
125
245
/// As [`tracing::info!`], but rate limited.
246
+ ///
247
+ /// Can be called with optional parameters to customize rate limiting:
248
+ /// - `period: <ms>` - rate limiting period in milliseconds
249
+ /// - `limit: <count>` - maximum events per period
250
+ ///
251
+ /// Examples:
252
+ /// ```
253
+ /// use tracelimit::info_ratelimited;
254
+ /// info_ratelimited!("simple message");
255
+ /// info_ratelimited!(period: 1000, limit: 5, "custom rate limit");
256
+ /// info_ratelimited!(period: 10000, "custom period only");
257
+ /// info_ratelimited!(limit: 50, "custom limit only");
258
+ /// ```
126
259
#[ macro_export]
127
260
macro_rules! info_ratelimited {
261
+ // With both period and limit
262
+ ( period: $period: expr, limit: $limit: expr, $( $rest: tt) * ) => {
263
+ {
264
+ static RATE_LIMITER : $crate:: RateLimiter = $crate:: RateLimiter :: new_default( ) ;
265
+ if let Ok ( missed_events) = RATE_LIMITER . event_with_config( Some ( $period) , Some ( $limit) ) {
266
+ $crate:: tracing:: info!( dropped_ratelimited = missed_events, $( $rest) * ) ;
267
+ }
268
+ }
269
+ } ;
270
+ // With period only
271
+ ( period: $period: expr, $( $rest: tt) * ) => {
272
+ {
273
+ static RATE_LIMITER : $crate:: RateLimiter = $crate:: RateLimiter :: new_default( ) ;
274
+ if let Ok ( missed_events) = RATE_LIMITER . event_with_config( Some ( $period) , None ) {
275
+ $crate:: tracing:: info!( dropped_ratelimited = missed_events, $( $rest) * ) ;
276
+ }
277
+ }
278
+ } ;
279
+ // With limit only
280
+ ( limit: $limit: expr, $( $rest: tt) * ) => {
281
+ {
282
+ static RATE_LIMITER : $crate:: RateLimiter = $crate:: RateLimiter :: new_default( ) ;
283
+ if let Ok ( missed_events) = RATE_LIMITER . event_with_config( None , Some ( $limit) ) {
284
+ $crate:: tracing:: info!( dropped_ratelimited = missed_events, $( $rest) * ) ;
285
+ }
286
+ }
287
+ } ;
288
+ // Default case (no custom parameters)
128
289
( $( $rest: tt) * ) => {
129
290
{
130
291
static RATE_LIMITER : $crate:: RateLimiter = $crate:: RateLimiter :: new_default( ) ;
0 commit comments