Blame SOURCES/0234-core-implement-per-unit-journal-rate-limiting.patch

ddca0b
From a26f2b2732733aa361fec0a3a8f0ba377f48e75c Mon Sep 17 00:00:00 2001
ddca0b
From: Anita Zhang <anitzhang@gmail.com>
ddca0b
Date: Sun, 7 Oct 2018 20:28:36 -0700
ddca0b
Subject: [PATCH] core: implement per unit journal rate limiting
ddca0b
ddca0b
Add LogRateLimitIntervalSec= and LogRateLimitBurst= options for
ddca0b
services. If provided, these values get passed to the journald
ddca0b
client context, and those values are used in the rate limiting
ddca0b
function in the journal over the the journald.conf values.
ddca0b
ddca0b
Part of #10230
ddca0b
ddca0b
(cherry picked from commit 90fc172e191f44979005a524521112f2bd1ff21b)
ddca0b
ddca0b
Resolves: #1719577
ddca0b
---
ddca0b
 catalog/systemd.catalog.in                  |  3 +-
ddca0b
 doc/TRANSIENT-SETTINGS.md                   |  2 +
ddca0b
 man/journald.conf.xml                       |  8 +-
ddca0b
 man/systemd.exec.xml                        | 16 ++++
ddca0b
 src/core/dbus-execute.c                     |  8 ++
ddca0b
 src/core/execute.c                          | 14 ++++
ddca0b
 src/core/execute.h                          |  3 +
ddca0b
 src/core/load-fragment-gperf.gperf.m4       |  2 +
ddca0b
 src/core/unit.c                             | 92 +++++++++++++++++++++
ddca0b
 src/core/unit.h                             |  2 +
ddca0b
 src/journal/journald-context.c              | 50 ++++++++++-
ddca0b
 src/journal/journald-context.h              |  3 +
ddca0b
 src/journal/journald-rate-limit.c           | 38 ++++-----
ddca0b
 src/journal/journald-rate-limit.h           |  4 +-
ddca0b
 src/journal/journald-server.c               |  4 +-
ddca0b
 src/shared/bus-unit-util.c                  |  8 ++
ddca0b
 test/fuzz/fuzz-unit-file/directives.service |  2 +
ddca0b
 17 files changed, 231 insertions(+), 28 deletions(-)
ddca0b
ddca0b
diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in
ddca0b
index f1bddc6f7d..8234e387cf 100644
ddca0b
--- a/catalog/systemd.catalog.in
ddca0b
+++ b/catalog/systemd.catalog.in
ddca0b
@@ -52,7 +52,8 @@ dropped, other services' messages are unaffected.
ddca0b
 
ddca0b
 The limits controlling when messages are dropped may be configured
ddca0b
 with RateLimitIntervalSec= and RateLimitBurst= in
ddca0b
-/etc/systemd/journald.conf. See journald.conf(5) for details.
ddca0b
+/etc/systemd/journald.conf or LogRateLimitIntervalSec= and LogRateLimitBurst=
ddca0b
+in the unit file. See journald.conf(5) and systemd.exec(5) for details.
ddca0b
 
ddca0b
 -- e9bf28e6e834481bb6f48f548ad13606
ddca0b
 Subject: Journal messages have been missed
ddca0b
diff --git a/doc/TRANSIENT-SETTINGS.md b/doc/TRANSIENT-SETTINGS.md
ddca0b
index ca9e8387b7..0ea444b133 100644
ddca0b
--- a/doc/TRANSIENT-SETTINGS.md
ddca0b
+++ b/doc/TRANSIENT-SETTINGS.md
ddca0b
@@ -135,6 +135,8 @@ All execution-related settings are available for transient units.
ddca0b
 ✓ SyslogLevelPrefix=
ddca0b
 ✓ LogLevelMax=
ddca0b
 ✓ LogExtraFields=
ddca0b
+✓ LogRateLimitIntervalSec=
ddca0b
+✓ LogRateLimitBurst=
ddca0b
 ✓ SecureBits=
ddca0b
 ✓ CapabilityBoundingSet=
ddca0b
 ✓ AmbientCapabilities=
ddca0b
diff --git a/man/journald.conf.xml b/man/journald.conf.xml
ddca0b
index ee8e8b7faf..b57a244b22 100644
ddca0b
--- a/man/journald.conf.xml
ddca0b
+++ b/man/journald.conf.xml
ddca0b
@@ -140,7 +140,13 @@
ddca0b
         following units: <literal>s</literal>, <literal>min</literal>,
ddca0b
         <literal>h</literal>, <literal>ms</literal>,
ddca0b
         <literal>us</literal>. To turn off any kind of rate limiting,
