[nginx] Core: error logging rate limiting.
Maxim Dounin
mdounin at mdounin.ru
Tue Jun 25 20:31:22 UTC 2024
details: http://freenginx.org/hg/nginx/rev/2706b60dc225
branches:
changeset: 9299:2706b60dc225
user: Maxim Dounin <mdounin at mdounin.ru>
date: Tue Jun 25 22:58:56 2024 +0300
description:
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.
diffstat:
src/core/ngx_connection.h | 1 +
src/core/ngx_log.c | 120 ++++++++++++++++++++++++++++++++++++++++++++-
src/core/ngx_log.h | 9 +++
3 files changed, 125 insertions(+), 5 deletions(-)
diffs (216 lines):
diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h
--- a/src/core/ngx_connection.h
+++ b/src/core/ngx_connection.h
@@ -207,6 +207,7 @@ struct ngx_connection_s {
#define ngx_set_connection_log(c, l) \
\
c->log->file = l->file; \
+ c->log->limit = l->limit; \
c->log->next = l->next; \
c->log->writer = l->writer; \
c->log->wdata = l->wdata; \
diff --git a/src/core/ngx_log.c b/src/core/ngx_log.c
--- a/src/core/ngx_log.c
+++ b/src/core/ngx_log.c
@@ -9,8 +9,9 @@
#include <ngx_core.h>
+static ngx_int_t ngx_log_check_rate(ngx_log_t *log, ngx_uint_t level);
static char *ngx_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
-static char *ngx_log_set_levels(ngx_conf_t *cf, ngx_log_t *log);
+static char *ngx_log_set_params(ngx_conf_t *cf, ngx_log_t *log);
static void ngx_log_insert(ngx_log_t *log, ngx_log_t *new_log);
@@ -164,6 +165,12 @@ ngx_log_error_core(ngx_uint_t level, ngx
break;
}
+ if (log->limit && !debug_connection) {
+ if (ngx_log_check_rate(log, level) == NGX_BUSY) {
+ goto next;
+ }
+ }
+
if (log->writer) {
log->writer(log, level, errstr, p - errstr);
goto next;
@@ -314,6 +321,69 @@ ngx_log_errno(u_char *buf, u_char *last,
}
+static ngx_int_t
+ngx_log_check_rate(ngx_log_t *log, ngx_uint_t level)
+{
+ ngx_log_t temp_log;
+ ngx_int_t excess, changed, burst;
+ ngx_atomic_int_t ms;
+ ngx_atomic_uint_t now, last;
+
+ now = ngx_current_msec;
+
+ last = log->limit->last;
+ excess = log->limit->excess;
+
+ ms = (ngx_atomic_int_t) (now - last);
+
+ if (ms < -60000) {
+ ms = 1;
+
+ } else if (ms < 0) {
+ ms = 0;
+ }
+
+ changed = excess - log->limit->rate * ms / 1000 + 1000;
+
+ if (changed < 0) {
+ changed = 0;
+ }
+
+ burst = (log->log_level - level + 1) * log->limit->rate;
+
+ if (changed > burst) {
+ if (excess <= burst) {
+
+ ngx_atomic_fetch_add(&log->limit->excess, 1000);
+
+ /* log message to this log only */
+
+ temp_log = *log;
+ temp_log.connection = 0;
+ temp_log.handler = NULL;
+ temp_log.limit = NULL;
+ temp_log.next = NULL;
+
+ ngx_log_error(level, &temp_log, 0,
+ "too many log messages, limiting");
+ }
+
+ return NGX_BUSY;
+ }
+
+ if (ms > 0
+ && ngx_atomic_cmp_set(&log->limit->last, last, now))
+ {
+ ngx_atomic_fetch_add(&log->limit->excess, changed - excess);
+
+ } else {
+ ngx_atomic_fetch_add(&log->limit->excess, 1000);
+ }
+
+ return NGX_OK;
+}
+
+
ngx_log_t *
ngx_log_init(u_char *prefix, u_char *error_log)
{
@@ -598,7 +668,7 @@ ngx_log_set_log(ngx_conf_t *cf, ngx_log_
}
}
- if (ngx_log_set_levels(cf, new_log) != NGX_CONF_OK) {
+ if (ngx_log_set_params(cf, new_log) != NGX_CONF_OK) {
return NGX_CONF_ERROR;
}
@@ -611,13 +681,17 @@ ngx_log_set_log(ngx_conf_t *cf, ngx_log_
static char *
-ngx_log_set_levels(ngx_conf_t *cf, ngx_log_t *log)
+ngx_log_set_params(ngx_conf_t *cf, ngx_log_t *log)
{
+ size_t len;
+ ngx_int_t rate;
ngx_uint_t i, n, d;
ngx_str_t *value;
value = cf->args->elts;
+ rate = 1000;
+
for (i = 2; i < cf->args->nelts; i++) {
for (n = 1; n <= NGX_LOG_DEBUG; n++) {
@@ -649,8 +723,33 @@ ngx_log_set_levels(ngx_conf_t *cf, ngx_l
}
}
- ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
- "invalid log level \"%V\"", &value[i]);
+ if (ngx_strncmp(value[i].data, "rate=", 5) == 0) {
+
+ len = value[i].len;
+
+ if (ngx_strncmp(value[i].data + len - 3, "m/s", 3) == 0) {
+ len -= 3;
+ }
+
+ rate = ngx_atoi(value[i].data + 5, len - 5);
+ if (rate < 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid rate \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ if (log->log_level) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[i]);
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid log level \"%V\"", &value[i]);
+ }
+
return NGX_CONF_ERROR;
next:
@@ -666,6 +765,17 @@ ngx_log_set_levels(ngx_conf_t *cf, ngx_l
log->log_level = NGX_LOG_DEBUG_ALL;
}
+ if (rate > 0
+ && log->log_level < NGX_LOG_DEBUG)
+ {
+ log->limit = ngx_pcalloc(cf->pool, sizeof(ngx_log_limit_t));
+ if (log->limit == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ log->limit->rate = rate * 1000;
+ }
+
return NGX_CONF_OK;
}
diff --git a/src/core/ngx_log.h b/src/core/ngx_log.h
--- a/src/core/ngx_log.h
+++ b/src/core/ngx_log.h
@@ -47,6 +47,13 @@ typedef void (*ngx_log_writer_pt) (ngx_l
u_char *buf, size_t len);
+typedef struct {
+ ngx_uint_t rate;
+ ngx_atomic_t excess;
+ ngx_atomic_t last;
+} ngx_log_limit_t;
+
+
struct ngx_log_s {
ngx_uint_t log_level;
ngx_open_file_t *file;
@@ -67,6 +74,8 @@ struct ngx_log_s {
char *action;
+ ngx_log_limit_t *limit;
+
ngx_log_t *next;
};
More information about the nginx-devel
mailing list