summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHenrik Edin <henrik.edin@mongodb.com>2020-01-14 13:34:10 +0000
committerA. Jesse Jiryu Davis <jesse@mongodb.com>2020-01-27 15:38:05 -0500
commit828ecc707dce616a81132d36f8504e29b27565d9 (patch)
treed7954aa03512146fa6c7317e16a070c8ad1273bf
parent0327b14dbf15fded2c1205637b56257d7992737f (diff)
downloadmongo-828ecc707dce616a81132d36f8504e29b27565d9.tar.gz
SERVER-45517 Allow logv2 to bind non-member toString and toBSON
-rw-r--r--src/mongo/logv2/attribute_storage.h30
-rw-r--r--src/mongo/logv2/log_test_v2.cpp38
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) {