ddca0b
-        set either value to 0.</para></listitem>
ddca0b
+        set either value to 0.</para>
ddca0b
+
ddca0b
+        <para>If a service provides rate limits for itself through
ddca0b
+        <varname>LogRateLimitIntervalSec=</varname> and/or <varname>LogRateLimitBurst=</varname>
ddca0b
+        in <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
ddca0b
+        those values will override the settings specified here.</para>
ddca0b
+        </listitem>
ddca0b
       </varlistentry>
ddca0b
 
ddca0b
       <varlistentry>
ddca0b
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
ddca0b
index 3bd790b485..737c52bcc4 100644
ddca0b
--- a/man/systemd.exec.xml
ddca0b
+++ b/man/systemd.exec.xml
ddca0b
@@ -1905,6 +1905,22 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
ddca0b
         matching. Assign an empty string to reset the list.</para></listitem>
ddca0b
       </varlistentry>
ddca0b
 
ddca0b
+      <varlistentry>
ddca0b
+        <term><varname>LogRateLimitIntervalSec=</varname></term>
ddca0b
+        <term><varname>LogRateLimitBurst=</varname></term>
ddca0b
+
ddca0b
+        <listitem><para>Configures the rate limiting that is applied to messages generated by this unit. If, in the
ddca0b
+        time interval defined by <varname>LogRateLimitIntervalSec=</varname>, more messages than specified in
ddca0b
+        <varname>LogRateLimitBurst=</varname> are logged by a service, all further messages within the interval are
ddca0b
+        dropped until the interval is over. A message about the number of dropped messages is generated. The time
ddca0b
+        specification for <varname>LogRateLimitIntervalSec=</varname> may be specified in the following units: "s",
ddca0b
+        "min", "h", "ms", "us" (see
ddca0b
+        <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry> for details).
ddca0b
+        The default settings are set by <varname>RateLimitIntervalSec=</varname> and <varname>RateLimitBurst=</varname>
ddca0b
+        configured in <citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
ddca0b
+        </para></listitem>
ddca0b
+      </varlistentry>
ddca0b
+
ddca0b
       <varlistentry>
ddca0b
         <term><varname>SyslogIdentifier=</varname></term>
ddca0b
 
ddca0b
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
ddca0b
index c44970c10c..33a91c012e 100644
ddca0b
--- a/src/core/dbus-execute.c
ddca0b
+++ b/src/core/dbus-execute.c
ddca0b
@@ -718,6 +718,8 @@ const sd_bus_vtable bus_exec_vtable[] = {
ddca0b
         SD_BUS_PROPERTY("SyslogLevel", "i", property_get_syslog_level, offsetof(ExecContext, syslog_priority), SD_BUS_VTABLE_PROPERTY_CONST),
ddca0b
         SD_BUS_PROPERTY("SyslogFacility", "i", property_get_syslog_facility, offsetof(ExecContext, syslog_priority), SD_BUS_VTABLE_PROPERTY_CONST),
ddca0b
         SD_BUS_PROPERTY("LogLevelMax", "i", bus_property_get_int, offsetof(ExecContext, log_level_max), SD_BUS_VTABLE_PROPERTY_CONST),
ddca0b
+        SD_BUS_PROPERTY("LogRateLimitIntervalUSec", "t", bus_property_get_usec, offsetof(ExecContext, log_rate_limit_interval_usec), SD_BUS_VTABLE_PROPERTY_CONST),
ddca0b
+        SD_BUS_PROPERTY("LogRateLimitBurst", "u", bus_property_get_unsigned, offsetof(ExecContext, log_rate_limit_burst), SD_BUS_VTABLE_PROPERTY_CONST),
ddca0b
         SD_BUS_PROPERTY("LogExtraFields", "aay", property_get_log_extra_fields, 0, SD_BUS_VTABLE_PROPERTY_CONST),
ddca0b
         SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST),
ddca0b
         SD_BUS_PROPERTY("CapabilityBoundingSet", "t", NULL, offsetof(ExecContext, capability_bounding_set), SD_BUS_VTABLE_PROPERTY_CONST),
