diff options
author | Henrik Edin <henrik.edin@mongodb.com> | 2019-11-25 16:14:00 +0000 |
---|---|---|
committer | evergreen <evergreen@mongodb.com> | 2019-11-25 16:14:00 +0000 |
commit | c9875ca948f54ac678ed5b535009fd8df9a28ff0 (patch) | |
tree | cd2cda8b6ff23a199564bcf87d88966a2a01161d /src/mongo/logv2 | |
parent | 9bb0231ccb28efe34a4bcd430049b85cc1b32ea0 (diff) | |
download | mongo-c9875ca948f54ac678ed5b535009fd8df9a28ff0.tar.gz |
SERVER-44625 Add formatting of BSON native types via BSONObjBuilder::append.
Diffstat (limited to 'src/mongo/logv2')
-rw-r--r-- | src/mongo/logv2/attribute_storage.h | 22 | ||||
-rw-r--r-- | src/mongo/logv2/json_formatter.cpp | 54 | ||||
-rw-r--r-- | src/mongo/logv2/log_test_v2.cpp | 203 |
3 files changed, 143 insertions, 136 deletions
diff --git a/src/mongo/logv2/attribute_storage.h b/src/mongo/logv2/attribute_storage.h index cd6c37b785e..dbbcce1aed1 100644 --- a/src/mongo/logv2/attribute_storage.h +++ b/src/mongo/logv2/attribute_storage.h @@ -30,6 +30,7 @@ #pragma once #include "mongo/base/string_data.h" +#include "mongo/bson/bsonobjbuilder.h" #include "mongo/stdx/variant.h" #include <functional> @@ -42,6 +43,7 @@ class TypeErasedAttributeStorage; // Custom type, storage for how to call its formatters struct CustomAttributeValue { std::function<BSONObj()> toBSON; + std::function<void(BSONObjBuilder&, StringData)> BSONAppend; std::function<std::string()> toString; }; @@ -55,6 +57,17 @@ struct HasToBSON : std::false_type {}; template <class T> struct HasToBSON<T, std::void_t<decltype(std::declval<T>().toBSON())>> : std::true_type {}; + +template <class T, class = void> +struct HasBSONBuilderAppend : std::false_type {}; + +template <class T> +struct HasBSONBuilderAppend<T, + std::void_t<decltype(std::declval<BSONObjBuilder>().append( + std::declval<StringData>(), std::declval<T>()))>> : std::true_type { +}; + + template <class T, class = void> struct HasToString : std::false_type {}; @@ -91,11 +104,16 @@ public: static_assert(HasToString<T>::value, "custom type needs toString() implementation"); CustomAttributeValue custom; + if constexpr (HasBSONBuilderAppend<T>::value) { + custom.BSONAppend = [&val](BSONObjBuilder& builder, StringData fieldName) { + builder.append(fieldName, val); + }; + } if constexpr (HasToBSON<T>::value) { - custom.toBSON = std::bind(&T::toBSON, val); + custom.toBSON = [&val]() { return val.toBSON(); }; } if constexpr (HasToString<T>::value) { - custom.toString = std::bind(&T::toString, val); + custom.toString = [&val]() { return val.toString(); }; } value = std::move(custom); diff --git a/src/mongo/logv2/json_formatter.cpp b/src/mongo/logv2/json_formatter.cpp index 38d879c4f90..7dc8dc31102 100644 --- a/src/mongo/logv2/json_formatter.cpp +++ b/src/mongo/logv2/json_formatter.cpp @@ -35,6 +35,7 @@ #include <boost/log/utility/formatting_ostream.hpp> #include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsonobjbuilder.h" #include "mongo/logv2/attribute_storage.h" #include "mongo/logv2/attributes.h" #include "mongo/logv2/constants.h" @@ -49,36 +50,33 @@ namespace mongo::logv2 { namespace { struct JsonValueExtractor { void operator()(StringData name, CustomAttributeValue const& val) { - if (val.toBSON) { - // This is a JSON subobject, select overload that does not surround value with quotes - operator()(name, val.toBSON().jsonString()); + if (val.BSONAppend) { + BSONObjBuilder builder; + val.BSONAppend(builder, name); + // This is a JSON subobject, no quotes needed + storeUnquoted(name, + builder.obj().getField(name).jsonString(JsonStringFormat::Strict, false)); + } else if (val.toBSON) { + // This is a JSON subobject, no quotes needed + storeUnquoted(name, val.toBSON().jsonString()); } else { - // This is a string, select overload that surrounds value with quotes - operator()(name, StringData(val.toString())); + // This is a string, surround value with quotes + storeQuoted(name, val.toString()); } } void operator()(StringData name, const BSONObj* val) { - // This is a JSON subobject, select overload that does not surround value with quotes - operator()(name, val->jsonString()); + // This is a JSON subobject, no quotes needed + storeUnquoted(name, val->jsonString()); } void operator()(StringData name, StringData value) { - // The first {} is for the member separator, added by store() - store(R"({}"{}":"{}")", name, value); + storeQuoted(name, value); } template <typename T> void operator()(StringData name, const T& value) { - // The first {} is for the member separator, added by store() - store(R"({}"{}":{})", name, value); - } - - template <typename T> - void store(const char* fmt_str, StringData name, const T& value) { - nameArgs.push_back(fmt::internal::make_arg<fmt::format_context>(name)); - fmt::format_to(buffer, fmt_str, _separator, name, value); - _separator = ","_sd; + storeUnquoted(name, value); } fmt::memory_buffer buffer; @@ -87,6 +85,26 @@ struct JsonValueExtractor { nameArgs; private: + template <typename T> + void storeUnquoted(StringData name, const T& value) { + // The first {} is for the member separator, added by storeImpl() + storeImpl(R"({}"{}":{})", name, value); + } + + + template <typename T> + void storeQuoted(StringData name, const T& value) { + // The first {} is for the member separator, added by storeImpl() + storeImpl(R"({}"{}":"{}")", name, value); + } + + template <typename T> + void storeImpl(const char* fmt_str, StringData name, const T& value) { + nameArgs.push_back(fmt::internal::make_arg<fmt::format_context>(name)); + fmt::format_to(buffer, fmt_str, _separator, name, value); + _separator = ","_sd; + } + StringData _separator = ""_sd; }; diff --git a/src/mongo/logv2/log_test_v2.cpp b/src/mongo/logv2/log_test_v2.cpp index bdbe1225c83..b0c9afa827b 100644 --- a/src/mongo/logv2/log_test_v2.cpp +++ b/src/mongo/logv2/log_test_v2.cpp @@ -41,6 +41,7 @@ #include "mongo/bson/bsonobj.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/json.h" +#include "mongo/bson/oid.h" #include "mongo/logv2/component_settings_filter.h" #include "mongo/logv2/formatter_base.h" #include "mongo/logv2/json_formatter.h" @@ -49,8 +50,10 @@ #include "mongo/logv2/plain_formatter.h" #include "mongo/logv2/ramlog_sink.h" #include "mongo/logv2/text_formatter.h" +#include "mongo/platform/decimal128.h" #include "mongo/stdx/thread.h" #include "mongo/unittest/temp_dir.h" +#include "mongo/util/uuid.h" #include <boost/log/attributes/constant.hpp> #include <boost/property_tree/json_parser.hpp> @@ -189,158 +192,126 @@ TEST_F(LogTestV2, Types) { ASSERT(ptree.get<decltype(expected)>("attr.name") == expected); }; - // bool + auto testNumeric = [&](auto dummy) { + using T = decltype(dummy); + + auto test = [&](auto value) { + text.clear(); + LOGV2("{}", "name"_attr = value); + ASSERT_EQUALS(text.back(), fmt::format("{}", value)); + validateJSON(value); + }; + + test(std::numeric_limits<T>::max()); + test(std::numeric_limits<T>::min()); + test(std::numeric_limits<T>::lowest()); + test(static_cast<T>(-10)); + test(static_cast<T>(-2)); + test(static_cast<T>(-1)); + test(static_cast<T>(0)); + test(static_cast<T>(1)); + test(static_cast<T>(2)); + test(static_cast<T>(10)); + }; + bool b = true; LOGV2("bool {}", "name"_attr = b); - ASSERT(text.back() == "bool true"); + ASSERT_EQUALS(text.back(), "bool true"); validateJSON(b); - // char gets promoted to int char c = 1; LOGV2("char {}", "name"_attr = c); - ASSERT(text.back() == "char 1"); + ASSERT_EQUALS(text.back(), "char 1"); validateJSON(static_cast<uint8_t>( c)); // cast, boost property_tree will try and parse as ascii otherwise - // signed char gets promoted to int - signed char sc = -1; - LOGV2("signed char {}", "name"_attr = sc); - ASSERT(text.back() == "signed char -1"); - validateJSON(sc); - - // unsigned char gets promoted to unsigned int - unsigned char uc = -1; - LOGV2("unsigned char {}", "name"_attr = uc); - ASSERT(text.back() == "unsigned char 255"); - validateJSON(uc); - - // short gets promoted to int - short s = 1; - LOGV2("short {}", "name"_attr = s); - ASSERT(text.back() == "short 1"); - validateJSON(s); - - // signed short gets promoted to int - signed short ss = -1; - LOGV2("signed short {}", "name"_attr = ss); - ASSERT(text.back() == "signed short -1"); - validateJSON(ss); - - // unsigned short gets promoted to unsigned int - unsigned short us = -1; - LOGV2("unsigned short {}", "name"_attr = us); - ASSERT(text.back() == "unsigned short 65535"); - validateJSON(us); - - // int types are preserved - int i = 1; - LOGV2("int {}", "name"_attr = i); - ASSERT(text.back() == "int 1"); - validateJSON(i); - - signed int si = -1; - LOGV2("signed int {}", "name"_attr = si); - ASSERT(text.back() == "signed int -1"); - validateJSON(si); - - unsigned int ui = -1; - LOGV2("unsigned int {}", "name"_attr = ui); - ASSERT(text.back() == "unsigned int 4294967295"); - validateJSON(ui); - - // int is treated as long long - long l = 1; - LOGV2("long {}", "name"_attr = l); - ASSERT(text.back() == "long 1"); - validateJSON(l); - - signed long sl = -1; - LOGV2("signed long {}", "name"_attr = sl); - ASSERT(text.back() == "signed long -1"); - validateJSON(sl); - - unsigned long ul = -1; - LOGV2("unsigned long {}", "name"_attr = ul); - ASSERT(text.back() == fmt::format("unsigned long {}", ul)); - validateJSON(ul); - - // long long types are preserved - long long ll = std::numeric_limits<unsigned int>::max(); - ll += 1; - LOGV2("long long {}", "name"_attr = ll); - ASSERT(text.back() == std::string("long long ") + std::to_string(ll)); - validateJSON(ll); - - signed long long sll = -1; - LOGV2("signed long long {}", "name"_attr = sll); - ASSERT(text.back() == "signed long long -1"); - validateJSON(sll); - - unsigned long long ull = -1; - LOGV2("unsigned long long {}", "name"_attr = ull); - ASSERT(text.back() == "unsigned long long 18446744073709551615"); - validateJSON(ull); - - // int64_t, uint64_t, size_t are fine - int64_t int64 = 1; - LOGV2("int64_t {}", "name"_attr = int64); - ASSERT(text.back() == "int64_t 1"); - validateJSON(int64); - - uint64_t uint64 = -1; - LOGV2("uint64_t {}", "name"_attr = uint64); - ASSERT(text.back() == "uint64_t 18446744073709551615"); - validateJSON(uint64); - - size_t size = 1; - LOGV2("size_t {}", "name"_attr = size); - ASSERT(text.back() == "size_t 1"); - validateJSON(size); - - // floating point types - float f = 1.0; - LOGV2("float {}", "name"_attr = f); - ASSERT(text.back() == "float 1.0"); - validateJSON(f); - - double d = 1.0; - LOGV2("double {}", "name"_attr = d); - ASSERT(text.back() == "double 1.0"); - validateJSON(d); + testNumeric(static_cast<signed char>(0)); + testNumeric(static_cast<unsigned char>(0)); + testNumeric(static_cast<short>(0)); + testNumeric(static_cast<unsigned short>(0)); + testNumeric(0); + testNumeric(0u); + testNumeric(0l); + testNumeric(0ul); + testNumeric(0ll); + testNumeric(0ull); + testNumeric(static_cast<int64_t>(0)); + testNumeric(static_cast<uint64_t>(0)); + testNumeric(static_cast<size_t>(0)); + testNumeric(0.0f); + testNumeric(0.0); // long double is prohibited, we don't use this type and favors Decimal128 instead. // string types const char* c_str = "a c string"; LOGV2("c string {}", "name"_attr = c_str); - ASSERT(text.back() == "c string a c string"); + ASSERT_EQUALS(text.back(), "c string a c string"); validateJSON(std::string(c_str)); std::string str = "a std::string"; LOGV2("std::string {}", "name"_attr = str); - ASSERT(text.back() == "std::string a std::string"); + ASSERT_EQUALS(text.back(), "std::string a std::string"); validateJSON(str); StringData str_data = "a StringData"_sd; LOGV2("StringData {}", "name"_attr = str_data); - ASSERT(text.back() == "StringData a StringData"); + ASSERT_EQUALS(text.back(), "StringData a StringData"); validateJSON(str_data.toString()); // BSONObj BSONObjBuilder builder; - builder.append("int32"_sd, i); - builder.append("int64"_sd, ll); - builder.append("double"_sd, d); + builder.append("int32"_sd, 0); + builder.append("int64"_sd, std::numeric_limits<int64_t>::max()); + builder.append("double"_sd, 0.0); builder.append("str"_sd, str_data); BSONObj bson = builder.obj(); LOGV2("bson {}", "name"_attr = bson); - ASSERT(text.back() == std::string("bson ") + bson.jsonString()); + ASSERT_EQUALS(text.back(), std::string("bson ") + bson.jsonString()); ASSERT(mongo::fromjson(json.back()) .getField("attr"_sd) .Obj() .getField("name") .Obj() .woCompare(bson) == 0); + + // Date_t + Date_t date = Date_t::now(); + LOGV2("Date_t {}", "name"_attr = date); + ASSERT_EQUALS(text.back(), std::string("Date_t ") + date.toString()); + ASSERT_EQUALS(mongo::fromjson(json.back()).getField("attr").Obj().getField("name").Date(), + date); + + // Decimal128 + LOGV2("Decimal128 {}", "name"_attr = Decimal128::kPi); + ASSERT_EQUALS(text.back(), std::string("Decimal128 ") + Decimal128::kPi.toString()); + ASSERT(mongo::fromjson(json.back()) + .getField("attr") + .Obj() + .getField("name") + .Decimal() + .isEqual(Decimal128::kPi)); + + // OID + OID oid = OID::gen(); + LOGV2("OID {}", "name"_attr = oid); + ASSERT_EQUALS(text.back(), std::string("OID ") + oid.toString()); + ASSERT_EQUALS(mongo::fromjson(json.back()).getField("attr").Obj().getField("name").OID(), oid); + + // Timestamp + Timestamp ts = Timestamp::max(); + LOGV2("Timestamp {}", "name"_attr = ts); + ASSERT_EQUALS(text.back(), std::string("Timestamp ") + ts.toString()); + ASSERT_EQUALS(mongo::fromjson(json.back()).getField("attr").Obj().getField("name").timestamp(), + ts); + + // UUID + UUID uuid = UUID::gen(); + LOGV2("UUID {}", "name"_attr = uuid); + ASSERT_EQUALS(text.back(), std::string("UUID ") + uuid.toString()); + ASSERT_EQUALS( + UUID::parse(mongo::fromjson(json.back()).getField("attr").Obj().getField("name").Obj()), + uuid); } TEST_F(LogTestV2, TextFormat) { |