Pārlūkot izejas kodu

Merge branch 'better_properties'

* better_properties:
  Clean up properties
  Start adding default configurations to appenders. - File Appender
  Export properties
  Do some TLC on to_json...
  Support int, bool. Use variant instead of tagged psuedo-union
  Start fleshing out file_appender.
Sam Jaffe 6 gadi atpakaļ
vecāks
revīzija
662eafbd65

+ 34 - 19
include/logger/detail/to_json.h

@@ -7,27 +7,42 @@
 
 #pragma once
 
-#if defined(LOGGING_USE_JSON)
+#include <iostream>
+
 #if defined(LOGGING_JSON_SJAFFE)
-#include "json/json.hpp"
-namespace logging {
-  typedef json::value json;
-}
-#elif defined(LOGGING_JSON_VALUE)
-#include "json/json_fwd.h"
-namespace logging {
-  typedef Json::Value json;
-}
+#  include "json/json.hpp"
+#endif
+#if defined(LOGGING_JSON_VALUE)
+#  include <memory>
+#  include "json/json.h"
 #endif
+
 namespace logging { namespace detail {
+  template <typename T> std::string to_string(T const & obj);
+
+// TODO: Establish bindings
+#if defined(LOGGING_JSON_SJAFFE)
+  template <typename T, typename S>
+  void to_stream(json::binder::visitor<T, S> const & visitor,
+                 std::ostream & out) {
+    json::parser::write(visitor, out);
+  }
+  
+  void to_stream(json::value const & obj, std::ostream & os) {
+    json::parser::write(obj, os);
+  }
+#endif
+
+#if defined(LOGGING_JSON_VALUE)
+  void to_stream(Json::Value const & obj, std::ostream & os) {
+    Json::StreamWriterBuilder build;
+    std::unique_ptr<Json::StreamWriter> ptr(build.newStreamWriter());
+    return ptr->write(obj, &os);
+  }
+#endif
+  
   template <typename T>
-  json to_json(T const & obj);
+  std::string to_json(T const & obj, bool compact = false) {
+    return to_string(obj);
+  }
 } }
-#else
-namespace logging {
-  typedef void json;
-}
-namespace logging { namespace detail {
-  template <typename T> void to_json(T const &) {}
-} }
-#endif

+ 33 - 6
include/logger/properties.h

@@ -12,18 +12,45 @@
 #include <string>
 #include <vector>
 
+#include "../../../types/variant/variant.hpp"
+
 namespace logging {
+  struct properties;
+  using object_t = std::map<std::string, properties>;
+  using array_t = std::vector<properties>;
+
   struct properties {
-    operator std::string const &() const;
-    properties const & operator[](std::size_t idx) const;
+    properties mergedWith(properties const & other) const;
+    
+    bool contains(std::string const & key) const;
+    bool contains(std::size_t idx) const;
+    
+    object_t const & object() const;
+    array_t const & array() const;
+
     properties const & operator[](std::string const & key) const;
+    properties const & operator[](char const * key) const {
+      return operator[](std::string(key));
+    }
+    properties const & operator[](std::size_t idx) const;
+    operator std::string const &() const;
+    std::string const & str() const;
+    operator int() const;
+    operator bool() const;
 
-    const enum { STRING, OBJECT, ARRAY } type;
-    const std::map<std::string, properties> obj;
-    const std::vector<properties> array;
-    const std::string value;
+    variant<object_t, array_t, std::string, int, bool> data;
   };
   
+  namespace property {
+    inline properties _obj(object_t const& m) { return {m}; }
+    inline properties _arr(array_t const& l) { return {l}; }
+    inline properties _v(std::string const & t) { return {t}; }
+    // [char const *] will cast to [int] over [std::string const &]
+    inline properties _v(char const * t) { return {std::string(t)}; }
+    inline properties _v(int t) { return {t}; }
+    inline properties _v(bool t) { return {t}; }
+  }
+  
   extern properties const DEFAULT_APPENDER_SCHEMA;
   extern properties const DEFAULT_LOGGER_SCHEMA;
 }

+ 1 - 9
include/logger/wrapper_object.h

