Mercurial > hg > nginx
comparison src/core/ngx_log.c @ 9299:2706b60dc225
Core: error logging rate limiting.
With this change, error logging to files can be rate-limited with
the "rate=" parameter. The parameter specifies allowed log messages
rate to a particular file (per worker), in messages per second (m/s).
By default, "rate=1000m/s" is used.
Rate limiting is implemented using the "leaky bucket" method, similarly
to the limit_req module.
Maximum burst size is set to the number of log messages per second
for each severity level, so "error" messages are logged even if the
rate limit is hit by "info" messages (but not vice versa). When the
limit is reached for a particular level, the "too many log messages,
limiting" message is logged at this level.
If debug logging is enabled, either for the particular log file or for
the particular connection, rate limiting is not used.
author | Maxim Dounin <mdounin@mdounin.ru> |
---|---|
date | Tue, 25 Jun 2024 22:58:56 +0300 |
parents | 14770557be17 |
children |
comparison
equal
deleted
inserted
replaced
9298:14770557be17 | 9299:2706b60dc225 |
---|---|
7 | 7 |
8 #include <ngx_config.h> | 8 #include <ngx_config.h> |
9 #include <ngx_core.h> | 9 #include <ngx_core.h> |
10 | 10 |
11 | 11 |
12 static ngx_int_t ngx_log_check_rate(ngx_log_t *log, ngx_uint_t level); | |
12 static char *ngx_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); | 13 static char *ngx_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); |
13 static char *ngx_log_set_levels(ngx_conf_t *cf, ngx_log_t *log); | 14 static char *ngx_log_set_params(ngx_conf_t *cf, ngx_log_t *log); |
14 static void ngx_log_insert(ngx_log_t *log, ngx_log_t *new_log); | 15 static void ngx_log_insert(ngx_log_t *log, ngx_log_t *new_log); |
15 | 16 |
16 | 17 |
17 #if (NGX_DEBUG) | 18 #if (NGX_DEBUG) |
18 | 19 |
162 | 163 |
163 if (log->log_level < level && !debug_connection) { | 164 if (log->log_level < level && !debug_connection) { |
164 break; | 165 break; |
165 } | 166 } |
166 | 167 |
168 if (log->limit && !debug_connection) { | |
169 if (ngx_log_check_rate(log, level) == NGX_BUSY) { | |
170 goto next; | |
171 } | |
172 } | |
173 | |
167 if (log->writer) { | 174 if (log->writer) { |
168 log->writer(log, level, errstr, p - errstr); | 175 log->writer(log, level, errstr, p - errstr); |
169 goto next; | 176 goto next; |
170 } | 177 } |
171 | 178 |
309 if (buf < last) { | 316 if (buf < last) { |
310 *buf++ = ')'; | 317 *buf++ = ')'; |
311 } | 318 } |
312 | 319 |
313 return buf; | 320 return buf; |
321 } | |
322 | |
323 | |
324 static ngx_int_t | |
325 ngx_log_check_rate(ngx_log_t *log, ngx_uint_t level) | |
326 { | |
327 ngx_log_t temp_log; | |
328 ngx_int_t excess, changed, burst; | |
329 ngx_atomic_int_t ms; | |
330 ngx_atomic_uint_t now, last; | |
331 | |
332 now = ngx_current_msec; | |
333 | |
334 last = log->limit->last; | |
335 excess = log->limit->excess; | |
336 | |
337 ms = (ngx_atomic_int_t) (now - last); | |
338 | |
339 if (ms < -60000) { | |
340 ms = 1; | |
341 | |
342 } else if (ms < 0) { | |
343 ms = 0; | |
344 } | |
345 | |
346 changed = excess - log->limit->rate * ms / 1000 + 1000; | |
347 | |
348 if (changed < 0) { | |
349 changed = 0; | |
350 } | |
351 | |
352 burst = (log->log_level - level + 1) * log->limit->rate; | |
353 | |
354 if (changed > burst) { | |
355 if (excess <= burst) { | |
356 | |
357 ngx_atomic_fetch_add(&log->limit->excess, 1000); | |
358 | |
359 /* log message to this log only */ | |
360 | |
361 temp_log = *log; | |
362 temp_log.connection = 0; | |
363 temp_log.handler = NULL; | |
364 temp_log.limit = NULL; | |
365 temp_log.next = NULL; | |
366 | |
367 ngx_log_error(level, &temp_log, 0, | |
368 "too many log messages, limiting"); | |
369 } | |
370 | |
371 return NGX_BUSY; | |
372 } | |
373 | |
374 if (ms > 0 | |
375 && ngx_atomic_cmp_set(&log->limit->last, last, now)) | |
376 { | |
377 ngx_atomic_fetch_add(&log->limit->excess, changed - excess); | |
378 | |
379 } else { | |
380 ngx_atomic_fetch_add(&log->limit->excess, 1000); | |
381 } | |
382 | |
383 return NGX_OK; | |
314 } | 384 } |
315 | 385 |
316 | 386 |
317 ngx_log_t * | 387 ngx_log_t * |
318 ngx_log_init(u_char *prefix, u_char *error_log) | 388 ngx_log_init(u_char *prefix, u_char *error_log) |
596 if (new_log->file == NULL) { | 666 if (new_log->file == NULL) { |
597 return NGX_CONF_ERROR; | 667 return NGX_CONF_ERROR; |
598 } | 668 } |
599 } | 669 } |
600 | 670 |
601 if (ngx_log_set_levels(cf, new_log) != NGX_CONF_OK) { | 671 if (ngx_log_set_params(cf, new_log) != NGX_CONF_OK) { |
602 return NGX_CONF_ERROR; | 672 return NGX_CONF_ERROR; |
603 } | 673 } |
604 | 674 |
605 if (*head != new_log) { | 675 if (*head != new_log) { |
606 ngx_log_insert(*head, new_log); | 676 ngx_log_insert(*head, new_log); |
609 return NGX_CONF_OK; | 679 return NGX_CONF_OK; |
610 } | 680 } |
611 | 681 |
612 | 682 |
613 static char * | 683 static char * |
614 ngx_log_set_levels(ngx_conf_t *cf, ngx_log_t *log) | 684 ngx_log_set_params(ngx_conf_t *cf, ngx_log_t *log) |
615 { | 685 { |
686 size_t len; | |
687 ngx_int_t rate; | |
616 ngx_uint_t i, n, d; | 688 ngx_uint_t i, n, d; |
617 ngx_str_t *value; | 689 ngx_str_t *value; |
618 | 690 |
619 value = cf->args->elts; | 691 value = cf->args->elts; |
692 | |
693 rate = 1000; | |
620 | 694 |
621 for (i = 2; i < cf->args->nelts; i++) { | 695 for (i = 2; i < cf->args->nelts; i++) { |
622 | 696 |
623 for (n = 1; n <= NGX_LOG_DEBUG; n++) { | 697 for (n = 1; n <= NGX_LOG_DEBUG; n++) { |
624 if (ngx_strcmp(value[i].data, err_levels[n].data) == 0) { | 698 if (ngx_strcmp(value[i].data, err_levels[n].data) == 0) { |
647 log->log_level |= d; | 721 log->log_level |= d; |
648 goto next; | 722 goto next; |
649 } | 723 } |
650 } | 724 } |
651 | 725 |
652 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 726 if (ngx_strncmp(value[i].data, "rate=", 5) == 0) { |
653 "invalid log level \"%V\"", &value[i]); | 727 |
728 len = value[i].len; | |
729 | |
730 if (ngx_strncmp(value[i].data + len - 3, "m/s", 3) == 0) { | |
731 len -= 3; | |
732 } | |
733 | |
734 rate = ngx_atoi(value[i].data + 5, len - 5); | |
735 if (rate < 0) { | |
736 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | |
737 "invalid rate \"%V\"", &value[i]); | |
738 return NGX_CONF_ERROR; | |
739 } | |
740 | |
741 continue; | |
742 } | |
743 | |
744 if (log->log_level) { | |
745 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | |
746 "invalid parameter \"%V\"", &value[i]); | |
747 | |
748 } else { | |
749 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | |
750 "invalid log level \"%V\"", &value[i]); | |
751 } | |
752 | |
654 return NGX_CONF_ERROR; | 753 return NGX_CONF_ERROR; |
655 | 754 |
656 next: | 755 next: |
657 | 756 |
658 continue; | 757 continue; |
662 log->log_level = NGX_LOG_ERR; | 761 log->log_level = NGX_LOG_ERR; |
663 } | 762 } |
664 | 763 |
665 if (log->log_level == NGX_LOG_DEBUG) { | 764 if (log->log_level == NGX_LOG_DEBUG) { |
666 log->log_level = NGX_LOG_DEBUG_ALL; | 765 log->log_level = NGX_LOG_DEBUG_ALL; |
766 } | |
767 | |
768 if (rate > 0 | |
769 && log->log_level < NGX_LOG_DEBUG) | |
770 { | |
771 log->limit = ngx_pcalloc(cf->pool, sizeof(ngx_log_limit_t)); | |
772 if (log->limit == NULL) { | |
773 return NGX_CONF_ERROR; | |
774 } | |
775 | |
776 log->limit->rate = rate * 1000; | |
667 } | 777 } |
668 | 778 |
669 return NGX_CONF_OK; | 779 return NGX_CONF_OK; |
670 } | 780 } |
671 | 781 |