Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions core/src/utils/statistics/prometheus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,32 @@ namespace {

enum class Typed { kYes, kNo };

// Neutralizes a label value for the Prometheus text exposition format. The
// format only allows a backslash, a double quote and a line feed inside a
// quoted label value when they are escaped; a raw trailing backslash would
// escape the closing quote and pull following output into the value, and a raw
// line feed would start a new sample line. The double quote keeps the
// historical replacement with a single quote.
void AppendEscapedLabelValue(fmt::memory_buffer& buf, std::string_view value) {
for (const char c : value) {
switch (c) {
case '\\':
buf.push_back('\\');
buf.push_back('\\');
break;
case '\n':
buf.push_back('\\');
buf.push_back('n');
break;
case '"':
buf.push_back('\'');
break;
default:
buf.push_back(c);
}
}
}

template <Typed IsTyped>
class FormatBuilder final : public utils::statistics::BaseFormatBuilder {
public:
Expand Down Expand Up @@ -141,8 +167,7 @@ class FormatBuilder final : public utils::statistics::BaseFormatBuilder {
buf_.push_back(',');
}
fmt::format_to(std::back_inserter(buf_), FMT_COMPILE("{}=\""), impl::ToPrometheusLabel(label.Name()));
const auto& value = label.Value();
std::ranges::replace_copy(value, std::back_inserter(buf_), '"', '\'');
AppendEscapedLabelValue(buf_, label.Value());
buf_.push_back('"');
sep = true;
}
Expand Down
34 changes: 29 additions & 5 deletions core/src/utils/statistics/prometheus_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,19 +250,43 @@ UTEST(Converter, SolomonChildrenLabelEscaping) {

constexpr std::string_view expected = R"(
# TYPE base_key_some_key_a_________g_test gauge
base_key_some_key_a_________g_test{application="processing",child_label____________name="label.value.#$/\ _{}''1"} 76
base_key_some_key_a_________g_test{application="processing",child_label____________name="label.value.#$/\\ _{}''1"} 76
# TYPE base_key_some_key_a_________g_test1 gauge
base_key_some_key_a_________g_test1{application="processing",child_label____________name="label.value.#$/\ _{}''1"} 90
base_key_some_key_a_________g_test1{application="processing",child_label____________name="label.value.#$/\\ _{}''1"} 90
# TYPE base_key_some_key_field1 gauge
base_key_some_key_field1{application="processing",child_label____________name="label.value.#$/\ _{}2"} 3
base_key_some_key_field1{application="processing",child_label____________name="label.value.#$/\\ _{}2"} 3
# TYPE base_key_some_key_field2 gauge
base_key_some_key_field2{application="processing",child_label____________name="label.value.#$/\ _{}2"} 6.67
base_key_some_key_field2{application="processing",child_label____________name="label.value.#$/\\ _{}2"} 6.67
# TYPE base_key_some_key_field3 gauge
base_key_some_key_field3{application="processing",overridden_label____________name="overridden.label.#$/\ _{}''value"} 9999
base_key_some_key_field3{application="processing",overridden_label____________name="overridden.label.#$/\\ _{}''value"} 9999
)";
TestToMetricsPrometheus(statistics_storage, expected.substr(1), true);
}

UTEST(MetricsPrometheus, LabelValueBackslashAndNewlineEscaped) {
auto producer = [](const utils::statistics::StatisticsRequest&) {
formats::json::ValueBuilder result;
utils::statistics::SolomonChildrenAreLabelValues(result, "label_name");
// A label value with an embedded line feed and a trailing backslash.
result["a\nb\\"]["value"] = 1;
return result;
};

utils::statistics::Storage statistics_storage;
auto statistics_holder = statistics_storage.RegisterExtender("root", producer);

const auto request = utils::statistics::Request::MakeWithPrefix({}, {{"application", "processing"}});
const auto result = ToPrometheusFormat(statistics_storage, request);

// The line feed must be written as `\n` and the trailing backslash doubled.
// Otherwise the raw backslash escapes the closing quote and the raw line
// feed starts a new sample line.
EXPECT_NE(result.find(R"(label_name="a\nb\\")"), std::string::npos) << result;
// A single gauge metric: only the `# TYPE` line and the sample line, so the
// escaped value must not add a third line feed.
EXPECT_EQ(std::ranges::count(result, '\n'), 2) << result;
}

UTEST(MetricsPrometheus, SimpleStatistics) {
auto producer1 = [](const utils::statistics::StatisticsRequest&) {
formats::json::ValueBuilder result;
Expand Down
Loading