Error / Event Log Framework

Framework to create an error log entry based on information
from the journal log.
1. elog.yaml - User defined reason codes for error scenarios that
   include description, priority level, and desired metadata fields.
2. elog_parser.py - Parser for the yaml file
3. elog-gen.hpp - Auto-generated file based created by the parser
   that includes the error log structures. Including for now for ref.
4. elog.hpp - Error log template to create (log a journal log
   entry with the predefined error log information).

Usage: elog<RC>(NAME(value), prev_entry<NAME>()...)
  where RC - Reason code, NAME - metadata name
  being logged to a journal entry, prev_entry - A Metadata entry that
  has previously being logged to a journal entry.
Example:
  log<level::DEBUG>(
        msg("Info trace to log filename"),
        entry(file_not_found::file_name::str, my_filename));
  elog<file_not_found>(file_not_found::errnum(2),
        file_not_found::file_path("/tmp/"),
        prev_entry<file_not_found::file_name>());

Change-Id: Ic51cee80b58e341c071c366c5e2146fd6694012c
Signed-off-by: Adriana Kobylak <anoo@us.ibm.com>
Signed-off-by: Andrew Geissler <andrewg@us.ibm.com>
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..0dd561a
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,8 @@
+bin_PROGRAMS = logging-test
+logging_test_SOURCES = test/logging_test.cpp
+
+# systemd required for journal interfaces
+logging_test_LDFLAGS = $(SYSTEMD_LIBS)
+
+# export these headers
+include_HEADERS = log.hpp elog.hpp
\ No newline at end of file
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..3f01c2e
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,25 @@
+AC_PREREQ([2.69])
+AC_INIT([phosphor-logging], [1.0], [https://github.com/openbmc/phosphor-logging/issues])
+
+AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign dist-xz])
+AM_PROG_AR()
+AM_SILENT_RULES([yes])
+
+# Checks for programs
+AC_PROG_CXX
+AX_CXX_COMPILE_STDCXX_14([noext])
+AX_APPEND_COMPILE_FLAGS([-fpic -Wall -Werror], [CXXFLAGS])
+AC_PROG_INSTALL #Checks/sets the install variable to be used
+AC_PROG_MAKE_SET
+
+# Surpress the --with-libtool-sysroot error
+LT_INIT
+
+PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221])
+
+AC_CHECK_HEADER(systemd/sd-journal.h, ,[AC_MSG_ERROR([Could not find \
+systemd/sd-journal.h...systemd developement package required])])
+
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
\ No newline at end of file
diff --git a/elog-gen.hpp b/elog-gen.hpp
new file mode 100644
index 0000000..52d8d9b
--- /dev/null
+++ b/elog-gen.hpp
@@ -0,0 +1,59 @@
+// this file was autogenerated.  do not edit.
+#pragma once
+
+#include <tuple>
+#include <type_traits>
+#include "log.hpp"
+
+namespace phosphor
+{
+
+namespace logging
+{
+
+namespace _file_not_found
+{
+struct errnum
+{
+    static constexpr auto str = "ERRNO=%d";
+    using type = std::tuple<std::decay_t<decltype(str)>,int>;
+    explicit constexpr errnum(int a) : _entry(entry(str, a)) {};
+    type _entry;
+};
+
+struct file_path
+{
+    static constexpr auto str = "FILE_PATH=%s";
+    using type = std::tuple<std::decay_t<decltype(str)>,const char*>;
+    explicit constexpr file_path(const char *a) : _entry(entry(str,a)) {};
+    type _entry;
+};
+
+struct file_name
+{
+   static constexpr auto str = "FILE_NAME=%s";
+   using type = std::tuple<std::decay_t<decltype(str)>,const char*>;
+   explicit constexpr file_name(const char *a) : _entry(entry(str,a)) {};
+   type _entry;
+};
+
+} // namespace _file_not_found
+
+struct file_not_found
+{
+    static constexpr auto err_code = "xyz.openbmc_project.logging.FILE_NOT_FOUND_ERROR";
+    static constexpr auto err_msg = "A required file was not found";
+    static constexpr auto L = level::INFO;
+
+    using errnum = _file_not_found::errnum;
+    using file_path = _file_not_found::file_path;
+    using file_name = _file_not_found::file_name;
+
+    using metadata_types = std::tuple<errnum, file_path, file_name>;
+};
+
+
+
+} // namespace logging
+
+} // namespace phosphor
diff --git a/elog.hpp b/elog.hpp
new file mode 100644
index 0000000..090b5e9
--- /dev/null
+++ b/elog.hpp
@@ -0,0 +1,106 @@
+#pragma once
+
+#include <tuple>
+#include <utility>
+#include "elog-gen.hpp"
+
+namespace phosphor
+{
+
+namespace logging
+{
+
+/**
+ * @brief Structure used by callers to indicate they want to use the last value
+ *        put in the journal for input parameter.
+*/
+template <typename T>
+struct prev_entry
+{
+    using type = T;
+};
+
+
+
+namespace details
+{
+/**
+ * @brief Used to return the generated tuple for the error code meta data
+ *
+ * The prev_entry (above) and deduce_entry_type structures below are used
+ * to verify at compile time the required parameters have been passed to
+ * the elog interface and then to forward on the appropriate tuple to the
+ * log interface.
+ */
+template <typename T>
+struct deduce_entry_type
+{
+
+    using type = T;
+    auto get() { return value._entry; }
+
+    T value;
+};
+
+/**
+ * @brief Used to return an empty tuple for prev_entry parameters
+ *
+ * This is done so we can still call the log() interface with the variable
+ * arg parameters to elog.  The log() interface will simply ignore the empty
+ * tuples which is what we want for prev_entry parameters.
+ */
+template <typename T>
+struct deduce_entry_type<prev_entry<T>>
+{
+    using type = T;
+    auto get() { return std::make_tuple(); }
+
+    prev_entry<T> value;
+};
+
+/**
+ * @brief Typedef for above structure usage
+ */
+template <typename T> using deduce_entry_type_t =
+        typename deduce_entry_type<T>::type;
+
+} // namespace details
+
+/** @fn commit()
+ *  @brief Create an error log entry based on journal
+ *          entry with a specified MSG_ID
+ *  @tparam E - Error log struct
+ */
+template <typename E>
+void commit()
+{
+    // TODO placeholder function call
+    // to call the new error log server to create
+    // an error log on the BMC flash
+    // dbus_commit(E.msgid); // call server
+}
+
+/** @fn elog()
+ *  @brief Create a journal log entry based on predefined
+ *          error log information
+ *  @tparam T - Error log type
+ *  @param[in] i_args - Metadata fields to be added to the journal entry
+ */
+template <typename T, typename ...Args>
+void elog(Args... i_args)
+{
+    // Validate the caller passed in the required parameters
+    static_assert(std::is_same<typename T::metadata_types,
+                               std::tuple<
+                               details::deduce_entry_type_t<Args>...>>
+                               ::value,
+                  "You are not passing in required arguments for this error");
+
+    log<T::L>(T::err_msg,
+              details::deduce_entry_type<Args>{i_args}.get()...);
+}
+
+} // namespace logging
+
+} // namespace phosphor
+
diff --git a/elog.yaml b/elog.yaml
new file mode 100644
index 0000000..79307a3
--- /dev/null
+++ b/elog.yaml
@@ -0,0 +1,13 @@
+---
+SW:
+    FILE_NOT_FOUND_ERROR:
+        msg: "A required file was not found"
+        level: INFO
+        meta_i: [ ERRNUM ]
+        meta_s: [ FILE_PATH, FILE_NAME ]
+    GETSCOM_ERROR:
+        msg: "Getscom call failed"
+        level: ERR
+        meta_i: [ DEV_ADDR,
+                  DEV_ID ]
+        meta_s: [ DEV_NAME ]
diff --git a/elog_parser.py b/elog_parser.py
new file mode 100755
index 0000000..4896700
--- /dev/null
+++ b/elog_parser.py
@@ -0,0 +1,16 @@
+#!/usr/bin/python -u
+
+import uuid
+import yaml
+
+with open('elog.yaml') as f:
+    ifile = yaml.safe_load(f)
+
+with open('elog-gen.hpp', 'w') as ofile:
+    ofile.write('namespace phosphor\n{\n\n')
+    ofile.write('namespace logging\n{\n\n')
+
+    # TBD
+
+if __name__ == '__main__':
+     print "TODO\n"
diff --git a/test/logging_test.cpp b/test/logging_test.cpp
new file mode 100644
index 0000000..2b75854
--- /dev/null
+++ b/test/logging_test.cpp
@@ -0,0 +1,46 @@
+#include <iostream>
+#include "elog.hpp"
+#include "log.hpp"
+
+using namespace phosphor;
+using namespace logging;
+
+int main()
+{
+    std::cout << "hello world!\n";
+
+    // Simple elog test
+    const char *test_string = "/tmp/test_string/";
+    elog<file_not_found>(file_not_found::errnum(1),
+                         file_not_found::file_path(test_string),
+                         file_not_found::file_name("elog_test_1.txt"));
+
+    log<level::DEBUG>("Info trace to log file path",
+                      entry(file_not_found::file_path::str,
+                            "/tmp/log_file_test/"));
+
+    // pass parameter and previous_entry
+    elog<file_not_found>(file_not_found::errnum(2),
+                         prev_entry<file_not_found::file_path>(),
+                         file_not_found::file_name("elog_test_2.txt"));
+
+    // Simple test to prove we fail to compile due to missing param
+    //elog<file_not_found>(file_not_found::errnum(1),
+    //                     file_not_found::file_path("test"));
+
+    // Simple test to prove we fail to compile due to invalid param
+    //elog<file_not_found>(file_not_found::errnum(1),
+    //                     file_not_found::file_path("test"),
+    //                     file_not_found::file_name(1));
+
+    // Log tests
+    log<level::DEBUG>("Simple Example");
+
+    const char *file_name = "HELLO.txt";
+    int number = 0xFEFE;
+
+    log<level::DEBUG>("THIS IS A PHOSPHOR LOGGING TEST",
+            entry("FILE_NAME=%s_%x", file_name, number));
+
+    return 0;
+}