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