ndmspc v1.2.0-0.1.rc5
Loading...
Searching...
No Matches
NLogger.cxx
1#include <iostream>
2#include <iomanip>
3#include <filesystem>
4#include <mutex>
5#include <chrono>
6#include <ctime>
7#include <cstdarg>
8#include <unistd.h>
9
10#include <Rtypes.h>
11#include <TError.h>
12
13#include "NLogger.h"
14
15namespace Ndmspc {
16
17// Singleton instance and mutex
18std::mutex NLogger::fgLoggerMutex;
19logs::Severity NLogger::fgMinSeverity = logs::Severity::kInfo;
20std::string NLogger::fgLogDirectory = "/tmp/.ndmspc/logs";
21bool NLogger::fgConsoleOutput = true;
22bool NLogger::fgFileOutput = false; // Default: no file logging
23std::string NLogger::fgProcessName = "";
24
26{
27 // Initialize the logger
28 Init();
29}
30
32{
33 Cleanup();
34}
35
37{
38 // Meyers' singleton for thread-safe, lazy initialization
39 static NLogger instance;
40 return &instance;
41}
42
44{
45 // Init logger
46 if (const char * env_severity = getenv("NDMSPC_LOG_LEVEL")) {
48 if (fgMinSeverity != logs::Severity::kInfo) {
49 std::cout << "NLogger: Setting log level to '" << env_severity << "' ... " << std::endl;
50 }
51 }
52 if (const char * env_logdir = getenv("NDMSPC_LOG_DIR")) {
53 fgLogDirectory = env_logdir;
54 }
55
56 // Check if process name is set via environment variable
57 if (const char * env_process_name = getenv("NDMSPC_PROCESS_NAME")) {
58 fgProcessName = env_process_name;
59 }
60 else {
61 // Default to process ID if no process name is set
62 fgProcessName = "ndmspc_" + std::to_string(getpid());
63 }
64
65 // Check if file output is enabled via environment variable
66 if (const char * env_file_output = getenv("NDMSPC_LOG_FILE")) {
67 std::string value(env_file_output);
68 fgFileOutput = (value == "1" || value == "true" || value == "TRUE");
69 } // Check if console output is controlled via environment variable
70
71 if (const char * env_console_output = getenv("NDMSPC_LOG_CONSOLE")) {
72 std::string value(env_console_output);
73 if (value == "0" || value == "false" || value == "FALSE") {
74 fgConsoleOutput = false;
75 gErrorIgnoreLevel = kFatal;
76 }
77 else if (value == "1" || value == "true" || value == "TRUE") {
78 fgConsoleOutput = true;
79 }
80 }
81
82 // Set default name for main thread
83 std::thread::id main_tid = std::this_thread::get_id();
84 std::string main_thread_name = "main";
85
86 // Check if main thread name is set via environment variable
87 if (const char * env_main_thread = getenv("NDMSPC_MAIN_THREAD_NAME")) {
88 main_thread_name = env_main_thread;
89 }
90
91 fThreadNames[main_tid] = main_thread_name;
92
93 // Only create log directory if file output is enabled
94 if (fgFileOutput) {
95 try {
96 std::filesystem::create_directories(fgLogDirectory);
97 }
98 catch (const std::exception & e) {
99 std::cerr << "NLogger: Failed to create log directory: " << e.what() << std::endl;
100 fgFileOutput = false; // Disable file output on error
101 }
102 }
103}
104
106{
107 // Cleanup logger
108 std::lock_guard<std::mutex> lock(fStreamMapMutex);
109 fThreadStreams.clear();
110 fThreadNames.clear();
111}
112
113void NLogger::SetLogDirectory(const std::string & dir)
114{
115 fgLogDirectory = dir;
116 if (fgFileOutput) {
117 try {
118 std::filesystem::create_directories(fgLogDirectory);
119 }
120 catch (const std::exception & e) {
121 std::cerr << "NLogger: Failed to create log directory: " << e.what() << std::endl;
122 }
123 }
124}
125
126void NLogger::SetProcessName(const std::string & name)
127{
128 std::lock_guard<std::mutex> lock(fgLoggerMutex);
129 fgProcessName = name;
130}
131
132void NLogger::SetThreadName(const std::string & name, std::thread::id thread_id)
133{
134 std::lock_guard<std::mutex> lock(Instance()->fStreamMapMutex);
135 Instance()->fThreadNames[thread_id] = name;
136}
137
139{
140 std::thread::id tid = std::this_thread::get_id();
141 std::lock_guard<std::mutex> lock(Instance()->fStreamMapMutex);
142
143 auto it = Instance()->fThreadNames.find(tid);
144 if (it != Instance()->fThreadNames.end()) {
145 return it->second;
146 }
147
148 // Return thread ID as string if no custom name
149 std::ostringstream oss;
150 oss << tid;
151 return oss.str();
152}
153
155{
156 std::thread::id tid = std::this_thread::get_id();
157
158 // Check if custom name exists (lock already held by caller)
159 auto it = fThreadNames.find(tid);
160 if (it != fThreadNames.end()) {
161 return it->second;
162 }
163
164 // Return thread ID as string
165 std::ostringstream oss;
166 oss << tid;
167 return oss.str();
168}
169
171{
172 std::thread::id tid = std::this_thread::get_id();
173
174 std::lock_guard<std::mutex> lock(fStreamMapMutex);
175
176 auto it = fThreadStreams.find(tid);
177 if (it != fThreadStreams.end()) {
178 return *(it->second);
179 }
180
181 // Get thread identifier (custom name or ID)
182 std::string thread_id = GetThreadIdentifier();
183
184 // Build filename with process name prefix
185 std::string filename_base;
186 if (!fgProcessName.empty()) {
187 filename_base = fgProcessName + "_" + thread_id;
188 }
189 else {
190 filename_base = thread_id;
191 }
192
193 // Sanitize filename (replace problematic characters)
194 for (char & c : filename_base) {
195 if (c == '/' || c == '\\' || c == ':' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|') {
196 c = '_';
197 }
198 }
199
200 // Create new log file for this thread
201 std::ostringstream filename;
202 filename << fgLogDirectory << "/" << filename_base << ".log";
203
204 auto stream = std::make_unique<std::ofstream>(filename.str(), std::ios::app);
205
206 if (!stream->is_open()) {
207 std::cerr << "NLogger: Failed to open log file: " << filename.str() << std::endl;
208 }
209 else {
210 // Write header with process and thread info
211 auto now = std::chrono::system_clock::now();
212 std::time_t now_time = std::chrono::system_clock::to_time_t(now);
213 *stream << "=== Log started at " << std::ctime(&now_time);
214 *stream << "=== Process: " << fgProcessName << " (PID: " << getpid() << ")" << std::endl;
215 *stream << "=== Thread: " << thread_id << std::endl;
216 stream->flush();
217 }
218
219 auto & ref = *stream;
220 fThreadStreams[tid] = std::move(stream);
221 return ref;
222}
223void NLogger::CloseThreadStream(std::thread::id thread_id)
224{
225 std::lock_guard<std::mutex> lock(fStreamMapMutex);
226 fThreadStreams.erase(thread_id);
227 fThreadNames.erase(thread_id);
228}
229
230std::string NLogger::SeverityToString(logs::Severity level)
231{
232 switch (level) {
233 case logs::Severity::kTrace: return "TRACE";
234 case logs::Severity::kDebug: return "DEBUG";
235 case logs::Severity::kInfo: return "INFO";
236 case logs::Severity::kWarn: return "WARN";
237 case logs::Severity::kError: return "ERROR";
238 case logs::Severity::kFatal: return "FATAL";
239 default: return "UNKNOWN";
240 }
241}
242
243logs::Severity NLogger::GetSeverityFromString(const std::string & severity_str)
244{
245 auto it = logs::fgSeverityMap.find(severity_str);
246 if (it != logs::fgSeverityMap.end()) {
247 return it->second;
248 }
249 // Default to INFO if unknown
250 return logs::Severity::kInfo;
251}
252
253void NLogger::Log(const char * file, int line, logs::Severity level, const char * format, ...)
254{
255 if (Instance()->GetMinSeverity() > level) {
256 return;
257 }
258
259 va_list args;
260 va_start(args, format);
261
262 // Format timestamp
263 auto now = std::chrono::system_clock::now();
264 std::time_t now_time = std::chrono::system_clock::to_time_t(now);
265 std::tm * now_tm = std::localtime(&now_time);
266 char time_buf[24];
267 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
268 std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", now_tm);
269
270 // Format message
271 char message_buf[4096];
272 vsnprintf(message_buf, sizeof(message_buf), format, args);
273 va_end(args);
274
275 // Build log line
276 std::ostringstream log_line;
277 log_line << "[" << time_buf << "." << std::setfill('0') << std::setw(3) << ms.count() << "] "
278 << "[" << SeverityToString(level) << "] ";
279
280 if (level <= logs::Severity::kDebug4) {
281 log_line << "[" << std::filesystem::path(file).filename().string() << ":" << line << "] ";
282 }
283
284 log_line << message_buf;
285
286 // Thread-safe file output (only if enabled)
287 if (fgFileOutput) {
288 auto & stream = Instance()->GetThreadStream();
289 if (stream.is_open()) {
290 stream << log_line.str() << std::endl;
291 stream.flush();
292 }
293 }
294
295 // Console output (if enabled)
296 if (fgConsoleOutput) {
297 std::lock_guard<std::mutex> lock(fgLoggerMutex);
298 std::cout << log_line.str() << std::endl;
299 }
300}
301
302} // namespace Ndmspc
static std::string fgLogDirectory
Directory for log files.
Definition NLogger.h:556
std::mutex fStreamMapMutex
Definition NLogger.h:589
std::unordered_map< std::thread::id, std::string > fThreadNames
Map of thread IDs to custom thread names.
Definition NLogger.h:597
void CloseThreadStream(std::thread::id thread_id)
Closes and removes the output file stream associated with a specific thread.
Definition NLogger.cxx:223
static bool fgFileOutput
Flag for file output.
Definition NLogger.h:558
static void SetProcessName(const std::string &name)
Sets the name of the current process.
Definition NLogger.cxx:126
static std::mutex fgLoggerMutex
Mutex for thread-safe singleton access.
Definition NLogger.h:554
static std::string GetThreadName()
Retrieves the name of the current thread.
Definition NLogger.cxx:138
static logs::Severity fgMinSeverity
Minimum severity level for logging.
Definition NLogger.h:555
void Init()
Initializes the logger.
Definition NLogger.cxx:43
NLogger()
Constructs a new NLogger instance.
Definition NLogger.cxx:25
static void SetThreadName(const std::string &name, std::thread::id thread_id=std::this_thread::get_id())
Sets the name of a thread.
Definition NLogger.cxx:132
static bool fgConsoleOutput
Flag for console output.
Definition NLogger.h:557
static logs::Severity GetMinSeverity()
Gets the current minimum severity level for logging.
Definition NLogger.h:503
std::string GetThreadIdentifier()
Get thread name or ID as string.
Definition NLogger.cxx:154
std::ofstream & GetThreadStream()
Retrieves the thread-local output file stream for logging.
Definition NLogger.cxx:170
static void Log(const char *file, int line, logs::Severity level, const char *format,...)
Logs a formatted message with the specified severity level.
Definition NLogger.cxx:253
static std::string fgProcessName
Process name prefix for log files.
Definition NLogger.h:559
std::unordered_map< std::thread::id, std::unique_ptr< std::ofstream > > fThreadStreams
Map storing unique output file streams for each thread.
Definition NLogger.h:596
static logs::Severity GetSeverityFromString(const std::string &severity_str)
Parses a string to obtain the corresponding logs::Severity enum value.
Definition NLogger.cxx:243
virtual ~NLogger()
Destroys the NLogger instance.
Definition NLogger.cxx:31
static std::string SeverityToString(logs::Severity level)
Converts a logs::Severity enum value to its string representation.
Definition NLogger.cxx:230
static NLogger * Instance()
Returns the singleton instance of NLogger.
Definition NLogger.cxx:36
static void SetLogDirectory(const std::string &dir)
Sets the directory where log files will be stored.
Definition NLogger.cxx:113
void Cleanup()
Cleans up logger resources.
Definition NLogger.cxx:105
Global callback function for libwebsockets client events.