tools: add gen-eventfilter.py

Generate event filtering code for lg2::commit from an input json
file, so that system integrators can create default filtering rules
to omit undesired events and errors from their systems.

Tested:

Used `log-create` to create an event and observed it.  Modified the
default event filter as follows and saw log was not created.

```
@@ -3,6 +3,9 @@
         "default": "allowed"
     },
     "errors": {
-        "default": "allowed"
+        "default": "allowed",
+        "ids": [
+            "xyz.openbmc_project.State.SMC.SMCFailed"
+        ]
     }
 }
```

```
$ ./builddir/log-create xyz.openbmc_project.State.SMC.SMCFailed --json '{ "IDENTIFIER": "/xyz/openbmc_project/inventory/SomeSMC", "FAILURE_TYPE": "Timeout for the SMC" }'
```

Change-Id: Ib6041481075758b994bb27a816dbf5e9f26c2841
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
diff --git a/README.md b/README.md
index c678507..565edb1 100644
--- a/README.md
+++ b/README.md
@@ -78,6 +78,28 @@
 lg2::resolve(logPath);
 ```
 
+### Event Log Filtering
+
+Vendors customizing phosphor-logging for their platforms may decide that they
+would like to prevent certain events from being added to the event log. This is
+especially true for informational / tracing events. The `lg2::commit` supports a
+compile-time event filtering mechanism that can accomplish this.
+
+The meson option `event-filter` can be used to specify a file containing
+filtering policy. When left unspecified, the [default
+policy][default-policy-json] of "allow all" is enabled. For both events and
+errors, a default policy of "allowed" or "blocked" can be specified and an
+additional set of events can be given for which the non-defaulted action should
+be taken. A JSON-Schema is available for the [policy
+JSON][filter-policy-schema].
+
+[default-policy-json]:
+  https://github.com/openbmc/phosphor-logging/blob/master/tools/phosphor-logging/default-eventfilter.json
+[filter-policy-schema]:
+  https://github.com/openbmc/phosphor-logging/blob/master/tools/phosphor-logging/schemas/eventfilter.schema.yaml
+
+### Deprecated Methods for Creating Event Logs
+
 There are two other, but now deprecated, methods to creating event logs in
 OpenBMC code. The first makes use of the systemd journal to store metadata
 needed for the log, and the second is a plain D-Bus method call.
diff --git a/lib/lg2_commit.cpp b/lib/lg2_commit.cpp
index a35b91e..a2ceb22 100644
--- a/lib/lg2_commit.cpp
+++ b/lib/lg2_commit.cpp
@@ -117,6 +117,16 @@
 auto commit(sdbusplus::exception::generated_event_base&& t)
     -> sdbusplus::message::object_path
 {
+    // Check event filters first.
+    if ((t.severity() == LOG_INFO) && details::filterEvent(t.name()))
+    {
+        return {};
+    }
+    else if (details::filterError(t.name()))
+    {
+        return {};
+    }
+
     if constexpr (LG2_COMMIT_JOURNAL)
     {
         lg2::error("OPENBMC_MESSAGE_ID={DATA}", "DATA", t.to_json().dump());
diff --git a/lib/lg2_commit.hpp b/lib/lg2_commit.hpp
index 1ab4302..9c76e04 100644
--- a/lib/lg2_commit.hpp
+++ b/lib/lg2_commit.hpp
@@ -20,4 +20,7 @@
     -> std::tuple<std::string, Entry::Level,
                   std::map<std::string, std::string>>;
 
+bool filterEvent(const std::string&);
+bool filterError(const std::string&);
+
 } // namespace lg2::details
diff --git a/lib/meson.build b/lib/meson.build
index 8446767..2679548 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -1,6 +1,19 @@
 phosphor_logging_includes = include_directories('include')
 
-phosphor_logging_gen = []
+event_filter_json = get_option('event-filter')
+if event_filter_json == ''
+    event_filter_json = default_eventfilter_json
+endif
+
+lg2_eventfilter_cpp_gen = custom_target(
+    'lg2_eventfilter.cpp',
+    input: [eventfilter_gen, template_eventfilter_gen, event_filter_json],
+    output: 'lg2_eventfilter.cpp',
+    command: [python_prog, '@INPUT0@', '@INPUT2@'],
+    capture: true,
+)
+
+phosphor_logging_gen = [lg2_eventfilter_cpp_gen]
 
 subdir('include/phosphor-logging')
 
@@ -43,4 +56,3 @@
     sources: phosphor_logging_gen,
     dependencies: phosphor_logging_deps,
 )
-
diff --git a/meson.options b/meson.options
index 6f8bdb4..5b3ef17 100644
--- a/meson.options
+++ b/meson.options
@@ -48,3 +48,9 @@
     choices: ['dbus', 'journal', 'both'],
     value: 'both',
 )
+
+option(
+    'event-filter',
+    type: 'string',
+    description: 'Path to the event filter JSON file.',
+)
diff --git a/tools/gen-eventfilter.py b/tools/gen-eventfilter.py
new file mode 100755
index 0000000..56151d6
--- /dev/null
+++ b/tools/gen-eventfilter.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+
+import argparse
+import json
+import os
+import sys
+
+import jsonschema
+from mako.template import Template
+
+import yaml
+
+# Determine the script's directory to find the schema file relative to it.
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+SCHEMA_FILE = os.path.join(
+    SCRIPT_DIR, "phosphor-logging", "schemas", "eventfilter.schema.yaml"
+)
+TEMPLATE_FILE = os.path.join(
+    SCRIPT_DIR, "phosphor-logging", "templates", "event-filter.cpp.mako"
+)
+
+
+def main() -> int:
+    """
+    Validates a JSON filter file against the eventfilter schema.
+    """
+    parser = argparse.ArgumentParser(
+        description="Validate an event filter JSON file against the schema."
+    )
+    parser.add_argument(
+        "filter",
+        type=str,
+        help="Path to the JSON filter file to validate.",
+    )
+    args = parser.parse_args()
+
+    with open(args.filter, "r") as f:
+        filter_data = json.load(f)
+
+    with open(SCHEMA_FILE, "r") as f:
+        schema_data = yaml.safe_load(f)
+
+    jsonschema.validate(instance=filter_data, schema=schema_data)
+
+    template = Template(filename=TEMPLATE_FILE)
+    output = template.render(data=filter_data)
+    print(output)
+
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/tools/meson.build b/tools/meson.build
index bb08ffb..fcde0bf 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -1,4 +1,7 @@
 tool_dir = meson.current_source_dir()
 
 elog_gen = files('elog-gen.py')
+eventfilter_gen = files('gen-eventfilter.py')
+default_eventfilter_json = files('phosphor-logging/default-eventfilter.json')
+
 subdir('phosphor-logging/templates')
diff --git a/tools/phosphor-logging/default-eventfilter.json b/tools/phosphor-logging/default-eventfilter.json
new file mode 100644
index 0000000..501ace6
--- /dev/null
+++ b/tools/phosphor-logging/default-eventfilter.json
@@ -0,0 +1,8 @@
+{
+    "events": {
+        "default": "allowed"
+    },
+    "errors": {
+        "default": "allowed"
+    }
+}
diff --git a/tools/phosphor-logging/schemas/eventfilter.schema.yaml b/tools/phosphor-logging/schemas/eventfilter.schema.yaml
new file mode 100644
index 0000000..aa7e511
--- /dev/null
+++ b/tools/phosphor-logging/schemas/eventfilter.schema.yaml
@@ -0,0 +1,36 @@
+$id: "http://openbmc-project.xyz/phosphor-logging/eventdefault.yaml"
+$schema: "https://json-schema.org/draft/2020-12/schema"
+title: "Event and Error defaults"
+
+$defs:
+    base-event:
+        type: object
+        properties:
+            default:
+                description:
+                    The default setting for any event not specified in ids.
+                type: string
+                enum:
+                    - "blocked"
+                    - "allowed"
+            ids:
+                description: The list of ids to do the opposite of 'default'.
+                type: array
+                items:
+                    type: string
+                    description: Event ids from phosphor-dbus-interfaces.
+                    pattern: "^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)+$"
+        required:
+            - default
+        additionalProperties: false
+
+type: object
+properties:
+    events:
+        $ref: "#/$defs/base-event"
+    errors:
+        $ref: "#/$defs/base-event"
+required:
+    - events
+    - errors
+additionalProperties: false
diff --git a/tools/phosphor-logging/templates/event-filter.cpp.mako b/tools/phosphor-logging/templates/event-filter.cpp.mako
new file mode 100644
index 0000000..0a4a4ef
--- /dev/null
+++ b/tools/phosphor-logging/templates/event-filter.cpp.mako
@@ -0,0 +1,62 @@
+## Note that this file is not auto generated, it is what generates the
+## lg2_eventfilter.cpp file
+// This file was autogenerated.  Do not edit!
+
+#include <string>
+#include <unordered_set>
+
+namespace lg2
+{
+namespace details
+{
+
+bool filterEvent([[maybe_unused]] const std::string& id)
+{
+    static constexpr bool default_return = \
+        % if data['events']['default'] == 'allowed':
+false;
+        % else:
+true;
+        % endif
+    % if "ids" in data['events'] and len(data['events']['ids']) != 0:
+    static const std::unordered_set<std::string> event_ids = {
+        % for item in data['events']['ids']:
+        "${item}",
+        % endfor
+    };
+
+    if (event_ids.contains(id))
+    {
+        return !default_return;
+    }
+    % endif
+
+    return default_return;
+}
+
+bool filterError([[maybe_unused]] const std::string& id)
+{
+    static constexpr bool default_return = \
+        % if data['errors']['default'] == 'allowed':
+false;
+        % else:
+true;
+        % endif
+    % if "ids" in data['errors'] and len(data['errors']['ids']) != 0:
+    static const std::unordered_set<std::string> error_ids = {
+        % for item in data['errors']['ids']:
+        "${item}",
+        % endfor
+    };
+
+    if (error_ids.contains(id))
+    {
+        return !default_return;
+    }
+    % endif
+
+    return default_return;
+}
+
+} // namespace details
+} // namespace lg2
diff --git a/tools/phosphor-logging/templates/meson.build b/tools/phosphor-logging/templates/meson.build
index e713aaf..ee9e24c 100644
--- a/tools/phosphor-logging/templates/meson.build
+++ b/tools/phosphor-logging/templates/meson.build
@@ -1 +1,2 @@
 template_elog_gen = files('elog-gen-template.hpp.mako')
+template_eventfilter_gen = files('event-filter.cpp.mako')