// // json_layout_test.cxx // logger_test // // Created by Sam Jaffe on 4/13/19. // #include #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" : 97, "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)); } #include "header_test_obj.h" #include "logger/logger.h" using namespace logging; using namespace logging::property; // clang-format off properties const JSON_HEADER_SCHEMA{_obj({ {"configuration", _obj({ {"appenders", _obj({ {"Stub", _obj({ {"JsonLayout", _obj({ {"complete", _v(true)}, {"compact", _v(true)}, {"eventEol", _v(true)} })} })} })}, {"loggers", _obj({ {"root", _obj({{"appenders", _obj({{"ref", _v("Stub")}})}})} })} })} })}; // clang-format on using JsonLayoutHeaderTest = HeaderFooterTest; TEST_F(JsonLayoutHeaderTest, ProvidesArrayBounds) { { manager mgr; mgr.configure(JSON_HEADER_SCHEMA); } using testing::Eq; EXPECT_THAT(appender->sstream.str(), Eq("[]")); } TEST_F(JsonLayoutHeaderTest, SeparatesLogsWithComma) { using testing::EndsWith; using testing::StartsWith; manager mgr; mgr.configure(JSON_HEADER_SCHEMA); mgr.get().log(level::error, "HELLO"); // Newline is printed as a part of the message... EXPECT_THAT(appender->sstream.str(), EndsWith("}\n")); appender->sstream.str(""); mgr.get().log(level::error, "HELLO"); // So the dividing comma gets attached to the next log EXPECT_THAT(appender->sstream.str(), StartsWith(",{")); } struct JsonLayoutIncompleteTest : public JsonLayoutHeaderTest { properties OVERRIDE{ _obj({{"configuration", _obj({{"appenders", _obj({{"Stub", _obj({{"JsonLayout", _obj({{"complete", _v(false)}})}})}})}})}})}; }; TEST_F(JsonLayoutIncompleteTest, NoLogsMeansNoOutput) { { manager mgr; mgr.configure(JSON_HEADER_SCHEMA.mergedWith(OVERRIDE)); } using testing::Eq; EXPECT_THAT(appender->sstream.str(), Eq("")); } TEST_F(JsonLayoutIncompleteTest, NonCompleteLogProducesJsonPerLine) { using testing::EndsWith; using testing::StartsWith; manager mgr; mgr.configure(JSON_HEADER_SCHEMA.mergedWith(OVERRIDE)); mgr.get().log(level::error, "HELLO"); // Newline is printed as a part of the message... EXPECT_THAT(appender->sstream.str(), EndsWith("}\n")); appender->sstream.str(""); mgr.get().log(level::error, "HELLO"); // So the dividing comma gets attached to the next log EXPECT_THAT(appender->sstream.str(), StartsWith("{")); }