lg2: commit: add support for journal-only method

The design document for the new event system references an
option to log events to the journal rather than to dbus[1] using
the 'OPENBMC_MESSAGE_ID' identifier.  Add support and test cases
for this as a meson option.

[1]: https://github.com/openbmc/docs/blob/master/designs/event-logging.md#phosphor-logging

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I6cb94453c6cc95a9ccbbbc11859b70ef12d375fd
diff --git a/config/config.h.meson b/config/config.h.meson
index 698b09d..f852bab 100644
--- a/config/config.h.meson
+++ b/config/config.h.meson
@@ -36,4 +36,7 @@
 static constexpr auto FIRST_CEREAL_CLASS_VERSION_WITH_RESOLUTION = "5";
 static constexpr size_t CLASS_VERSION = 5;
 
+static constexpr bool LG2_COMMIT_DBUS = @lg2_commit_dbus@;
+static constexpr bool LG2_COMMIT_JOURNAL = @lg2_commit_journal@;
+
 // vim: ft=cpp
diff --git a/config/meson.build b/config/meson.build
index 661ded3..946ec39 100644
--- a/config/meson.build
+++ b/config/meson.build
@@ -4,6 +4,16 @@
 conf_data.set('error_info_cap', get_option('error_info_cap'))
 conf_data.set('rsyslog_server_conf', get_option('rsyslog_server_conf'))
 
+lg2_commit_strategy = get_option('lg2_commit_strategy')
+conf_data.set(
+    'lg2_commit_dbus',
+    lg2_commit_strategy == 'dbus' or lg2_commit_strategy == 'both' ? 'true' : 'false',
+)
+conf_data.set(
+    'lg2_commit_journal',
+    lg2_commit_strategy == 'journal' or lg2_commit_strategy == 'both' ? 'true' : 'false',
+)
+
 cxx = meson.get_compiler('cpp')
 if cxx.has_header('poll.h')
     add_project_arguments('-DPLDM_HAS_POLL=1', language: 'cpp')
@@ -15,5 +25,5 @@
         input: 'config.h.meson',
         output: 'config.h',
         configuration: conf_data,
-    )
+    ),
 )
diff --git a/lib/lg2_commit.cpp b/lib/lg2_commit.cpp
index 6f11a4c..4f37cb6 100644
--- a/lib/lg2_commit.cpp
+++ b/lib/lg2_commit.cpp
@@ -1,8 +1,11 @@
+#include "config.h"
+
 #include "lg2_commit.hpp"
 
 #include <sys/syslog.h>
 
 #include <nlohmann/json.hpp>
+#include <phosphor-logging/lg2.hpp>
 #include <phosphor-logging/lg2/commit.hpp>
 #include <sdbusplus/async.hpp>
 #include <sdbusplus/exception.hpp>
@@ -97,26 +100,47 @@
 auto commit(sdbusplus::exception::generated_event_base&& t)
     -> sdbusplus::message::object_path
 {
-    auto b = sdbusplus::bus::new_default();
-    auto m = b.new_method_call(Create::default_service, Create::instance_path,
-                               Create::interface, "Create");
+    if constexpr (LG2_COMMIT_JOURNAL)
+    {
+        lg2::error("OPENBMC_MESSAGE_ID={DATA}", "DATA", t.to_json().dump());
+    }
 
-    m.append(t.name(), severity_from_syslog(t.severity()), data_from_json(t));
+    if constexpr (LG2_COMMIT_DBUS)
+    {
+        auto b = sdbusplus::bus::new_default();
+        auto m =
+            b.new_method_call(Create::default_service, Create::instance_path,
+                              Create::interface, "Create");
 
-    auto reply = b.call(m);
+        m.append(t.name(), severity_from_syslog(t.severity()),
+                 data_from_json(t));
 
-    return reply.unpack<sdbusplus::message::object_path>();
+        auto reply = b.call(m);
+
+        return reply.unpack<sdbusplus::message::object_path>();
+    }
+
+    return {};
 }
 
 auto commit(sdbusplus::async::context& ctx,
             sdbusplus::exception::generated_event_base&& t)
     -> sdbusplus::async::task<sdbusplus::message::object_path>
 {
-    co_return co_await Create(ctx)
-        .service(Create::default_service)
-        .path(Create::instance_path)
-        .create(t.name(), severity_from_syslog(t.severity()),
-                data_from_json(t));
+    if constexpr (LG2_COMMIT_JOURNAL)
+    {
+        lg2::error("OPENBMC_MESSAGE_ID={DATA}", "DATA", t.to_json().dump());
+    }
+
+    if constexpr (LG2_COMMIT_DBUS)
+    {
+        co_return co_await Create(ctx)
+            .service(Create::default_service)
+            .path(Create::instance_path)
+            .create(t.name(), severity_from_syslog(t.severity()),
+                    data_from_json(t));
+    }
+    co_return {};
 }
 
 auto extractEvent(sdbusplus::exception::generated_event_base&& t)
