Jelajahi Sumber

Merge branch 'predefs'

* predefs:
  Add tests for source location.
  Remove unneeded includes
  Default to capital letters for log level.
  Fixing test cases, biting the bullet and including jsoncpp on a permanent basis.
  Write out string fallback code for to_json.
  Add tests for EOL, objectAsJSON.
  Add support for compact JSON output.
  Start writing JSON Layout tests.
  Fix file appender rules truncation now that I use a custom filebuf.
  Add file for json_layout. Implementation to follow.
  Start with pattern_layout configuration...
  Enhance file_appender with all property settings.
Sam Jaffe 6 tahun lalu
induk
melakukan
f557d7712c

+ 1 - 0
include/logger/detail/appender.h

@@ -5,6 +5,7 @@
 namespace logging {
   class layout;
   struct appender {
+    appender(level min = level::debug) : min_log_level(min) {}
     virtual ~appender() = default;
     virtual void write(logpacket const & pkt) = 0;
     virtual void flush() = 0;

+ 3 - 30
include/logger/detail/to_json.h

@@ -7,42 +7,15 @@
 
 #pragma once
 
-#include <iostream>
+#include <string>
 
-#if defined(LOGGING_JSON_SJAFFE)
-#  include "json/json.hpp"
-#endif
-#if defined(LOGGING_JSON_VALUE)
-#  include <memory>
-#  include "json/json.h"
-#endif
+#include <json/json.h>
 
 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>
-  std::string to_json(T const & obj, bool compact = false) {
+  Json::Value to_json(T const & obj) {
     return to_string(obj);
   }
 } }

+ 1 - 1
include/logger/format.h

@@ -42,7 +42,7 @@ namespace logging {
     message(std::string const & fmt, Args && ...args);
     
     std::string str() const;
-    
+    Json::Value json() const;
   private:
     std::string format_code;
     std::vector<detail::object> objects;

+ 0 - 1
include/logger/logger.h

@@ -26,7 +26,6 @@
 #include "logger_fwd.h"
 #include "format.h"
 
-#define log_here { __FILE__, __LINE__, STRING(FUNCTION) }
 #define log_message( logger, lvl, ... )   \
   logger.log(level::lvl, log_here, __VA_ARGS__)
 

+ 8 - 0
include/logger/logger_fwd.h

@@ -9,6 +9,8 @@
 
 #include <string>
 
+#define log_here { __FILE__, __LINE__, __FUNCTION__ }
+
 namespace logging {
   enum class level : int;
   struct logpacket;
@@ -20,6 +22,12 @@ namespace logging {
     int line = 0;
     char const * function = "";
   };
+  
+#if defined( _WIN32 )
+  constexpr char const * const NEWLINE_TOKEN{"\r\n"};
+#else
+  constexpr char const * const NEWLINE_TOKEN{"\n"};
+#endif
 }
 
 #define LIST_OF_LOGGING_LEVELS \

+ 12 - 2
include/logger/wrapper_object.h

@@ -18,15 +18,19 @@ namespace logging { namespace detail {
   public:
     template <typename T> explicit object(T & object);
     
+    Json::Value json() const { return to_json_(ptr_); }
+    
   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::Value to_json_impl(void * ptr);
 
   private:
     void * ptr_;
     std::string (*to_string_)(void*);
+    Json::Value (*to_json_)(void*);
   };
   
   
@@ -34,11 +38,17 @@ namespace logging { namespace detail {
   std::string object::to_string_impl(void * ptr) {
     return to_string(*static_cast<T*>(ptr));
   }
-  
+
+  template <typename T>
+  Json::Value 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_string_(&object::to_string_impl<T>),
+  to_json_(&object::to_json_impl<T>) {
     
   }
 } }

+ 32 - 0
logger.xcodeproj/project.pbxproj

@@ -26,10 +26,14 @@
 		CD760CB922621776008A62DE /* pattern_layout_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD760CB822621776008A62DE /* pattern_layout_test.cxx */; };
 		CD760CBF226221F6008A62DE /* console_appender_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD760CBE226221F6008A62DE /* console_appender_test.cxx */; };
 		CD760CC1226226CC008A62DE /* file_appender_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD760CC0226226CC008A62DE /* file_appender_test.cxx */; };