ddca0b
@@ -1073,6 +1075,12 @@ int bus_exec_context_set_transient_property(
ddca0b
         if (streq(name, "CPUSchedulingPriority"))
ddca0b
                 return bus_set_transient_sched_priority(u, name, &c->cpu_sched_priority, message, flags, error);
ddca0b
 
ddca0b
+        if (streq(name, "LogRateLimitIntervalUSec"))
ddca0b
+                return bus_set_transient_usec(u, name, &c->log_rate_limit_interval_usec, message, flags, error);
ddca0b
+
ddca0b
+        if (streq(name, "LogRateLimitBurst"))
ddca0b
+                return bus_set_transient_unsigned(u, name, &c->log_rate_limit_burst, message, flags, error);
ddca0b
+
ddca0b
         if (streq(name, "Personality"))
ddca0b
                 return bus_set_transient_personality(u, name, &c->personality, message, flags, error);
ddca0b
 
ddca0b
diff --git a/src/core/execute.c b/src/core/execute.c
ddca0b
index c62f3cf849..8293c522bc 100644
ddca0b
--- a/src/core/execute.c
ddca0b
+++ b/src/core/execute.c
ddca0b
@@ -3693,6 +3693,9 @@ void exec_context_done(ExecContext *c) {
ddca0b
 
ddca0b
         exec_context_free_log_extra_fields(c);
ddca0b
 
ddca0b
+        c->log_rate_limit_interval_usec = 0;
ddca0b
+        c->log_rate_limit_burst = 0;
ddca0b
+
ddca0b
         c->stdin_data = mfree(c->stdin_data);
ddca0b
         c->stdin_data_size = 0;
ddca0b
 }
ddca0b
@@ -4153,6 +4156,17 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
ddca0b
                 fprintf(f, "%sLogLevelMax: %s\n", prefix, strna(t));
ddca0b
         }
ddca0b
 
