Initial commit

Signed-off-by: Artem Senichev <a.senichev@yadro.com>
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..2197ef3
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,35 @@
+YAML_IFACE = xyz/openbmc_project/HostLogger.interface.yaml
+DBUS_IFACE = xyz.openbmc_project.HostLogger
+GENERATED_HPP = xyz/openbmc_project/HostLogger/server.hpp
+GENERATED_CPP = xyz/openbmc_project/HostLogger/server.cpp
+
+sbin_PROGRAMS = hostlogger
+
+nobase_nodist_include_HEADERS = \
+	$(GENERATED_HPP)
+
+hostlogger_SOURCES = \
+	$(GENERATED_CPP) \
+	src/main.cpp \
+	src/dbus_server.hpp \
+	src/dbus_server.cpp \
+	src/dbus_watch.hpp \
+	src/dbus_watch.cpp \
+	src/log_manager.hpp \
+	src/log_manager.cpp \
+	src/log_storage.hpp \
+	src/log_storage.cpp
+
+hostlogger_CXXFLAGS = $(SDBUSPLUS_CFLAGS) $(PHOSPHOR_DBUS_INTERFACES_CFLAGS)
+hostlogger_LDADD = $(SDBUSPLUS_LIBS) $(PHOSPHOR_DBUS_INTERFACES_LIBS)
+
+BUILT_SOURCES = $(GENERATED_HPP) $(GENERATED_CPP)
+CLEANFILES = $(BUILT_SOURCES)
+
+$(GENERATED_HPP): $(YAML_IFACE)
+	@mkdir -p $(@D)
+	$(SDBUSPLUSPLUS) -r $(srcdir) interface server-header $(DBUS_IFACE) > $@
+
+$(GENERATED_CPP): $(YAML_IFACE) $(GENERATED_HPP)
+	@mkdir -p $(@D)
+	$(SDBUSPLUSPLUS) -r $(srcdir) interface server-cpp $(DBUS_IFACE) > $@
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4a1f01f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,98 @@
+# Host Logger
+
+The main purpose of the Host Logger project is to handle and store host's
+console output data, such as boot logs or Linux kernel messages printed to the
+system console.
+
+## Architecture
+Host Logger is a standalone service (daemon) that works on top of the
+obmc-console-server module and uses its UNIX domain socket to read the console
+output.
+```
++-------------+                                       +----------------+
+|     Host    | State  +---------------------+ Event  |  Host Logger   |
+|             +------->|        D-Bus        |------->|                |
+|             |        +---------------------+        | +------------+ |
+|             |                                  D +--->| Log buffer | |
+|             |        +---------------------+   a |  | +------+-----+ |
+|             |        | obmc-console-server |   t |  |        |       |
+| +---------+ |  Data  |   +-------------+   |   a |  | +------+-----+ |
+| | console |--------->|   | UNIX socket |---------+  | |  Log file  | |
+| +---------+ |        |   +-------------+   |        | +------------+ |
++-------------+        +---------------------+        +----------------+
+```
+Unlike the obmc-console project, where buffer with console output is a
+"black box" and exist only as a binary byte array, the Host Logger service
+interprets the input stream: splits it into separated messages, adds a time
+stamp and pushes the message into an internal buffer.
+The buffer will be written to a file when one of the external events occurs,
+such as a request for turn on the host or direct call of the function `Flush`
+via D-Bus.
+It gives us the ability to save the last boot log and subsequent messages in
+separated files.
+The service uses a set of rules (policy) that defines how large a buffer can
+be (limit by time or message numbers), in which cases the buffer should be
+flushed to a file, and how many previous log files can be saved on a flash
+drive (log rotation).
+
+
+## Configuration
+
+The policy can be defined in two ways:
+- By options passed to the "configure" script during the build process, this
+  method sets up the default policy used by the service;
+- By options passed to the executable module of the service, they can be
+  specified as command line parameters inside the systemd service file
+  used to launch the service.
+
+### Limit of message buffer size
+- `--szlimit`: Set maximum number of messages stored inside the buffer. Default
+  value is `3000`. The value `0` will disable the rule.
+- `--tmlimit`: Set maximum age of a message (in hours), all messages that older
+  then the specified value will be cleaned. This rule can be used to store, for
+  example, only messages for the latest hour. Default value is `0` (rule
+  disabled).
+Both of these options can be combined together, in this case a message will
+be cleaned if it complies any of limit types. You can create an unlimited
+buffer by passing `0` for both of these options.
+
+### File write policy (buffer flush)
+- `--path`:  Set the path used to store log files, default values is
+  /var/lib/obmc/hostlogs.
+- `--flush`: Set the period (in hours) to flush buffer to a file. Default value
+  is `0`, which means that the flush operation will be started at every
+  significant host state change - power on/off or OS boot complete events.
+
+### File rotation policy
+- `--rotate`: Set the maximum number of files stored on the flash memory.
+  Default value is `10`. The service will remove the oldest files during buffer
+  the flush procedure.
+
+
+## Integration
+Directory "integration" contains example files used to integrate the service
+into OpenBMC system:
+- `systemd.service`: systemd unit file used to describe the service;
+- `dreport.plugin`: plugin for dreport module, used to add host's logs into
+  a dump archive.
+
+## D-Bus
+The service provides an implementation of a D-Bus interface
+`xyz.openbmc_project.HostLogger.service`.
+Currently it has only one method `Flush` used for manually flushing logs.
+
+
+## Example
+```
+# ls /var/lib/obmc/hostlogs
+host_20181024_092655.log.gz  host_20181025_120520.log.gz
+host_20181024_114342.log.gz  host_20181025_120910.log.gz
+
+# zcat /var/lib/obmc/hostlogs/host_20181025_120910.log.gz
+[ 12:05:30 ]: >>> Log collection started at 25.10.2018 12:05:30
+[ 12:05:30 ]: --== Welcome to Hostboot v2.1-199-g38a9f30/hbicore.bin/ ==--
+[ 12:05:34 ]:   5.52713|ISTEP  6. 3 - host_init_fsi
+[ 12:05:34 ]:   6.13176|ISTEP  6. 4 - host_set_ipl_parms
+[ 12:05:34 ]:   6.13309|ISTEP  6. 5 - host_discover_targets
+...
+```
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..690c2a1
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,66 @@
+AC_PREREQ([2.69])
+AC_INIT([phosphor-hostlogger],
+        [m4_esyscmd_s([git describe --always --dirty --long])],
+        [https://github.com/YADRO-KNS/phosphor-hostlogger], ,
+        [https://github.com/YADRO-KNS/phosphor-hostlogger])
+AC_LANG([C++])
+AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign])
+AM_SILENT_RULES([yes])
+
+# Check for programs
+AC_PROG_CXX
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+AC_PROG_MKDIR_P
+AC_HEADER_STDC
+
+# Compiler flags
+AX_APPEND_COMPILE_FLAGS([-fpic -Wall], [CFLAGS])
+AX_APPEND_COMPILE_FLAGS([-fpic -Wall], [CXXFLAGS])
+
+# Checks for external dependencies
+AC_CHECK_LIB([z], [gzopen], [], [AC_MSG_ERROR(zlib not found)])
+PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221], ,
+    AC_MSG_ERROR(["systemd required but not found."]))
+PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces], ,
+    AC_MSG_ERROR(["Requires phosphor-dbus-interfaces package."]))
+PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus], ,
+    AC_MSG_ERROR(["Requires sdbusplus package."]))
+AC_PATH_PROG([SDBUSPLUSPLUS], [sdbus++])
+AS_IF([test "x$SDBUSPLUSPLUS" == "x"],
+    AC_MSG_ERROR(["Requires sdbus++"]))
+
+# Configuration property: path to the ouput directory of log files
+AC_ARG_VAR([LOG_OUTPUT_PATH], [Path used to store logs.])
+AS_IF([test "x$LOG_OUTPUT_PATH" == "x"], [LOG_OUTPUT_PATH="/var/lib/obmc/hostlogs"])
+AC_DEFINE_UNQUOTED([LOG_OUTPUT_PATH], ["$LOG_OUTPUT_PATH"])
+
+# Configuration property: log storage limit (maximum number of messages to store)
+AC_ARG_VAR([LOG_STORAGE_SIZE_LIMIT], [Limit number of messages in store, default is 3000])
+AS_IF([test "x$LOG_STORAGE_SIZE_LIMIT" == "x"], [LOG_STORAGE_SIZE_LIMIT=3000])
+AC_DEFINE_UNQUOTED([LOG_STORAGE_SIZE_LIMIT], [${LOG_STORAGE_SIZE_LIMIT}])
+
+# Configuration property: log storage limit (oldest time in hours)
+AC_ARG_VAR([LOG_STORAGE_TIME_LIMIT], [Limit message store by oldest time in hours, default is 0])
+AS_IF([test "x$LOG_STORAGE_TIME_LIMIT" == "x"], [LOG_STORAGE_TIME_LIMIT=0])
+AC_DEFINE_UNQUOTED([LOG_STORAGE_TIME_LIMIT], [${LOG_STORAGE_TIME_LIMIT}])
+
+# Configuration property: flush period policy.
+AC_ARG_VAR([LOG_FLUSH_PERIOD], [Set the default flush period time, default is 0])
+AS_IF([test "x$LOG_FLUSH_PERIOD" == "x"], [LOG_FLUSH_PERIOD=0])
+AC_DEFINE_UNQUOTED([LOG_FLUSH_PERIOD], [${LOG_FLUSH_PERIOD}])
+
+# Configuration property: limit for log rotation - maximum number of files to store in the log's directory
+AC_ARG_VAR([LOG_ROTATION_LIMIT], [Limit for log rotation, default is 10])
+AS_IF([test "x$LOG_ROTATION_LIMIT" == "x"], [LOG_ROTATION_LIMIT=10])
+AC_DEFINE_UNQUOTED([LOG_ROTATION_LIMIT], [${LOG_ROTATION_LIMIT}])
+
+# Debug property
+AC_ARG_ENABLE(debug,
+    [AS_HELP_STRING([--enable-debug], [enable debugging [disabled]])],
+    [AM_CPPFLAGS="$AM_CPPFLAGS -DDEBUG -g -O0"]
+    [AM_CPPFLAGS="$AM_CPPFLAGS -DNDEBUG"]
+)
+
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/integration/dreport.plugin b/integration/dreport.plugin
new file mode 100644
index 0000000..d884785
--- /dev/null
+++ b/integration/dreport.plugin
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# config: 123 20
+# @brief: Collect host's logs
+#
+
+. $DREPORT_INCLUDE/functions
+
+desc="Logs from host"
+file_name="/var/lib/obmc/hostlogs"
+
+# Flush currently collected messages
+busctl --no-pager --verbose call \
+    xyz.openbmc_project.HostLogger /xyz/openbmc_project/HostLogger \
+    xyz.openbmc_project.HostLogger Flush
+
+add_copy_file "$file_name" "$desc"
diff --git a/integration/systemd.service b/integration/systemd.service
new file mode 100644
index 0000000..ac49668
--- /dev/null
+++ b/integration/systemd.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Host logging
+Wants=obmc-console@ttyVUART0.service
+After=obmc-console@ttyVUART0.service
+
+[Service]
+ExecStart=/usr/bin/hostlogger
+Restart=always
+Type=dbus
+BusName=xyz.openbmc_project.HostLogger
+
+[Install]
+WantedBy=obmc-standby.target
diff --git a/src/config.hpp b/src/config.hpp
new file mode 100644
index 0000000..5264a5e
--- /dev/null
+++ b/src/config.hpp
@@ -0,0 +1,41 @@
+/**
+ * @brief Logger configuration.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+/** @struct Config
+ *  @brief Represent the configuration of the logger.
+ */
+struct Config
+{
+    /** @brief Path to write log files. */
+    const char* path;
+    /** @brief Storage limit (message count). */
+    int storageSizeLimit;
+    /** @brief Storage limit (max time). */
+    int storageTimeLimit;
+    /** @brief Flush policy: save logs every flushPeriod hours. */
+    int flushPeriod;
+    /** @brief Rotation limit (max files to store). */
+    int rotationLimit;
+};
+
+/** @brief Global logger configuration instance. */
+extern Config loggerConfig;
diff --git a/src/dbus_server.cpp b/src/dbus_server.cpp
new file mode 100644
index 0000000..53a2087
--- /dev/null
+++ b/src/dbus_server.cpp
@@ -0,0 +1,37 @@
+/**
+ * @brief Server side implementation of the D-Bus interface
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "dbus_server.hpp"
+#include <xyz/openbmc_project/Common/File/error.hpp>
+
+
+DbusServer::DbusServer(LogManager& logManager, sdbusplus::bus::bus& bus, const char* path /*= HOSTLOGGER_DBUS_PATH*/)
+: server_inherit(bus, path),
+  logManager_(logManager)
+{
+}
+
+
+void DbusServer::flush()
+{
+    const int rc = logManager_.flush();
+    if (rc != 0)
+        throw sdbusplus::xyz::openbmc_project::Common::File::Error::Write();
+}
diff --git a/src/dbus_server.hpp b/src/dbus_server.hpp
new file mode 100644
index 0000000..1e6d47c
--- /dev/null
+++ b/src/dbus_server.hpp
@@ -0,0 +1,67 @@
+/**
+ * @brief Server side implementation of the D-Bus interface
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "log_manager.hpp"
+#include <xyz/openbmc_project/HostLogger/server.hpp>
+
+/** @brief D-Bus interface name. */
+#define HOSTLOGGER_DBUS_IFACE "xyz.openbmc_project.HostLogger"
+/** @brief D-Bus path. */
+#define HOSTLOGGER_DBUS_PATH  "/xyz/openbmc_project/HostLogger"
+
+/** @brief unique_ptr functor to release an event reference. */
+struct EventDeleter
+{
+    void operator()(sd_event* event) const
+    {
+        sd_event_unref(event);
+    }
+};
+
+/* @brief Alias 'event' to a unique_ptr type for auto-release. */
+using EventPtr = std::unique_ptr<sd_event, EventDeleter>;
+
+// Typedef for super class
+using server_inherit = sdbusplus::server::object_t<sdbusplus::xyz::openbmc_project::server::HostLogger>;
+
+
+/** @class DbusServer
+ *  @brief D-Bus service by host logger.
+ */
+class DbusServer: public server_inherit
+{
+public:
+    /** @brief Constructor.
+     *
+     *  @param[in] logManager - log manager
+     *  @param[in] bus - bus to attach to
+     *  @param[in] path - bath to attach at, optional, default is HOSTLOGGER_DBUS_PATH
+     */
+    DbusServer(LogManager& logManager, sdbusplus::bus::bus& bus, const char* path = HOSTLOGGER_DBUS_PATH);
+
+    // From server_inherit
+    void flush();
+
+private:
+    /** @brief Log manager instance. */
+    LogManager& logManager_;
+};
diff --git a/src/dbus_watch.cpp b/src/dbus_watch.cpp
new file mode 100644
index 0000000..fa1bcb1
--- /dev/null
+++ b/src/dbus_watch.cpp
@@ -0,0 +1,165 @@
+/**
+ * @brief D-Bus signal watcher.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.hpp"
+#include "dbus_watch.hpp"
+#include <set>
+#include <string>
+#include <chrono>
+
+// D-Bus path to the host state object
+#define DBUS_HOST_OBJECT_PATH "/xyz/openbmc_project/state/host0"
+
+// Macro to normalize SDBus status code:
+// positive code is not an error in the systemd dbus implementation.
+#define DBUS_RC_TO_ERR(c) (c = (c <= 0 ? -c : 0))
+
+
+DbusWatcher::DbusWatcher(LogManager& logManager, sdbusplus::bus::bus& bus)
+: logManager_(logManager),
+  bus_(bus)
+{
+}
+
+
+int DbusWatcher::initialize()
+{
+    int rc;
+
+    // Add IO callback for host's log stream socket
+    rc = sd_event_add_io(bus_.get_event(), NULL,
+                         logManager_.getHostLogFd(),
+                         EPOLLIN, &DbusWatcher::ioCallback, this);
+    if (DBUS_RC_TO_ERR(rc)) {
+        fprintf(stderr, "Unable to add IO handler: %i %s\n",
+                        rc, strerror(rc));
+        return rc;
+    }
+
+    // Add flush handler
+    if (loggerConfig.flushPeriod == 0)
+        registerEventHandler();
+    else
+        rc = registerTimerHandler();
+
+    return rc;
+}
+
+
+void DbusWatcher::registerEventHandler()
+{
+    conds_["xyz.openbmc_project.State.Host"] = {
+        .property = "RequestedHostTransition",
+        .values = {
+            "xyz.openbmc_project.State.Host.Transition.On"
+        }
+    };
+    conds_["xyz.openbmc_project.State.OperatingSystem.Status"] = {
+        .property = "OperatingSystemState",
+        .values = {
+            "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.BootComplete",
+            "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.Inactive"
+        }
+    };
+    for (auto& cond: conds_) {
+        cond.second.match = std::make_unique<sdbusplus::bus::match_t>(bus_,
+                            sdbusplus::bus::match::rules::propertiesChanged(
+                            DBUS_HOST_OBJECT_PATH, cond.first),
+                            [this](auto& msg){ this->hostStateHandler(msg); });
+    }
+}
+
+
+int DbusWatcher::registerTimerHandler()
+{
+    int rc;
+    sd_event_source* ev = NULL;
+
+    rc = sd_event_add_time(bus_.get_event(), &ev, CLOCK_MONOTONIC,
+                           UINT64_MAX, 0, &DbusWatcher::timerCallback, this);
+    if (DBUS_RC_TO_ERR(rc)) {
+        fprintf(stderr, "Unable to add timer handler: %i %s\n", rc, strerror(rc));
+        return rc;
+    }
+
+    rc = sd_event_source_set_enabled(ev, SD_EVENT_ON);
+    if (DBUS_RC_TO_ERR(rc)) {
+        fprintf(stderr, "Unable to enable timer handler: %i %s\n", rc, strerror(rc));
+        return rc;
+    }
+
+    return setupTimer(ev);
+}
+
+
+int DbusWatcher::setupTimer(sd_event_source* event)
+{
+    // Get the current time and add the delta (flush period)
+    using namespace std::chrono;
+    auto now = steady_clock::now().time_since_epoch();
+    hours timeOut(loggerConfig.flushPeriod);
+    auto expireTime = duration_cast<microseconds>(now) +
+                      duration_cast<microseconds>(timeOut);
+
+    //Set the time
+    int rc = sd_event_source_set_time(event, expireTime.count());
+    if (DBUS_RC_TO_ERR(rc))
+        fprintf(stderr, "Unable to set timer handler: %i %s\n", rc, strerror(rc));
+
+    return rc;
+}
+
+
+void DbusWatcher::hostStateHandler(sdbusplus::message::message& msg)
+{
+    std::map<std::string, sdbusplus::message::variant<std::string>> properties;
+    std::string interface;
+
+    msg.read(interface, properties);
+
+    bool needFlush = false;
+    const auto itc = conds_.find(interface);
+    if (itc != conds_.end()) {
+        const auto itp = properties.find(itc->second.property);
+        if (itp != properties.end()) {
+            const auto& propVal = itp->second.get<std::string>();
+            needFlush = itc->second.values.find(propVal) != itc->second.values.end();
+        }
+    }
+
+    if (needFlush)
+        logManager_.flush();
+}
+
+
+int DbusWatcher::ioCallback(sd_event_source* /*event*/, int /*fd*/, uint32_t /*revents*/, void* data)
+{
+    DbusWatcher* instance = static_cast<DbusWatcher*>(data);
+    instance->logManager_.handleHostLog();
+    return 0;
+}
+
+
+int DbusWatcher::timerCallback(sd_event_source* event, uint64_t /*usec*/, void* data)
+{
+    DbusWatcher* instance = static_cast<DbusWatcher*>(data);
+    instance->logManager_.flush();
+    return instance->setupTimer(event);
+}
diff --git a/src/dbus_watch.hpp b/src/dbus_watch.hpp
new file mode 100644
index 0000000..1f5201f
--- /dev/null
+++ b/src/dbus_watch.hpp
@@ -0,0 +1,111 @@
+/**
+ * @brief D-Bus signal watcher.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "log_manager.hpp"
+
+#include <set>
+#include <map>
+#include <string>
+
+#include <sdbusplus/bus/match.hpp>
+
+
+
+/** @class DbusServer
+ *  @brief D-Bus service by host logger.
+ */
+class DbusWatcher
+{
+public:
+    /** @brief Constructor.
+     *
+     *  @param[in] logManager - log manager
+     *  @param[in] bus - bus to attach to
+     */
+    DbusWatcher(LogManager& logManager, sdbusplus::bus::bus& bus);
+
+    /** @brief Initialize watcher.
+     *
+     *  @return error code, 0 if operation completed successfully
+     */
+    int initialize();
+
+private:
+    /** @brief Register D-Bus event handler. */
+    void registerEventHandler();
+
+    /** @brief Register D-Bus timer handler.
+     *
+     *  @return error code, 0 if operation completed successfully
+     */
+    int registerTimerHandler();
+
+    /** @brief Setup D-Bus timer.
+     *
+     *  @param[in] event - event source to setup
+     *
+     *  @return error code, 0 if operation completed successfully
+     */
+    int setupTimer(sd_event_source* event);
+
+    /** @brief Callback function for host state change.
+     *
+     *  @param[in] msg - data associated with subscribed signal
+     */
+    void hostStateHandler(sdbusplus::message::message& msg);
+
+    /** @brief D-Bus IO callback used to handle incoming data on the opened file.
+     *         See sd_event_io_handler_t for details.
+     */
+    static int ioCallback(sd_event_source* event, int fd, uint32_t revents, void* data);
+
+    /** @brief D-Bus timer callback used to flush log store.
+     *         See sd_event_add_time for details.
+     */
+    static int timerCallback(sd_event_source* event, uint64_t usec, void* data);
+
+private:
+    /** @struct FlushCondition
+     *  @brief Describes flush conditions for log manager based on host state event.
+     */
+    struct FlushCondition
+    {
+        /** @brief D-Bus property name to watch. */
+        std::string property;
+        /** @brief Set of possible values for property watched. */
+        std::set<std::string> values;
+        /** @brief Match object to create D-Bus handler. */
+        std::unique_ptr<sdbusplus::bus::match_t> match;
+    };
+
+private:
+    /** @brief Log manager instance. */
+    LogManager& logManager_;
+
+    /** @brief D-Bus bus. */
+    sdbusplus::bus::bus& bus_;
+
+    /** @brief Log storage flush conditions.
+     *         Map D-Bus interface name to condition description.
+     */
+    std::map<std::string, FlushCondition> conds_;
+};
diff --git a/src/log_manager.cpp b/src/log_manager.cpp
new file mode 100644
index 0000000..b78b7e8
--- /dev/null
+++ b/src/log_manager.cpp
@@ -0,0 +1,254 @@
+/**
+ * @brief Log manager.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.hpp"
+#include "log_manager.hpp"
+
+#include <set>
+#include <vector>
+#include <cstring>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+// Path to the Unix Domain socket file used to read host's logs
+#define HOSTLOG_SOCKET_PATH     "\0obmc-console"
+// Number of connection attempts
+#define HOSTLOG_SOCKET_ATTEMPTS 60
+// Pause between connection attempts in seconds
+#define HOSTLOG_SOCKET_PAUSE    1
+// Max buffer size to read from socket
+#define MAX_SOCKET_BUFFER_SIZE  512
+
+
+LogManager::LogManager()
+: fd_(-1)
+{
+}
+
+
+LogManager::~LogManager()
+{
+    closeHostLog();
+}
+
+
+int LogManager::openHostLog()
+{
+    int rc;
+
+    do {
+        // Create socket
+        fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
+        if (fd_ == -1) {
+            rc = errno;
+            fprintf(stderr, "Unable to create socket: error [%i] %s\n", rc, strerror(rc));
+            break;
+        }
+
+        // Set non-blocking mode for socket
+        int opt = 1;
+        rc = ioctl(fd_, FIONBIO, &opt);
+        if (rc != 0) {
+            rc = errno;
+            fprintf(stderr, "Unable to set non-blocking mode for log socket: error [%i] %s\n", rc, strerror(rc));
+            break;
+        }
+
+        sockaddr_un sa = { 0 };
+        sa.sun_family = AF_UNIX;
+        constexpr int min_path = sizeof(HOSTLOG_SOCKET_PATH) < sizeof(sa.sun_path) ?
+                                 sizeof(HOSTLOG_SOCKET_PATH) : sizeof(sa.sun_path);
+        memcpy(&sa.sun_path, HOSTLOG_SOCKET_PATH, min_path);
+
+        // Connect to host's log stream via socket.
+        // The owner of the socket (server) is obmc-console service and
+        // we have a dependency on it written in the systemd unit file, but
+        // we can't guarantee that the socket is initialized at the moment.
+        rc = -1;
+        for (int attempt = 0; rc != 0 && attempt < HOSTLOG_SOCKET_ATTEMPTS; ++attempt) {
+            rc = connect(fd_, reinterpret_cast<const sockaddr*>(&sa), sizeof(sa));
+            sleep(HOSTLOG_SOCKET_PAUSE);
+        }
+        if (rc < 0) {
+            rc = errno;
+            fprintf(stderr, "Unable to connect to host log socket: error [%i] %s\n", rc, strerror(rc));
+        }
+    }
+    while (false);
+
+    if (rc != 0)
+        closeHostLog();
+
+    return rc;
+}
+
+
+void LogManager::closeHostLog()
+{
+    if (fd_ != -1) {
+        ::close(fd_);
+        fd_ = -1;
+    }
+}
+
+
+int LogManager::getHostLogFd() const
+{
+    return fd_;
+}
+
+
+int LogManager::handleHostLog()
+{
+    int rc = 0;
+    std::vector<char> buff(MAX_SOCKET_BUFFER_SIZE);
+    size_t readLen = MAX_SOCKET_BUFFER_SIZE;
+
+    // Read all existing data from log stream
+    while (rc == 0 && readLen != 0) {
+        rc = readHostLog(&buff[0], buff.size(), readLen);
+        if (rc == 0 && readLen != 0)
+            storage_.parse(&buff[0], readLen);
+    }
+
+    return rc;
+}
+
+
+int LogManager::flush()
+{
+    int rc;
+
+    if (storage_.empty())
+        return 0; // Nothing to save
+
+    const std::string logFile = prepareLogPath();
+    if (logFile.empty())
+        return EIO;
+
+    rc = storage_.write(logFile.c_str());
+    if (rc != 0)
+        return rc;
+
+    storage_.clear();
+
+    // Non critical tasks, don't check returned status
+    rotateLogFiles();
+
+    return 0;
+}
+
+
+int LogManager::readHostLog(char* buffer, size_t bufferLen, size_t& readLen) const
+{
+    int rc = 0;
+
+    const ssize_t rsz = ::read(fd_, buffer, bufferLen);
+    if (rsz >= 0)
+        readLen = static_cast<size_t>(rsz);
+    else {
+        readLen = 0;
+        if (errno != EAGAIN && errno != EWOULDBLOCK) {
+            rc = errno;
+            fprintf(stderr, "Unable to read host log: error [%i] %s\n", rc, strerror(rc));
+        }
+    }
+
+    return rc;
+}
+
+
+std::string LogManager::prepareLogPath() const
+{
+    // Create path for logs
+    if (::access(loggerConfig.path, F_OK) != 0) {
+        const std::string logPath(loggerConfig.path);
+        const size_t len = logPath.length();
+        size_t pos = 0;
+        while (pos < len - 1) {
+            pos = logPath.find('/', pos + 1);
+            const std::string createPath = logPath.substr(0, pos);
+            if (::mkdir(createPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 && errno != EEXIST) {
+                const int rc = errno;
+                fprintf(stderr, "Unable to create dir %s: error [%i] %s\n", createPath.c_str(), rc, strerror(rc));
+                return std::string();
+            }
+        }
+    }
+
+    // Construct log file name
+    time_t ts;
+    time(&ts);
+    tm lt = { 0 };
+    localtime_r(&ts, &lt);
+    char fileName[64];
+    snprintf(fileName, sizeof(fileName), "/host_%i%02i%02i_%02i%02i%02i.log.gz",
+            lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday,
+            lt.tm_hour, lt.tm_min, lt.tm_sec);
+
+    return std::string(loggerConfig.path) + fileName;
+}
+
+
+int LogManager::rotateLogFiles() const
+{
+    if (loggerConfig.rotationLimit == 0)
+        return 0; // Not applicable
+
+    int rc = 0;
+
+    // Get file list to std::set
+    std::set<std::string> logFiles;
+    DIR* dh = opendir(loggerConfig.path);
+    if (!dh) {
+        rc = errno;
+        fprintf(stderr, "Unable to open directory %s: error [%i] %s\n", loggerConfig.path, rc, strerror(rc));
+        return rc;
+    }
+    dirent *dir;
+    while ((dir = readdir(dh))) {
+        if (dir->d_type != DT_DIR)
+            logFiles.insert(dir->d_name);
+    }
+    closedir(dh);
+
+    // Log file has a name with a timestamp generated by prepareLogPath().
+    // The sorted array of names (std::set) will contain the oldest file on the
+    // top.
+    // Remove oldest files.
+    int filesToRemove = static_cast<int>(logFiles.size()) - loggerConfig.rotationLimit;
+    while (rc == 0 && --filesToRemove >= 0) {
+        std::string fileToRemove = loggerConfig.path;
+        fileToRemove += '/';
+        fileToRemove += *logFiles.begin();
+        if (::unlink(fileToRemove.c_str()) == -1) {
+            rc = errno;
+            fprintf(stderr, "Unable to delete file %s: error [%i] %s\n", fileToRemove.c_str(), rc, strerror(rc));
+        }
+        logFiles.erase(fileToRemove);
+    }
+
+    return rc;
+}
diff --git a/src/log_manager.hpp b/src/log_manager.hpp
new file mode 100644
index 0000000..5dac0f9
--- /dev/null
+++ b/src/log_manager.hpp
@@ -0,0 +1,106 @@
+/**
+ * @brief Log manager.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "log_storage.hpp"
+
+
+/** @class LogManager
+ *  @brief Log manager.
+ *         All functions within this class are not thread-safe.
+ */
+class LogManager
+{
+public:
+    /** @brief Constructor. */
+    LogManager();
+
+    /** @brief Destructor. */
+    ~LogManager();
+
+    /** @brief Open the host's log stream.
+     *
+     *  @return error code, 0 if operation completed successfully
+     */
+    int openHostLog();
+
+    /** @brief Close the host's log stream.
+    */
+    void closeHostLog();
+
+    /** @brief Get file descriptor by host's log stream.
+     *         Descriptor can be used to register it in an external polling manager.
+     *
+     *  @return file descriptor (actually it is an opened socket)
+     */
+    int getHostLogFd() const;
+
+    /** @brief Handle incoming data from host's log stream.
+     *
+     *  @return error code, 0 if operation completed successfully
+     */
+    int handleHostLog();
+
+    /** @brief Flush log storage: save currently collected messages to a file,
+     *         reset the storage and rotate log files.
+     *
+     *  @return error code, 0 if operation completed successfully
+     */
+    int flush();
+
+private:
+    /** @brief Read incoming data from host's log stream.
+     *
+     *  @param[out] buffer - buffer to write incoming data
+     *  @param[in] bufferLen - maximum size of the buffer in bytes
+     *  @param[out] readLen - on output contain number of bytes read from stream
+     *
+     *  @return error code, 0 if operation completed successfully
+     */
+    int readHostLog(char* buffer, size_t bufferLen, size_t& readLen) const;
+
+    /** @brief Prepare the path to save the log.
+     *         Warning: the name is used in function rotateLogFiles(),
+     *                  make sure you don't brake sorting rules.
+     *
+     * @return path to new log file including its name
+     */
+    std::string prepareLogPath() const;
+
+    /** @brief Create path for log files.
+     *
+     *  @return error code, 0 if operation completed successfully
+     */
+    int createLogPath() const;
+
+    /** @brief Rotate log files in the directory.
+     *         Function remove oldest files and keep up to maxFiles_ log files.
+     *
+     *  @return error code, 0 if operation completed successfully
+     */
+    int rotateLogFiles() const;
+
+private:
+    /** @brief Log storage. */
+    LogStorage storage_;
+    /** @brief File descriptor of the input log stream. */
+    int fd_;
+};
diff --git a/src/log_storage.cpp b/src/log_storage.cpp
new file mode 100644
index 0000000..1ada1c3
--- /dev/null
+++ b/src/log_storage.cpp
@@ -0,0 +1,173 @@
+/**
+ * @brief Log storage.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.hpp"
+#include "log_storage.hpp"
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+
+
+LogStorage::LogStorage()
+: last_complete_(true)
+{
+}
+
+
+void LogStorage::parse(const char* data, size_t len)
+{
+    // Split log stream to separate messages.
+    // Stream may not be ended with EOL, so we handle this situation by
+    // last_complete_ flag.
+    size_t pos = 0;
+    while (pos < len) {
+        // Search for EOL ('\n')
+        size_t eol = pos;
+        while (eol < len && data[eol] != '\n')
+            ++eol;
+        const bool eolFound = eol < len;
+        const char* msgText = data + pos;
+        size_t msgLen = (eolFound ? eol : len) - pos;
+
+        // Remove '\r' from the end of message
+        while (msgLen && msgText[msgLen - 1] == '\r')
+            --msgLen;
+
+        // Append message to store
+        if (msgLen)
+            append(msgText, msgLen);
+
+        pos = eol + 1; // Skip '\n'
+        last_complete_ = eolFound;
+    }
+}
+
+
+void LogStorage::append(const char* msg, size_t len)
+{
+    if (!last_complete_) {
+        // The last message is incomplete, add msg as part of it
+        if (!messages_.empty()) {
+            Message& last_msg = *messages_.rbegin();
+            last_msg.text.append(msg, len);
+        }
+    }
+    else {
+        Message new_msg;
+        time(&new_msg.timeStamp);
+        new_msg.text.assign(msg, len);
+        messages_.push_back(new_msg);
+        shrink();
+    }
+}
+
+
+void LogStorage::clear()
+{
+    messages_.clear();
+    last_complete_ = true;
+}
+
+
+bool LogStorage::empty() const
+{
+    return messages_.empty();
+}
+
+
+int LogStorage::write(const char* fileName) const
+{
+    int rc = 0;
+
+    if (empty()) {
+        printf("No messages to write\n");
+        return 0;
+    }
+
+    const gzFile fd = gzopen(fileName, "w");
+    if (fd == Z_NULL) {
+        rc = errno;
+        fprintf(stderr, "Unable to open file %s: error [%i] %s\n",
+                fileName, rc, strerror(rc));
+        return rc;
+    }
+
+    // Write full datetime stamp as the first record
+    const time_t& logStartTime = messages_.begin()->timeStamp;
+    tm localTime = { 0 };
+    localtime_r(&logStartTime, &localTime);
+    char msgText[64];
+    snprintf(msgText, sizeof(msgText),
+             ">>> Log collection started at %02i.%02i.%i %02i:%02i:%02i",
+             localTime.tm_mday, localTime.tm_mon + 1, localTime.tm_year + 1900,
+             localTime.tm_hour, localTime.tm_min, localTime.tm_sec);
+    const Message startMsg = { logStartTime, msgText };
+    rc |= write(fd, startMsg);
+
+    // Write messages
+    for (auto it = messages_.begin(); rc == 0 && it != messages_.end(); ++it)
+        rc |= write(fd, *it);
+
+    rc = gzclose_w(fd);
+    if (rc != Z_OK)
+        fprintf(stderr, "Unable to close file %s: error [%i]\n", fileName, rc);
+
+    return rc;
+}
+
+
+int LogStorage::write(gzFile fd, const Message& msg) const
+{
+    // Convert timestamp to local time
+    tm localTime = { 0 };
+    localtime_r(&msg.timeStamp, &localTime);
+
+    // Write message to the file
+    const int rc = gzprintf(fd, "[ %02i:%02i:%02i ]: %s\n",
+                            localTime.tm_hour,
+                            localTime.tm_min,
+                            localTime.tm_sec,
+                            msg.text.c_str());
+    if (rc <= 0) {
+        fprintf(stderr, "Unable to write file: error [%i]\n", -rc);
+        return EIO;
+    }
+
+    return 0;
+}
+
+
+void LogStorage::shrink()
+{
+    if (loggerConfig.storageSizeLimit) {
+        while (messages_.size() > static_cast<size_t>(loggerConfig.storageSizeLimit))
+            messages_.pop_front();
+    }
+    if (loggerConfig.storageTimeLimit) {
+        // Get time for N hours ago
+        time_t oldestTimeStamp;
+        time(&oldestTimeStamp);
+        oldestTimeStamp -= loggerConfig.storageTimeLimit * 60 * 60;
+        while (!messages_.empty() && messages_.begin()->timeStamp < oldestTimeStamp)
+            messages_.pop_front();
+    }
+}
diff --git a/src/log_storage.hpp b/src/log_storage.hpp
new file mode 100644
index 0000000..b2a6271
--- /dev/null
+++ b/src/log_storage.hpp
@@ -0,0 +1,99 @@
+/**
+ * @brief Log storage.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ctime>
+#include <list>
+#include <string>
+#include <zlib.h>
+
+
+/** @class LogStorage
+ *  @brief Log storage implementation.
+ *         All functions within this class are not thread-safe.
+ */
+class LogStorage
+{
+public:
+    /** @brief Constructor. */
+    LogStorage();
+
+    /** @brief Parse input log stream and append messages to the storage.
+     *
+     *  @param[in] data - pointer to the message buffer
+     *  @param[in] len - length of the buffer in bytes
+     */
+    void parse(const char* data, size_t len);
+
+    /** @brief Clear (reset) storage. */
+    void clear();
+
+    /** @brief Check storage for empty.
+     *
+     *  @return true if storage is empty
+     */
+    bool empty() const;
+
+    /** @brief Save messages from storage to the specified file.
+     *
+     *  @param[in] fileName - path to the file
+     *
+     *  @return error code, 0 if operation completed successfully
+     */
+    int write(const char* fileName) const;
+
+private:
+    /** @struct Message
+     *  @brief Represent log message (single line from host log).
+     */
+    struct Message
+    {
+        /** @brief Timestamp (message creation time). */
+        time_t timeStamp;
+        /** @brief Text of the message. */
+        std::string text;
+    };
+
+    /** @brief Append new message to the storage.
+     *
+     *  @param[in] msg - pointer to the message buffer
+     *  @param[in] len - length of the buffer in bytes
+     */
+    void append(const char* msg, size_t len);
+
+    /** @brief Write message to the file.
+     *
+     *  @param[in] fd - descriptor of the file to write
+     *  @param[in] msg - message to write
+     *
+     *  @return error code, 0 if operation completed successfully
+     */
+    int write(gzFile fd, const Message& msg) const;
+
+    /** @brief Shrink storage by removing oldest messages. */
+    void shrink();
+
+private:
+    /** @brief List of messages. */
+    std::list<Message> messages_;
+    /** @brief Flag to indicate that the last message is incomplete. */
+    bool last_complete_;
+};
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..7f58ef0
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,191 @@
+/**
+ * @brief Host logger service entry point.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.hpp"
+#include "dbus_server.hpp"
+#include "dbus_watch.hpp"
+#include "log_manager.hpp"
+#include <getopt.h>
+#include <cstdio>
+#include <cstdlib>
+#include <climits>
+
+
+// Global logger configuration instance
+Config loggerConfig = {
+    .path = LOG_OUTPUT_PATH,
+    .storageSizeLimit = LOG_STORAGE_SIZE_LIMIT,
+    .storageTimeLimit = LOG_STORAGE_TIME_LIMIT,
+    .flushPeriod = LOG_FLUSH_PERIOD,
+    .rotationLimit = LOG_ROTATION_LIMIT
+};
+
+
+/** @brief Print title with version info. */
+static void printTitle()
+{
+    printf("Host logger service " PACKAGE_VERSION ".\n");
+}
+
+
+/** @brief Print help usage info.
+ *
+ *  @param[in] app - application's file name
+ */
+static void printHelp(const char* app)
+{
+    printTitle();
+    printf("Copyright (c) 2018 YADRO.\n");
+    printf("Usage: %s [options]\n", app);
+    printf("Options (defaults are specified in brackets):\n"
+           "  -p, --path=PATH   Path used to store logs [%s]\n"
+           "Intermediate storage buffer capacity setup:\n"
+           "  -s, --szlimit=N   Store up to N last messages [%i], 0=unlimited\n"
+           "  -t, --tmlimit=N   Store messages for last N hours [%i], 0=unlimited\n"
+           "Flush storage buffer policy:\n"
+           "  -f, --flush=N     Flush logs every N hours [%i]\n"
+           "                    If this option is set to 0 flush will be called at\n"
+           "                    every host state change event from D-Bus.\n"
+           "Log files rotation policy:\n"
+           "  -r, --rotate=N    Store up to N files in the log directory [%i],\n"
+           "                    0=unlimited\n"
+           "Common options:\n"
+           "  -v, --version     Print version and exit\n"
+           "  -h, --help        Print this help and exit\n",
+           loggerConfig.path,
+           loggerConfig.storageSizeLimit,
+           loggerConfig.storageTimeLimit,
+           loggerConfig.flushPeriod,
+           loggerConfig.rotationLimit);
+}
+
+
+/** @brief Get numeric positive value from string argument.
+ *
+ *  @param[in] param - parameter name
+ *  @param[in] arg - parameter text value
+ *
+ *  @return positive numeric value from string argument or -1 on errors
+ */
+static int getNumericArg(const char* param, const char* arg)
+{
+    char* ep = nullptr;
+    const unsigned long val = strtoul(arg, &ep, 0);
+    if (val > INT_MAX || !ep || ep == arg || *ep != 0) {
+        fprintf(stderr, "Invalid %s param: %s, expected 0<=N<=%i\n",
+                param, arg, INT_MAX);
+        return -1;
+    }
+    return static_cast<int>(val);
+}
+
+
+/** @brief Application entry point. */
+int main(int argc, char *argv[])
+{
+    int opt_val;
+    const struct option opts[] = {
+        { "path",    required_argument, 0, 'p' },
+        { "szlimit", required_argument, 0, 's' },
+        { "tmlimit", required_argument, 0, 't' },
+        { "flush",   required_argument, 0, 'f' },
+        { "rotate",  required_argument, 0, 'r' },
+        { "version", no_argument,       0, 'v' },
+        { "help",    no_argument,       0, 'h' },
+        { 0,         0,                 0,  0  }
+    };
+
+    opterr = 0;
+    while ((opt_val = getopt_long(argc, argv, "p:s:t:f:r:vh", opts, NULL)) != -1) {
+        switch (opt_val) {
+            case 'p':
+                loggerConfig.path = optarg;
+                if (*loggerConfig.path != '/') {
+                    fprintf(stderr, "Invalid directory: %s, expected absolute path\n", loggerConfig.path);
+                    return EXIT_FAILURE;
+                }
+                break;
+            case 's':
+                loggerConfig.storageSizeLimit = getNumericArg(opts[optind - 1].name, optarg);
+                if (loggerConfig.storageSizeLimit < 0)
+                    return EXIT_FAILURE;
+                break;
+            case 't':
+                loggerConfig.storageTimeLimit = getNumericArg(opts[optind - 1].name, optarg);
+                if (loggerConfig.storageTimeLimit < 0)
+                    return EXIT_FAILURE;
+                break;
+            case 'f':
+                loggerConfig.flushPeriod = getNumericArg(opts[optind - 1].name, optarg);
+                if (loggerConfig.flushPeriod < 0)
+                    return EXIT_FAILURE;
+                break;
+            case 'r':
+                loggerConfig.rotationLimit = getNumericArg(opts[optind - 1].name, optarg);
+                if (loggerConfig.rotationLimit < 0)
+                    return EXIT_FAILURE;
+                break;
+            case 'v':
+                printTitle();
+                return EXIT_SUCCESS;
+            case 'h':
+                printHelp(argv[0]);
+                return EXIT_SUCCESS;
+            default:
+                fprintf(stderr, "Invalid option: %s\n", argv[optind - 1]);
+                return EXIT_FAILURE;
+        }
+    }
+
+    int rc;
+
+    // Initialize log manager
+    LogManager logManager;
+    rc = logManager.openHostLog();
+    if (rc != 0)
+        return rc;
+
+    // Initialize D-Bus server
+    sdbusplus::bus::bus bus = sdbusplus::bus::new_default();
+    sd_event* event = nullptr;
+    rc = sd_event_default(&event);
+    if (rc < 0) {
+        fprintf(stderr, "Error occurred during the sd_event_default: %i\n", rc);
+        return EXIT_FAILURE;
+    }
+    EventPtr eventPtr(event);
+    bus.attach_event(eventPtr.get(), SD_EVENT_PRIORITY_NORMAL);
+
+    DbusServer dbusMgr(logManager, bus);
+    bus.request_name(HOSTLOGGER_DBUS_IFACE);
+
+    // Initialize D-Bus watcher
+    DbusWatcher dbusWatch(logManager, bus);
+    rc = dbusWatch.initialize();
+    if (rc < 0)
+        return EXIT_FAILURE;
+
+    // D-Bus event processing
+    rc = sd_event_loop(eventPtr.get());
+    if (rc != 0)
+        fprintf(stderr, "Error occurred during the sd_event_loop: %i\n", rc);
+
+    return rc ? rc : -1; // Allways retrun an error code
+}
diff --git a/xyz/openbmc_project/HostLogger.interface.yaml b/xyz/openbmc_project/HostLogger.interface.yaml
new file mode 100644
index 0000000..2c6331f
--- /dev/null
+++ b/xyz/openbmc_project/HostLogger.interface.yaml
@@ -0,0 +1,10 @@
+description: >
+    Implementation of the host log storage service.
+
+methods:
+    - name: Flush
+      description: >
+        Flush currently collected logs to the file
+        and switch to the next one.
+      errors:
+        - xyz.openbmc_project.Common.File.Error.Write