+		CD760CC922627202008A62DE /* json_layout_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD760CC822627202008A62DE /* json_layout_test.cxx */; };
+		CD760CD122628A63008A62DE /* libjsoncpp.1.8.4.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CD760CCE226288AB008A62DE /* libjsoncpp.1.8.4.dylib */; };
+		CD760CD222628A73008A62DE /* libjsoncpp.1.8.4.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CD760CCE226288AB008A62DE /* libjsoncpp.1.8.4.dylib */; };
 		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 */; };
+		CDEA62D5225A3B0B00A6FAE0 /* json_layout.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDEA62D4225A3B0B00A6FAE0 /* json_layout.cxx */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -96,11 +100,14 @@
 		CD760CB822621776008A62DE /* pattern_layout_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = pattern_layout_test.cxx; sourceTree = "<group>"; };
 		CD760CBE226221F6008A62DE /* console_appender_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = console_appender_test.cxx; sourceTree = "<group>"; };
 		CD760CC0226226CC008A62DE /* file_appender_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = file_appender_test.cxx; sourceTree = "<group>"; };
+		CD760CC822627202008A62DE /* json_layout_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = json_layout_test.cxx; sourceTree = "<group>"; };
+		CD760CCE226288AB008A62DE /* libjsoncpp.1.8.4.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libjsoncpp.1.8.4.dylib; path = ../../../../../../../../opt/local/lib/libjsoncpp.1.8.4.dylib; sourceTree = "<group>"; };
 		CD88E9552252BDFC00927F40 /* log_manager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = log_manager.cxx; sourceTree = "<group>"; };
 		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>"; };
+		CDEA62D4225A3B0B00A6FAE0 /* json_layout.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = json_layout.cxx; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -108,6 +115,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				CD760CD122628A63008A62DE /* libjsoncpp.1.8.4.dylib in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -115,6 +123,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				CD760CD222628A73008A62DE /* libjsoncpp.1.8.4.dylib in Frameworks */,
 				CD6F7406225187F40081ED74 /* liblogging.dylib in Frameworks */,
 				CD6F746C22518A2C0081ED74 /* GoogleMock.framework in Frameworks */,
 			);
@@ -150,6 +159,7 @@
 			children = (
 				CD1CDE862252E5B900E5B6B2 /* properties.cxx */,
 				CD1CDEB42256C94000E5B6B2 /* pattern_layout.cxx */,
+				CDEA62D4225A3B0B00A6FAE0 /* json_layout.cxx */,
 				CD1CDEB022557FB600E5B6B2 /* default_layout.cxx */,
 				CD1CDE8A2252E61800E5B6B2 /* console_appender.cxx */,
 				CD1CDE882252E60900E5B6B2 /* file_appender.cxx */,
@@ -179,6 +189,7 @@
 				CD6F73FC225187E10081ED74 /* logger_test.cxx */,
 				CD1CDEB22256B04600E5B6B2 /* format_test.cxx */,
 				CD760CB822621776008A62DE /* pattern_layout_test.cxx */,
+				CD760CC822627202008A62DE /* json_layout_test.cxx */,
 				CD760CBE226221F6008A62DE /* console_appender_test.cxx */,
 				CD760CC0226226CC008A62DE /* file_appender_test.cxx */,
 				CD1CDE8C22540D9B00E5B6B2 /* c_logger_test.cxx */,
@@ -200,6 +211,7 @@
 		CD6F742B225188600081ED74 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				CD760CCE226288AB008A62DE /* libjsoncpp.1.8.4.dylib */,
 				CD6F746B22518A2C0081ED74 /* GoogleMock.framework */,
 				CD6F742F225189470081ED74 /* GoogleMock.xcodeproj */,
 				CD6F742D225189290081ED74 /* libcfmt_logger.dylib */,
@@ -356,6 +368,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				CD29739B1D7B401F00E37217 /* logger.cxx in Sources */,
+				CDEA62D5225A3B0B00A6FAE0 /* json_layout.cxx in Sources */,
 				CD88E9572252BDFC00927F40 /* log_manager.cxx in Sources */,
 				CDA494DE2256D5F40041620C /* date_format.cxx in Sources */,
 				CD3C80C01D6A2CA300ACC795 /* format.cxx in Sources */,
@@ -380,6 +393,7 @@
 				CD760CB922621776008A62DE /* pattern_layout_test.cxx in Sources */,
 				CD760CC1226226CC008A62DE /* file_appender_test.cxx in Sources */,
 				CD1CDE9222543E7E00E5B6B2 /* test_properties.cxx in Sources */,
+				CD760CC922627202008A62DE /* json_layout_test.cxx in Sources */,
 				CD1CDEB32256B04600E5B6B2 /* format_test.cxx in Sources */,
 				CD1CDE8D22540D9B00E5B6B2 /* c_logger_test.cxx in Sources */,
 			);
