LogIt++
Header-only, macro-first C++ logging library
Loading...
Searching...
No Matches
PrometheusHttpServerLogger.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_PROMETHEUSHTTPSERVERLOGGER_HPP_INCLUDED
3#define LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_PROMETHEUSHTTPSERVERLOGGER_HPP_INCLUDED
4
7
8#ifndef LOGIT_WITH_PROMETHEUS_SERVER
9# error "PrometheusHttpServerLogger requires LOGIT_WITH_PROMETHEUS_SERVER=1. Enable LOGIT_WITH_PROMETHEUS_SERVER in CMake."
10#endif
11
12#include "ILogger.hpp"
15
16#include <server_http.hpp>
17
18#include <atomic>
19#include <chrono>
20#include <cstdint>
21#include <functional>
22#include <mutex>
23#include <string>
24#include <thread>
25#include <vector>
26
27namespace logit {
28
36 class PrometheusHttpServerLogger final : public ILogger {
37 public:
38 using HttpServer = SimpleWeb::Server<SimpleWeb::HTTP>;
39
40 struct Config {
42 std::string address = "0.0.0.0";
43 unsigned short port = 9090;
44 std::string path = "/metrics";
45 std::string health_path = "/health";
47 std::function<void(std::vector<PrometheusMetricFamily>&)> on_collect;
48 bool start_immediately = true;
49 };
50
53
56 explicit PrometheusHttpServerLogger(const Config& config)
57 : m_config(config) {
58
60 m_server.config.port = m_config.port;
61
62 // Lifecycle note: this lambda captures `this`. The destructor calls stop()
63 // which invokes server.stop() and joins m_server_thread, ensuring no
64 // active handlers reference the logger after destruction.
65 m_server.resource[m_config.path]["GET"] =
66 [this](std::shared_ptr<HttpServer::Response> response,
67 std::shared_ptr<HttpServer::Request>) {
68 try {
69 m_scrapes_total.fetch_add(1);
71 std::string payload = this->collect_payload();
72 response->write(
73 SimpleWeb::StatusCode::success_ok,
74 payload,
75 {{"Content-Type", "text/plain; version=0.0.4; charset=utf-8"},
76 {"Cache-Control", "no-store"}});
77 } catch (...) {
78 m_scrape_errors_total.fetch_add(1);
79 response->write(SimpleWeb::StatusCode::server_error_internal_server_error);
80 }
81 };
82
84 m_server.resource[m_config.health_path]["GET"] =
85 [](std::shared_ptr<HttpServer::Response> response,
86 std::shared_ptr<HttpServer::Request>) {
87 response->write(SimpleWeb::StatusCode::success_ok, "ok");
88 };
89 }
90
91 m_server.default_resource["GET"] =
92 [](std::shared_ptr<HttpServer::Response> response,
93 std::shared_ptr<HttpServer::Request>) {
94 response->write(
95 SimpleWeb::StatusCode::client_error_not_found,
96 "not found");
97 };
98
100 start();
101 }
102 }
103
105 stop();
106 }
107
110
112 void start() {
113 if (m_running.load()) {
114 return;
115 }
116 m_running.store(true);
117 m_server_thread = std::thread([this]() {
118 m_server.start();
119 });
120 }
121
125 void log(const LogRecord& record, const std::string& message) override {
126 (void)message;
127 m_metrics.on_log(record.timestamp_ms);
128 }
129
131 void wait() override {}
132
134 void shutdown() override {
135 stop();
136 }
137
140 std::string collect_payload() {
141 auto start = std::chrono::steady_clock::now();
142 std::vector<PrometheusMetricFamily> families;
143 {
144 std::lock_guard<std::mutex> lock(m_collect_mutex);
145 m_metrics.build_builtin_metrics(
146 families, m_config.format, "prometheus_http_server",
148 build_scrape_metrics(families);
149 if (m_config.on_collect) {
150 try {
151 m_config.on_collect(families);
152 } catch (...) {
153 m_metrics.add_failed_export();
154 }
155 }
156 }
157 auto end = std::chrono::steady_clock::now();
158 m_last_collect_duration_sec.store(
159 std::chrono::duration<double>(end - start).count());
160 return build_prometheus_text_payload(families, m_config.format);
161 }
162
166 std::string get_string_param(const LoggerParam& param) const override {
167 switch (param) {
168 case LoggerParam::LastLogTimestamp: return std::to_string(m_metrics.last_log_ts());
169 case LoggerParam::TimeSinceLastLog: return std::to_string(m_metrics.time_since_last_log_ms(LOGIT_CURRENT_TIMESTAMP_MS()));
170 case LoggerParam::DroppedLogCount: return std::to_string(m_metrics.dropped_count());
171 case LoggerParam::FailedExportCount: return std::to_string(m_metrics.failed_export_count());
172 default:
173 break;
174 }
175 return std::string();
176 }
177
181 int64_t get_int_param(const LoggerParam& param) const override {
182 switch (param) {
183 case LoggerParam::LastLogTimestamp: return m_metrics.last_log_ts();
184 case LoggerParam::TimeSinceLastLog: return m_metrics.time_since_last_log_ms(LOGIT_CURRENT_TIMESTAMP_MS());
185 case LoggerParam::DroppedLogCount: return PrometheusLoggerMetrics::counter_to_int64(m_metrics.dropped_count());
186 case LoggerParam::FailedExportCount: return PrometheusLoggerMetrics::counter_to_int64(m_metrics.failed_export_count());
187 default:
188 break;
189 }
190 return 0;
191 }
192
196 double get_float_param(const LoggerParam& param) const override {
197 switch (param) {
199 return static_cast<double>(m_metrics.last_log_ts()) / 1000.0;
201 return static_cast<double>(m_metrics.time_since_last_log_ms(LOGIT_CURRENT_TIMESTAMP_MS())) / 1000.0;
203 return static_cast<double>(m_metrics.dropped_count());
205 return static_cast<double>(m_metrics.failed_export_count());
206 default:
207 break;
208 }
209 return 0.0;
210 }
211
214 void set_log_level(LogLevel level) override {
215 m_log_level = static_cast<int>(level);
216 }
217
220 LogLevel get_log_level() const override {
221 return static_cast<LogLevel>(m_log_level.load());
222 }
223
225 uint64_t dropped_count() const {
226 return m_metrics.dropped_count();
227 }
228
230 uint64_t failed_export_count() const {
231 return m_metrics.failed_export_count();
232 }
233
234 private:
237 std::thread m_server_thread;
238 std::mutex m_collect_mutex;
239 std::atomic<bool> m_running = ATOMIC_VAR_INIT(false);
241
242 std::atomic<int> m_log_level = ATOMIC_VAR_INIT(static_cast<int>(LogLevel::LOG_LVL_TRACE));
243
244 std::atomic<uint64_t> m_scrapes_total{0};
245 std::atomic<uint64_t> m_scrape_errors_total{0};
246 std::atomic<int64_t> m_last_scrape_timestamp_ms{0};
247 std::atomic<double> m_last_collect_duration_sec{0.0};
248
249 void build_scrape_metrics(std::vector<PrometheusMetricFamily>& families) const {
250 const std::string& prefix = m_config.format.metric_prefix;
251
252 // logit_prometheus_scrapes_total
253 {
255 mf.name = prefix + "prometheus_scrapes_total";
256 mf.help = "Total number of Prometheus scrape requests";
259 s.name = mf.name;
260 s.value = static_cast<double>(m_scrapes_total.load());
261 add_scrape_labels(s);
262 mf.samples.push_back(s);
263 families.push_back(mf);
264 }
265
266 // logit_prometheus_scrape_errors_total
267 {
269 mf.name = prefix + "prometheus_scrape_errors_total";
270 mf.help = "Total number of failed Prometheus scrape requests";
273 s.name = mf.name;
274 s.value = static_cast<double>(m_scrape_errors_total.load());
275 add_scrape_labels(s);
276 mf.samples.push_back(s);
277 families.push_back(mf);
278 }
279
280 // logit_prometheus_last_scrape_timestamp_ms
281 {
283 mf.name = prefix + "prometheus_last_scrape_timestamp_ms";
284 mf.help = "Timestamp of the last Prometheus scrape request";
287 s.name = mf.name;
288 s.value = static_cast<double>(m_last_scrape_timestamp_ms.load());
289 add_scrape_labels(s);
290 mf.samples.push_back(s);
291 families.push_back(mf);
292 }
293
294 // logit_prometheus_collect_duration_seconds
295 {
297 mf.name = prefix + "prometheus_collect_duration_seconds";
298 mf.help = "Duration of the last metrics collection in seconds";
301 s.name = mf.name;
302 s.value = m_last_collect_duration_sec.load();
303 add_scrape_labels(s);
304 mf.samples.push_back(s);
305 families.push_back(mf);
306 }
307 }
308
310 if (m_config.format.include_logger_label) {
311 sample.labels.push_back({
312 m_config.format.logger_label_name,
313 "prometheus_http_server"});
314 }
315 if (m_config.format.include_instance_label) {
316 sample.labels.push_back({
318 m_config.format.instance_label_value});
319 }
320 }
321
322 void stop() {
323 if (!m_running.exchange(false)) {
324 return;
325 }
326 m_server.stop();
327 if (m_server_thread.joinable()) {
328 m_server_thread.join();
329 }
330 }
331 };
332
333} // namespace logit
334
335#endif // LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_PROMETHEUSHTTPSERVERLOGGER_HPP_INCLUDED
Defines the interface for loggers used in the logging system.
Shared built-in metrics state and serialization for Prometheus loggers.
Prometheus text exposition format serialization helpers.
Optional buffered snapshot access returns empty results by default.
Definition ILogger.hpp:47
Serves Prometheus metrics via an embedded HTTP server.
int64_t get_int_param(const LoggerParam &param) const override
Retrieves an integer parameter from the logger.
void wait() override
No-op; server is independently serving metrics.
std::string get_string_param(const LoggerParam &param) const override
Retrieves a string parameter from the logger.
void log(const LogRecord &record, const std::string &message) override
Updates internal metric counters for a log message.
PrometheusHttpServerLogger(const PrometheusHttpServerLogger &)=delete
void shutdown() override
Stops the HTTP server and joins the server thread.
SimpleWeb::Server< SimpleWeb::HTTP > HttpServer
uint64_t failed_export_count() const
Returns number of failed export attempts.
LogLevel get_log_level() const override
Gets minimal log level for this logger.
uint64_t dropped_count() const
Returns number of dropped records.
void set_log_level(LogLevel level) override
Sets minimal log level for this logger.
PrometheusHttpServerLogger()
Constructs Prometheus HTTP server logger with default configuration.
double get_float_param(const LoggerParam &param) const override
Retrieves a floating-point parameter from the logger.
PrometheusHttpServerLogger & operator=(const PrometheusHttpServerLogger &)=delete
void build_scrape_metrics(std::vector< PrometheusMetricFamily > &families) const
void add_scrape_labels(PrometheusSample &sample) const
std::string collect_payload()
Collects current metrics and returns serialized Prometheus text payload.
PrometheusHttpServerLogger(const Config &config)
Constructs Prometheus HTTP server logger with custom configuration.
void start()
Starts the HTTP server thread.
Tracks log-record counters and builds built-in Prometheus metric families.
static int64_t counter_to_int64(uint64_t value)
Safely convert uint64_t counter to int64_t (clamping on overflow).
#define LOGIT_CURRENT_TIMESTAMP_MS()
Macro to get the current timestamp in milliseconds.
Definition config.hpp:84
The primary namespace for the LogIt++ library.
LogLevel
Logging levels.
Definition enums.hpp:15
@ LOG_LVL_TRACE
Trace level logging.
std::string build_prometheus_text_payload(const std::vector< PrometheusMetricFamily > &families, const PrometheusTextFormatConfig &config)
Builds a complete Prometheus text exposition format payload.
LoggerParam
Enumeration for different logger parameters that can be retrieved.
Definition enums.hpp:47
@ TimeSinceLastLog
The time elapsed since the last log in seconds.
@ LastLogTimestamp
The timestamp of the last log.
@ DroppedLogCount
Number of log records dropped by the backend.
@ FailedExportCount
Number of failed export attempts reported by the backend.
Stores log metadata and content.
Definition LogRecord.hpp:17
const int64_t timestamp_ms
Timestamp in milliseconds.
Definition LogRecord.hpp:19
std::function< void(std::vector< PrometheusMetricFamily > &)> on_collect
A group of related samples sharing a name, help, and type.
std::vector< PrometheusSample > samples
Samples in this family.
std::string name
Metric family name.
PrometheusMetricType type
Metric type.
A single metric sample with optional labels and timestamp.
std::string name
Full metric name (including suffixes).
std::vector< PrometheusLabel > labels
Labels for this sample.
Configuration for Prometheus text exposition format output.
std::string logger_label_name
Name of logger label.
std::string instance_label_value
Value of instance label.
bool include_instance_label
Add instance label to built-in metrics.
std::string instance_label_name
Name of instance label.
bool include_logger_label
Add logger label to built-in metrics.
std::string metric_prefix
Prefix applied to built-in metric names.