ndmspc  v1.2.0-0.1.rc3
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 
15 namespace Ndmspc {
16 
17 // Singleton instance and mutex
18 std::mutex NLogger::fgLoggerMutex;
19 logs::Severity NLogger::fgMinSeverity = logs::Severity::kInfo;
20 std::string NLogger::fgLogDirectory = "/tmp/.ndmspc/logs";
21 bool NLogger::fgConsoleOutput = true;
22 bool NLogger::fgFileOutput = false; // Default: no file logging
23 std::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")) {
47  fgMinSeverity = GetSeverityFromString(env_severity);
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 
113 void 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 
126 void NLogger::SetProcessName(const std::string & name)
127 {
128  std::lock_guard<std::mutex> lock(fgLoggerMutex);
129  fgProcessName = name;
130 }
131 
132 void 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 
170 std::ofstream & NLogger::GetThreadStream()
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 }
223 void 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 
230 std::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 
243 logs::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 
253 void 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
Thread-safe singleton logger with per-thread file output.
Definition: NLogger.h:446
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