Add ElogWithMetaDataCapture class

This callback class will create an error log with
exactly 1 metadata field, which takes a string.  This
metadata field will be filled in with the property paths,
names, and values of the properties that passed the
condition checks that caused the callback to be called
in the first place.

Tested: Ran with YAML rules that used this callback and
        checked that everything worked as specified.

Change-Id: Ib37206c63385939c583a09e7ba979d6e016691f6
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/src/elog.hpp b/src/elog.hpp
index c3b0d02..3f8ae3b 100644
--- a/src/elog.hpp
+++ b/src/elog.hpp
@@ -12,6 +12,25 @@
 namespace monitoring
 {
 
+/** @struct ToString
+ * @brief Convert numbers to strings
+ */
+template <typename T> struct ToString
+{
+    static auto op(T&& value)
+    {
+        return std::to_string(std::forward<T>(value));
+    }
+};
+
+template <> struct ToString<std::string>
+{
+    static auto op(const std::string& value)
+    {
+        return value;
+    }
+};
+
 /** @class ElogBase
  *  @brief Elog callback implementation.
  *
@@ -89,6 +108,103 @@
 
 };
 
+
+/**
+ * @class ElogWithMetadataCapture
+ *
+ * @brief A callback class that will save the paths, names, and
+ *       current values of certain properties in the metadata of the
+ *       error log it creates.
+ *
+ * The intended use case of this class is to create an error log with
+ * metadata that includes the property names and values that caused
+ * the condition to issue this callback.  When the condition ran, it had
+ * set the pass/fail field on each property it checked in the properties'
+ * entries in the Storage array.  This class then looks at those pass/fail
+ * fields to see which properties to log.
+ *
+ * Note that it's OK if different conditions and callbacks share the same
+ * properties because everything runs serially, so another condition can't
+ * touch those pass/fail fields until all of the first condition's callbacks
+ * are done.
+ *
+ * This class requires that the error log created only have 1 metadata field,
+ * and it must take a string.
+ *
+ * @tparam errorType - Error log type
+ * @tparam metadataType - The metadata to use
+ * @tparam propertyType - The data type of the captured properties
+ */
+template<typename errorType,
+         typename metadataType,
+         typename propertyType>
+class ElogWithMetadataCapture : public IndexedCallback
+{
+    public:
+        ElogWithMetadataCapture() = delete;
+        ElogWithMetadataCapture(const ElogWithMetadataCapture&) = delete;
+        ElogWithMetadataCapture(ElogWithMetadataCapture&&) = default;
+        ElogWithMetadataCapture& operator=(
+                const ElogWithMetadataCapture&) = delete;
+        ElogWithMetadataCapture& operator=(
+                ElogWithMetadataCapture&&) = default;
+        virtual ~ElogWithMetadataCapture() = default;
+        explicit ElogWithMetadataCapture(
+                const PropertyIndex& index) :
+            IndexedCallback(index) {}
+
+        /**
+         * @brief Callback interface implementation that
+         *        creates an error log
+         */
+        void operator()(Context ctx) override
+        {
+            auto data = captureMetadata();
+
+            phosphor::logging::report<errorType>(
+                    metadataType(data.c_str()));
+        }
+
+    private:
+
+        /**
+         * @brief Builds a metadata string with property information
+         *
+         * Finds all of the properties in the index that have
+         * their condition pass/fail fields (get<1>(storage))
+         * set to true, and then packs those paths, names, and values
+         * into a metadata string that looks like:
+         *
+         * |path1:name1=value1|path2:name2=value2|...
+         *
+         * @return The metadata string
+         */
+        std::string captureMetadata()
+        {
+            std::string metadata{'|'};
+
+            for (const auto& n : index)
+            {
+                const auto& storage = std::get<2>(n.second).get();
+                const auto& result = std::get<1>(storage);
+
+                if (!result.empty() && any_ns::any_cast<bool>(result))
+                {
+                    const auto& path = std::get<0>(n.first).get();
+                    const auto& propertyName = std::get<2>(n.first).get();
+                    auto value = ToString<propertyType>::op(
+                            any_ns::any_cast<propertyType>(
+                                    std::get<0>(storage)));
+
+                    metadata += path + ":" + propertyName +
+                            '=' + value + '|';
+                }
+            }
+
+            return metadata;
+        };
+};
+
 /** @brief Argument type deduction for constructing Elog instances.
  *
  *  @tparam T - Error log type