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