@@ -426,6 +440,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
+				HEADER_SEARCH_PATHS = /opt/local/include/;
 				ONLY_ACTIVE_ARCH = YES;
 				SDKROOT = macosx;
 				USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/include/ $(PROJECT_DIR)/extern/expect/include/ $(PROJECT_DIR)/extern/resource_factory/include/ $(PROJECT_DIR)/extern/scoped_buffer_capture/include/ $(PROJECT_DIR)/extern/";
@@ -461,6 +476,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
+				HEADER_SEARCH_PATHS = /opt/local/include/;
 				SDKROOT = macosx;
 				USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/include/ $(PROJECT_DIR)/extern/expect/include/ $(PROJECT_DIR)/extern/resource_factory/include/ $(PROJECT_DIR)/extern/scoped_buffer_capture/include/ $(PROJECT_DIR)/extern/";
 			};
@@ -507,6 +523,10 @@
 				GCC_WARN_UNDECLARED_SELECTOR = YES;
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					/opt/local/lib,
+				);
 				MACOSX_DEPLOYMENT_TARGET = 10.10;
 				MTL_ENABLE_DEBUG_INFO = YES;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -551,6 +571,10 @@
 				GCC_WARN_UNDECLARED_SELECTOR = YES;
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					/opt/local/lib,
+				);
 				MACOSX_DEPLOYMENT_TARGET = 10.10;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -588,6 +612,10 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				INFOPLIST_FILE = logger_test/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					/opt/local/lib,
+				);
 				MACOSX_DEPLOYMENT_TARGET = 10.13;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
@@ -625,6 +653,10 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				INFOPLIST_FILE = logger_test/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					/opt/local/lib,
+				);
 				MACOSX_DEPLOYMENT_TARGET = 10.13;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_FAST_MATH = YES;

+ 1 - 1
src/common.cxx

@@ -45,6 +45,6 @@ namespace logging {
   }
   
   std::ostream & operator<<(std::ostream & os, level l) {
-    return os << std::string(level_to_string(l));
+    return os << std::string(to_string(l, true));
   }
 }

+ 5 - 7
src/format.cxx

@@ -21,12 +21,6 @@
 #include "logger/logger.h"
 #include "logger/logpacket.h"
 
