Переглянути джерело

Merge branch 'deferred_formatting'

* deferred_formatting:
  Clean up some duplicate checks
  Collate objects into a message struct that can be lazily handled.
  Add tests and fix things to get support for log level in config
  Switch to using an enum class for log_level
  Cleanup code a little bit
  Support ADL
  Encapsulate argument vector in an object that can deduce things.
  Separate out date parsing code into its own file
  Clean up excess code in format
Sam Jaffe 6 роки тому
батько
коміт
1b09b2fb3e

+ 18 - 38
include/logger/c_logger.h

@@ -25,6 +25,14 @@
 
 #define VA_LOG(level) VA_LOGN(level, c_logger::LOGF_MAX_SIZE)
 
+#define MAKE_LOGGER_FMT(lvl)                                        \
+  inline void lvl##f(char const * fmt, ...) { VA_LOG(level::lvl); } \
+  inline void lvl##nf(size_t n, char const * fmt, ...) {            \
+    VA_LOGN(level::lvl, n);                                         \
+  }                                                                 \
+  inline void lvl(char const * msg) { log(level::lvl, msg); }       \
+  inline void lvl(std::string const & msg) { log(level::lvl, msg); }
+
 namespace logging {
   class logger_impl;
   
@@ -32,43 +40,16 @@ namespace logging {
   public:
     static const size_t LOGF_MAX_SIZE = 2048;
   public:
-    inline void tracef(char const* fmt, ...) { VA_LOG(LTRACE); }
-    inline void tracenf(size_t n, char const* fmt, ...) { VA_LOGN(LTRACE, n); }
-    inline void trace(char const* msg) { log(LTRACE, msg); }
-    inline void trace(std::string const& msg) { log(LTRACE, msg); }
-    
-    inline void debugf(char const* fmt, ...) { VA_LOG(LDEBUG); }
-    inline void debugnf(size_t n, char const* fmt, ...) { VA_LOGN(LDEBUG, n); }
-    inline void debug(char const* msg) { log(LDEBUG, msg); }
-    inline void debug(std::string const& msg) { log(LDEBUG, msg); }
-    
-    inline void infof(char const* fmt, ...) { VA_LOG(LINFO); }
-    inline void infonf(size_t n, char const* fmt, ...) { VA_LOGN(LINFO, n); }
-    inline void info(char const* msg) { log(LINFO, msg); }
-    inline void info(std::string const& msg) { log(LINFO, msg); }
-    
-    inline void warnf(char const* fmt, ...) { VA_LOG(LWARNING); }
-    inline void warnnf(size_t n, char const* fmt, ...) { VA_LOGN(LWARNING, n); }
-    inline void warn(char const* msg) { log(LWARNING, msg); }
-    inline void warn(std::string const& msg) { log(LWARNING, msg); }
-    
-    inline void errorf(char const* fmt, ...) { VA_LOG(LERROR); }
-    inline void errornf(size_t n, char const* fmt, ...) { VA_LOGN(LERROR, n); }
-    inline void error(char const* msg) { log(LERROR, msg); }
-    inline void error(std::string const& msg) { log(LERROR, msg); }
-    
-    inline void criticalf(char const* fmt, ...) { VA_LOG(LCRITICAL); }
-    inline void criticalnf(size_t n, char const* fmt, ...) { VA_LOGN(LCRITICAL, n); }
-    inline void critical(char const* msg) { log(LCRITICAL, msg); }
-    inline void critical(std::string const& msg) { log(LCRITICAL, msg); }
-    
-    inline void fatalf(char const* fmt, ...) { VA_LOG(LFATAL); }
-    inline void fatalnf(size_t n, char const* fmt, ...) { VA_LOGN(LFATAL, n); }
-    inline void fatal(char const* msg) { log(LFATAL, msg); }
-    inline void fatal(std::string const& msg) { log(LFATAL, msg); }
+    MAKE_LOGGER_FMT(trace)
+    MAKE_LOGGER_FMT(debug)
+    MAKE_LOGGER_FMT(info)
+    MAKE_LOGGER_FMT(warn)
+    MAKE_LOGGER_FMT(error)
+    MAKE_LOGGER_FMT(critical)
+    MAKE_LOGGER_FMT(fatal)
     
     template <typename... Args>
-    inline void log(log_level ll, std::string const & interp, Args && ...args);
+    inline void log(level ll, std::string const & interp, Args && ...args);
     
     void flush();
     
@@ -79,11 +60,10 @@ namespace logging {
     c_logger(std::string const & name, std::shared_ptr<logger_impl> impl);
 
   private:
-    void vlognf(log_level, size_t, char const*, va_list);
-    void log(log_level, std::string const&);
+    void vlognf(level, size_t, char const*, va_list);
+    void log(level, std::string const&);
     
   private:
-    log_level min_level_;
     std::string logger_name_;
     std::shared_ptr<logger_impl> impl_;
   };

+ 3 - 5
include/logger/detail/appender.h

@@ -3,17 +3,15 @@
 #include "logger/logger_fwd.h"
 
 namespace logging {
-  class appender {
-  public:
+  struct appender {
     virtual ~appender() = default;
     virtual void write(std::string const & msg) = 0;
     virtual void flush() = 0;
     
-    bool should_log(log_level ll) const {
+    bool should_log(level ll) const {
       return ll >= min_log_level;
     }
     
-  protected:
-    log_level min_log_level;
+    level min_log_level;
   };
 }

+ 3 - 3
include/logger/detail/logger_impl.h

@@ -14,11 +14,11 @@ namespace logging {
     using p_appender = std::shared_ptr<appender>;
     using p_layout = std::shared_ptr<layout>;
 
-    bool should_log(log_level ll) const;
+    bool should_log(level ll) const;
     void write(logpacket const & pkt);
     void flush();
 
-    std::vector<std::pair<p_appender, p_layout>> impls_;
-    log_level min_log_level_;
+    std::vector<std::pair<p_appender, p_layout>> impls;
+    level min_log_level;
   };
 }

+ 33 - 0
include/logger/detail/to_json.h

@@ -0,0 +1,33 @@
+//
+//  to_json.h
+//  logger
+//
+//  Created by Sam Jaffe on 4/4/19.
+//
+
+#pragma once
+
+#if defined(LOGGING_USE_JSON)
+#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;
+}
+#endif
+namespace logging { namespace detail {
+  template <typename T>
+  json to_json(T const & obj);
+} }
+#else
+namespace logging {
+  typedef void json;
+}
+namespace logging { namespace detail {
+  template <typename T> void to_json(T const &) {}
+} }
+#endif

+ 25 - 0
include/logger/detail/to_string.h

@@ -0,0 +1,25 @@
+//
+//  to_string.h
+//  logger
+//
+//  Created by Sam Jaffe on 4/4/19.
+//
+
+#pragma once
+
+#include <iostream>
+#include <sstream>
+
+namespace logging { namespace detail {
+  template <typename T>
+  void to_stream(T const & obj, std::ostream & os) {
+    os << obj;
+  }
+
+  template <typename T>
+  std::string to_string(T const & obj) {
+    std::stringstream ss;
+    to_stream(obj, ss);
+    return ss.str();
+  }
+} }

+ 5 - 0
include/logger/exception.h

@@ -21,4 +21,9 @@ namespace logging {
   public:
     using std::logic_error::logic_error;
   };
+
+  class invalid_property : public std::logic_error {
+  public:
+    using std::logic_error::logic_error;
+  };
 }

+ 19 - 32
include/logger/format.h

@@ -13,6 +13,7 @@
 #include <vector>
 
 #include "logger_fwd.h"
+#include "wrapper_object.h"
 
 namespace logging {
   class format {
@@ -29,41 +30,27 @@ namespace logging {
   private:
     std::vector<generator> gen;
   };
+    
+  using string_generator = std::function<std::string(logpacket const &)>;
+  string_generator get_date_formatter(std::string fmt);
   
-  inline void format_msg(std::stringstream & msg, std::string const & interp,
-                         size_t pos) {
-    msg << interp.substr(pos);
-  }
-  
-  struct format_point_t {
-    size_t start;
-    size_t end;
+  class message {
+  public:
+    message() = default;
+    message(char const * fmt) : format_code(fmt) {}
+    template <typename... Args>
+    message(std::string const & fmt, Args && ...args);
+    
+    std::string str() const;
+    
+  private:
+    std::string format_code;
+    std::vector<detail::object> objects;
   };
   
-  format_point_t get_next_format_specifier(std::string const & interp,
-                                           size_t pos );
-  
-  template <typename Arg0, typename... Args>
-  inline void format_msg(std::stringstream & msg, std::string const & interp,
-                         size_t pos, Arg0 && arg0, Args && ...args) {
-    format_point_t next = get_next_format_specifier( interp, pos );
-    msg << interp.substr(pos, next.start);
-    if (next.start == std::string::npos) {
-      return; // throw?
-    } else if (!strncmp(interp.c_str() + next.start - 1, "{{}}", 4)) {
-      format_msg(msg, interp, next.end, std::forward<Arg0>(arg0),
-                 std::forward<Args>(args)... );
-    } else {
-      msg << arg0;
-      format_msg(msg, interp, next.end, std::forward<Args>(args)... );
-    }
-  }
-  
   template <typename... Args>
-  inline std::string format_msg(std::string const & interp,
-                                Args && ...args) {
-    std::stringstream msg;
-    format_msg<Args...>( msg, interp, 0, std::forward<Args>(args)... );
-    return msg.str();
+  message::message(std::string const & fmt, Args && ...args)
+  : format_code(fmt), objects({detail::object(args)...}) {
+    
   }
 }

+ 7 - 12
include/logger/logger.h

@@ -27,8 +27,8 @@
 #include "format.h"
 
 #define log_here { __FILE__, __LINE__, STRING( FUNCTION ) }
-#define log_message( logger, level, ... )   \
-  logger.log( level, log_here, __VA_ARGS__ )
+#define log_message( logger, lvl, ... )   \
+logger.log( level::lvl, log_here, __VA_ARGS__ )
 
 namespace logging {
   class logger_impl;
@@ -39,17 +39,14 @@ namespace logging {
     ~logger();
 
     template <typename... Args>
-    inline void log(log_level ll, std::string const & interp,
-                    Args && ...args) {
-      log( ll, location_info{}, interp, std::forward<Args>(args)... );
+    inline void log(level ll, std::string const & interp, Args && ...args) {
+      log(ll, location_info{}, interp, std::forward<Args>(args)...);
     }
     
     template <typename... Args>
-    inline void log(log_level ll, location_info info,
+    inline void log(level ll, location_info info,
                     std::string const & interp, Args && ...args) {
-      if ( should_log( ll ) ) {
-        log( ll, info, format_msg( interp, std::forward<Args>(args)... ) );
-      }
+      log(ll, info, message(interp, std::forward<Args>(args)...));
     }
     
     void flush();
@@ -59,11 +56,9 @@ namespace logging {
     
   private:
     friend class manager;
-    bool should_log( log_level ) const;
-    void log( log_level ll, location_info info, std::string const& );
+    void log(level ll, location_info info, message const&);
 
   private:
-    log_level min_level_;
     std::string const logger_name_;
     std::shared_ptr<logger_impl> impl_;
   };

+ 13 - 18
include/logger/logger_fwd.h

@@ -10,29 +10,24 @@
 #include <string>
 
 namespace logging {
-  enum log_level {
-    LTRACE,
-    LDEBUG,
-    LINFO,
-    LWARNING,
-    LERROR,
-    LCRITICAL,
-    LFATAL,
-    LNONE
-  };
-    
+  enum class level : int;
+  struct logpacket;
+  
+  std::ostream & operator<<(std::ostream & os, level l);
+  
   struct location_info {
     char const * filename = "???";
     int line = 0;
     char const * function = "";
   };
+}
 
-  struct logpacket {
-    struct timeval time;
-    //    int thread_id;
-    log_level level;
-    location_info info;
-    std::string logger;
-    std::string message;
+#define LIST_OF_LOGGING_LEVELS \
+  X(trace) X(debug) X(info) X(warning) X(error) X(critical) X(fatal) X(none)
+#define X(token) token,
+namespace logging {
+  enum class level : int {
+    LIST_OF_LOGGING_LEVELS warn = warning
   };
 }
+#undef X

+ 16 - 0
include/logger/logpacket.h

@@ -0,0 +1,16 @@
+
+#pragma once
+
+#include "logger_fwd.h"
+#include "format.h"
+
+namespace logging {
+  struct logpacket {
+    struct timeval time;
+    //    int thread_id;
+    level level;
+    location_info info;
+    std::string logger;
+    message message;
+  };
+}

+ 52 - 0
include/logger/wrapper_object.h

@@ -0,0 +1,52 @@
+//
+//  wrapper_object.hpp
+//  logging
+//
+//  Created by Sam Jaffe on 4/4/19.
+//
+
+#pragma once
+
+#include <iostream>
+#include <string>
+
+#include "detail/to_string.h"
+#include "detail/to_json.h"
+
+namespace logging { namespace detail {
+  class object {
+  public:
+    template <typename T> explicit object(T & object);
+    
+  private:
+    friend std::ostream & operator<<(std::ostream & os, object const & obj) {
+      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*);
+  };
+  
+  
+  template <typename T>
+  std::string object::to_string_impl(void * ptr) {
+    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>) {
+    
+  }
+} }

+ 7 - 3
logger.xcodeproj/project.pbxproj

@@ -26,6 +26,7 @@
 		CD88E9572252BDFC00927F40 /* log_manager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD88E9552252BDFC00927F40 /* log_manager.cxx */; };
 		CD88E95F2252D3EF00927F40 /* c_logger.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD88E95D2252D3EF00927F40 /* c_logger.cxx */; };
 		CD88E9632252D67A00927F40 /* common.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD88E9612252D67A00927F40 /* common.cxx */; };
+		CDA494DE2256D5F40041620C /* date_format.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDA494DD2256D5F40041620C /* date_format.cxx */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -93,6 +94,7 @@
 		CD88E95D2252D3EF00927F40 /* c_logger.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = c_logger.cxx; sourceTree = "<group>"; };
 		CD88E9612252D67A00927F40 /* common.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = common.cxx; sourceTree = "<group>"; };
 		CD88E9642252D6C700927F40 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = "<group>"; };
+		CDA494DD2256D5F40041620C /* date_format.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = date_format.cxx; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -141,10 +143,10 @@
 			isa = PBXGroup;
 			children = (
 				CD1CDE862252E5B900E5B6B2 /* properties.cxx */,
-				CD1CDE882252E60900E5B6B2 /* file_appender.cxx */,
 				CD1CDEB42256C94000E5B6B2 /* pattern_layout.cxx */,
 				CD1CDEB022557FB600E5B6B2 /* default_layout.cxx */,
 				CD1CDE8A2252E61800E5B6B2 /* console_appender.cxx */,
+				CD1CDE882252E60900E5B6B2 /* file_appender.cxx */,
 			);
 			path = loggers;
 			sourceTree = "<group>";
@@ -153,12 +155,13 @@
 			isa = PBXGroup;
 			children = (
 				CD1CDE812252E54100E5B6B2 /* loggers */,
+				CD88E95D2252D3EF00927F40 /* c_logger.cxx */,
 				CD2973991D7B401F00E37217 /* logger.cxx */,
 				CD1CDEAE22556B7E00E5B6B2 /* logger_impl.cxx */,
+				CD88E9552252BDFC00927F40 /* log_manager.cxx */,
 				CD88E9642252D6C700927F40 /* common.h */,
 				CD88E9612252D67A00927F40 /* common.cxx */,
-				CD88E95D2252D3EF00927F40 /* c_logger.cxx */,
-				CD88E9552252BDFC00927F40 /* log_manager.cxx */,
+				CDA494DD2256D5F40041620C /* date_format.cxx */,
 				CD3C80BE1D6A2CA300ACC795 /* format.cxx */,
 			);
 			path = src;
@@ -345,6 +348,7 @@
 			files = (
 				CD29739B1D7B401F00E37217 /* logger.cxx in Sources */,
 				CD88E9572252BDFC00927F40 /* log_manager.cxx in Sources */,
+				CDA494DE2256D5F40041620C /* date_format.cxx in Sources */,
 				CD3C80C01D6A2CA300ACC795 /* format.cxx in Sources */,
 				CD88E95F2252D3EF00927F40 /* c_logger.cxx in Sources */,
 				CD1CDE872252E5B900E5B6B2 /* properties.cxx in Sources */,

+ 6 - 6
src/c_logger.cxx

@@ -9,12 +9,13 @@
 
 #include "common.h"
 #include "logger/detail/logger_impl.h"
+#include "logger/logpacket.h"
 
 using namespace logging;
 
 c_logger::c_logger(std::string const & name,
                    std::shared_ptr<logger_impl> impl)
-: min_level_(LDEBUG), logger_name_(name), impl_(impl)
+: logger_name_(name), impl_(impl)
 {
 }
 
@@ -22,19 +23,18 @@ c_logger::~c_logger() {
   if (impl_) impl_->flush();
 }
 
-void c_logger::vlognf(log_level level, size_t num_bytes, char const* fmt,
+void c_logger::vlognf(level level, size_t num_bytes, char const* fmt,
                       va_list args) {
-  if (level < min_level_ || !impl_->should_log(level)) return;
+  if (!impl_->should_log(level)) return;
   const std::unique_ptr<char[]> data(new char[num_bytes]);
   int n = vsnprintf(data.get(), num_bytes, fmt, args);
   if (n >= 0) {
     data[num_bytes-1] = '\0';
-    impl_->write({ now(), level, {}, logger_name_, data.get() });
+    impl_->write({ now(), level, {}, logger_name_, {data.get()} });
   }
 }
 
-void c_logger::log(log_level level, std::string const& msg) {
-  if (level < min_level_ || !impl_->should_log(level)) return;
+void c_logger::log(level level, std::string const& msg) {
   impl_->write({ now(), level, {}, logger_name_, msg });
 }
 

+ 34 - 32
src/common.cxx

@@ -10,39 +10,41 @@
 #include <cassert>
 #include <ctime>
 
-#define X(l)          \
-  case logging::L##l: \
-    return #l;
-const char* logging::level_header(log_level l) {
-  assert(l != logging::LNONE);
-  switch (l) {
-    X(FATAL)
-    X(CRITICAL)
-    X(ERROR)
-    X(WARNING)
-    X(INFO)
-    X(DEBUG)
-    X(TRACE)
-    X(NONE)
-  }
-}
-#undef X
-
-timeval logging::now() {
-  struct timeval tv;
-  gettimeofday( &tv, nullptr );
-  return tv;
-}
-
-std::string logging::timestamp() {
-  time_t round = std::time(NULL);
-  char buf[32];
-  std::strftime(buf, sizeof(buf), "%Y%m%dT%H%M%S", std::localtime(&round));
-  return buf;
-}
+#include "logger/exception.h"
 
 namespace logging {
-  std::ostream & operator<<(std::ostream & os, log_level l) {
-    return os << std::string(level_header(l));
+  timeval now() {
+    struct timeval tv;
+    gettimeofday( &tv, nullptr );
+    return tv;
+  }
+  
+#define X(token) if (lower_case == #token) return level::token; else
+  level level_from_string(std::string const & str) {
+    std::string lower_case;
+    std::transform(str.begin(), str.end(),
+                   std::back_inserter(lower_case), &tolower);
+    LIST_OF_LOGGING_LEVELS
+    throw invalid_property{str + " is not a log level"};
+  }
+#undef X
+  
+#define X(token) if (lvl == level::token) return #token; else
+  char const * level_to_string(level lvl) {
+    LIST_OF_LOGGING_LEVELS
+    throw std::domain_error{"unknown logging level in switch"};
+  }
+#undef X
+  
+  std::string to_string(level lvl, bool uppercase) {
+    std::string str = level_to_string(lvl);
+    if (uppercase) {
+      std::transform(str.begin(), str.end(), str.begin(), &toupper);
+    }
+    return str;
+  }
+  
+  std::ostream & operator<<(std::ostream & os, level l) {
+    return os << std::string(level_to_string(l));
   }
 }

+ 3 - 3
src/common.h

@@ -12,9 +12,9 @@
 #include "logger/logger_fwd.h"
 
 namespace logging {
-  const char* level_header(log_level l);
   timeval now();
-  std::string timestamp();
 
-  std::ostream & operator<<(std::ostream & os, log_level l);
+  level level_from_string(std::string const & value);
+  char const * level_to_string(level lvl);
+  std::string to_string(level lvl, bool uppercase);
 }

+ 68 - 0
src/date_format.cxx

@@ -0,0 +1,68 @@
+//
+//  date_format.cxx
+//  logging
+//
+//  Created by Sam Jaffe on 4/4/19.
+//
+
+#include <ctime>
+#include <functional>
+#include <string>
+
+#include "logger/exception.h"
+#include "logger/format.h"
+#include "logger/logger_fwd.h"
+#include "logger/logpacket.h"
+
+namespace logging {
+  std::string fmt_time(struct timeval round, char const * const fmt) {
+    struct tm time;
+    // Supports localtime when requested, but you can't really test that
+    if (strstr(fmt, "%z") || strstr(fmt, "%Z")) {
+      localtime_r(&round.tv_sec, &time);
+    } else {
+      gmtime_r(&round.tv_sec, &time);
+    }
+    char buf[64] = {'\0'};
+    std::strftime(buf, sizeof(buf), fmt, &time);
+    return buf;
+  }
+  
+  std::string fmt_time(struct timeval round, std::string const & fmt) {
+    return fmt_time(round, fmt.c_str());
+  }
+  
+  std::string fmt_time_with_milis(struct timeval round,
+                                  std::string const & fmt) {
+    char buf[64] = {'\0'};
+    snprintf(buf, sizeof(buf), fmt.c_str(), round.tv_usec/1000);
+    return fmt_time(round, buf);
+  }
+  
+  string_generator get_date_formatter(std::string fmt) {
+    if (fmt.find("%_ms") != std::string::npos) {
+      size_t pos = 0;
+      while ((pos = fmt.find("%", pos)) != std::string::npos) {
+        fmt.replace(pos, 1, "%%");
+        pos += 2;
+      }
+      fmt.replace(fmt.find("%%_ms"), 5, "%.03d");
+      return [=](logpacket const & lp) {
+        return fmt_time_with_milis(lp.time, fmt);
+      };
+    } else {
+      return [=](logpacket const & lp) {
+        return fmt_time(lp.time, fmt);
+      };
+    }
+  }
+  
+  string_generator parse_date_format_string(char const * str) {
+    char const * const end = strchr(str, '}');
+    if (end == nullptr) {
+      std::string error_msg{"expected date-format specifier to terminate"};
+      throw format_parsing_exception{error_msg};
+    }
+    return get_date_formatter(std::string(str, end));
+  }
+}

+ 24 - 62
src/format.cxx

@@ -19,6 +19,7 @@
 #include "common.h"
 #include "logger/exception.h"
 #include "logger/logger.h"
+#include "logger/logpacket.h"
 
 #if defined( _WIN32 )
 # define NEWLINE "\r\n"
@@ -27,64 +28,14 @@
 #endif
 
 namespace logging {
-  namespace {
-    std::string fmt_time(struct timeval round, char const * const fmt) {
-      struct tm time;
-      // Supports localtime when requested, but you can't really test that
-      if (strstr(fmt, "%z") || strstr(fmt, "%Z")) {
-        localtime_r(&round.tv_sec, &time);
-      } else {
-        gmtime_r(&round.tv_sec, &time);
-      }
-      char buf[64] = {'\0'};
-      std::strftime(buf, sizeof(buf), fmt, &time);
-      return buf;
-    }
-    
-    std::string fmt_time(struct timeval round, std::string const & fmt) {
-      return fmt_time(round, fmt.c_str());
-    }
-    
-    std::string fmt_time_with_milis(struct timeval round,
-                                    std::string const & fmt) {
-      char buf[64] = {'\0'};
-      snprintf(buf, sizeof(buf), fmt.c_str(), round.tv_usec/1000);
-      return fmt_time(round, buf);
-    }
-  }
-  using string_generator = std::function<std::string(logpacket const &)>;
-  
-  string_generator parse_date_format_string( char const * str ) {
-    char const * const end = strchr(str, '}');
-    if (end == nullptr) {
-      std::string error_msg{"expected date-format specifier to terminate"};
-      throw format_parsing_exception{error_msg};
-    }
-
-    char const * const has_millis = strstr( str, "%_ms");
-    std::string fmtstring{str, end};
-    if ( has_millis && has_millis < end ) {
-      size_t pos = 0;
-      while ( (pos = fmtstring.find("%", pos)) != std::string::npos ) {
-        fmtstring.replace(pos, 1, "%%");
-        pos += 2;
-      }
-      fmtstring.replace(fmtstring.find("%%_ms"), 5, "%.03d");
-      return [=](logpacket const & lp) {
-        return fmt_time_with_milis(lp.time, fmtstring);
-      };
-    } else {
-      return [=](logpacket const & lp) {
-        return fmt_time(lp.time, fmtstring);
-      };
-    }
-  }
+  std::string fmt_time_with_milis(struct timeval, std::string const &);
+  string_generator parse_date_format_string(char const *);
   
 #define is( chr ) *next == chr
 #define is_string( str ) ! strncmp(next, str, strlen(str))
   string_generator date_token(char const * next) {
     std::string predef_format = "%%Y-%%m-%%d %%H:%%M:%%S,%.03d";
-    if ( is_string("{ISO8601}")) {
+    if (is_string("{ISO8601}")) {
       predef_format = "%%Y-%%m-%%dT%%H:%%M:%%S.%.03dZ";
     } else if (is_string("{ABSOLUTE}")) {
       predef_format = "%%H:%%M:%%S,%.03d";
@@ -97,9 +48,9 @@ namespace logging {
       return fmt_time_with_milis(lp.time, predef_format);
     };
   }
-  
+
   string_generator string_token(std::string str) {
-    return [=]( logpacket const & ){
+    return [=](logpacket const &){
       return str;
     };
   }
@@ -111,11 +62,11 @@ namespace logging {
       };
     } else if (is('p')) {
       return [](logpacket const & lp){
-        return level_header(lp.level);
+        return to_string(lp.level, true);
       };
     } else if (is('m')) {
       return [](logpacket const & lp){
-        return lp.message;
+        return lp.message.str();
       };
     } else {
       std::string error_msg{"unknown format character: '"};
@@ -205,12 +156,23 @@ namespace logging {
     process(pkt, ss);
     return ss.str();
   }
-}
 
-namespace logging {
-  format_point_t get_next_format_specifier(std::string const & interp,
-                                           size_t pos) {
+  void format_msg(std::ostream & os, std::string const & interp, size_t pos,
+                  std::vector<detail::object> const & objs, size_t idx) {
     size_t next = interp.find("{}", pos);
-    return { next, next + 2 };
+    os << interp.substr(pos, next);
+    if (next == std::string::npos) {
+      return; // throw?
+    } else if (!strncmp(interp.c_str() + next - 1, "{{}}", 4)) {
+      format_msg(os, interp, next+2, objs, idx);
+    } else {
+      format_msg(os << objs[idx], interp, next+2, objs, idx+1);
+    }
+  }
+  
+  std::string message::str() const {
+    std::stringstream ss;
+    format_msg(ss, format_code, 0, objects, 0);
+    return ss.str();
   }
 }

+ 18 - 4
src/log_manager.cxx

@@ -13,7 +13,9 @@
 #include "../../../paradigm/declarative/expect/include/expect/expect.hpp"
 #include "../../../types/resource_factory/include/resource_factory/prototype_factory.hpp"
 
+#include "common.h"
 #include "logger/c_logger.h"
+#include "logger/detail/appender.h"
 #include "logger/exception.h"
 #include "logger/logger.h"
 #include "logger/properties.h"
@@ -43,10 +45,10 @@ manager_impl::get_logger(properties const & props) {
   auto out = std::make_shared<logger_impl>();
   if (props.type == properties::OBJECT) {
     // TODO include settings on the parent
-    out->impls_.push_back(appenders.at(props["ref"]));
+    out->impls.push_back(appenders.at(props["ref"]));
   } else if (props.type == properties::ARRAY) {
     for (auto & part : props.array) {
-      out->impls_.push_back(appenders.at(part["ref"]));
+      out->impls.push_back(appenders.at(part["ref"]));
     }
   } else {
     throw invalid_property_type{"appenders cannot be a STRING"};
@@ -73,6 +75,13 @@ static p_layout load_layout(std::string const & source,
   return layouts::instance().get("default", {});
 }
 
+template <typename T>
+void inject_log_level(properties const & props, T & val) {
+  if (props.obj.count("level")) {
+    val.min_log_level = level_from_string(props["level"]);
+  }
+}
+
 void manager_impl::configure_appenders(properties const & props) {
   // TODO: Load logger_impl here
   // TODO: Support multiple File loggers, etc.
@@ -80,6 +89,7 @@ void manager_impl::configure_appenders(properties const & props) {
   for (auto & app : props["appenders"].obj) {
     auto pair = std::make_pair(load_appender(app.first, app.second),
                                load_layout(app.first, app.second));
+    inject_log_level(app.second, *pair.first);
     appenders.emplace(app.first, pair);
   }
 }
@@ -87,9 +97,13 @@ 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) {
-    loggers.emplace(log.first, get_logger(log.second["appenders"]));
+    auto plog = get_logger(log.second["appenders"]);
+    inject_log_level(log.second, *plog);
+    loggers.emplace(log.first, plog);
+  }
+  if (loggers.count("root")) {
+    default_logger = loggers["root"];
   }
-  default_logger = get_logger(props["loggers"]["root"]["appenders"]);
 }
 
 manager::manager() : pimpl_(new manager_impl) {}

+ 4 - 7
src/logger.cxx

@@ -17,10 +17,11 @@
 
 #include "common.h"
 #include "logger/detail/logger_impl.h"
+#include "logger/logpacket.h"
 
 namespace logging {
   logger::logger(std::string const & name, std::shared_ptr<logger_impl> impl)
-  : min_level_(LDEBUG), logger_name_(name), impl_(impl)
+  : logger_name_(name), impl_(impl)
   {
   }
   
@@ -28,15 +29,11 @@ namespace logging {
     if (impl_) impl_->flush();
   }
   
-  void logger::log(log_level ll, location_info info,
-                   std::string const & msg) {
+  void logger::log(level ll, location_info info,
+                   message const & msg) {
     impl_->write({ now(), ll, info, logger_name_, msg });
   }
   
-  bool logger::should_log(log_level ll) const {
-    return ll >= min_level_ && impl_->should_log( ll );
-  }
-  
   void logger::flush() {
     impl_->flush();
   }

+ 6 - 4
src/logger_impl.cxx

@@ -9,15 +9,17 @@
 
 #include "logger/detail/appender.h"
 #include "logger/detail/layout.h"
+#include "logger/logpacket.h"
 
 using namespace logging;
 
-bool logger_impl::should_log(log_level ll) const {
-  return ll >= min_log_level_;
+bool logger_impl::should_log(level ll) const {
+  return ll >= min_log_level;
 }
 
 void logger_impl::write(logpacket const & pkt) {
-  for (auto & pair : impls_) {
+  if (!should_log(pkt.level)) return;
+  for (auto & pair : impls) {
     if (pair.first->should_log(pkt.level)) {
       pair.first->write(pair.second->format(pkt));
     }
@@ -25,7 +27,7 @@ void logger_impl::write(logpacket const & pkt) {
 }
 
 void logger_impl::flush() {
-  for (auto & pair : impls_) {
+  for (auto & pair : impls) {
     pair.first->flush();
   }
 }

+ 2 - 1
src/loggers/default_layout.cxx

@@ -9,6 +9,7 @@
 
 #include "logger/detail/layout.h"
 #include "logger/log_manager.h"
+#include "logger/logpacket.h"
 
 using namespace logging;
 
@@ -22,7 +23,7 @@ std::shared_ptr<layout> default_layout::create(properties const &) {
 }
 
 std::string default_layout::format(logpacket const & pkt) const {
-  return pkt.message;
+  return pkt.message.str();
 }
 
 namespace {

+ 5 - 8
test/c_logger_test.cxx

@@ -32,9 +32,7 @@ TEST_F(CLoggerTest, FlushesOnFlushCall) {
 
 TEST_F(CLoggerTest, LogsWithFmtCode) {
   using testing::_;
-  using testing::Field;
-  // TODO: Eq
-  EXPECT_CALL(*layout, format(Field(&logpacket::message, "5"))).Times(1);
+  EXPECT_CALL(*layout, format(MessageEq("5"))).Times(1);
   t_logger("", pimpl).errorf("%d", 5);
 }
 
@@ -50,27 +48,26 @@ TEST_F(CLoggerTest, FmtLogHasNameInHeader) {
 TEST_F(CLoggerTest, FmtLogHasLevelInHeader) {
   using testing::_;
   using testing::Field;
-  EXPECT_CALL(*layout, format(Field(&logpacket::level, LERROR))).Times(1);
+  EXPECT_CALL(*layout, format(Field(&logpacket::level, level::error))).Times(1);
   t_logger("TEST", pimpl).errorf("%d", 5);
 }
 
 TEST_F(CLoggerTest, LogsRawData) {
   using testing::_;
-  using testing::Field;
-  EXPECT_CALL(*layout, format(Field(&logpacket::message, "5"))).Times(1);
+  EXPECT_CALL(*layout, format(MessageEq("5"))).Times(1);
   t_logger("", pimpl).error("5");
 }
 
 TEST_F(CLoggerTest, DoesNotLogAboveLevel) {
   using testing::_;
-  pimpl->min_log_level_ = LFATAL;
+  pimpl->min_log_level = level::fatal;
   EXPECT_CALL(*appender, write(_)).Times(0);
   t_logger("", pimpl).errorf("%d", 5);
 }
 
 TEST_F(CLoggerTest, DoesNotRawLogAboveLevel) {
   using testing::_;
-  pimpl->min_log_level_ = LFATAL;
+  pimpl->min_log_level = level::fatal;
   EXPECT_CALL(*appender, write(_)).Times(0);
   t_logger("", pimpl).error("5");
 }

+ 9 - 8
test/format_test.cxx

@@ -9,6 +9,7 @@
 
 #include "logger/exception.h"
 #include "logger/format.h"
+#include "logger/logpacket.h"
 
 using namespace logging;
 
@@ -105,21 +106,21 @@ TEST(FormatTest, SupportsHumanDateFormat) {
 TEST(FormatTest, LoggerIdIsCToken) {
   using testing::Eq;
   auto fmt = format::parse_format_string("%c");
-  EXPECT_THAT(fmt.process({{}, LERROR, {}, "UNIT_TEST", "HELLO"}),
+  EXPECT_THAT(fmt.process({{}, level::error, {}, "UNIT_TEST", "HELLO"}),
               Eq("UNIT_TEST"));
 }
 
 TEST(FormatTest, LogLevelIsPToken) {
   using testing::Eq;
   auto fmt = format::parse_format_string("%p");
-  EXPECT_THAT(fmt.process({{}, LERROR, {}, "UNIT_TEST", "HELLO"}),
+  EXPECT_THAT(fmt.process({{}, level::error, {}, "UNIT_TEST", "HELLO"}),
               Eq("ERROR"));
 }
 
 TEST(FormatTest, LogMessageIsMToken) {
   using testing::Eq;
   auto fmt = format::parse_format_string("%m");
-  EXPECT_THAT(fmt.process({{}, LERROR, {}, "UNIT_TEST", "HELLO"}),
+  EXPECT_THAT(fmt.process({{}, level::error, {}, "UNIT_TEST", "HELLO"}),
               Eq("HELLO"));
 }
 
@@ -132,29 +133,29 @@ TEST(FormatTest, ThrowsOnUnknownToken) {
 TEST(FormatTest, TokenCanBeTruncatedInFormat) {
   using testing::Eq;
   auto fmt = format::parse_format_string("%.3m");
-  EXPECT_THAT(fmt.process({{}, LERROR, {}, "UNIT_TEST", "HELLO"}),
+  EXPECT_THAT(fmt.process({{}, level::error, {}, "UNIT_TEST", "HELLO"}),
               Eq("HEL"));
 }
 
 TEST(FormatTest, TokenCanBeLeftPadded) {
   using testing::Eq;
   auto fmt = format::parse_format_string("%6m");
-  EXPECT_THAT(fmt.process({{}, LERROR, {}, "UNIT_TEST", "HELLO"}),
+  EXPECT_THAT(fmt.process({{}, level::error, {}, "UNIT_TEST", "HELLO"}),
               Eq(" HELLO"));
 }
 
 TEST(FormatTest, TokenCanBeRightPadded) {
   using testing::Eq;
   auto fmt = format::parse_format_string("%-6m");
-  EXPECT_THAT(fmt.process({{}, LERROR, {}, "UNIT_TEST", "HELLO"}),
+  EXPECT_THAT(fmt.process({{}, level::error, {}, "UNIT_TEST", "HELLO"}),
               Eq("HELLO "));
 }
 
 TEST(FormatTest, TokenCanBeSizeBound) {
   using testing::Eq;
   auto fmt = format::parse_format_string("%6.8m");
-  EXPECT_THAT(fmt.process({{}, LERROR, {}, "UNIT_TEST", "HELLO"}),
+  EXPECT_THAT(fmt.process({{}, level::error, {}, "UNIT_TEST", "HELLO"}),
               Eq(" HELLO"));
-  EXPECT_THAT(fmt.process({{}, LERROR, {}, "UNIT_TEST", "HELLO FRIEND"}),
+  EXPECT_THAT(fmt.process({{}, level::error, {}, "UNIT_TEST", "HELLO FRIEND"}),
               Eq("HELLO FR"));
 }

+ 31 - 6
test/log_manager_test.cxx

@@ -43,6 +43,8 @@ void LogManagerTest::TearDown() {
   lbinding_.reset();
 }
 
+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;
 
@@ -54,13 +56,13 @@ TEST_F(LogManagerTest, CanInjectMock) {
   EXPECT_THAT(appender, ::testing::NotNull());
 }
 
+using ::testing::_;
+using ::testing::AnyNumber;
+
 TEST_F(LogManagerTest, CanFetchInjectedMock) {
   manager mgr;
   mgr.configure(MIN_PROPERTY_SCHEMA);
   
-  using ::testing::_;
-  using ::testing::AnyNumber;
-  using ::testing::Field;
   EXPECT_CALL(*appender, flush()).Times(AnyNumber());
   EXPECT_CALL(*appender, write("TEST MESSAGE"));
   EXPECT_CALL(*layout, format(_)).WillRepeatedly(ReturnMessage());
@@ -73,9 +75,6 @@ TEST_F(LogManagerTest, MultiplexMockLogsToMultipleImpls) {
   manager mgr;
   mgr.configure(MULTIPLEX_PROPERTY_SCHEMA);
   
-  using ::testing::_;
-  using ::testing::AnyNumber;
-  using ::testing::Field;
   EXPECT_CALL(*appender, flush()).Times(AnyNumber());
   EXPECT_CALL(*appender, write("TEST MESSAGE")).Times(2);
   EXPECT_CALL(*layout, format(_)).WillRepeatedly(ReturnMessage());
@@ -83,3 +82,29 @@ TEST_F(LogManagerTest, MultiplexMockLogsToMultipleImpls) {
   c_logger l = mgr.c_get();
   l.error("TEST MESSAGE");
 }
+
+TEST_F(LogManagerTest, LevelCanBeBakedIntoAppenderProperties) {
+  manager mgr;
+  mgr.configure(APPENDER_LEVEL_PROPERTY_SCHEMA);
+  
+  EXPECT_CALL(*appender, flush()).Times(AnyNumber());
+  EXPECT_CALL(*appender, write("TEST MESSAGE")).Times(1);
+  EXPECT_CALL(*appender, write("LOWER MESSAGE")).Times(0);
+  EXPECT_CALL(*layout, format(_)).WillRepeatedly(ReturnMessage());
+  
+  c_logger l = mgr.c_get();
+  l.warn("TEST MESSAGE");
+  l.info("LOWER MESSAGE");
+}
+
+TEST_F(LogManagerTest, LevelCanBeBakedIntoLoggerProperties) {
+  manager mgr;
+  mgr.configure(LOGGER_LEVEL_PROPERTY_SCHEMA);
+  
+  EXPECT_CALL(*appender, flush()).Times(AnyNumber());
+  EXPECT_CALL(*appender, write("TEST MESSAGE")).Times(0);
+  EXPECT_CALL(*layout, format(_)).Times(0);
+  
+  c_logger l = mgr.c_get();
+  l.warn("TEST MESSAGE");
+}

+ 6 - 8
test/logger_test.cxx

@@ -29,20 +29,18 @@ TEST_F(LoggerTest, FlushesOnFlushCall) {
 }
 
 TEST_F(LoggerTest, LogsWithBraceFmtCode) {
-  using testing::Field;
-  EXPECT_CALL(*layout, format(Field(&logpacket::message, "5"))).Times(1);
-  t_logger("", pimpl).log(LERROR, "{}", 5);
+  EXPECT_CALL(*layout, format(MessageEq("5"))).Times(1);
+  t_logger("", pimpl).log(level::error, "{}", 5);
 }
 
 TEST_F(LoggerTest, DoesNotLogAboveLevel) {
   using testing::_;
-  pimpl->min_log_level_ = LFATAL;
+  pimpl->min_log_level = level::fatal;
   EXPECT_CALL(*appender, write(_)).Times(0);
-  t_logger("", pimpl).log(LERROR, "{}", 5);
+  t_logger("", pimpl).log(level::error, "{}", 5);
 }
 
 TEST_F(LoggerTest, LogCurlyBraceLiteralByDoubling) {
-  using testing::Field;
-  EXPECT_CALL(*layout, format(Field(&logpacket::message, "{}"))).Times(1);
-  t_logger("", pimpl).log(LERROR, "{{}}", 5);
+  EXPECT_CALL(*layout, format(MessageEq("{}"))).Times(1);
+  t_logger("", pimpl).log(level::error, "{{}}", 5);
 }

+ 10 - 5
test/mock_logger.h

@@ -13,6 +13,7 @@
 #include "logger/detail/appender.h"
 #include "logger/detail/layout.h"
 #include "logger/detail/logger_impl.h"
+#include "logger/logpacket.h"
 
 namespace logging {
   inline void PrintTo(location_info const & info, std::ostream * os) {
@@ -27,13 +28,13 @@ namespace logging {
   inline void PrintTo(logpacket const & pkt, std::ostream * os) {
     (*os) << "{ " << pkt.level << ", ";
     PrintTo(pkt.info, os);
-    (*os) << ", \"" << pkt.logger << "\", \"" << pkt.message << "\" }";
+    (*os) << ", \"" << pkt.logger << "\", \"" << pkt.message.str() << "\" }";
   }
 }
 
 struct MockAppender : public logging::appender {
-  MockAppender() { SetLogLevel(logging::LTRACE); }
-  void SetLogLevel(logging::log_level ll) { min_log_level = ll; }
+  MockAppender() { SetLogLevel(logging::level::trace); }
+  void SetLogLevel(logging::level ll) { min_log_level = ll; }
   MOCK_METHOD0(flush, void());
   MOCK_METHOD1(write, void(std::string const &));
 };
@@ -43,7 +44,11 @@ struct MockLayout : public logging::layout {
 };
 
 ACTION(ReturnMessage) {
-  return arg0.message;
+  return arg0.message.str();
+}
+
+MATCHER_P(MessageEq, value, "") {
+  return arg.message.str() == value;
 }
 
 struct LoggerTest : public testing::Test {
@@ -51,7 +56,7 @@ struct LoggerTest : public testing::Test {
     appender.reset(new MockAppender);
     layout.reset(new MockLayout);
     pimpl = std::make_shared<logging::logger_impl>();
-    pimpl->impls_.push_back({appender, layout});
+    pimpl->impls.push_back({appender, layout});
     
     using testing::_;
     using testing::AnyNumber;

+ 39 - 0
test/test_properties.cxx

@@ -21,6 +21,8 @@ namespace {
   }
 }
 
+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;
 
@@ -41,6 +43,43 @@ properties const MIN_PROPERTY_SCHEMA{_property({
   })}
 })};
 
+properties const APPENDER_LEVEL_PROPERTY_SCHEMA{_property({
+  {"configuration", _property({
+    {"appenders", _property({
+      {"Mock", _property({
+        {"level", _value("Warning")},
+        {"MockLayout", _value("")}
+      })}
+    })},
+    {"loggers", _property({
+      {"root", _property({
+        {"appenders", _property({
+          {"ref", _value("Mock")}
+        })}
+      })}
+    })}
+  })}
+})};
+
+properties const LOGGER_LEVEL_PROPERTY_SCHEMA{_property({
+  {"configuration", _property({
+    {"appenders", _property({
+      {"Mock", _property({
+        {"level", _value("Warning")},
+        {"MockLayout", _value("")}
+      })}
+    })},
+    {"loggers", _property({
+      {"root", _property({
+        {"level", _value("Error")},
+        {"appenders", _property({
+          {"ref", _value("Mock")}
+        })}
+      })}
+    })}
+  })}
+})};
+
 properties const MULTIPLEX_PROPERTY_SCHEMA{_property({
   {"configuration", _property({
     {"appenders", _property({