diff --git a/meson.options b/meson.options
index 0dd7c3e..2bab22c 100644
--- a/meson.options
+++ b/meson.options
@@ -41,3 +41,10 @@
     value: '/etc/rsyslog.d/server.conf',
     description: 'Path to rsyslog server conf file',
 )
+
+option(
+    'lg2_commit_strategy',
+    type: 'combo',
+    choices: ['dbus', 'journal', 'both'],
+    value: 'both'
+)
diff --git a/test/log_manager_dbus_tests.cpp b/test/log_manager_dbus_tests.cpp
index e2bc8be..12136c1 100644
--- a/test/log_manager_dbus_tests.cpp
+++ b/test/log_manager_dbus_tests.cpp
@@ -85,6 +85,43 @@
     };
 
     std::unique_ptr<fixture_data> data;
+
+    static constexpr auto journal_unavailable = "UNAVAILABLE";
+    std::string last_journal_entry()
+    {
+        if constexpr (LG2_COMMIT_JOURNAL)
+        {
+            // When running under Docker, the journal is not available and
+            // sd-journal calls just silently pass.  Return a string to make
+            // it obvious.
+            if (!std::filesystem::exists("/run/systemd/journal/socket"))
+            {
+                return journal_unavailable;
+            }
+
+            sd_journal* j = nullptr;
+
+            sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
+            sd_journal_add_match(j, "SYSLOG_IDENTIFIER=test_manager_dbus_tests",
+                                 SIZE_MAX);
+
+            SD_JOURNAL_FOREACH_BACKWARDS(j)
+            {
+                const char* data = nullptr;
+                size_t length = 0;
+
+                sd_journal_get_data(j, "MESSAGE", (const void**)&data, &length);
+
+                std::string entry(data, length);
+                if (entry.contains("OPENBMC_MESSAGE_ID="))
+                {
+                    return entry;
+                }
+            }
+        }
+
+        return "";
+    }
 };
 
 // Ensure we can successfully create and throw an sdbusplus event.
@@ -100,11 +137,27 @@
 TEST_F(TestLogManagerDbus, CallCommitSync)
 {
     auto path = lg2::commit(LoggingCleared("NUMBER_OF_LOGS", 3));
-    ASSERT_FALSE(path.str.empty());
-    EXPECT_THAT(path.str,
-                ::testing::StartsWith(
-                    std::filesystem::path(LoggingEntry::namespace_path::value) /
-                    LoggingEntry::namespace_path::entry));
+
+    if constexpr (LG2_COMMIT_DBUS)
+    {
+        ASSERT_FALSE(path.str.empty());
+        EXPECT_THAT(
+            path.str,
+            ::testing::StartsWith(
+                std::filesystem::path(LoggingEntry::namespace_path::value) /
+                LoggingEntry::namespace_path::entry));
+    }
+
+    if constexpr (LG2_COMMIT_JOURNAL)
+    {
+        auto entry = last_journal_entry();
+        if (entry != journal_unavailable)
+        {
+            EXPECT_THAT(entry, ::testing::HasSubstr(
+                                   "\"xyz.openbmc_project.Logging.Cleared\":"));
+            EXPECT_THAT(entry, ::testing::HasSubstr("\"NUMBER_OF_LOGS\":3"));
+        }
+    }
 }
 
 // Call the asynchronous version of the commit function and verify that the
