From fb6e470dfe0a864a67fad86bc8c44266afb634a4 Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Fri, 22 May 2026 04:23:25 +0300 Subject: [PATCH 1/2] feat(prometheus): add helper builders for custom metrics Add PrometheusMetricBuilders.hpp with convenience helpers for constructing PrometheusMetricFamily and PrometheusSample objects: - make_prometheus_label(name, value) - make_prometheus_sample(name, value, labels, timestamp) - make_prometheus_counter/gauge/untyped(name, help, value, [labels]) - add_prometheus_counter/gauge/untyped(families&, name, help, value, [labels]) This collapses the ~8-line boilerplate for adding a custom metric in on_collect callbacks down to a single line. Updated existing tests and examples to use the new helpers. Added dedicated prometheus_metric_builders_test with 10 test cases. Scope-risk: narrow Co-Authored-By: Claude Opus 4.7 --- examples/example_logit_prometheus_server.cpp | 18 +-- .../prometheus/PrometheusMetricBuilders.hpp | 152 ++++++++++++++++++ tests/CMakeLists.txt | 2 + tests/prometheus_http_server_logger_test.cpp | 11 +- tests/prometheus_metric_builders_test.cpp | 121 ++++++++++++++ tests/prometheus_payload_logger_test.cpp | 11 +- 6 files changed, 288 insertions(+), 27 deletions(-) create mode 100644 include/logit_cpp/logit/loggers/prometheus/PrometheusMetricBuilders.hpp create mode 100644 tests/prometheus_metric_builders_test.cpp diff --git a/examples/example_logit_prometheus_server.cpp b/examples/example_logit_prometheus_server.cpp index b8a3ee2..6eecf1c 100644 --- a/examples/example_logit_prometheus_server.cpp +++ b/examples/example_logit_prometheus_server.cpp @@ -1,5 +1,9 @@ #include +#ifdef LOGIT_WITH_PROMETHEUS_SERVER +#include +#endif + int main() { #ifndef LOGIT_WITH_PROMETHEUS_SERVER LOGIT_ADD_CONSOLE_DEFAULT(); @@ -15,15 +19,11 @@ int main() { // Optional: add custom metrics on each scrape config.on_collect = [](std::vector& families) { - logit::PrometheusMetricFamily mf; - mf.name = "myapp_uptime_seconds"; - mf.help = "Application uptime in seconds"; - mf.type = logit::PrometheusMetricType::Gauge; - logit::PrometheusSample s; - s.name = "myapp_uptime_seconds"; - s.value = 42.0; - mf.samples.push_back(s); - families.push_back(mf); + logit::add_prometheus_gauge( + families, + "myapp_uptime_seconds", + "Application uptime in seconds", + 42.0); }; LOGIT_ADD_LOGGER( diff --git a/include/logit_cpp/logit/loggers/prometheus/PrometheusMetricBuilders.hpp b/include/logit_cpp/logit/loggers/prometheus/PrometheusMetricBuilders.hpp new file mode 100644 index 0000000..bc743e0 --- /dev/null +++ b/include/logit_cpp/logit/loggers/prometheus/PrometheusMetricBuilders.hpp @@ -0,0 +1,152 @@ +#pragma once +#ifndef _LOGIT_PROMETHEUS_METRIC_BUILDERS_HPP_INCLUDED +#define _LOGIT_PROMETHEUS_METRIC_BUILDERS_HPP_INCLUDED + +/// \file PrometheusMetricBuilders.hpp +/// \brief Convenience helpers for constructing Prometheus metric families and samples. + +#include "PrometheusTextFormatConfig.hpp" + +#include +#include + +namespace logit { + + /// \brief Create a single label pair. + inline PrometheusLabel make_prometheus_label( + const std::string& name, + const std::string& value) { + PrometheusLabel label; + label.name = name; + label.value = value; + return label; + } + + /// \brief Create a sample for a Prometheus metric family. + inline PrometheusSample make_prometheus_sample( + const std::string& name, + double value, + std::vector labels = {}, + int64_t timestamp_ms = 0) { + PrometheusSample s; + s.name = name; + s.value = value; + s.labels = std::move(labels); + s.timestamp_ms = timestamp_ms; + return s; + } + + /// \brief Create a Counter metric family with one unlabeled sample. + inline PrometheusMetricFamily make_prometheus_counter( + const std::string& name, + const std::string& help, + double value) { + PrometheusMetricFamily mf; + mf.name = name; + mf.help = help; + mf.type = PrometheusMetricType::Counter; + mf.samples.push_back(make_prometheus_sample(name, value)); + return mf; + } + + /// \brief Create a Counter metric family with one labeled sample. + inline PrometheusMetricFamily make_prometheus_counter( + const std::string& name, + const std::string& help, + double value, + std::vector labels) { + PrometheusMetricFamily mf; + mf.name = name; + mf.help = help; + mf.type = PrometheusMetricType::Counter; + mf.samples.push_back(make_prometheus_sample(name, value, std::move(labels))); + return mf; + } + + /// \brief Create a Gauge metric family with one unlabeled sample. + inline PrometheusMetricFamily make_prometheus_gauge( + const std::string& name, + const std::string& help, + double value) { + PrometheusMetricFamily mf; + mf.name = name; + mf.help = help; + mf.type = PrometheusMetricType::Gauge; + mf.samples.push_back(make_prometheus_sample(name, value)); + return mf; + } + + /// \brief Create a Gauge metric family with one labeled sample. + inline PrometheusMetricFamily make_prometheus_gauge( + const std::string& name, + const std::string& help, + double value, + std::vector labels) { + PrometheusMetricFamily mf; + mf.name = name; + mf.help = help; + mf.type = PrometheusMetricType::Gauge; + mf.samples.push_back(make_prometheus_sample(name, value, std::move(labels))); + return mf; + } + + /// \brief Create an Untyped metric family with one unlabeled sample. + inline PrometheusMetricFamily make_prometheus_untyped( + const std::string& name, + const std::string& help, + double value) { + PrometheusMetricFamily mf; + mf.name = name; + mf.help = help; + mf.type = PrometheusMetricType::Untyped; + mf.samples.push_back(make_prometheus_sample(name, value)); + return mf; + } + + /// \brief Create an Untyped metric family with one labeled sample. + inline PrometheusMetricFamily make_prometheus_untyped( + const std::string& name, + const std::string& help, + double value, + std::vector labels) { + PrometheusMetricFamily mf; + mf.name = name; + mf.help = help; + mf.type = PrometheusMetricType::Untyped; + mf.samples.push_back(make_prometheus_sample(name, value, std::move(labels))); + return mf; + } + + /// \brief Append a Counter metric family to the families vector (convenience for on_collect). + inline void add_prometheus_counter( + std::vector& families, + const std::string& name, + const std::string& help, + double value, + std::vector labels = {}) { + families.push_back(make_prometheus_counter(name, help, value, std::move(labels))); + } + + /// \brief Append a Gauge metric family to the families vector (convenience for on_collect). + inline void add_prometheus_gauge( + std::vector& families, + const std::string& name, + const std::string& help, + double value, + std::vector labels = {}) { + families.push_back(make_prometheus_gauge(name, help, value, std::move(labels))); + } + + /// \brief Append an Untyped metric family to the families vector (convenience for on_collect). + inline void add_prometheus_untyped( + std::vector& families, + const std::string& name, + const std::string& help, + double value, + std::vector labels = {}) { + families.push_back(make_prometheus_untyped(name, help, value, std::move(labels))); + } + +} // namespace logit + +#endif // _LOGIT_PROMETHEUS_METRIC_BUILDERS_HPP_INCLUDED diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0ca98c1..9d15517 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -57,6 +57,7 @@ else() prometheus_text_serializer_test.cpp prometheus_payload_logger_test.cpp prometheus_http_server_logger_test.cpp + prometheus_metric_builders_test.cpp per_logger_isolation_test.cpp per_logger_mixed_mode_test.cpp printf_format_macros_test.cpp @@ -92,6 +93,7 @@ else() list(REMOVE_ITEM TEST_SOURCES prometheus_text_serializer_test.cpp) list(REMOVE_ITEM TEST_SOURCES prometheus_payload_logger_test.cpp) list(REMOVE_ITEM TEST_SOURCES prometheus_http_server_logger_test.cpp) + list(REMOVE_ITEM TEST_SOURCES prometheus_metric_builders_test.cpp) endif() if(LOGIT_WITH_PROMETHEUS AND NOT LOGIT_WITH_PROMETHEUS_SERVER) list(REMOVE_ITEM TEST_SOURCES prometheus_http_server_logger_test.cpp) diff --git a/tests/prometheus_http_server_logger_test.cpp b/tests/prometheus_http_server_logger_test.cpp index 5c4c7f4..2d895b4 100644 --- a/tests/prometheus_http_server_logger_test.cpp +++ b/tests/prometheus_http_server_logger_test.cpp @@ -1,5 +1,6 @@ #include #include +#include #if defined(LOGIT_WITH_PROMETHEUS_SERVER) @@ -93,15 +94,7 @@ int main() { logit::PrometheusHttpServerLogger::Config config; config.port = 43194; config.on_collect = [](std::vector& families) { - logit::PrometheusMetricFamily mf; - mf.name = "custom_metric"; - mf.help = "A custom metric"; - mf.type = logit::PrometheusMetricType::Gauge; - logit::PrometheusSample s; - s.name = "custom_metric"; - s.value = 77.0; - mf.samples.push_back(s); - families.push_back(mf); + logit::add_prometheus_gauge(families, "custom_metric", "A custom metric", 77.0); }; config.start_immediately = false; diff --git a/tests/prometheus_metric_builders_test.cpp b/tests/prometheus_metric_builders_test.cpp new file mode 100644 index 0000000..34b6075 --- /dev/null +++ b/tests/prometheus_metric_builders_test.cpp @@ -0,0 +1,121 @@ +#include +#include + +#ifdef LOGIT_WITH_PROMETHEUS + +#include +#include + +int main() { + // Test 1: make_prometheus_label + { + auto label = logit::make_prometheus_label("env", "production"); + assert(label.name == "env"); + assert(label.value == "production"); + } + + // Test 2: make_prometheus_sample (unlabeled) + { + auto sample = logit::make_prometheus_sample("cpu_seconds_total", 123.45); + assert(sample.name == "cpu_seconds_total"); + assert(sample.value == 123.45); + assert(sample.labels.empty()); + assert(sample.timestamp_ms == 0); + } + + // Test 3: make_prometheus_sample (with labels and timestamp) + { + auto sample = logit::make_prometheus_sample( + "cpu_seconds_total", 67.89, + {logit::make_prometheus_label("cpu", "0")}, + 1710000000123LL); + assert(sample.name == "cpu_seconds_total"); + assert(sample.value == 67.89); + assert(sample.labels.size() == 1); + assert(sample.labels[0].name == "cpu"); + assert(sample.labels[0].value == "0"); + assert(sample.timestamp_ms == 1710000000123LL); + } + + // Test 4: make_prometheus_counter (unlabeled) + { + auto mf = logit::make_prometheus_counter("requests_total", "Total requests", 42.0); + assert(mf.name == "requests_total"); + assert(mf.help == "Total requests"); + assert(mf.type == logit::PrometheusMetricType::Counter); + assert(mf.samples.size() == 1); + assert(mf.samples[0].name == "requests_total"); + assert(mf.samples[0].value == 42.0); + assert(mf.samples[0].labels.empty()); + } + + // Test 5: make_prometheus_counter (labeled) + { + auto mf = logit::make_prometheus_counter( + "requests_total", "Total requests", 7.0, + {logit::make_prometheus_label("method", "GET")}); + assert(mf.samples[0].labels.size() == 1); + assert(mf.samples[0].labels[0].name == "method"); + assert(mf.samples[0].labels[0].value == "GET"); + } + + // Test 6: make_prometheus_gauge (unlabeled) + { + auto mf = logit::make_prometheus_gauge("queue_size", "Current queue size", 3.0); + assert(mf.name == "queue_size"); + assert(mf.help == "Current queue size"); + assert(mf.type == logit::PrometheusMetricType::Gauge); + assert(mf.samples[0].value == 3.0); + } + + // Test 7: make_prometheus_gauge (labeled) + { + auto mf = logit::make_prometheus_gauge( + "temperature_celsius", "Room temperature", 22.5, + {logit::make_prometheus_label("room", "server_room_a")}); + assert(mf.type == logit::PrometheusMetricType::Gauge); + assert(mf.samples[0].labels[0].value == "server_room_a"); + } + + // Test 8: make_prometheus_untyped (unlabeled) + { + auto mf = logit::make_prometheus_untyped("raw_value", "Some raw value", 99.0); + assert(mf.name == "raw_value"); + assert(mf.type == logit::PrometheusMetricType::Untyped); + assert(mf.samples[0].value == 99.0); + } + + // Test 9: add_prometheus_counter via vector + { + std::vector families; + logit::add_prometheus_counter(families, "errors_total", "Total errors", 5.0); + assert(families.size() == 1); + assert(families[0].name == "errors_total"); + assert(families[0].type == logit::PrometheusMetricType::Counter); + + logit::add_prometheus_gauge( + families, "active_sessions", "Active sessions", 12.0, + {logit::make_prometheus_label("region", "eu-west")}); + assert(families.size() == 2); + assert(families[1].name == "active_sessions"); + assert(families[1].samples[0].labels[0].value == "eu-west"); + } + + // Test 10: add_prometheus_untyped via vector + { + std::vector families; + logit::add_prometheus_untyped(families, "misc", "Misc value", 0.0); + assert(families.size() == 1); + assert(families[0].type == logit::PrometheusMetricType::Untyped); + } + + return 0; +} + +#else + +int main() { + return 0; +} + +#endif diff --git a/tests/prometheus_payload_logger_test.cpp b/tests/prometheus_payload_logger_test.cpp index e840ee6..345a66e 100644 --- a/tests/prometheus_payload_logger_test.cpp +++ b/tests/prometheus_payload_logger_test.cpp @@ -1,5 +1,6 @@ #include #include +#include #ifdef LOGIT_WITH_PROMETHEUS @@ -111,15 +112,7 @@ int main() { { logit::PrometheusPayloadLogger::Config config; config.on_collect = [](std::vector& families) { - logit::PrometheusMetricFamily mf; - mf.name = "custom_metric"; - mf.help = "A custom metric"; - mf.type = logit::PrometheusMetricType::Gauge; - logit::PrometheusSample s; - s.name = "custom_metric"; - s.value = 99.0; - mf.samples.push_back(s); - families.push_back(mf); + logit::add_prometheus_gauge(families, "custom_metric", "A custom metric", 99.0); }; auto logger = std::unique_ptr(new logit::PrometheusPayloadLogger(config)); From 650167cc24d4764eccd20be18b6446525956208e Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Fri, 22 May 2026 04:39:28 +0300 Subject: [PATCH 2/2] fix(prometheus): add missing include to PrometheusMetricBuilders PrometheusMetricBuilders.hpp uses std::move but did not explicitly include . It happened to compile transitively, but the header must be self-contained. Also remove unused from the builders test. Co-Authored-By: Claude Opus 4.7 --- .../logit/loggers/prometheus/PrometheusMetricBuilders.hpp | 1 + tests/prometheus_metric_builders_test.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/include/logit_cpp/logit/loggers/prometheus/PrometheusMetricBuilders.hpp b/include/logit_cpp/logit/loggers/prometheus/PrometheusMetricBuilders.hpp index bc743e0..5677c3e 100644 --- a/include/logit_cpp/logit/loggers/prometheus/PrometheusMetricBuilders.hpp +++ b/include/logit_cpp/logit/loggers/prometheus/PrometheusMetricBuilders.hpp @@ -8,6 +8,7 @@ #include "PrometheusTextFormatConfig.hpp" #include +#include #include namespace logit { diff --git a/tests/prometheus_metric_builders_test.cpp b/tests/prometheus_metric_builders_test.cpp index 34b6075..99bd516 100644 --- a/tests/prometheus_metric_builders_test.cpp +++ b/tests/prometheus_metric_builders_test.cpp @@ -1,4 +1,3 @@ -#include #include #ifdef LOGIT_WITH_PROMETHEUS