ddca0b
+        if (c->log_rate_limit_interval_usec > 0) {
ddca0b
+                char buf_timespan[FORMAT_TIMESPAN_MAX];
ddca0b
+
ddca0b
+                fprintf(f,
ddca0b
+                        "%sLogRateLimitIntervalSec: %s\n",
ddca0b
+                        prefix, format_timespan(buf_timespan, sizeof(buf_timespan), c->log_rate_limit_interval_usec, USEC_PER_SEC));
ddca0b
+        }
ddca0b
+
ddca0b
+        if (c->log_rate_limit_burst > 0)
ddca0b
+                fprintf(f, "%sLogRateLimitBurst: %u\n", prefix, c->log_rate_limit_burst);
ddca0b
+
ddca0b
         if (c->n_log_extra_fields > 0) {
ddca0b
                 size_t j;
ddca0b
 
ddca0b
diff --git a/src/core/execute.h b/src/core/execute.h
ddca0b
index bff1634b88..8c91636adc 100644
ddca0b
--- a/src/core/execute.h
ddca0b
+++ b/src/core/execute.h
ddca0b
@@ -216,6 +216,9 @@ struct ExecContext {
ddca0b
         struct iovec* log_extra_fields;
ddca0b
         size_t n_log_extra_fields;
ddca0b
 
ddca0b
+        usec_t log_rate_limit_interval_usec;
ddca0b
+        unsigned log_rate_limit_burst;
ddca0b
+
ddca0b
         bool cpu_sched_reset_on_fork;
ddca0b
         bool non_blocking;
ddca0b
         bool private_tmp;
ddca0b
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
ddca0b
index 15fb47838c..1066bcfb8f 100644
ddca0b
--- a/src/core/load-fragment-gperf.gperf.m4
ddca0b
+++ b/src/core/load-fragment-gperf.gperf.m4
ddca0b
@@ -57,6 +57,8 @@ $1.SyslogFacility,               config_parse_log_facility,          0,
ddca0b
 $1.SyslogLevel,                  config_parse_log_level,             0,                             offsetof($1, exec_context.syslog_priority)
ddca0b
 $1.SyslogLevelPrefix,            config_parse_bool,                  0,                             offsetof($1, exec_context.syslog_level_prefix)
ddca0b
 $1.LogLevelMax,                  config_parse_log_level,             0,                             offsetof($1, exec_context.log_level_max)
ddca0b
+$1.LogRateLimitIntervalSec,      config_parse_sec,                   0,                             offsetof($1, exec_context.log_rate_limit_interval_usec)
ddca0b
+$1.LogRateLimitBurst,            config_parse_unsigned,              0,                             offsetof($1, exec_context.log_rate_limit_burst)
ddca0b
 $1.LogExtraFields,               config_parse_log_extra_fields,      0,                             offsetof($1, exec_context)
ddca0b
 $1.Capabilities,                 config_parse_warn_compat,           DISABLED_LEGACY,               offsetof($1, exec_context)
ddca0b
 $1.SecureBits,                   config_parse_exec_secure_bits,      0,                             offsetof($1, exec_context.secure_bits)
ddca0b
diff --git a/src/core/unit.c b/src/core/unit.c
ddca0b
index b0b1c77ef7..115739f4c6 100644
ddca0b
--- a/src/core/unit.c
ddca0b
+++ b/src/core/unit.c
ddca0b
@@ -3245,6 +3245,8 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
ddca0b
         unit_serialize_item(u, f, "exported-invocation-id", yes_no(u->exported_invocation_id));
ddca0b
         unit_serialize_item(u, f, "exported-log-level-max", yes_no(u->exported_log_level_max));
ddca0b
         unit_serialize_item(u, f, "exported-log-extra-fields", yes_no(u->exported_log_extra_fields));
ddca0b
+        unit_serialize_item(u, f, "exported-log-rate-limit-interval", yes_no(u->exported_log_rate_limit_interval));
ddca0b
+        unit_serialize_item(u, f, "exported-log-rate-limit-burst", yes_no(u->exported_log_rate_limit_burst));
ddca0b
 
ddca0b
         unit_serialize_item_format(u, f, "cpu-usage-base", "%" PRIu64, u->cpu_usage_base);
ddca0b
         if (u->cpu_usage_last != NSEC_INFINITY)
ddca0b
@@ -3508,6 +3510,26 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
ddca0b
 
ddca0b
                         continue;
ddca0b
 
ddca0b
+                } else if (streq(l, "exported-log-rate-limit-interval")) {
ddca0b
+
ddca0b
+                        r = parse_boolean(v);
ddca0b
+                        if (r < 0)
ddca0b
+                                log_unit_debug(u, "Failed to parse exported log rate limit interval %s, ignoring.", v);
ddca0b
+                        else
ddca0b
+                                u->exported_log_rate_limit_interval = r;
ddca0b
+
ddca0b
+                        continue;
ddca0b
+
ddca0b
+                } else if (streq(l, "exported-log-rate-limit-burst")) {
ddca0b
+
ddca0b
+                        r = parse_boolean(v);
ddca0b
+                        if (r < 0)
ddca0b
+                                log_unit_debug(u, "Failed to parse exported log rate limit burst %s, ignoring.", v);
ddca0b
+                        else
ddca0b
+                                u->exported_log_rate_limit_burst = r;
ddca0b
+
ddca0b
+                        continue;
ddca0b
+
ddca0b
                 } else if (STR_IN_SET(l, "cpu-usage-base", "cpuacct-usage-base")) {
ddca0b
 
ddca0b
                         r = safe_atou64(v, &u->cpu_usage_base);
ddca0b
@@ -5241,6 +5263,60 @@ fail:
ddca0b
         return r;
ddca0b
 }
ddca0b
 
ddca0b
+static int unit_export_log_rate_limit_interval(Unit *u, const ExecContext *c) {
ddca0b
+        _cleanup_free_ char *buf = NULL;
ddca0b
+        const char *p;
ddca0b
+        int r;
ddca0b
+
ddca0b
+        assert(u);
ddca0b
+        assert(c);
ddca0b
+
ddca0b
+        if (u->exported_log_rate_limit_interval)
ddca0b
+                return 0;
ddca0b
+
ddca0b
+        if (c->log_rate_limit_interval_usec == 0)
ddca0b
+                return 0;
ddca0b
+
ddca0b
+        p = strjoina("/run/systemd/units/log-rate-limit-interval:", u->id);
ddca0b
+
ddca0b
+        if (asprintf(&buf, "%" PRIu64, c->log_rate_limit_interval_usec) < 0)
ddca0b
+                return log_oom();
ddca0b
+
ddca0b
+        r = symlink_atomic(buf, p);
ddca0b
+        if (r < 0)
ddca0b
+                return log_unit_debug_errno(u, r, "Failed to create log rate limit interval symlink %s: %m", p);
ddca0b
+
ddca0b
+        u->exported_log_rate_limit_interval = true;
ddca0b
+        return 0;
ddca0b
+}
ddca0b
+
ddca0b
+static int unit_export_log_rate_limit_burst(Unit *u, const ExecContext *c) {
ddca0b
+        _cleanup_free_ char *buf = NULL;
ddca0b
+        const char *p;
ddca0b
+        int r;
ddca0b
+
ddca0b
+        assert(u);
ddca0b
+        assert(c);
ddca0b
+
ddca0b
+        if (u->exported_log_rate_limit_burst)
ddca0b
+                return 0;
ddca0b
+
ddca0b
+        if (c->log_rate_limit_burst == 0)
ddca0b
+                return 0;
ddca0b
+
ddca0b
+        p = strjoina("/run/systemd/units/log-rate-limit-burst:", u->id);
ddca0b
+
ddca0b
+        if (asprintf(&buf, "%u", c->log_rate_limit_burst) < 0)
ddca0b
+                return log_oom();
ddca0b
+
ddca0b
+        r = symlink_atomic(buf, p);
ddca0b
+        if (r < 0)
ddca0b
+                return log_unit_debug_errno(u, r, "Failed to create log rate limit burst symlink %s: %m", p);
ddca0b
+
ddca0b
+        u->exported_log_rate_limit_burst = true;
ddca0b
+        return 0;
ddca0b
+}
ddca0b
+
ddca0b
 void unit_export_state_files(Unit *u) {
ddca0b
         const ExecContext *c;
ddca0b
 
ddca0b
@@ -5274,6 +5350,8 @@ void unit_export_state_files(Unit *u) {
ddca0b
         if (c) {
ddca0b
                 (void) unit_export_log_level_max(u, c);
ddca0b
                 (void) unit_export_log_extra_fields(u, c);
ddca0b
+                (void) unit_export_log_rate_limit_interval(u, c);
ddca0b
+                (void) unit_export_log_rate_limit_burst(u, c);
ddca0b
         }
ddca0b
 }
ddca0b
 
ddca0b
@@ -5310,6 +5388,20 @@ void unit_unlink_state_files(Unit *u) {
ddca0b
 
ddca0b
                 u->exported_log_extra_fields = false;
ddca0b
         }
ddca0b
+
ddca0b
+        if (u->exported_log_rate_limit_interval) {
ddca0b
+                p = strjoina("/run/systemd/units/log-rate-limit-interval:", u->id);
ddca0b
+                (void) unlink(p);
ddca0b
+
ddca0b
+                u->exported_log_rate_limit_interval = false;
ddca0b
+        }
ddca0b
+
ddca0b
+        if (u->exported_log_rate_limit_burst) {
ddca0b
+                p = strjoina("/run/systemd/units/log-rate-limit-burst:", u->id);
ddca0b
+                (void) unlink(p);
ddca0b
+
ddca0b
+                u->exported_log_rate_limit_burst = false;
ddca0b
+        }
ddca0b
 }
ddca0b
 
ddca0b
 int unit_prepare_exec(Unit *u) {
ddca0b
diff --git a/src/core/unit.h b/src/core/unit.h
ddca0b
index 68cc1869e4..99755823eb 100644
ddca0b
--- a/src/core/unit.h
ddca0b
+++ b/src/core/unit.h
ddca0b
@@ -349,6 +349,8 @@ typedef struct Unit {
ddca0b
         bool exported_invocation_id:1;
ddca0b
         bool exported_log_level_max:1;
ddca0b
         bool exported_log_extra_fields:1;
ddca0b
+        bool exported_log_rate_limit_interval:1;
ddca0b
+        bool exported_log_rate_limit_burst:1;
ddca0b
 
ddca0b
         /* When writing transient unit files, stores which section we stored last. If < 0, we didn't write any yet. If
ddca0b
          * == 0 we are in the [Unit] section, if > 0 we are in the unit type-specific section. */
ddca0b
diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c
ddca0b
index dba3525ed8..c8e97e16de 100644
ddca0b
--- a/src/journal/journald-context.c
ddca0b
+++ b/src/journal/journald-context.c
ddca0b
@@ -140,6 +140,8 @@ static int client_context_new(Server *s, pid_t pid, ClientContext **ret) {
ddca0b
         c->timestamp = USEC_INFINITY;
ddca0b
         c->extra_fields_mtime = NSEC_INFINITY;
ddca0b
         c->log_level_max = -1;
ddca0b
+        c->log_rate_limit_interval = s->rate_limit_interval;
ddca0b
+        c->log_rate_limit_burst = s->rate_limit_burst;
ddca0b
 
ddca0b
         r = hashmap_put(s->client_contexts, PID_TO_PTR(pid), c);
ddca0b
         if (r < 0) {
ddca0b
@@ -151,7 +153,8 @@ static int client_context_new(Server *s, pid_t pid, ClientContext **ret) {
ddca0b
         return 0;
ddca0b
 }
ddca0b
 
ddca0b
-static void client_context_reset(ClientContext *c) {
ddca0b
+static void client_context_reset(Server *s, ClientContext *c) {
ddca0b
+        assert(s);
ddca0b
         assert(c);
ddca0b
 
ddca0b
         c->timestamp = USEC_INFINITY;
ddca0b
@@ -186,6 +189,9 @@ static void client_context_reset(ClientContext *c) {
ddca0b
         c->extra_fields_mtime = NSEC_INFINITY;
ddca0b
 
ddca0b
         c->log_level_max = -1;
ddca0b
+
ddca0b
+        c->log_rate_limit_interval = s->rate_limit_interval;
ddca0b
+        c->log_rate_limit_burst = s->rate_limit_burst;
ddca0b
 }
ddca0b
 
ddca0b
 static ClientContext* client_context_free(Server *s, ClientContext *c) {
ddca0b
@@ -199,7 +205,7 @@ static ClientContext* client_context_free(Server *s, ClientContext *c) {
ddca0b
         if (c->in_lru)
ddca0b
                 assert_se(prioq_remove(s->client_contexts_lru, c, &c->lru_index) >= 0);
ddca0b
 
ddca0b
-        client_context_reset(c);
ddca0b
+        client_context_reset(s, c);
ddca0b
 
ddca0b
         return mfree(c);
ddca0b
 }
ddca0b
@@ -464,6 +470,42 @@ static int client_context_read_extra_fields(
ddca0b
         return 0;
ddca0b
 }
ddca0b
 
ddca0b
+static int client_context_read_log_rate_limit_interval(ClientContext *c) {
ddca0b
+        _cleanup_free_ char *value = NULL;
ddca0b
+        const char *p;
ddca0b
+        int r;
ddca0b
+
ddca0b
+        assert(c);
ddca0b
+
ddca0b
+        if (!c->unit)
ddca0b
+                return 0;
ddca0b
+
ddca0b
+        p = strjoina("/run/systemd/units/log-rate-limit-interval:", c->unit);
ddca0b
+        r = readlink_malloc(p, &value);
ddca0b
+        if (r < 0)
ddca0b
+                return r;
ddca0b
+
ddca0b
+        return safe_atou64(value, &c->log_rate_limit_interval);
ddca0b
+}
ddca0b
+
ddca0b
+static int client_context_read_log_rate_limit_burst(ClientContext *c) {
ddca0b
+        _cleanup_free_ char *value = NULL;
ddca0b
+        const char *p;
ddca0b
+        int r;
ddca0b
+
ddca0b
+        assert(c);
ddca0b
+
ddca0b
+        if (!c->unit)
ddca0b
+                return 0;
ddca0b
+
ddca0b
+        p = strjoina("/run/systemd/units/log-rate-limit-burst:", c->unit);
ddca0b
+        r = readlink_malloc(p, &value);
ddca0b
+        if (r < 0)
ddca0b
+                return r;
ddca0b
+
ddca0b
+        return safe_atou(value, &c->log_rate_limit_burst);
ddca0b
+}
ddca0b
+
ddca0b
 static void client_context_really_refresh(
ddca0b
                 Server *s,
ddca0b
                 ClientContext *c,
ddca0b
@@ -490,6 +532,8 @@ static void client_context_really_refresh(
ddca0b
         (void) client_context_read_invocation_id(s, c);
ddca0b
         (void) client_context_read_log_level_max(s, c);
ddca0b
         (void) client_context_read_extra_fields(s, c);
ddca0b
+        (void) client_context_read_log_rate_limit_interval(c);
ddca0b
+        (void) client_context_read_log_rate_limit_burst(c);
ddca0b
 
ddca0b
         c->timestamp = timestamp;
ddca0b
 
ddca0b
@@ -520,7 +564,7 @@ void client_context_maybe_refresh(
ddca0b
         /* If the data isn't pinned and if the cashed data is older than the upper limit, we flush it out
ddca0b
          * entirely. This follows the logic that as long as an entry is pinned the PID reuse is unlikely. */
ddca0b
         if (c->n_ref == 0 && c->timestamp + MAX_USEC < timestamp) {
ddca0b
-                client_context_reset(c);
ddca0b
+                client_context_reset(s, c);
ddca0b
                 goto refresh;
ddca0b
         }
ddca0b
 
ddca0b
diff --git a/src/journal/journald-context.h b/src/journal/journald-context.h
ddca0b
index 9df3a38eff..5e19c71f14 100644
ddca0b
--- a/src/journal/journald-context.h
ddca0b
+++ b/src/journal/journald-context.h
ddca0b
@@ -49,6 +49,9 @@ struct ClientContext {
ddca0b
         size_t extra_fields_n_iovec;
ddca0b
         void *extra_fields_data;
ddca0b
         nsec_t extra_fields_mtime;
ddca0b
+
ddca0b
+        usec_t log_rate_limit_interval;
ddca0b
+        unsigned log_rate_limit_burst;
ddca0b
 };
ddca0b
 
ddca0b
 int client_context_get(
ddca0b
diff --git a/src/journal/journald-rate-limit.c b/src/journal/journald-rate-limit.c
ddca0b
index 6a8a36a736..539efb8669 100644
ddca0b
--- a/src/journal/journald-rate-limit.c
ddca0b
+++ b/src/journal/journald-rate-limit.c
ddca0b
@@ -39,6 +39,10 @@ struct JournalRateLimitGroup {
ddca0b
         JournalRateLimit *parent;
ddca0b
 
ddca0b
         char *id;
ddca0b
+
ddca0b
+        /* Interval is stored to keep track of when the group expires */
ddca0b
+        usec_t interval;
ddca0b
+
ddca0b
         JournalRateLimitPool pools[POOLS_MAX];
ddca0b
         uint64_t hash;
ddca0b
 
ddca0b
@@ -47,8 +51,6 @@ struct JournalRateLimitGroup {
ddca0b
 };
ddca0b
 
ddca0b
 struct JournalRateLimit {
ddca0b
-        usec_t interval;
ddca0b
-        unsigned burst;
ddca0b
 
ddca0b
         JournalRateLimitGroup* buckets[BUCKETS_MAX];
ddca0b
         JournalRateLimitGroup *lru, *lru_tail;
ddca0b
@@ -58,18 +60,13 @@ struct JournalRateLimit {
ddca0b
         uint8_t hash_key[16];
ddca0b
 };
ddca0b
 
ddca0b
-JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst) {
ddca0b
+JournalRateLimit *journal_rate_limit_new(void) {
ddca0b
         JournalRateLimit *r;
ddca0b
 
ddca0b
-        assert(interval > 0 || burst == 0);
ddca0b
-
ddca0b
         r = new0(JournalRateLimit, 1);
ddca0b
         if (!r)
ddca0b
                 return NULL;
ddca0b
 
ddca0b
-        r->interval = interval;
ddca0b
-        r->burst = burst;
ddca0b
-
ddca0b
         random_bytes(r->hash_key, sizeof(r->hash_key));
ddca0b
 
ddca0b
         return r;
ddca0b
@@ -109,7 +106,7 @@ _pure_ static bool journal_rate_limit_group_expired(JournalRateLimitGroup *g, us
ddca0b
         assert(g);
ddca0b
 
ddca0b
         for (i = 0; i < POOLS_MAX; i++)
ddca0b
-                if (g->pools[i].begin + g->parent->interval >= ts)
ddca0b
+                if (g->pools[i].begin + g->interval >= ts)
ddca0b
                         return false;
ddca0b
 
ddca0b
         return true;
ddca0b
@@ -126,7 +123,7 @@ static void journal_rate_limit_vacuum(JournalRateLimit *r, usec_t ts) {
ddca0b
                 journal_rate_limit_group_free(r->lru_tail);
ddca0b
 }
ddca0b
 
ddca0b
-static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t ts) {
ddca0b
+static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t interval, usec_t ts) {
ddca0b
         JournalRateLimitGroup *g;
ddca0b
         struct siphash state;
ddca0b
 
ddca0b
@@ -145,6 +142,8 @@ static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r,
ddca0b
         string_hash_func(g->id, &state);
ddca0b
         g->hash = siphash24_finalize(&state);
ddca0b
 
ddca0b
+        g->interval = interval;
ddca0b
+
ddca0b
         journal_rate_limit_vacuum(r, ts);
ddca0b
 
ddca0b
         LIST_PREPEND(bucket, r->buckets[g->hash % BUCKETS_MAX], g);
ddca0b
@@ -189,7 +188,7 @@ static unsigned burst_modulate(unsigned burst, uint64_t available) {
ddca0b
         return burst;
ddca0b
 }
ddca0b
 
ddca0b
-int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available) {
ddca0b
+int journal_rate_limit_test(JournalRateLimit *r, const char *id, usec_t rl_interval, unsigned rl_burst, int priority, uint64_t available) {
ddca0b
         uint64_t h;
ddca0b
         JournalRateLimitGroup *g;
ddca0b
         JournalRateLimitPool *p;
ddca0b
@@ -209,11 +208,6 @@ int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, u
ddca0b
         if (!r)
ddca0b
                 return 1;
ddca0b
 
ddca0b
-        if (r->interval == 0 || r->burst == 0)
ddca0b
-                return 1;
ddca0b
-
ddca0b
-        burst = burst_modulate(r->burst, available);
ddca0b
-
ddca0b
         ts = now(CLOCK_MONOTONIC);
ddca0b
 
ddca0b
         siphash24_init(&state, r->hash_key);
ddca0b
@@ -226,10 +220,16 @@ int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, u
ddca0b
                         break;
ddca0b
 
ddca0b
         if (!g) {
ddca0b
-                g = journal_rate_limit_group_new(r, id, ts);
ddca0b
+                g = journal_rate_limit_group_new(r, id, rl_interval, ts);
ddca0b
                 if (!g)
ddca0b
                         return -ENOMEM;
ddca0b
-        }
ddca0b
+        } else
ddca0b
+                g->interval = rl_interval;
ddca0b
+
ddca0b
+        if (rl_interval == 0 || rl_burst == 0)
ddca0b
+                return 1;
ddca0b
+
ddca0b
+        burst = burst_modulate(rl_burst, available);
ddca0b
 
ddca0b
         p = &g->pools[priority_map[priority]];
ddca0b
 
ddca0b
@@ -240,7 +240,7 @@ int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, u
ddca0b
                 return 1;
ddca0b
         }
ddca0b
 
ddca0b
-        if (p->begin + r->interval < ts) {
ddca0b
+        if (p->begin + rl_interval < ts) {
ddca0b
                 unsigned s;
ddca0b
 
ddca0b
                 s = p->suppressed;
ddca0b
diff --git a/src/journal/journald-rate-limit.h b/src/journal/journald-rate-limit.h
ddca0b
index 3a7f106de0..a2992800fe 100644
ddca0b
--- a/src/journal/journald-rate-limit.h
ddca0b
+++ b/src/journal/journald-rate-limit.h
ddca0b
@@ -5,6 +5,6 @@
ddca0b
 
ddca0b
 typedef struct JournalRateLimit JournalRateLimit;
ddca0b
 
ddca0b
-JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst);
ddca0b
+JournalRateLimit *journal_rate_limit_new(void);
ddca0b
 void journal_rate_limit_free(JournalRateLimit *r);
ddca0b
-int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available);
ddca0b
+int journal_rate_limit_test(JournalRateLimit *r, const char *id, usec_t rl_interval, unsigned rl_burst, int priority, uint64_t available);
ddca0b
diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
ddca0b
index 8de45552f6..0c983e102a 100644
ddca0b
--- a/src/journal/journald-server.c
ddca0b
+++ b/src/journal/journald-server.c
ddca0b
@@ -943,7 +943,7 @@ void server_dispatch_message(
ddca0b
         if (c && c->unit) {
ddca0b
                 (void) determine_space(s, &available, NULL);
ddca0b
 
ddca0b
-                rl = journal_rate_limit_test(s->rate_limit, c->unit, priority & LOG_PRIMASK, available);
ddca0b
+                rl = journal_rate_limit_test(s->rate_limit, c->unit, c->log_rate_limit_interval, c->log_rate_limit_burst, priority & LOG_PRIMASK, available);
ddca0b
                 if (rl == 0)
ddca0b
                         return;
ddca0b
 
ddca0b
@@ -1852,7 +1852,7 @@ int server_init(Server *s) {
ddca0b
         if (!s->udev)
ddca0b
                 return -ENOMEM;
ddca0b
 
ddca0b
-        s->rate_limit = journal_rate_limit_new(s->rate_limit_interval, s->rate_limit_burst);
ddca0b
+        s->rate_limit = journal_rate_limit_new();
ddca0b
         if (!s->rate_limit)
ddca0b
                 return -ENOMEM;
ddca0b
 
ddca0b
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
ddca0b
index 3238b442c0..271cc054da 100644
ddca0b
--- a/src/shared/bus-unit-util.c
ddca0b
+++ b/src/shared/bus-unit-util.c
ddca0b
@@ -755,6 +755,14 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
ddca0b
 
ddca0b
                 return bus_append_parse_nsec(m, field, eq);
ddca0b
 
ddca0b
+        if (STR_IN_SET(field, "LogRateLimitIntervalSec"))
ddca0b
+
ddca0b
+                return bus_append_parse_sec_rename(m, field, eq);
ddca0b
+
ddca0b
+        if (streq(field, "LogRateLimitBurst"))
ddca0b
+
ddca0b
+                return bus_append_safe_atou(m, field, eq);
ddca0b
+
ddca0b
         if (streq(field, "MountFlags"))
ddca0b
 
ddca0b
                 return bus_append_mount_propagation_flags_from_string(m, field, eq);
ddca0b
diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service
ddca0b
index c2334d3b19..d8d1fc68b8 100644
ddca0b
--- a/test/fuzz/fuzz-unit-file/directives.service
ddca0b
+++ b/test/fuzz/fuzz-unit-file/directives.service
ddca0b
@@ -792,6 +792,8 @@ LineMax=
ddca0b
 LockPersonality=
ddca0b
 LogExtraFields=
ddca0b
 LogLevelMax=
ddca0b
+LogRateLimitIntervalSec=
ddca0b
+LogRateLimitBurst=
ddca0b
 LogsDirectory=
ddca0b
 LogsDirectoryMode=
ddca0b
 MACVLAN=