@@ -121,30 +174,33 @@
         path = co_await lg2::commit(data->client_ctx,
                                     LoggingCleared("NUMBER_OF_LOGS", 6));
 
-        // Grab the additional data.
-        auto additionalData = co_await LoggingEntry(data->client_ctx)
-                                  .service(Entry::default_service)
-                                  .path(path.str)
-                                  .additional_data();
-
-        // Extract the NUMBER_OF_LOGS, PID, and CODE_FILE.
-        for (const auto& value : additionalData)
+        if constexpr (LG2_COMMIT_DBUS)
         {
-            auto getValue = [&value]() {
-                return value.substr(value.find_first_of('=') + 1);
-            };
+            // Grab the additional data.
+            auto additionalData = co_await LoggingEntry(data->client_ctx)
+                                      .service(Entry::default_service)
+                                      .path(path.str)
+                                      .additional_data();
 
-            if (value.starts_with("NUMBER_OF_LOGS="))
+            // Extract the NUMBER_OF_LOGS, PID, and CODE_FILE.
+            for (const auto& value : additionalData)
             {
-                log_count = getValue();
-            }
-            if (value.starts_with("_PID="))
-            {
-                pid = std::stoull(getValue());
-            }
-            if (value.starts_with("_CODE_FILE="))
-            {
-                source_file = getValue();
+                auto getValue = [&value]() {
+                    return value.substr(value.find_first_of('=') + 1);
+                };
+
+                if (value.starts_with("NUMBER_OF_LOGS="))
+                {
+                    log_count = getValue();
+                }
+                if (value.starts_with("_PID="))
+                {
+                    pid = std::stoull(getValue());
+                }
+                if (value.starts_with("_CODE_FILE="))
+                {
+                    source_file = getValue();
+                }
             }
         }
 
@@ -153,17 +209,32 @@
 
     run(create_log());
 
-    ASSERT_FALSE(path.str.empty());
-    ASSERT_FALSE(log_count.empty());
+    if constexpr (LG2_COMMIT_DBUS)
+    {
+        ASSERT_FALSE(path.str.empty());
+        ASSERT_FALSE(log_count.empty());
 
-    EXPECT_THAT(path.str,
-                ::testing::StartsWith(
-                    std::filesystem::path(LoggingEntry::namespace_path::value) /
-                    LoggingEntry::namespace_path::entry));
+        EXPECT_THAT(
+            path.str,
+            ::testing::StartsWith(
+                std::filesystem::path(LoggingEntry::namespace_path::value) /
+                LoggingEntry::namespace_path::entry));
 
-    EXPECT_EQ(log_count, "6");
-    EXPECT_EQ(pid, getpid());
-    EXPECT_EQ(source_file, std::source_location::current().file_name());
+        EXPECT_EQ(log_count, "6");
+        EXPECT_EQ(pid, getpid());
+        EXPECT_EQ(source_file, std::source_location::current().file_name());
+    }
+
+    if constexpr (LG2_COMMIT_JOURNAL)
+    {
+        auto entry = last_journal_entry();
+        if (entry != journal_unavailable)
+        {
+            EXPECT_THAT(entry, ::testing::HasSubstr(
+                                   "\"xyz.openbmc_project.Logging.Cleared\":"));
+            EXPECT_THAT(entry, ::testing::HasSubstr("\"NUMBER_OF_LOGS\":6"));
+        }
+    }
 }
 
 } // namespace phosphor::logging::test