@@ -23,12 +23,10 @@ namespace logging { namespace detail {
       return os << obj.to_string_(obj.ptr_);
     }
     template <typename> static std::string to_string_impl(void * ptr);
-    template <typename> static json to_json_impl(void * ptr);
 
   private:
     void * ptr_;
     std::string (*to_string_)(void*);
-    json (*to_json_)(void*);
   };
   
   
@@ -37,16 +35,10 @@ namespace logging { namespace detail {
     return to_string(*static_cast<T*>(ptr));
   }
   
-  template <typename T>
-  json object::to_json_impl(void * ptr) {
-    return to_json(*static_cast<T*>(ptr));
-  }
-  
   template <typename T>
   object::object(T & object)
   : ptr_(&object),
-  to_string_(&object::to_string_impl<T>),
-  to_json_(&object::to_json_impl<T>) {
+  to_string_(&object::to_string_impl<T>) {
     
   }
 } }

+ 1 - 1
logger.xcodeproj/xcuserdata/samjaffe.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -7,7 +7,7 @@
 		<key>logger_test.xcscheme_^#shared#^_</key>
 		<dict>
 			<key>orderHint</key>
-			<integer>46</integer>
+			<integer>44</integer>
 		</dict>
 		<key>logging.xcscheme</key>
 		<dict>

+ 7 - 9
src/log_manager.cxx

