summaryrefslogtreecommitdiff
path: root/src/mongo/logv2/json_formatter.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/logv2/json_formatter.h')
-rw-r--r--src/mongo/logv2/json_formatter.h138
1 files changed, 85 insertions, 53 deletions
diff --git a/src/mongo/logv2/json_formatter.h b/src/mongo/logv2/json_formatter.h
index d045a93c2ce..1d266fd871a 100644
--- a/src/mongo/logv2/json_formatter.h
+++ b/src/mongo/logv2/json_formatter.h
@@ -29,13 +29,16 @@
#pragma once
+#include <boost/container/small_vector.hpp>
#include <boost/log/attributes/value_visitation.hpp>
#include <boost/log/core/record_view.hpp>
#include <boost/log/expressions/message.hpp>
#include <boost/log/utility/formatting_ostream.hpp>
-#include "mongo/logv2/attribute_argument_set.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/logv2/attribute_storage.h"
#include "mongo/logv2/attributes.h"
+#include "mongo/logv2/constants.h"
#include "mongo/logv2/log_component.h"
#include "mongo/logv2/log_severity.h"
#include "mongo/logv2/log_tag.h"
@@ -46,44 +49,65 @@
namespace mongo {
namespace logv2 {
+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());
+ } else {
+ // This is a string, select overload that surrounds value with quotes
+ operator()(name, StringData(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());
+ }
+
+ void operator()(StringData name, StringData value) {
+ // The first {} is for the member separator, added by store()
+ store(R"({}"{}":"{}")", 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;
+ }
+
+ fmt::memory_buffer buffer;
+ boost::container::small_vector<fmt::basic_format_arg<fmt::format_context>,
+ constants::kNumStaticAttrs>
+ nameArgs;
+
+private:
+ StringData _separator = ""_sd;
+};
+
class JsonFormatter {
public:
static bool binary() {
return false;
};
- void operator()(boost::log::record_view const& rec, boost::log::formatting_ostream& strm) {
+ void operator()(boost::log::record_view const& rec,
+ boost::log::formatting_ostream& strm) const {
using namespace boost::log;
// Build a JSON object for the user attributes.
- const auto& attrs = extract<AttributeArgumentSet>(attributes::attributes(), rec).get();
- std::stringstream ss;
- bool first = true;
- ss << "{";
- for (std::size_t i = 0; i < attrs._names.size(); ++i) {
- if (!first)
- ss << ",";
- first = false;
- bool is_string = attrs._values.get(i).type() == fmt::internal::type::string_type ||
- attrs._values.get(i).type() == fmt::internal::type::cstring_type;
- ss << "\"" << attrs._names[i] << "\":";
- if (is_string)
- ss << "\"";
-
- // Call libfmt to extract formatted value from type erased format_args. If we have a
- // custom formatter, ask for formatting in JSON using :j format specifier.
- fmt::memory_buffer buffer;
- auto format_str = fmt::format(
- attrs._values.get(i).type() == fmt::internal::type::custom_type ? "{{{}:j}}"
- : "{{{}}}",
- i);
- fmt::vformat_to(buffer, format_str, attrs._values);
- ss << fmt::to_string(buffer);
-
- if (is_string)
- ss << "\"";
- }
- ss << "}";
+ const auto& attrs =
+ extract<TypeErasedAttributeStorage>(attributes::attributes(), rec).get();
+
+ JsonValueExtractor extractor;
+ attrs.apply(extractor);
std::string id;
auto stable_id = extract<StringData>(attributes::stableId(), rec).get();
@@ -93,17 +117,13 @@ public:
std::string message;
fmt::memory_buffer buffer;
- std::vector<fmt::basic_format_arg<fmt::format_context>> name_args;
- for (auto&& attr_name : attrs._names) {
- name_args.emplace_back(fmt::internal::make_arg<fmt::format_context>(attr_name));
- }
fmt::vformat_to<NamedArgFormatter, char>(
buffer,
extract<StringData>(attributes::message(), rec).get().toString(),
- fmt::basic_format_args<fmt::format_context>(name_args.data(), name_args.size()));
+ fmt::basic_format_args<fmt::format_context>(extractor.nameArgs.data(),
+ extractor.nameArgs.size()));
message = fmt::to_string(buffer);
-
StringData severity =
extract<LogSeverity>(attributes::severity(), rec).get().toStringDataCompact();
StringData component =
@@ -114,23 +134,35 @@ public:
tag = fmt::format(",\"tags\":{}", tags.toJSONArray());
}
- auto formatted_body = fmt::format(
- "{{\"t\":\"{}\",\"s\":\"{}\"{: <{}}\"c\":\"{}\"{: "
- "<{}}\"ctx\":\"{}\",{}\"msg\":\"{}\"{}{}{}}}",
- dateToISOStringUTC(extract<Date_t>(attributes::timeStamp(), rec).get()),
- severity,
- ",",
- 3 - severity.size(),
- component,
- ",",
- 9 - component.size(),
- extract<StringData>(attributes::threadName(), rec).get(),
- id,
- message,
- attrs._names.empty() ? "" : ",\"attr\":",
- attrs._names.empty() ? "" : ss.str(),
- tag);
- strm << formatted_body;
+ strm << fmt::format(R"({{)"
+ R"("t":"{}",)" // timestamp
+ R"("s":"{}"{: <{}})" // severity with padding for the comma
+ R"("c":"{}"{: <{}})" // component with padding for the comma
+ R"("ctx":"{}",)" // context
+ R"({})" // optional stable id
+ R"("msg":"{}")" // message
+ R"({})", // optional attribute key
+ dateToISOStringUTC(extract<Date_t>(attributes::timeStamp(), rec).get()),
+ severity,
+ ",",
+ 3 - severity.size(),
+ component,
+ ",",
+ 9 - component.size(),
+ extract<StringData>(attributes::threadName(), rec).get(),
+ id,
+ message,
+ attrs.empty() ? "" : ",\"attr\":{");
+
+ if (!attrs.empty()) {
+ strm << fmt::to_string(extractor.buffer);
+ }
+
+ strm << fmt::format(R"({})" // optional attribute closing
+ R"({})" // optional tags
+ R"(}})",
+ attrs.empty() ? "" : "}",
+ tag);
}
private: