libe3 0.2.5
C++ library for E3AP RAN vendor integration
Loading...
Searching...
No Matches
logger.hpp
Go to the documentation of this file.
1/**
2 * @file logger.hpp
3 * @brief Logging facility for libe3
4 *
5 * Provides a lightweight, thread-safe logging mechanism that can be
6 * configured at runtime. Supports integration with external logging
7 * frameworks through callbacks.
8 *
9 * SPDX-FileCopyrightText: Copyright (c) 2026 Northeastern University
10 * SPDX-License-Identifier: Apache-2.0
11 */
12
13#ifndef LIBE3_LOGGER_HPP
14#define LIBE3_LOGGER_HPP
15
16#include <string>
17#include <functional>
18#include <memory>
19#include <sstream>
20#include <mutex>
21#include <atomic>
22#include <cstdio>
23#include <cerrno>
24#include <cstring>
25#include <ctime>
26#include <chrono>
27
28namespace libe3 {
29
30/**
31 * @brief Logging levels
32 */
33enum class LogLevel : int {
34 NONE = 0,
35 ERROR = 1,
36 WARN = 2,
37 INFO = 3,
38 DEBUG = 4,
40};
41
42/**
43 * @brief Log callback function type
44 *
45 * Allows external logging frameworks to receive log messages.
46 * @param level Log level
47 * @param component Component name (e.g., "E3Agent", "Connector")
48 * @param message Log message
49 */
50using LogCallback = std::function<void(LogLevel level, const std::string& component, const std::string& message)>;
51
52/**
53 * @brief Logger singleton class
54 */
55class Logger {
56public:
57 /**
58 * @brief Get the singleton logger instance
59 */
60 static Logger& instance() {
61 static Logger logger;
62 return logger;
63 }
64
65 /**
66 * @brief Set the log level
67 */
68 void set_level(LogLevel level) noexcept {
69 level_.store(level, std::memory_order_relaxed);
70 }
71
72 /**
73 * @brief Set the log level from integer
74 */
75 void set_level(int level) noexcept {
76 level_.store(static_cast<LogLevel>(level), std::memory_order_relaxed);
77 }
78
79 /**
80 * @brief Get current log level
81 */
82 LogLevel level() const noexcept {
83 return level_.load(std::memory_order_relaxed);
84 }
85
86 /**
87 * @brief Set external log callback
88 *
89 * If set, all log messages will be forwarded to this callback
90 * instead of being written to stderr.
91 */
92 void set_callback(LogCallback callback) {
93 std::lock_guard<std::mutex> lock(mutex_);
94 callback_ = std::move(callback);
95 }
96
97 /**
98 * @brief Clear external log callback
99 */
101 std::lock_guard<std::mutex> lock(mutex_);
102 callback_ = nullptr;
103 }
104
105 /**
106 * @brief Set log file path (append mode)
107 *
108 * If set, logs are written to this file instead of stderr
109 * when no callback is configured.
110 */
111 void set_log_file(const std::string& path) {
112 std::lock_guard<std::mutex> lock(mutex_);
113 if (file_) {
114 std::fclose(file_);
115 file_ = nullptr;
116 }
117
118 if (!path.empty()) {
119 file_ = std::fopen(path.c_str(), "a");
120 if (file_) {
121 std::setvbuf(file_, nullptr, _IOLBF, 0);
122 } else {
123 std::fprintf(stderr, "[Logger] Failed to open log file %s: %s (errno %d)\n",
124 path.c_str(), std::strerror(errno), errno);
125 }
126 }
127 }
128
129 /**
130 * @brief Check if a level should be logged
131 */
132 bool should_log(LogLevel level) const noexcept {
133 return static_cast<int>(level) <= static_cast<int>(level_.load(std::memory_order_relaxed));
134 }
135
136 /**
137 * @brief Log a message
138 */
139 void log(LogLevel level, const std::string& component, const std::string& message) {
140 if (!should_log(level)) {
141 return;
142 }
143
144 std::lock_guard<std::mutex> lock(mutex_);
145
146 if (callback_) {
147 callback_(level, component, message);
148 } else {
149 FILE* out = file_ ? file_ : stderr;
150 // Default: write to stderr with timestamp
151 auto now = std::chrono::system_clock::now();
152 auto time_t = std::chrono::system_clock::to_time_t(now);
153 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
154 now.time_since_epoch()) % 1000;
155
156 char time_buf[32];
157 std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", std::localtime(&time_t));
158
159 std::fprintf(out, "[%s.%03d] [%s] [%s] %s\n",
160 time_buf, static_cast<int>(ms.count()),
161 level_to_string(level), component.c_str(), message.c_str());
162 std::fflush(out);
163 }
164 }
165
166private:
167 Logger() = default;
168 ~Logger() = default;
169 Logger(const Logger&) = delete;
170 Logger& operator=(const Logger&) = delete;
171
172 static const char* level_to_string(LogLevel level) noexcept {
173 switch (level) {
174 case LogLevel::ERROR: return "ERROR";
175 case LogLevel::WARN: return "WARN ";
176 case LogLevel::INFO: return "INFO ";
177 case LogLevel::DEBUG: return "DEBUG";
178 case LogLevel::TRACE: return "TRACE";
179 default: return "?????";
180 }
181 }
182
183 // Read on every log call from many threads and written by set_level();
184 // atomic (relaxed: the level needs no ordering w.r.t. other state) so the
185 // read/write pair is not a data race.
186 std::atomic<LogLevel> level_{LogLevel::INFO};
187 LogCallback callback_;
188 FILE* file_{nullptr};
189 std::mutex mutex_;
190};
191
192/**
193 * @brief Log stream helper for efficient message construction
194 */
196public:
197 LogStream(LogLevel level, std::string component)
198 : level_(level), component_(std::move(component)) {}
199
201 if (!message_.str().empty()) {
202 Logger::instance().log(level_, component_, message_.str());
203 }
204 }
205
206 template<typename T>
207 LogStream& operator<<(const T& value) {
208 message_ << value;
209 return *this;
210 }
211
212private:
213 LogLevel level_;
214 std::string component_;
215 std::ostringstream message_;
216};
217
218} // namespace libe3
219
220// Convenience macros for logging
221#define E3_LOG(level, component) \
222 if (::libe3::Logger::instance().should_log(level)) \
223 ::libe3::LogStream(level, component)
224
225#define E3_LOG_ERROR(component) E3_LOG(::libe3::LogLevel::ERROR, component)
226#define E3_LOG_WARN(component) E3_LOG(::libe3::LogLevel::WARN, component)
227#define E3_LOG_INFO(component) E3_LOG(::libe3::LogLevel::INFO, component)
228#define E3_LOG_DEBUG(component) E3_LOG(::libe3::LogLevel::DEBUG, component)
229#define E3_LOG_TRACE(component) E3_LOG(::libe3::LogLevel::TRACE, component)
230
231#endif // LIBE3_LOGGER_HPP
LogStream & operator<<(const T &value)
Definition logger.hpp:207
LogStream(LogLevel level, std::string component)
Definition logger.hpp:197
Logger singleton class.
Definition logger.hpp:55
void set_log_file(const std::string &path)
Set log file path (append mode).
Definition logger.hpp:111
bool should_log(LogLevel level) const noexcept
Check if a level should be logged.
Definition logger.hpp:132
void set_level(int level) noexcept
Set the log level from integer.
Definition logger.hpp:75
void clear_callback()
Clear external log callback.
Definition logger.hpp:100
static Logger & instance()
Get the singleton logger instance.
Definition logger.hpp:60
LogLevel level() const noexcept
Get current log level.
Definition logger.hpp:82
void log(LogLevel level, const std::string &component, const std::string &message)
Log a message.
Definition logger.hpp:139
void set_callback(LogCallback callback)
Set external log callback.
Definition logger.hpp:92
void set_level(LogLevel level) noexcept
Set the log level.
Definition logger.hpp:68
std::function< void(LogLevel level, const std::string &component, const std::string &message)> LogCallback
Log callback function type.
Definition logger.hpp:50
LogLevel
Logging levels.
Definition logger.hpp:33