-#if defined( _WIN32 )
-# define NEWLINE "\r\n"
-#else
-# define NEWLINE "\n"
-#endif
-
 namespace logging {
   std::string fmt_time_with_milis(struct timeval, std::string const &);
   string_generator parse_date_format_string(char const *);
@@ -125,7 +119,7 @@ namespace logging {
         out.gen.push_back(convert(date_token(next)));
         if (is('{')) next = std::strchr(next, '}');
       } else if (is('n')) {
-        out.gen.push_back(convert(string_token(NEWLINE)));
+        out.gen.push_back(convert(string_token(NEWLINE_TOKEN)));
       } else if (is('%')) {
         out.gen.push_back(convert(string_token("%")));
       } else if (is('.') || is('-') || isdigit( *next )) {
@@ -170,4 +164,8 @@ namespace logging {
     format_msg(ss, format_code, 0, objects, 0);
     return ss.str();
   }
+  
+  Json::Value message::json() const {
+    return objects[0].json();
+  }
 }

+ 35 - 12
src/loggers/file_appender.cxx

@@ -21,17 +21,24 @@ namespace logging {
   level level_from_string(std::string const & value);
 }
 
+struct buffer : std::filebuf {
+  buffer(properties const & props);
+  buffer * setbuf(char* data, std::streamsize n);
+};
+
 class file_appender : public appender {
 public:
-  static std::shared_ptr<appender> create(properties const&);
+  static std::shared_ptr<appender> create(properties const & props);
   
-  explicit file_appender(const std::string& fname, bool append);
+  explicit file_appender(properties const & props);
   
   void write(logpacket const & pkt) override;
   void flush() override;
 private:
   bool flush_immediately_{false};
-  std::ofstream out_;
+  buffer rdbuf_;
+  std::ostream out_;
+  std::unique_ptr<char[]> buffer_;
 };
 
 using namespace logging::property;
@@ -39,25 +46,41 @@ properties const DEFAULT_FILE_APPENDER{_obj({
   {"immediateFlush", _v(true)},
   {"threshold", _v("ERROR")},
   {"filename", {}}, // Will throw if accessed
-  {"fileAppend", _v(true)}/*,
+  {"fileAppend", _v(true)},
   {"bufferedIO", _v(false)},
-  {"bufferSize", _v(8192)}*/
+  {"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));
+  return std::make_shared<file_appender>(actual);
 }
 
 static std::ios_base::openmode mode(bool append) {
-  return (append ? std::ios_base::app : 0) | std::ios_base::out;
+  return append ? std::ios_base::app : std::ios_base::out;
+}
+
+buffer::buffer(properties const & props) : std::filebuf() {
+  if (open(props["filename"], mode(props["fileAppend"])) == nullptr) {
+    throw std::runtime_error("Cannot open file");
+  }
 }
 
-file_appender::file_appender(const std::string& fname, bool append)
-: out_(fname, mode(append)) {}
+buffer * buffer::setbuf(char* data, std::streamsize n) {
+  std::filebuf::setbuf(data, n);
+  return this;
+}
+
+file_appender::file_appender(properties const & props)
+: appender(level_from_string(props["threshold"])),
+  flush_immediately_(props["immediateFlush"]),
+  rdbuf_(props), out_(&rdbuf_) {
+  if (props["bufferedIO"]) {
+    int const size = props["bufferSize"];
+    buffer_.reset(new char[size]);
+    rdbuf_.setbuf(buffer_.get(), size);
+  }
+}
 
 void file_appender::write(logpacket const & pkt) {
   layout->format(out_, pkt);

+ 90 - 0
src/loggers/json_layout.cxx

@@ -0,0 +1,90 @@
+//
+//  json_layout.cxx
+//  logging
+//
+//  Created by Sam Jaffe on 4/7/19.
+//
+
+#include <iterator>
+#include <regex>
+
+#include "resource_factory/prototype_factory.hpp"
+
+#include "common.h"
+#include "logger/detail/layout.h"
+#include "logger/log_manager.h"
+#include "logger/logpacket.h"
+#include "logger/properties.h"
+
+using namespace logging;
+
+class json_layout : public layout {
+public:
+  static std::shared_ptr<layout> create(properties const &);
+  
+  json_layout(properties const & props);
+  void format(std::ostream & os, logpacket const & pkt) const override;
+  
+private:
+  Json::StreamWriterBuilder build;
+  bool eol_, log_as_json_, include_location_;
+};
+
+using namespace logging::property;
+properties const DEFAULT_JSON_LAYOUT{_obj({
+  {"charset", _v("en_US.UTF-8")},
+  {"compact", _v(false)},
+  {"eventEol", _v(false)},
+  {"complete", _v(false)},
+  {"locationInfo", _v(false)},
+  {"includeNullDelimiter", _v(false)},
+  {"objectMessageAsJsonObject", _v(false)}
+})};
+
+std::shared_ptr<layout> json_layout::create(properties const & props) {
+  properties const actual = DEFAULT_JSON_LAYOUT.mergedWith(props);
+  return std::make_shared<json_layout>(actual);
+}
+
+json_layout::json_layout(properties const & props)
+: eol_(props["eventEol"]),
+  log_as_json_(props["objectMessageAsJsonObject"]),
+  include_location_(props["locationInfo"])
+{
+  build["indentation"] = props["compact"] ? "" : "  ";
+}
+
+char const * filename(char const * str) {
+  return strrchr(str, '/')+1;
+}
+
+void json_layout::format(std::ostream & os, logpacket const & pkt) const {
+  Json::Value root;
+  root["instant"]["epochSecond"] = (Json::UInt64) pkt.time.tv_sec;
+  root["instant"]["nanoOfSecond"] = pkt.time.tv_usec * 1000;
+  root["level"] = to_string(pkt.level, true);
+  root["loggerName"] = pkt.logger;
+  if (log_as_json_) {
+    root["message"] = pkt.message.json();
+  } else {
+    root["message"] = pkt.message.str();
+  }
+  if (include_location_) {
+    root["source"]["method"] = pkt.info.function;
+    root["source"]["file"] = filename(pkt.info.filename);
+    root["source"]["line"] = pkt.info.line;
+  }
+  std::string data = Json::writeString(build, root);
+  // jsoncpp is dumb and writes a newline after each ':' token if
+  // the child is an object or array.
+  std::regex_replace(std::ostreambuf_iterator<char>(os),
+                     data.begin(), data.end(),
+                     std::regex(": \n(  )*(\\{|\\[)"), ": $2");
+  if (eol_) {
+    os << NEWLINE_TOKEN;
+  }
+}
+
+namespace {
+  bool _ = layouts::instance().bind("JsonLayout", json_layout::create);
+}

+ 17 - 4
src/loggers/pattern_layout.cxx

@@ -17,18 +17,31 @@ using namespace logging;
 struct pattern_layout : public layout {
   static std::shared_ptr<layout> create(properties const &);
   
-  pattern_layout(std::string const & fmt);
+  pattern_layout(properties const & props);
   void format(std::ostream & os, logpacket const & pkt) const override;
   
   class format formatter;
 };
 
+using namespace logging::property;
+properties const DEFAULT_PATTERN_LAYOUT{_obj({
+  {"charset", _v("en_US.UTF-8")},
+  {"pattern", {}},
+  {"patternSelector", {}},
+  {"replace", _v("")},
+  {"header", _v("")},
+  {"footer", _v("")},
+  {"disableAnsi", _v(false)},
+  {"noConsoleNoAnsi", _v(false)}
+})};
+
 std::shared_ptr<layout> pattern_layout::create(properties const & props) {
-  return std::make_shared<pattern_layout>(props["pattern"]);
+  properties const actual = DEFAULT_PATTERN_LAYOUT.mergedWith(props);
+  return std::make_shared<pattern_layout>(actual);
 }
 
-pattern_layout::pattern_layout(std::string const & fmt) :
-  formatter(format::parse_format_string(fmt)) {}
+pattern_layout::pattern_layout(properties const & props) :
+  formatter(format::parse_format_string(props["pattern"])) {}
 
 void pattern_layout::format(std::ostream & os, logpacket const & pkt) const {
   os << formatter.process(pkt);

+ 190 - 0
test/json_layout_test.cxx

@@ -0,0 +1,190 @@
+//
+//  json_layout_test.cxx
+//  logger_test
+//
+//  Created by Sam Jaffe on 4/13/19.
+//
+
+#include <gmock/gmock.h>
+
+#include "resource_factory/prototype_factory.hpp"
+
+#include "logger/detail/layout.h"
+#include "logger/log_manager.h"
+#include "logger/logpacket.h"
+#include "logger/properties.h"
+
+TEST(JsonLayoutTest, CanConstructWithNoConfig) {
+  EXPECT_NO_THROW(logging::layouts::instance().get("JsonLayout", {}));
+}
+
+// Thursday, April 4, 2019 6:17:20 PM GMT
+namespace {
+  constexpr const int NOW = 1554401840;
+}
+
+std::string const formatted_output = R"({
+  "instant" : {
+    "epochSecond" : 1554401840,
+    "nanoOfSecond" : 123456000
+  },
+  "level" : "WARNING",
+  "loggerName" : "UnitTest",
+  "message" : "This is a test message"
+})";
+
+TEST(JsonLayoutTest, LogsInformationInJSON) {
+  using namespace logging;
+  auto playout = layouts::instance().get("JsonLayout", {});
+  
+  std::stringstream ss;
+  playout->format(ss, {{NOW, 123456}, level::warning, {}, "UnitTest",
+    "This is a test message"});
+  
+  using testing::Eq;
+  EXPECT_THAT(ss.str(), Eq(formatted_output));
+}
+
+std::string const compact_output = "{\"instant\":{\"epochSecond\":"
+  "1554401840,\"nanoOfSecond\":123456000},\"level\":\"WARNING\","
+  "\"loggerName\":\"UnitTest\",\"message\":\"This is a test message\"}";
+
+TEST(JsonLayoutTest, CompactMeansNoWhitespace) {
+  using namespace logging;
+  using namespace logging::property;
+  properties const props{_obj({
+    {"compact", _v(true)}
+  })};
+  auto playout = layouts::instance().get("JsonLayout", props);
+  
+  std::stringstream ss;
+  playout->format(ss, {{NOW, 123456}, level::warning, {}, "UnitTest",
+    "This is a test message"});
+  
+  using testing::Eq;
+  EXPECT_THAT(ss.str(), Eq(compact_output));
+}
+
+std::string const location_output = R"({
+  "instant" : {
+    "epochSecond" : 1554401840,
+    "nanoOfSecond" : 123456000
+  },
+  "level" : "WARNING",
+  "loggerName" : "UnitTest",
+  "message" : "This is a test message",
+  "source" : {
+    "file" : "json_layout_test.cxx",
+    "line" : 92,
+    "method" : "TestBody"
+  }
+})";
+
+TEST(JsonLayoutTest, ShowsLocationInfo) {
+  using namespace logging;
+  using namespace logging::property;
+  properties const props{_obj({
+    {"locationInfo", _v(true)}
+  })};
+  auto playout = layouts::instance().get("JsonLayout", props);
+
+  std::stringstream ss;
+  playout->format(ss, {{NOW, 123456}, level::warning, log_here, "UnitTest",
+    "This is a test message"});
+  
+  using testing::Eq;
+  EXPECT_THAT(ss.str(), Eq(location_output));
+}
+
+TEST(JsonLayoutTest, EOLPropertyAppendsNewline) {
+  using namespace logging;
+  using namespace logging::property;
+  properties const props{_obj({
+    {"eventEol", _v(true)}
+  })};
+  auto playout = layouts::instance().get("JsonLayout", props);
+  
+  std::stringstream ss;
+  playout->format(ss, {{NOW, 123456}, level::warning, {}, "UnitTest",
+    "This is a test message"});
+  
+  using testing::EndsWith;
+  using testing::StartsWith;
+  EXPECT_THAT(ss.str(), StartsWith(formatted_output));
+  EXPECT_THAT(ss.str(), EndsWith(logging::NEWLINE_TOKEN));
+}
+
+std::string const struct_output = R"({
+  "instant" : {
+    "epochSecond" : 1554401840,
+    "nanoOfSecond" : 123456000
+  },
+  "level" : "WARNING",
+  "loggerName" : "UnitTest",
+  "message" : "{225}"
+})";
+
+struct example {
+  int content;
+};
+std::ostream & operator<<(std::ostream & os, example const & ex) {
+  return os << '{' << ex.content << '}';
+}
+
+std::string const struct_json_output = R"({
+  "instant" : {
+    "epochSecond" : 1554401840,
+    "nanoOfSecond" : 123456000
+  },
+  "level" : "WARNING",
+  "loggerName" : "UnitTest",
+  "message" : {
+    "content" : 225
+  }
+})";
+
+struct json_example {
+  int content;
+};
+std::ostream & operator<<(std::ostream & os, json_example const & ex) {
+  return os << '{' << ex.content << '}';
+}
+Json::Value to_json(json_example const & ex) {
+  Json::Value json;
+  json["content"] = ex.content;
+  return json;
+}
+
+TEST(JsonLayoutTest, MessageCanBeLoggedAsJSON) {
+  using namespace logging;
+  using namespace logging::property;
+  properties const props{_obj({
+    {"objectMessageAsJsonObject", _v(true)}
+  })};
+  auto playout = layouts::instance().get("JsonLayout", props);
+  
+  std::stringstream ss;
+  json_example ex{225};
+  playout->format(ss, {{NOW, 123456}, level::warning, {}, "UnitTest",
+    {"This is a test message", ex}});
+  
+  using testing::Eq;
+  EXPECT_THAT(ss.str(), Eq(struct_json_output));
+}
+
+TEST(JsonLayoutTest, ObjectLackingJsonDefaultsToString) {
+  using namespace logging;
+  using namespace logging::property;
+  properties const props{_obj({
+    {"objectMessageAsJsonObject", _v(true)}
+  })};
+  auto playout = layouts::instance().get("JsonLayout", props);
+  
+  std::stringstream ss;
+  example ex{225};
+  playout->format(ss, {{NOW, 123456}, level::warning, {}, "UnitTest",
+    {"This is a test message", ex}});
+  
+  using testing::Eq;
+  EXPECT_THAT(ss.str(), Eq(struct_output));
+}