@@ -51,11 +51,11 @@ manager_impl::manager_impl() {
 std::shared_ptr<logger_impl>
 manager_impl::get_logger(properties const & props) {
   auto out = std::make_shared<logger_impl>();
-  if (props.type == properties::OBJECT) {
+  if (props.data.is<object_t>()) {
     // TODO include settings on the parent
     out->impls.push_back(appenders.at(props["ref"]));
-  } else if (props.type == properties::ARRAY) {
-    for (auto & part : props.array) {
+  } else if (props.data.is<array_t>()) {
+    for (auto & part : props.array()) {
       out->impls.push_back(appenders.at(part["ref"]));
     }
   } else {
@@ -75,7 +75,7 @@ static bool is_layout(std::string const & str) {
 
 static p_layout load_layout(std::string const & source,
                             properties const & props) {
-  for (auto & layout : props.obj) {
+  for (auto & layout : props.object()) {
     if (is_layout(layout.first)) {
       return layouts::instance().get(layout.first, layout.second);
     }
@@ -85,15 +85,14 @@ static p_layout load_layout(std::string const & source,
 
 template <typename T>
 void inject_log_level(properties const & props, T & val) {
-  if (props.obj.count("level")) {
+  if (props.contains("level")) {
     val.min_log_level = level_from_string(props["level"]);
   }
 }
 
 void manager_impl::configure_appenders(properties const & props) {
   // TODO: Support multiple File loggers, etc.
-  expects(props["appenders"].type == properties::OBJECT);
-  for (auto & app : props["appenders"].obj) {
+  for (auto & app : props["appenders"].object()) {
     auto pair = std::make_pair(load_appender(app.first, app.second),
                                load_layout(app.first, app.second));
     inject_log_level(app.second, *pair.first);
@@ -102,8 +101,7 @@ void manager_impl::configure_appenders(properties const & props) {
 }
 
 void manager_impl::configure_loggers(properties const & props) {
-  expects(props["loggers"].type == properties::OBJECT);
-  for (auto & log : props["loggers"].obj) {
+  for (auto & log : props["loggers"].object()) {
     auto plog = get_logger(log.second["appenders"]);
     inject_log_level(log.second, *plog);
     loggers[log.first] = plog;

+ 1 - 10
src/loggers/console_appender.cxx

@@ -16,15 +16,6 @@
 
 using namespace logging;
 
-static std::string validate_or_throw(logging::properties const& props) {
-  expects(props.type == properties::OBJECT, "expected properties to be a map");
-  auto it = props.obj.find("target");
-  expects(it != props.obj.end(), "expected a console target entry");
-  expects(it->second.type == properties::STRING,
-          "expected properties[\"target\"] to be a string");
-  return it->second.value;
-}
-
 class console_appender : public appender {
 public:
   static std::shared_ptr<appender> create(properties const& props);
@@ -39,7 +30,7 @@ private:
 };
 
 std::shared_ptr<appender> console_appender::create(properties const& props) {
-  const std::string target = validate_or_throw(props);
+  const std::string target = props["target"];
   if (target == "SYSTEM_OUT") {
     return std::make_shared<console_appender>(std::cout);
   } else if (target == "SYSTEM_ERR") {

+ 1 - 1
src/loggers/default_layout.cxx

@@ -23,7 +23,7 @@ std::shared_ptr<layout> default_layout::create(properties const &) {
 }
 
 std::string default_layout::format(logpacket const & pkt) const {
-  return pkt.message.str();
+  return pkt.message.str() + "\n";
 }
 
 namespace {

+ 34 - 7
src/loggers/file_appender.cxx

@@ -16,28 +16,55 @@
 
 using namespace logging;
 
+namespace logging {
+  level level_from_string(std::string const & value);
+}
+
 class file_appender : public appender {
 public:
   static std::shared_ptr<appender> create(properties const&);
   
-  explicit file_appender(const std::string& fname);
+  explicit file_appender(const std::string& fname, bool append);
   
+  void write(std::string const & msg) override;
   void flush() override;
 private:
+  bool flush_immediately_{false};
   std::ofstream out_;
 };
 
-std::shared_ptr<appender> file_appender::create(properties const&) {
-  return std::shared_ptr<appender>(); // TODO
+using namespace logging::property;
+properties const DEFAULT_FILE_APPENDER{_obj({
+  {"immediateFlush", _v(true)},
+  {"threshold", _v("ERROR")},
+  {"filename", {}}, // Will throw if accessed
+  {"fileAppend", _v(true)}/*,
+  {"bufferedIO", _v(false)},
+  {"bufferSize", _v(8192)}*/
+})};
+
+std::shared_ptr<appender> file_appender::create(properties const & props) {
+  properties const actual = DEFAULT_FILE_APPENDER.mergedWith(props);
+  file_appender app(actual["filename"], actual["fileAppend"]);
+  app.flush_immediately_ = actual["immediateFlush"];
+  app.min_log_level = level_from_string(actual["threshold"]);
+  return std::make_shared<file_appender>(std::move(app));
 }
 
-file_appender::file_appender(const std::string& fname)
-: out_(fname) {}
+static std::ios_base::openmode mode(bool append) {
+  return (append ? std::ios_base::app : 0) | std::ios_base::out;
+}
+
+file_appender::file_appender(const std::string& fname, bool append)
+: out_(fname, mode(append)) {}
 
-void file_appender::flush() {
-  out_.flush();
+void file_appender::write(std::string const & msg) {
+  out_ << msg;
+  if (flush_immediately_) { flush(); }
 }
 
+void file_appender::flush() { out_.flush(); }
+
 namespace {
   bool _ = appenders::instance().bind("File", file_appender::create);
 }

+ 76 - 39
src/loggers/properties.cxx

@@ -18,19 +18,8 @@
  "PatternLayout"
  }
  */
+using namespace logging::property;
 namespace logging {
-  namespace {
-    properties _property(std::map<std::string, properties> const& m) {
-      return properties{properties::OBJECT, m, {}, {}};
-    }
-//    properties _list(std::vector<properties> const& l) {
-//      return properties{properties::ARRAY, {}, l, {}};
-//    }
-    properties _value(std::string const& s) {
-      return properties{properties::STRING, {}, {}, s};
-    }
-  }
-  
   /*
    * %d{%h:%M:%s.%_ms} [%t] %-5.5p %.36c - %m%n
    *
@@ -46,45 +35,93 @@ namespace logging {
   extern properties const DEFAULT_APPENDER_SCHEMA;
   extern properties const DEFAULT_LOGGER_SCHEMA;
   
-  static char const * const DEFAULT_PATTERN =
-    "%d{%h:%M:%s.%_ms} [%t] %-5.5p %.36c - %m%n";
-
-  properties const DEFAULT_APPENDER_SCHEMA{_property({
-    {"appenders", _property({
-      {"Console", _property({
-        {"target", _value("SYSTEM_OUT")},
-        {"PatternLayout", _property({
-          {"pattern", _value(DEFAULT_PATTERN)}
+  properties const DEFAULT_APPENDER_SCHEMA{_obj({
+    {"appenders", _obj({
+      {"Console", _obj({
+        {"target", _v("SYSTEM_OUT")},
+        {"PatternLayout", _obj({
+          {"pattern", _v("%d{%h:%M:%s.%_ms} [%t] %-5.5p %.36c - %m%n")}
         })}
       })}
     })}
   })};
-  
-  properties const DEFAULT_LOGGER_SCHEMA{_property({
-    {"loggers", _property({
-      {"root", _property({
-        {"level", _value("Error")},
-        {"appenders", _property({
-          {"ref", _value("Console")}
+    
+  properties const DEFAULT_LOGGER_SCHEMA{_obj({
+    {"loggers", _obj({
+      {"root", _obj({
+        {"level", _v("Error")},
+        {"appenders", _obj({
+          {"ref", _v("Console")}
         })}
       })}
     })}
   })};
   
-  properties::operator std::string const &() const {
-    expects(type == STRING, invalid_property_type, "expected STRING");
-    return value;
+  static properties mergeKey(std::string const & key,
+                             properties const & defVal,
+                             properties const & other) {
+    return other.contains(key) ? defVal.mergedWith(other[key]) : defVal;
+  }
+  
+  properties properties::mergedWith(properties const & other) const {
+    if (data.is<object_t>()) {
+      properties out;
+      for (auto & pair : object()) {
+        auto & to = out.data.get<object_t>()[pair.first];
+        to = mergeKey(pair.first, pair.second, other);
+      }
+      return out;
+    } else {
+      return other.data.valid() ? other : *this;
+    }
+  }
+  
+  bool properties::contains(std::string const & key) const {
+    return data.is<object_t>() && object().count(key);
+  }
+  
+  bool properties::contains(std::size_t idx) const {
+    return data.is<array_t>() && array().size() < idx;
+  }
+  
+  object_t const & properties::object() const {
+    expects(data.is<object_t>(), invalid_property_type, "expected OBJECT");
+    return data.get<object_t>();
   }
+  
+  array_t const & properties::array() const {
+    expects(data.is<array_t>(), invalid_property_type, "expected ARRAY");
+    return data.get<array_t>();
+  }
+
+  properties const & properties::operator[](std::string const & key) const {
+    expects(data.is<object_t>(), invalid_property_type, "expected OBJECT");
+    expects(contains(key), missing_property, "Missing key: " + key);
+    return object().at(key);
+  }
+
   properties const & properties::operator[](std::size_t idx) const {
-    expects(type == ARRAY, invalid_property_type, "expected ARRAY");
-    expects(idx < array.size(), missing_property,
+    expects(data.is<array_t>(), invalid_property_type, "expected ARRAY");
+    expects(contains(idx), missing_property,
             "Out of bounds: " + std::to_string(idx));
-    return array.at(idx);
+    return array().at(idx);
   }
-  properties const & properties::operator[](std::string const & key) const {
-    expects(type == OBJECT, invalid_property_type, "expected OBJECT");
-    expects(obj.find(key) != obj.end(), missing_property,
-            "Missing key: " + key);
-    return obj.at(key);
+  
+  properties::operator std::string const &() const {
+    expects(data.is<std::string>(), invalid_property_type, "expected STRING");
+    return data.get<std::string>();
+  }
+  
+  properties::operator int() const {
+    expects(data.is<int>(), invalid_property_type, "expected INT");
+    return data.get<int>();
   }
+  
+  properties::operator bool() const {
+    expects(data.is<bool>(), invalid_property_type, "expected BOOL");
+    return data.get<bool>();
+  }
+  
+  std::string const & properties::str() const { return *this; }
+
 }

+ 41 - 52
test/test_properties.cxx

@@ -8,90 +8,79 @@
 #include "logger/properties.h"
 
 using logging::properties;
-
-namespace {
-  properties _property(std::map<std::string, properties> const& m) {
-    return properties{properties::OBJECT, m, {}, {}};
-  }
-  properties _list(std::vector<properties> const& l) {
-    return properties{properties::ARRAY, {}, l, {}};
-  }
-  properties _value(std::string const& s) {
-    return properties{properties::STRING, {}, {}, s};
-  }
-}
+using namespace logging::property;
 
 extern properties const APPENDER_LEVEL_PROPERTY_SCHEMA;
 extern properties const LOGGER_LEVEL_PROPERTY_SCHEMA;
 extern properties const MIN_PROPERTY_SCHEMA;
 extern properties const MULTIPLEX_PROPERTY_SCHEMA;
 
-properties const MIN_PROPERTY_SCHEMA{_property({
-  {"configuration", _property({
-    {"appenders", _property({
-      {"Mock", _property({
-        {"MockLayout", _value("")}
+properties const MIN_PROPERTY_SCHEMA{_obj({
+  {"configuration", _obj({
+    {"appenders", _obj({
+      {"Mock", _obj({
+        {"MockLayout", _v("")}
       })}
     })},
-    {"loggers", _property({
-      {"root", _property({
-        {"appenders", _property({
-          {"ref", _value("Mock")}
+    {"loggers", _obj({
+      {"root", _obj({
+        {"appenders", _obj({
+          {"ref", _v("Mock")}
         })}
       })}
     })}
   })}
 })};
 
-properties const APPENDER_LEVEL_PROPERTY_SCHEMA{_property({
-  {"configuration", _property({
-    {"appenders", _property({
-      {"Mock", _property({
-        {"level", _value("Warning")},
-        {"MockLayout", _value("")}
+properties const APPENDER_LEVEL_PROPERTY_SCHEMA{_obj({
+  {"configuration", _obj({
+    {"appenders", _obj({
+      {"Mock", _obj({
+        {"level", _v("Warning")},
+        {"MockLayout", _v("")}
       })}
     })},
-    {"loggers", _property({
-      {"root", _property({
-        {"appenders", _property({
-          {"ref", _value("Mock")}
+    {"loggers", _obj({
+      {"root", _obj({
+        {"appenders", _obj({
+          {"ref", _v("Mock")}
         })}
       })}
     })}
   })}
 })};
 
-properties const LOGGER_LEVEL_PROPERTY_SCHEMA{_property({
-  {"configuration", _property({
-    {"appenders", _property({
-      {"Mock", _property({
-        {"level", _value("Warning")},
-        {"MockLayout", _value("")}
+properties const LOGGER_LEVEL_PROPERTY_SCHEMA{_obj({
+  {"configuration", _obj({
+    {"appenders", _obj({
+      {"Mock", _obj({
+        {"level", _v("Warning")},
+        {"MockLayout", _v("")}
       })}
     })},
-    {"loggers", _property({
-      {"root", _property({
-        {"level", _value("Error")},
-        {"appenders", _property({
-          {"ref", _value("Mock")}
+    {"loggers", _obj({
+      {"root", _obj({
+        {"level", _v("Error")},
+        {"appenders", _obj({
+          {"ref", _v("Mock")}
         })}
       })}
     })}
   })}
 })};
 
-properties const MULTIPLEX_PROPERTY_SCHEMA{_property({
-  {"configuration", _property({
-    {"appenders", _property({
-      {"Mock", _property({
-        {"MockLayout", _value("")}
+properties const MULTIPLEX_PROPERTY_SCHEMA{_obj({
+  {"configuration", _obj({
+    {"appenders", _obj({
+      {"Mock", _obj({
+        {"MockLayout", _v("")}
       })}
     })},
-    {"loggers", _property({
-      {"root", _property({
-        {"appenders", _list({
-          _property({{"ref", _value("Mock")}}),
-          _property({{"ref", _value("Mock")}})
+    {"loggers", _obj({
+      {"root", _obj({
+        {"appenders", _arr({
+          _obj({{"ref", _v("Mock")}}),
+          _obj({{"ref", _v("Mock")}})
         })}
       })}
     })}