]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
journal: log filtering options support in PID1
authorQuentin Deslandes <qde@naccy.de>
Mon, 7 Nov 2022 19:30:01 +0000 (20:30 +0100)
committerQuentin Deslandes <qde@naccy.de>
Thu, 15 Dec 2022 09:57:39 +0000 (09:57 +0000)
Define new unit parameter (LogFilterPatterns) to filter logs processed by
journald.

This option is used to store a regular expression which is carried from
PID1 to systemd-journald through a cgroup xattrs:
`user.journald_log_filter_patterns`.

14 files changed:
docs/TRANSIENT-SETTINGS.md
man/org.freedesktop.systemd1.xml
man/systemd.exec.xml
src/core/cgroup.c
src/core/cgroup.h
src/core/dbus-execute.c
src/core/execute.c
src/core/execute.h
src/core/load-fragment-gperf.gperf.in
src/core/load-fragment.c
src/core/load-fragment.h
src/shared/bus-unit-util.c
src/test/test-load-fragment.c
test/fuzz/fuzz-unit-file/directives-all.service

index 2c893cad6e41bc30e88db40823ba870887a1b98b..07e248f8d522a73866429dc0d7dbf4970a8cd662 100644 (file)
@@ -148,6 +148,7 @@ All execution-related settings are available for transient units.
 ✓ SyslogLevelPrefix=
 ✓ LogLevelMax=
 ✓ LogExtraFields=
+✓ LogFilterPatterns=
 ✓ LogRateLimitIntervalSec=
 ✓ LogRateLimitBurst=
 ✓ SecureBits=
