Add the stream mode

The stream mode behaves differently in versus the existing buffer mode.

1. It leverages rsyslog to persist logs;
2. It leverages logrotate to rotate and compress logs;
3. It persists logs as soon as they are collected.

Add configuration options to choose modes at start up time. When stream
mode is disabled, no difference compared to the existing service.

See README.md for details.

This change also adds mock classes for unit test purpose.

Change-Id: Ic7d02e826c7d9372621c096c6e768e6216974150
Signed-off-by: Nan Zhou <nanzhoumails@gmail.com>
diff --git a/src/buffer_service.cpp b/src/buffer_service.cpp
new file mode 100644
index 0000000..e17f7f0
--- /dev/null
+++ b/src/buffer_service.cpp
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#include "buffer_service.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+#include <vector>
+
+using namespace phosphor::logging;
+
+// clang-format off
+/** @brief Host state monitor properties.
+ *  Used for automatic flushing the log buffer to the persistent file.
+ *  Contains a list of properties and a set of their values that trigger the
+ *  flush operation.
+ *  For example, the current log buffer will be saved to a file when the
+ *  "OperatingSystemState" property obtains one of the
+ *  listed values ("xyz.openbmc_project...BootComplete", "Inactive", etc).
+ */
+static const DbusLoop::WatchProperties watchProperties{
+  {"xyz.openbmc_project.State.Host", {{
+    "RequestedHostTransition", {
+      "xyz.openbmc_project.State.Host.Transition.On"}}}},
+  {"xyz.openbmc_project.State.OperatingSystem.Status", {{
+    "OperatingSystemState", {
+      "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.BootComplete",
+      "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.Inactive",
+      "Inactive",
+      "Standby"}}}}
+};
+// clang-format on
+
+BufferService::BufferService(const Config& config, DbusLoop& dbusLoop,
+                             HostConsole& hostConsole, LogBuffer& logBuffer,
+                             FileStorage& fileStorage) :
+    config(config),
+    dbusLoop(&dbusLoop), hostConsole(&hostConsole), logBuffer(&logBuffer),
+    fileStorage(&fileStorage)
+{}
+
+void BufferService::run()
+{
+    if (config.bufFlushFull)
+    {
+        logBuffer->setFullHandler([this]() { this->flush(); });
+    }
+
+    hostConsole->connect();
+
+    // Add SIGUSR1 signal handler for manual flushing
+    dbusLoop->addSignalHandler(SIGUSR1, [this]() { this->flush(); });
+    // Add SIGTERM signal handler for service shutdown
+    dbusLoop->addSignalHandler(SIGTERM, [this]() { this->dbusLoop->stop(0); });
+
+    // Register callback for socket IO
+    dbusLoop->addIoHandler(*hostConsole, [this]() { this->readConsole(); });
+
+    // Register host state watcher
+    if (*config.hostState)
+    {
+        dbusLoop->addPropertyHandler(config.hostState, watchProperties,
+                                     [this]() { this->flush(); });
+    }
+
+    if (!*config.hostState && !config.bufFlushFull)
+    {
+        log<level::WARNING>("Automatic flush disabled");
+    }
+
+    log<level::DEBUG>("Initialization complete",
+                      entry("SocketId=%s", config.socketId),
+                      entry("BufMaxSize=%lu", config.bufMaxSize),
+                      entry("BufMaxTime=%lu", config.bufMaxTime),
+                      entry("BufFlushFull=%s", config.bufFlushFull ? "y" : "n"),
+                      entry("HostState=%s", config.hostState),
+                      entry("OutDir=%s", config.outDir),
+                      entry("MaxFiles=%lu", config.maxFiles));
+
+    // Run D-Bus event loop
+    const int rc = dbusLoop->run();
+    if (!logBuffer->empty())
+    {
+        flush();
+    }
+    if (rc < 0)
+    {
+        std::error_code ec(-rc, std::generic_category());
+        throw std::system_error(ec, "Error in event loop");
+    }
+}
+
+void BufferService::flush()
+{
+    if (logBuffer->empty())
+    {
+        log<level::INFO>("Ignore flush: buffer is empty");
+        return;
+    }
+    try
+    {
+        const std::string fileName = fileStorage->save(*logBuffer);
+        logBuffer->clear();
+
+        std::string msg = "Host logs flushed to ";
+        msg += fileName;
+        log<level::INFO>(msg.c_str());
+    }
+    catch (const std::exception& ex)
+    {
+        log<level::ERR>(ex.what());
+    }
+}
+
+void BufferService::readConsole()
+{
+    constexpr size_t bufSize = 128; // enough for most line-oriented output
+    std::vector<char> bufData(bufSize);
+    char* buf = bufData.data();
+
+    try
+    {
+        while (const size_t rsz = hostConsole->read(buf, bufSize))
+        {
+            logBuffer->append(buf, rsz);
+        }
+    }
+    catch (const std::system_error& ex)
+    {
+        log<level::ERR>(ex.what());
+    }
+}