diff options
author | Henrik Edin <henrik.edin@mongodb.com> | 2020-01-14 13:34:10 +0000 |
---|---|---|
committer | A. Jesse Jiryu Davis <jesse@mongodb.com> | 2020-01-27 15:38:05 -0500 |
commit | 828ecc707dce616a81132d36f8504e29b27565d9 (patch) | |
tree | d7954aa03512146fa6c7317e16a070c8ad1273bf | |
parent | 0327b14dbf15fded2c1205637b56257d7992737f (diff) | |
download | mongo-828ecc707dce616a81132d36f8504e29b27565d9.tar.gz |
SERVER-45517 Allow logv2 to bind non-member toString and toBSON
-rw-r--r-- | src/mongo/logv2/attribute_storage.h | 30 | ||||
-rw-r--r-- | src/mongo/logv2/log_test_v2.cpp | 38 |
2 files changed, 66 insertions, 2 deletions
diff --git a/src/mongo/logv2/attribute_storage.h b/src/mongo/logv2/attribute_storage.h index e0d0da24c71..22bcfdb006b 100644 --- a/src/mongo/logv2/attribute_storage.h +++ b/src/mongo/logv2/attribute_storage.h @@ -141,6 +141,19 @@ struct HasToString : std::false_type {}; template <class T> struct HasToString<T, std::void_t<decltype(std::declval<T>().toString())>> : std::true_type {}; +template <class T, class = void> +struct HasNonMemberToString : std::false_type {}; + +template <class T> +struct HasNonMemberToString<T, std::void_t<decltype(toString(std::declval<T>()))>> + : std::true_type {}; + +template <class T, class = void> +struct HasNonMemberToBSON : std::false_type {}; + +template <class T> +struct HasNonMemberToBSON<T, std::void_t<decltype(toBSON(std::declval<T>()))>> : std::true_type {}; + } // namespace // Mapping functions on how to map a logged value to how it is stored in variant (reused by @@ -209,7 +222,13 @@ inline CustomAttributeValue mapValue(boost::none_t val) { template <typename T, std::enable_if_t<std::is_enum_v<T>, int> = 0> auto mapValue(T val) { - return mapValue(static_cast<std::underlying_type_t<T>>(val)); + if constexpr (HasNonMemberToString<T>::value) { + CustomAttributeValue custom; + custom.toString = [val]() { return toString(val); }; + return custom; + } else { + return mapValue(static_cast<std::underlying_type_t<T>>(val)); + } } template <typename T, std::enable_if_t<IsContainer<T>::value && !HasMappedType<T>::value, int> = 0> @@ -233,7 +252,8 @@ template <typename T, !std::is_enum_v<T> && !IsContainer<T>::value, int> = 0> CustomAttributeValue mapValue(const T& val) { - static_assert(HasToString<T>::value || HasStringSerialize<T>::value, + static_assert(HasToString<T>::value || HasStringSerialize<T>::value || + HasNonMemberToString<T>::value, "custom type needs toString() or serialize(fmt::memory_buffer&) implementation"); CustomAttributeValue custom; @@ -250,11 +270,17 @@ CustomAttributeValue mapValue(const T& val) { }; } else if constexpr (HasToBSONArray<T>::value) { custom.toBSONArray = [&val]() { return val.toBSONArray(); }; + } else if constexpr (HasNonMemberToBSON<T>::value) { + custom.BSONSerialize = [&val](BSONObjBuilder& builder) { + builder.appendElements(toBSON(val)); + }; } if constexpr (HasStringSerialize<T>::value) { custom.stringSerialize = [&val](fmt::memory_buffer& buffer) { val.serialize(buffer); }; } else if constexpr (HasToString<T>::value) { custom.toString = [&val]() { return val.toString(); }; + } else if constexpr (HasNonMemberToString<T>::value) { + custom.toString = [&val]() { return toString(val); }; } return custom; diff --git a/src/mongo/logv2/log_test_v2.cpp b/src/mongo/logv2/log_test_v2.cpp index 7e616bad34a..6a2726d9f0e 100644 --- a/src/mongo/logv2/log_test_v2.cpp +++ b/src/mongo/logv2/log_test_v2.cpp @@ -145,6 +145,24 @@ struct TypeWithBSONArray { } }; +enum UnscopedEnumWithToString { UnscopedEntryWithToString }; + +std::string toString(UnscopedEnumWithToString val) { + return "UnscopedEntryWithToString"; +} + +struct TypeWithNonMemberFormatting {}; + +std::string toString(const TypeWithNonMemberFormatting&) { + return "TypeWithNonMemberFormatting"; +} + +BSONObj toBSON(const TypeWithNonMemberFormatting&) { + BSONObjBuilder builder; + builder.append("first"_sd, "TypeWithNonMemberFormatting"); + return builder.obj(); +} + class LogTestBackend : public boost::log::sinks:: basic_formatted_sink_backend<char, boost::log::sinks::synchronized_feeding> { @@ -385,6 +403,12 @@ TEST_F(LogTestV2, Types) { validateJSON(expectedScoped); ASSERT_EQUALS(lastBSONElement().Number(), expectedScoped); + LOGV2("{}", "name"_attr = UnscopedEntryWithToString); + ASSERT_EQUALS(text.back(), toString(UnscopedEntryWithToString)); + validateJSON(toString(UnscopedEntryWithToString)); + ASSERT_EQUALS(lastBSONElement().String(), toString(UnscopedEntryWithToString)); + + // string types const char* c_str = "a c string"; LOGV2("c string {}", "name"_attr = c_str); @@ -603,6 +627,10 @@ TEST_F(LogTestV2, TextFormat) { TypeWithoutBSON t2(1.0, 2.0); LOGV2("{} custom formatting, no bson", "name"_attr = t2); ASSERT(lines.back().rfind(t.toString() + " custom formatting, no bson") != std::string::npos); + + TypeWithNonMemberFormatting t3; + LOGV2("{}", "name"_attr = t3); + ASSERT(lines.back().rfind(toString(t3)) != std::string::npos); } TEST_F(LogTestV2, JsonBsonFormat) { @@ -768,6 +796,16 @@ TEST_F(LogTestV2, JsonBsonFormat) { }; validateCustomAttrBSONArray(mongo::fromjson(lines.back())); validateCustomAttrBSONArray(BSONObj(linesBson.back().data())); + + TypeWithNonMemberFormatting t6; + LOGV2("{}", "name"_attr = t6); + auto validateNonMemberToBSON = [&t6](const BSONObj& obj) { + ASSERT( + obj.getField(kAttributesFieldName).Obj().getField("name").Obj().woCompare(toBSON(t6)) == + 0); + }; + validateNonMemberToBSON(mongo::fromjson(lines.back())); + validateNonMemberToBSON(BSONObj(linesBson.back().data())); } TEST_F(LogTestV2, Containers) { |