index 4f8936e86671ba7b19a427b66e6ea440a2399248..32ead7f272d1e7197ef07a8fd2f1c69fcae32019 100644 (file)
@@ -2928,6 +2928,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly aay LogExtraFields = [[...], ...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly a(bs) LogFilterPatterns = [...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s LogNamespace = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly i SecureBits = ...;
@@ -3479,6 +3481,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <!--property LogExtraFields is not documented!-->
 
+    <!--property LogFilterPatterns is not documented!-->
+
     <!--property LogNamespace is not documented!-->
 
     <!--property AmbientCapabilities is not documented!-->
@@ -4083,6 +4087,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <variablelist class="dbus-property" generated="True" extra-ref="LogExtraFields"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="LogFilterPatterns"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="LogNamespace"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="SecureBits"/>
@@ -4822,6 +4828,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly aay LogExtraFields = [[...], ...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly a(bs) LogFilterPatterns = [...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s LogNamespace = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly i SecureBits = ...;
@@ -5397,6 +5405,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
 
     <!--property LogExtraFields is not documented!-->
 
+    <!--property LogFilterPatterns is not documented!-->
+
     <!--property LogNamespace is not documented!-->
 
     <!--property AmbientCapabilities is not documented!-->
@@ -5995,6 +6005,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
 
     <variablelist class="dbus-property" generated="True" extra-ref="LogExtraFields"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="LogFilterPatterns"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="LogNamespace"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="SecureBits"/>
@@ -6623,6 +6635,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly aay LogExtraFields = [[...], ...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly a(bs) LogFilterPatterns = [...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s LogNamespace = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly i SecureBits = ...;
@@ -7126,6 +7140,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <!--property LogExtraFields is not documented!-->
 
+    <!--property LogFilterPatterns is not documented!-->
+
     <!--property LogNamespace is not documented!-->
 
     <!--property AmbientCapabilities is not documented!-->
@@ -7642,6 +7658,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <variablelist class="dbus-property" generated="True" extra-ref="LogExtraFields"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="LogFilterPatterns"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="LogNamespace"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="SecureBits"/>
@@ -8397,6 +8415,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly aay LogExtraFields = [[...], ...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly a(bs) LogFilterPatterns = [...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s LogNamespace = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly i SecureBits = ...;
@@ -8886,6 +8906,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
 
     <!--property LogExtraFields is not documented!-->
 
+    <!--property LogFilterPatterns is not documented!-->
+
     <!--property LogNamespace is not documented!-->
 
     <!--property AmbientCapabilities is not documented!-->
@@ -9388,6 +9410,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
 
     <variablelist class="dbus-property" generated="True" extra-ref="LogExtraFields"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="LogFilterPatterns"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="LogNamespace"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="SecureBits"/>
index d003ab183865eff5cabeb1b367225f742c903259..5e6658ff061cf3e6ca1d9673707c67579402d91b 100644 (file)
@@ -2919,6 +2919,34 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
         </para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>LogFilterPatterns=</varname></term>
+
+        <listitem><para>Define an extended regular expression to filter log messages based on the
+        <varname>MESSAGE=</varname> field of the structured message. If the first character of the pattern is
+        <literal>~</literal>, log entries matching the pattern should be discarded. This option takes a single
+        pattern as an argument but can be used multiple times to create a list of allowed and denied patterns.
+        If the empty string is assigned, the filter is reset, and all prior assignments will have no effect.</para>
+
+        <para>Because the <literal>~</literal> character is used to define denied patterns, it must be replaced
+        with <literal>\x7e</literal> to allow a message starting with <literal>~</literal>. For example,
+        <literal>~foobar</literal> would add a pattern matching <literal>foobar</literal> to the deny list, while
+        <literal>\x7efoobar</literal> would add a pattern matching <literal>~foobar</literal> to the allow list.</para>
+
+        <para>Log messages are tested against denied patterns (if any), then against allowed patterns
+        (if any). If a log message matches any of the denied patterns, it will be discarded, whatever the
+        allowed patterns. Then, remaining log messages are tested against allowed patterns. Messages matching
+        against none of the allowed pattern are discarded. If no allowed patterns are defined, then all
+        messages are processed directly after going through denied filters.</para>
+
+        <para>Filtering is based on the unit for which <varname>LogFilterPatterns=</varname> is defined, meaning log
+        messages coming from
+        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry> about the
+        unit are not taken into account. Filtered log messages won't be forwarded to traditional syslog daemons,
+        the kernel log buffer (kmsg), the systemd console, or sent as wall messages to all logged-in
+        users.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>LogNamespace=</varname></term>
 
index ecc3cb32effa4b97481aaea4a46dd7b8d220fbb7..2d671566ac836c914af60479cc1b1020b992d36b 100644 (file)
@@ -781,6 +781,51 @@ void cgroup_oomd_xattr_apply(Unit *u, const char *cgroup_path) {
                 unit_remove_xattr_graceful(u, cgroup_path, "user.oomd_omit");
 }
 
+int cgroup_log_xattr_apply(Unit *u, const char *cgroup_path) {
+        ExecContext *c;
+        size_t len, allowed_patterns_len, denied_patterns_len;
+        _cleanup_free_ char *patterns = NULL, *allowed_patterns = NULL, *denied_patterns = NULL;
+        int r;
+
+        assert(u);
+
+        c = unit_get_exec_context(u);
+        if (!c)
+                /* Some unit types have a cgroup context but no exec context, so we do not log
+                 * any error here to avoid confusion. */
+                return 0;
+
+        if (set_isempty(c->log_filter_allowed_patterns) && set_isempty(c->log_filter_denied_patterns)) {
+                unit_remove_xattr_graceful(u, cgroup_path, "user.journald_log_filter_patterns");
+                return 0;
+        }
+
+        r = set_make_nulstr(c->log_filter_allowed_patterns, &allowed_patterns, &allowed_patterns_len);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to make nulstr from set: %m");
+
+        r = set_make_nulstr(c->log_filter_denied_patterns, &denied_patterns, &denied_patterns_len);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to make nulstr from set: %m");
+
+        /* Use nul character separated strings without trailing nul */
+        allowed_patterns_len = LESS_BY(allowed_patterns_len, 1u);
+        denied_patterns_len = LESS_BY(denied_patterns_len, 1u);
+
+        len = allowed_patterns_len + 1 + denied_patterns_len;
+        patterns = new(char, len);
+        if (!patterns)
+                return log_oom_debug();
+
+        memcpy_safe(patterns, allowed_patterns, allowed_patterns_len);
+        patterns[allowed_patterns_len] = '\xff';
+        memcpy_safe(&patterns[allowed_patterns_len + 1], denied_patterns, denied_patterns_len);
+
+        unit_set_xattr_graceful(u, cgroup_path, "user.journald_log_filter_patterns", patterns, len);
+
+        return 0;
+}
+
 static void cgroup_xattr_apply(Unit *u) {
         bool b;
 
@@ -788,6 +833,7 @@ static void cgroup_xattr_apply(Unit *u) {
 
         /* The 'user.*' xattrs can be set from a user manager. */
         cgroup_oomd_xattr_apply(u, u->cgroup_path);
+        cgroup_log_xattr_apply(u, u->cgroup_path);
 
         if (!MANAGER_IS_SYSTEM(u->manager))
                 return;
index 09352bafc6353b004e81a16eb5aaadf81001afcd..a6a3d186ac7a635c21ffafd7844a5238f2bd411f 100644 (file)
@@ -240,6 +240,7 @@ int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode)
 int cgroup_add_bpf_foreign_program(CGroupContext *c, uint32_t attach_type, const char *path);
 
 void cgroup_oomd_xattr_apply(Unit *u, const char *cgroup_path);
+int cgroup_log_xattr_apply(Unit *u, const char *cgroup_path);
 
 CGroupMask unit_get_own_mask(Unit *u);
 CGroupMask unit_get_delegate_mask(Unit *u);
index c5a51bf5cd8d388b72345dce5615f8cb49d79d69..86f88cb1c0d9b0ba62c57153c02b6f194d10ad55 100644 (file)
@@ -31,6 +31,7 @@
 #include "namespace.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "pcre2-util.h"
 #include "process-util.h"
 #include "rlimit-util.h"
 #if HAVE_SECCOMP
@@ -799,6 +800,53 @@ static int property_get_log_extra_fields(
         return sd_bus_message_close_container(reply);
 }
 
+static int sd_bus_message_append_log_filter_patterns(sd_bus_message *reply, Set *patterns, bool is_allowlist) {
+        const char *pattern;
+        int r;
+
+        assert(reply);
+
+        SET_FOREACH(pattern, patterns) {
+                r = sd_bus_message_append(reply, "(bs)", is_allowlist, pattern);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static int property_get_log_filter_patterns(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        ExecContext *c = userdata;
+        int r;
+
+        assert(c);
+        assert(reply);
+
+        r = sd_bus_message_open_container(reply, 'a', "(bs)");
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_append_log_filter_patterns(reply, c->log_filter_allowed_patterns,
+                                                      /* is_allowlist = */ true);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_append_log_filter_patterns(reply, c->log_filter_denied_patterns,
+                                                      /* is_allowlist = */ false);
+        if (r < 0)
+                return r;
+
+        return sd_bus_message_close_container(reply);
+}
+
 static int property_get_set_credential(
                 sd_bus *bus,
                 const char *path,
@@ -1195,6 +1243,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
         SD_BUS_PROPERTY("LogRateLimitIntervalUSec", "t", bus_property_get_usec, offsetof(ExecContext, log_ratelimit_interval_usec), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("LogRateLimitBurst", "u", bus_property_get_unsigned, offsetof(ExecContext, log_ratelimit_burst), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("LogExtraFields", "aay", property_get_log_extra_fields, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("LogFilterPatterns", "a(bs)", property_get_log_filter_patterns, 0, SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("LogNamespace", "s", NULL, offsetof(ExecContext, log_namespace), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("CapabilityBoundingSet", "t", NULL, offsetof(ExecContext, capability_bounding_set), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1792,6 +1841,61 @@ int bus_exec_context_set_transient_property(
         if (streq(name, "LogRateLimitBurst"))
                 return bus_set_transient_unsigned(u, name, &c->log_ratelimit_burst, message, flags, error);
 
+        if (streq(name, "LogFilterPatterns")) {
+                /* Use _cleanup_free_, not _cleanup_strv_free_, as we don't want the content of the strv
+                 * to be freed. */
+                _cleanup_free_ char **allow_list = NULL, **deny_list = NULL;
+                const char *pattern;
+                int is_allowlist;
+
+                r = sd_bus_message_enter_container(message, 'a', "(bs)");
+                if (r < 0)
+                        return r;
+
+                while ((r = sd_bus_message_read(message, "(bs)", &is_allowlist, &pattern)) > 0) {
+                        _cleanup_(pattern_freep) pcre2_code *compiled_pattern = NULL;
+
+                        if (isempty(pattern))
+                                continue;
+
+                        r = pattern_compile_and_log(pattern, 0, &compiled_pattern);
+                        if (r < 0)
+                                return r;
+
+                        r = strv_push(is_allowlist ? &allow_list : &deny_list, (char *)pattern);
+                        if (r < 0)
+                                return r;
+                }
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_exit_container(message);
+                if (r < 0)
+                        return r;
+
+                if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+                        if (strv_isempty(allow_list) && strv_isempty(deny_list)) {
+                                c->log_filter_allowed_patterns = set_free(c->log_filter_allowed_patterns);
+                                c->log_filter_denied_patterns = set_free(c->log_filter_denied_patterns);
+                                unit_write_settingf(u, flags, name, "%s=", name);
+                        } else {
+                                r = set_put_strdupv(&c->log_filter_allowed_patterns, allow_list);
+                                if (r < 0)
+                                        return r;
+                                r = set_put_strdupv(&c->log_filter_denied_patterns, deny_list);
+                                if (r < 0)
+                                        return r;
+
+                                STRV_FOREACH(unit_pattern, allow_list)
+                                        unit_write_settingf(u, flags, name, "%s=%s", name, *unit_pattern);
+                                STRV_FOREACH(unit_pattern, deny_list)
+                                        unit_write_settingf(u, flags, name, "%s=~%s", name, *unit_pattern);
+                        }
+                }
+
+                return 1;
+        }
+
         if (streq(name, "Personality"))
                 return bus_set_transient_personality(u, name, &c->personality, message, flags, error);
 
index 7963582ea2f24095ee545249fa39965ef528fe3e..42c95556ac799c0414333587ab4fae6398a4c3ce 100644 (file)
@@ -5250,9 +5250,10 @@ int exec_spawn(Unit *unit,
                         if (r < 0)
                                 return log_unit_error_errno(unit, r, "Failed to create control group '%s': %m", subcgroup_path);
 
-                        /* Normally we would not propagate the oomd xattrs to children but since we created this
+                        /* Normally we would not propagate the xattrs to children but since we created this
                          * sub-cgroup internally we should do it. */
                         cgroup_oomd_xattr_apply(unit, subcgroup_path);
+                        cgroup_log_xattr_apply(unit, subcgroup_path);
                 }
         }
 
@@ -5406,6 +5407,8 @@ void exec_context_done(ExecContext *c) {
         c->log_level_max = -1;
 
         exec_context_free_log_extra_fields(c);
+        c->log_filter_allowed_patterns = set_free(c->log_filter_allowed_patterns);
+        c->log_filter_denied_patterns = set_free(c->log_filter_denied_patterns);
 
         c->log_ratelimit_interval_usec = 0;
         c->log_ratelimit_burst = 0;
@@ -6000,6 +6003,17 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
         if (c->log_ratelimit_burst > 0)
                 fprintf(f, "%sLogRateLimitBurst: %u\n", prefix, c->log_ratelimit_burst);
 
+        if (!set_isempty(c->log_filter_allowed_patterns) || !set_isempty(c->log_filter_denied_patterns)) {
+                fprintf(f, "%sLogFilterPatterns:", prefix);
+
+                char *pattern;
+                SET_FOREACH(pattern, c->log_filter_allowed_patterns)
+                        fprintf(f, " %s", pattern);
+                SET_FOREACH(pattern, c->log_filter_denied_patterns)
+                        fprintf(f, " ~%s", pattern);
+                fputc('\n', f);
+        }
+
         for (size_t j = 0; j < c->n_log_extra_fields; j++) {
                 fprintf(f, "%sLogExtraFields: ", prefix);
                 fwrite(c->log_extra_fields[j].iov_base,
index a2cf22806b2b8df40045760c24a8ccf8665f76ab..24cd4640d7d200d8a5023fd09eb1c20e4edca508 100644 (file)
@@ -24,6 +24,7 @@ typedef struct Manager Manager;
 #include "nsflags.h"
 #include "numa-util.h"
 #include "path-util.h"
+#include "set.h"
 #include "time-util.h"
 
 #define EXEC_STDIN_DATA_MAX (64U*1024U*1024U)
@@ -286,6 +287,8 @@ struct ExecContext {
 
         struct iovec* log_extra_fields;
         size_t n_log_extra_fields;
+        Set *log_filter_allowed_patterns;
+        Set *log_filter_denied_patterns;
 
         usec_t log_ratelimit_interval_usec;
         unsigned log_ratelimit_burst;
index b315dd0afa50c3c4cdf5c9786558a47c12e82a89..2850da5cc10c50f79ead2f77ec2f02885179a39c 100644 (file)
@@ -52,6 +52,7 @@
 {{type}}.LogRateLimitIntervalSec,          config_parse_sec,                            0,                                  offsetof({{type}}, exec_context.log_ratelimit_interval_usec)
 {{type}}.LogRateLimitBurst,                config_parse_unsigned,                       0,                                  offsetof({{type}}, exec_context.log_ratelimit_burst)
 {{type}}.LogExtraFields,                   config_parse_log_extra_fields,               0,                                  offsetof({{type}}, exec_context)
+{{type}}.LogFilterPatterns,                config_parse_log_filter_patterns,            0,                                  offsetof({{type}}, exec_context)
 {{type}}.Capabilities,                     config_parse_warn_compat,                    DISABLED_LEGACY,                    offsetof({{type}}, exec_context)
 {{type}}.SecureBits,                       config_parse_exec_secure_bits,               0,                                  offsetof({{type}}, exec_context.secure_bits)
 {{type}}.CapabilityBoundingSet,            config_parse_capability_set,                 0,                                  offsetof({{type}}, exec_context.capability_bounding_set)
index 734a5941cc134d322b1b149f44f63290f55c80c5..83e88971be066ffa3172c91acedf1eb0a114267e 100644 (file)
@@ -52,6 +52,7 @@
 #include "parse-helpers.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "pcre2-util.h"
 #include "percent-util.h"
 #include "process-util.h"
 #if HAVE_SECCOMP
@@ -6225,6 +6226,7 @@ void unit_dump_config_items(FILE *f) {
                 { config_parse_job_mode,              "MODE" },
                 { config_parse_job_mode_isolate,      "BOOLEAN" },
                 { config_parse_personality,           "PERSONALITY" },
+                { config_parse_log_filter_patterns,   "REGEX" },
         };
 
         const char *prev = NULL;
@@ -6489,3 +6491,54 @@ int config_parse_tty_size(
 
         return config_parse_unsigned(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, userdata);
 }
+
+int config_parse_log_filter_patterns(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        ExecContext *c = ASSERT_PTR(data);
+        _cleanup_(pattern_freep) pcre2_code *compiled_pattern = NULL;
+        const char *pattern = ASSERT_PTR(rvalue);
+        bool is_allowlist = true;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+
+        if (isempty(pattern)) {
+                /* Empty assignment resets the lists. */
+                c->log_filter_allowed_patterns = set_free(c->log_filter_allowed_patterns);
+                c->log_filter_denied_patterns = set_free(c->log_filter_denied_patterns);
+                return 0;
+        }
+
+        if (pattern[0] == '~') {
+                is_allowlist = false;
+                pattern++;
+                if (isempty(pattern))
+                        /* LogFilterPatterns=~ is not considered a valid pattern. */
+                        return log_syntax(unit, LOG_WARNING, filename, line, 0,
+                                          "Regex pattern invalid, ignoring: %s=%s", lvalue, rvalue);
+        }
+
+        if (pattern_compile_and_log(pattern, 0, &compiled_pattern) < 0)
+                return 0;
+
+        r = set_put_strdup(is_allowlist ? &c->log_filter_allowed_patterns : &c->log_filter_denied_patterns,
+                           pattern);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to store log filtering pattern, ignoring: %s=%s", lvalue, rvalue);
+                return 0;
+        }
+
+        return 0;
+}
index c57a6b227760c8023db80998d9e9b4d3affa9bc0..74b36336950a279a291bbf6680d2c340558eabd0 100644 (file)
@@ -150,6 +150,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_socket_bind);
 CONFIG_PARSER_PROTOTYPE(config_parse_restrict_network_interfaces);
 CONFIG_PARSER_PROTOTYPE(config_parse_watchdog_sec);
 CONFIG_PARSER_PROTOTYPE(config_parse_tty_size);
+CONFIG_PARSER_PROTOTYPE(config_parse_log_filter_patterns);
 
 /* gperf prototypes */
 const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
index 6b6383b60b780b61da00203981db0f917a8953da..12b14ae58484097fb99e2c6326b367d82572743e 100644 (file)
@@ -1218,6 +1218,16 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                 return 1;
         }
 
+        if (streq(field, "LogFilterPatterns")) {
+                r = sd_bus_message_append(m, "(sv)", "LogFilterPatterns", "a(bs)", 1,
+                                          eq[0] != '~',
+                                          eq[0] != '~' ? eq : eq + 1);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                return 1;
+        }
+
         if (STR_IN_SET(field, "StandardInput",
                               "StandardOutput",
                               "StandardError")) {
index 997c2b252461c93135fb4cc1fb2d974adc362fcc..8d3c58a800edd61fe24989a02373ec445e673326 100644 (file)
@@ -22,6 +22,7 @@
 #include "load-fragment.h"
 #include "macro.h"
 #include "memory-util.h"
+#include "pcre2-util.h"
 #include "rm-rf.h"
 #include "specifier.h"
 #include "string-util.h"
@@ -997,6 +998,56 @@ TEST(unit_is_recursive_template_dependency) {
         assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.mount", "foobar@%n.mount") == 0);
 }
 
+#define TEST_PATTERN(_regex, _allowed_patterns_count, _denied_patterns_count)   \
+        {                                                                       \
+                .regex = _regex,                                                \
+                .allowed_patterns_count = _allowed_patterns_count,              \
+                .denied_patterns_count = _denied_patterns_count                 \
+        }
+
+TEST(config_parse_log_filter_patterns) {
+        ExecContext c = {};
+        int r;
+
+        static const struct {
+                const char *regex;
+                size_t allowed_patterns_count;
+                size_t denied_patterns_count;
+        } regex_tests[] = {
+                TEST_PATTERN("", 0, 0),
+                TEST_PATTERN(".*", 1, 0),
+                TEST_PATTERN("~.*", 1, 1),
+                TEST_PATTERN("", 0, 0),
+                TEST_PATTERN("~.*", 0, 1),
+                TEST_PATTERN("[.*", 0, 1),              /* Invalid pattern. */
+                TEST_PATTERN(".*gg.*", 1, 1),
+                TEST_PATTERN("~.*", 1, 1),              /* Already in the patterns list. */
+                TEST_PATTERN("[.*", 1, 1),              /* Invalid pattern. */
+                TEST_PATTERN("\\x7ehello", 2, 1),
+                TEST_PATTERN("", 0, 0),
+                TEST_PATTERN("~foobar", 0, 1),
+        };
+
+        if (ERRNO_IS_NOT_SUPPORTED(dlopen_pcre2()))
+                return (void) log_tests_skipped("PCRE2 support is not available");
+
+        for (size_t i = 0; i < ELEMENTSOF(regex_tests); i++) {
+                r = config_parse_log_filter_patterns(NULL, "fake", 1, "section", 1, "LogFilterPatterns", 1,
+                                                     regex_tests[i].regex, &c, NULL);
+                assert_se(r >= 0);
+
+                assert_se(set_size(c.log_filter_allowed_patterns) == regex_tests[i].allowed_patterns_count);
+                assert_se(set_size(c.log_filter_denied_patterns) == regex_tests[i].denied_patterns_count);
+
+                /* Ensure `~` is properly removed */
+                const char *p;
+                SET_FOREACH(p, c.log_filter_allowed_patterns)
+                        assert_se(p && p[0] != '~');
+                SET_FOREACH(p, c.log_filter_denied_patterns)
+                        assert_se(p && p[0] != '~');
+        }
+}
+
 static int intro(void) {
         if (enter_cgroup_subroot(NULL) == -ENOMEDIUM)
                 return log_tests_skipped("cgroupfs not available");
index b4cfca2814fd1aede9283a1c2698cad12ce6c008..f8237d74ebbbabee315dd286c248ea2c06f5afc2 100644 (file)
@@ -846,6 +846,7 @@ LogExtraFields=
 LogLevelMax=
 LogRateLimitIntervalSec=
 LogRateLimitBurst=
+LogFilterPatterns=
 LogsDirectory=
 LogsDirectoryMode=
 MACVLAN=