LogIt++
Header-only, macro-first C++ logging library
Loading...
Searching...
No Matches
MemoryLogger.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_MEMORYLOGGER_HPP_INCLUDED
3#define LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_MEMORYLOGGER_HPP_INCLUDED
4
7
8#include "ILogger.hpp"
9#include "ILogReader.hpp"
10#include "ILogSubscriber.hpp"
11
12#include <map>
13
14namespace logit {
15
22 class MemoryLogger : public ILogger, public ILogReader, public ILogSubscriber {
23 public:
26 struct Config {
27 std::size_t max_records = 1000;
28 std::size_t max_bytes = 1024 * 1024;
29 int64_t max_age_ms = 24LL * 60 * 60 * 1000;
30
31 Config() {}
32
37 };
38
40 MemoryLogger() = default;
41
43 explicit MemoryLogger(const Config& config) : m_config(config) {}
44
46 MemoryLogger(std::size_t max_records, std::size_t max_bytes, int64_t max_age_ms) :
47 m_config{max_records, max_bytes, max_age_ms} {}
48
50 void log(const LogRecord& record, const std::string& message) override {
51 if (!record.raw_mode &&
52 static_cast<int>(record.log_level) < m_log_level.load(std::memory_order_relaxed)) {
53 return;
54 }
55
56 BufferedLogEntry entry;
57 entry.level = record.log_level;
58 entry.timestamp_ms = record.timestamp_ms;
59 entry.file = record.file;
60 entry.line = record.line;
61 entry.function = record.function;
62 entry.message = message;
63
64 LogRecordSnapshot written_snapshot;
65 {
66 std::lock_guard<std::mutex> lock(m_mutex);
68
69 // Capture an owning snapshot before moving the entry; callbacks
70 // are dispatched only after the buffer update finishes.
71 written_snapshot = to_snapshot(entry);
73 m_entries.push_back(std::move(entry));
74 m_last_log_ts.store(record.timestamp_ms, std::memory_order_relaxed);
75 m_last_log_mono_ts.store(LOGIT_MONOTONIC_MS(), std::memory_order_relaxed);
76
78 }
79
80 notify_callbacks(written_snapshot);
81 }
82
84 std::string get_string_param(const LoggerParam& param) const override {
85 switch (param) {
87 return std::to_string(get_last_log_ts());
89 return std::to_string(get_time_since_last_log());
90 default:
91 break;
92 }
93 return std::string();
94 }
95
97 int64_t get_int_param(const LoggerParam& param) const override {
98 switch (param) {
100 return get_last_log_ts();
103 default:
104 break;
105 }
106 return 0;
107 }
108
110 double get_float_param(const LoggerParam& param) const override {
111 switch (param) {
113 return static_cast<double>(get_last_log_ts()) / 1000.0;
115 return static_cast<double>(get_time_since_last_log()) / 1000.0;
116 default:
117 break;
118 }
119 return 0.0;
120 }
121
123 void set_log_level(LogLevel level) override {
124 m_log_level.store(static_cast<int>(level), std::memory_order_relaxed);
125 }
126
128 LogLevel get_log_level() const override {
129 return static_cast<LogLevel>(m_log_level.load(std::memory_order_relaxed));
130 }
131
134 std::vector<std::string> get_buffered_strings() const override {
135 std::lock_guard<std::mutex> lock(m_mutex);
137
138 std::vector<std::string> snapshot;
139 snapshot.reserve(m_entries.size());
140 for (const auto& entry : m_entries) {
141 snapshot.push_back(entry.message);
142 }
143 return snapshot;
144 }
145
148 std::vector<BufferedLogEntry> get_buffered_entries() const override {
149 std::lock_guard<std::mutex> lock(m_mutex);
151 return std::vector<BufferedLogEntry>(m_entries.begin(), m_entries.end());
152 }
153
155 void wait() override {}
156
157 uint64_t add_log_callback(Callback callback) override {
158 std::lock_guard<std::mutex> lock(m_callbacks_mutex);
159 const uint64_t id = m_next_callback_id.fetch_add(1, std::memory_order_relaxed);
160 m_callbacks.emplace(id, std::move(callback));
161 return id;
162 }
163
164 bool remove_log_callback(uint64_t callback_id) override {
165 std::lock_guard<std::mutex> lock(m_callbacks_mutex);
166 return m_callbacks.erase(callback_id) > 0;
167 }
168
171 (void)options;
172 std::lock_guard<std::mutex> lock(m_mutex);
173 LogClearResult result;
174 result.ok = true;
176 result.cleared_records = m_entries.size();
177 result.message = "cleared";
178 m_entries.clear();
179 m_total_bytes = 0;
180 m_last_log_ts.store(0, std::memory_order_relaxed);
181 m_last_log_mono_ts.store(0, std::memory_order_relaxed);
182 return result;
183 }
184
186 std::vector<LogRecordSnapshot> read_range(
187 int64_t from_ms,
188 int64_t to_ms,
189 std::size_t limit = 0) const override {
190#if __cplusplus >= 201703L
191 auto result = read_range_result(from_ms, to_ms, limit);
192 return result.value ? *result.value : std::vector<LogRecordSnapshot>();
193#else
194 std::vector<LogRecordSnapshot> out;
195 if (to_ms <= from_ms) {
196 return out;
197 }
198
199 std::lock_guard<std::mutex> lock(m_mutex);
201
202 for (const auto& entry : m_entries) {
203 if (entry.timestamp_ms >= from_ms && entry.timestamp_ms < to_ms) {
204 out.push_back(to_snapshot(entry));
205 if (limit > 0 && out.size() >= limit) {
206 break;
207 }
208 }
209 }
210 return out;
211#endif
212 }
213
214#if __cplusplus >= 201703L
216 LogReadResult<std::vector<LogRecordSnapshot>> read_range_result(
217 int64_t from_ms,
218 int64_t to_ms,
219 std::size_t limit = 0) const override {
220 LogReadResult<std::vector<LogRecordSnapshot>> result;
221 result.value.emplace();
222 if (to_ms <= from_ms) {
223 return result;
224 }
225
226 std::lock_guard<std::mutex> lock(m_mutex);
228
229 for (const auto& entry : m_entries) {
230 if (entry.timestamp_ms >= from_ms && entry.timestamp_ms < to_ms) {
231 result.value->push_back(to_snapshot(entry));
232 if (limit > 0 && result.value->size() >= limit) {
233 break;
234 }
235 }
236 }
237 return result;
238 }
239#endif
240
242 std::vector<LogRecordSnapshot> read_recent(
243 std::size_t limit,
244 int64_t period_ms = 0,
245 LogReadOrder order = LogReadOrder::Ascending) const override {
246#if __cplusplus >= 201703L
247 auto result = read_recent_result(limit, period_ms, order);
248 return result.value ? *result.value : std::vector<LogRecordSnapshot>();
249#else
250 std::vector<LogRecordSnapshot> out;
251 std::lock_guard<std::mutex> lock(m_mutex);
253
254 const int64_t now_ms = LOGIT_CURRENT_TIMESTAMP_MS();
255 const int64_t from_ms = (period_ms > 0) ? (now_ms - period_ms) : 0;
256
257 for (auto it = m_entries.rbegin(); it != m_entries.rend(); ++it) {
258 if (period_ms <= 0 || it->timestamp_ms >= from_ms) {
259 out.push_back(to_snapshot(*it));
260 if (limit > 0 && out.size() >= limit) {
261 break;
262 }
263 }
264 }
265
266 if (order == LogReadOrder::Ascending) {
267 std::reverse(out.begin(), out.end());
268 }
269 return out;
270#endif
271 }
272
273#if __cplusplus >= 201703L
275 LogReadResult<std::vector<LogRecordSnapshot>> read_recent_result(
276 std::size_t limit,
277 int64_t period_ms = 0,
278 LogReadOrder order = LogReadOrder::Ascending) const override {
279 LogReadResult<std::vector<LogRecordSnapshot>> result;
280 result.value.emplace();
281 std::lock_guard<std::mutex> lock(m_mutex);
283
284 const int64_t now_ms = LOGIT_CURRENT_TIMESTAMP_MS();
285 const int64_t from_ms = (period_ms > 0) ? (now_ms - period_ms) : 0;
286
287 for (auto it = m_entries.rbegin(); it != m_entries.rend(); ++it) {
288 if (period_ms <= 0 || it->timestamp_ms >= from_ms) {
289 result.value->push_back(to_snapshot(*it));
290 if (limit > 0 && result.value->size() >= limit) {
291 break;
292 }
293 }
294 }
295
296 if (order == LogReadOrder::Ascending) {
297 std::reverse(result.value->begin(), result.value->end());
298 }
299 return result;
300 }
301#endif
302
303 private:
305 LogRecordSnapshot snapshot;
306 snapshot.level = e.level;
307 snapshot.timestamp_ms = e.timestamp_ms;
308 snapshot.file = e.file;
309 snapshot.line = e.line;
310 snapshot.function = e.function;
311 snapshot.message = e.message;
312 return snapshot;
313 }
314
315 void notify_callbacks(const LogRecordSnapshot& snapshot) const {
316 std::vector<Callback> callbacks_copy;
317 {
318 std::lock_guard<std::mutex> lock(m_callbacks_mutex);
319 callbacks_copy.reserve(m_callbacks.size());
320 for (const auto& kv : m_callbacks) {
321 callbacks_copy.push_back(kv.second);
322 }
323 }
324 for (const auto& cb : callbacks_copy) {
325 try {
326 cb(snapshot);
327 } catch (...) {
328 }
329 }
330 }
331
332 // Count only the retained formatted payload, not the full object footprint.
333 static std::size_t m_entry_bytes(const BufferedLogEntry& entry) {
334 return entry.message.size();
335 }
336
337 void m_pop_front_locked() const {
338 if (m_entries.empty()) {
339 return;
340 }
342 m_entries.pop_front();
343 }
344
345 void m_evict_expired_locked(int64_t now_ms) const {
346 if (m_config.max_age_ms <= 0) {
347 return;
348 }
349
350 const int64_t cutoff = now_ms - m_config.max_age_ms;
351 while (!m_entries.empty() && m_entries.front().timestamp_ms < cutoff) {
353 }
354 }
355
356 void m_enforce_limits_locked(int64_t now_ms) const {
358
359 if (m_config.max_records > 0) {
360 while (m_entries.size() > m_config.max_records) {
362 }
363 }
364
365 if (m_config.max_bytes > 0) {
366 // If a single message exceeds the byte budget, eviction removes it too.
367 while (m_total_bytes > m_config.max_bytes && !m_entries.empty()) {
369 }
370 }
371 }
372
373 int64_t get_last_log_ts() const {
374 return m_last_log_ts.load(std::memory_order_relaxed);
375 }
376
377 int64_t get_time_since_last_log() const {
378 const int64_t last = m_last_log_mono_ts.load(std::memory_order_relaxed);
379 if (last <= 0) {
380 return 0;
381 }
382 const int64_t now = LOGIT_MONOTONIC_MS();
383 return now > last ? (now - last) : 0;
384 }
385
387 mutable std::mutex m_mutex;
388 mutable std::deque<BufferedLogEntry> m_entries;
389 mutable std::size_t m_total_bytes = 0;
390 std::atomic<int64_t> m_last_log_ts = ATOMIC_VAR_INIT(0);
391 std::atomic<int64_t> m_last_log_mono_ts = ATOMIC_VAR_INIT(0);
392 std::atomic<int> m_log_level = ATOMIC_VAR_INIT(static_cast<int>(LogLevel::LOG_LVL_TRACE));
393 mutable std::mutex m_callbacks_mutex;
394 std::map<uint64_t, Callback> m_callbacks;
395 std::atomic<uint64_t> m_next_callback_id{1};
396 };
397
398} // namespace logit
399
400#endif // LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_MEMORYLOGGER_HPP_INCLUDED
Optional read-only interface for log backends that support querying stored records.
Optional live-subscription interface for log backends that can push newly written records.
Defines the interface for loggers used in the logging system.
Optional interface for backends that expose stored records.
Optional interface for backends that can notify callers when a record is successfully written.
std::function< void(const LogRecordSnapshot &)> Callback
Optional buffered snapshot access returns empty results by default.
Definition ILogger.hpp:47
Stores the latest formatted log messages in memory.
std::vector< LogRecordSnapshot > read_range(int64_t from_ms, int64_t to_ms, std::size_t limit=0) const override
Reads records in [from_ms, to_ms) ordered by timestamp.
std::atomic< int > m_log_level
MemoryLogger(std::size_t max_records, std::size_t max_bytes, int64_t max_age_ms)
Construct with explicit count/size/age limits.
void set_log_level(LogLevel level) override
Set the minimum accepted log level.
std::vector< LogRecordSnapshot > read_recent(std::size_t limit, int64_t period_ms=0, LogReadOrder order=LogReadOrder::Ascending) const override
Reads the most recent records.
std::atomic< int64_t > m_last_log_ts
double get_float_param(const LoggerParam &param) const override
Retrieve legacy floating-point metadata.
std::atomic< int64_t > m_last_log_mono_ts
std::atomic< uint64_t > m_next_callback_id
void log(const LogRecord &record, const std::string &message) override
Store a formatted message in the buffer and evict old entries.
static std::size_t m_entry_bytes(const BufferedLogEntry &entry)
void notify_callbacks(const LogRecordSnapshot &snapshot) const
std::map< uint64_t, Callback > m_callbacks
std::deque< BufferedLogEntry > m_entries
void m_pop_front_locked() const
std::size_t m_total_bytes
static LogRecordSnapshot to_snapshot(const BufferedLogEntry &e)
std::vector< std::string > get_buffered_strings() const override
Return buffered formatted strings in chronological order.
std::string get_string_param(const LoggerParam &param) const override
Retrieve legacy string-based metadata.
std::vector< BufferedLogEntry > get_buffered_entries() const override
Return buffered structured entries in chronological order.
LogLevel get_log_level() const override
Get the minimum accepted log level.
void m_evict_expired_locked(int64_t now_ms) const
LogClearResult clear_logs(const LogClearOptions &options=LogClearOptions()) override
Clears buffered entries.
int64_t get_int_param(const LoggerParam &param) const override
Retrieve legacy integer-based metadata.
int64_t get_time_since_last_log() const
MemoryLogger()=default
Construct with default retention settings.
int64_t get_last_log_ts() const
bool remove_log_callback(uint64_t callback_id) override
Unregisters a previously added callback.
void m_enforce_limits_locked(int64_t now_ms) const
uint64_t add_log_callback(Callback callback) override
Registers a callback to be invoked after each successfully written record.
void wait() override
Memory logger is synchronous, so no flush step is needed.
MemoryLogger(const Config &config)
Construct with explicit configuration.
std::mutex m_callbacks_mutex
#define LOGIT_CURRENT_TIMESTAMP_MS()
Macro to get the current timestamp in milliseconds.
Definition config.hpp:84
#define LOGIT_MONOTONIC_MS()
Definition config.hpp:74
The primary namespace for the LogIt++ library.
LogLevel
Logging levels.
Definition enums.hpp:15
@ LOG_LVL_TRACE
Trace level logging.
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.
LogReadOrder
Ordering for query results returned by ILogReader.
@ Ascending
Oldest first.
Structured representation of a buffered log snapshot entry.
LogLevel level
Severity of the log entry.
std::string file
Source file path or leaf name.
int64_t timestamp_ms
Wall-clock timestamp in milliseconds.
int line
Source line number.
std::string function
Function name captured at the call site.
std::string message
Formatted message text stored in the buffer.
Selects which data categories a logger cleanup should remove.
Definition ILogger.hpp:19
Result returned by logger cleanup operations.
Definition ILogger.hpp:35
std::string message
Optional diagnostic or unsupported reason.
Definition ILogger.hpp:39
std::size_t cleared_records
Number of primary records/files cleared when available.
Definition ILogger.hpp:38
LogClearStatus status
Detailed cleanup status.
Definition ILogger.hpp:37
bool ok
True when the backend completed cleanup.
Definition ILogger.hpp:36
Common owning snapshot of a stored log record.
std::string message
Formatted message or payload preview.
int64_t timestamp_ms
Log timestamp in milliseconds.
int line
Source line number.
std::string file
Source file path.
std::string function
Source function name.
LogLevel level
Log severity level.
Stores log metadata and content.
Definition LogRecord.hpp:17
const std::string function
Function name.
Definition LogRecord.hpp:22
const int line
Line number in the source file.
Definition LogRecord.hpp:21
const LogLevel log_level
Log level (severity).
Definition LogRecord.hpp:18
const bool raw_mode
Flag indicating if formatter and level filters should be bypassed.
Definition LogRecord.hpp:31
const int64_t timestamp_ms
Timestamp in milliseconds.
Definition LogRecord.hpp:19
const std::string file
Source file name.
Definition LogRecord.hpp:20
Retention limits for the in-memory buffer.
std::size_t max_bytes
Maximum buffered formatted-message bytes (0 = unlimited).
int64_t max_age_ms
Maximum age of buffered entries (0 = unlimited).
std::size_t max_records
Maximum number of buffered entries (0 = unlimited).
Config(std::size_t max_records, std::size_t max_bytes, int64_t max_age_ms)