|
9 | 9 | #include "ze_logger.h" |
10 | 10 | #include "ze_util.h" |
11 | 11 |
|
| 12 | +#include <cerrno> |
12 | 13 | #include <chrono> |
| 14 | +#include <cstdio> |
13 | 15 | #include <ctime> |
14 | 16 | #include <iomanip> |
15 | 17 | #include <iostream> |
@@ -101,6 +103,24 @@ const char *levelColor(LogLevel l) { |
101 | 103 | } |
102 | 104 | } |
103 | 105 |
|
| 106 | +// Thread-safe, portable errno-to-string conversion. |
| 107 | +// MSVC deprecates strerror() in favour of strerror_s(); POSIX provides strerror_r(). |
| 108 | +static std::string errnoToString(int err) { |
| 109 | + char buf[256]; |
| 110 | +#ifdef _WIN32 |
| 111 | + strerror_s(buf, sizeof(buf), err); |
| 112 | + return buf; |
| 113 | +#elif defined(_GNU_SOURCE) |
| 114 | + // GNU strerror_r returns char* (may or may not use buf) |
| 115 | + const char *result = strerror_r(err, buf, sizeof(buf)); |
| 116 | + return result ? result : buf; |
| 117 | +#else |
| 118 | + // XSI-compliant strerror_r returns int |
| 119 | + strerror_r(err, buf, sizeof(buf)); |
| 120 | + return buf; |
| 121 | +#endif |
| 122 | +} |
| 123 | + |
104 | 124 | } // anonymous namespace |
105 | 125 |
|
106 | 126 | // --------------------------------------------------------------------------- |
@@ -158,9 +178,10 @@ LogLevel logLevelFromString(const std::string &s) { |
158 | 178 | if (s == "trace") return LogLevel::trace; |
159 | 179 | if (s == "debug") return LogLevel::debug; |
160 | 180 | if (s == "info") return LogLevel::info; |
161 | | - if (s == "warn") return LogLevel::warn; |
162 | | - if (s == "error") return LogLevel::err; |
163 | | - if (s == "critical") return LogLevel::critical; |
| 181 | + if (s == "warn" || s == "warning") return LogLevel::warn; |
| 182 | + if (s == "err" || s == "error") return LogLevel::err; |
| 183 | + if (s == "crit" || s == "critical") return LogLevel::critical; |
| 184 | + if (s == "off") return LogLevel::off; |
164 | 185 | return LogLevel::warn; // default |
165 | 186 | } |
166 | 187 |
|
@@ -205,7 +226,7 @@ void ZeLogger::flush() { |
205 | 226 | // Default pattern tokens: |
206 | 227 | // %Y-%m-%d %H:%M:%S.%e — timestamp with milliseconds |
207 | 228 | // %t — thread id (cached thread_local, STB_LOCAL — safe for dlclose) |
208 | | -// %P — process id (cached at construction) |
| 229 | +// %P — process id |
209 | 230 | // %^%l%$ — level label (with color when tty) |
210 | 231 | // %v — message |
211 | 232 | // |
@@ -516,16 +537,47 @@ std::shared_ptr<ZeLogger> createLogger(const std::string &caller) { |
516 | 537 | logger = std::make_shared<ZeLogger>(/*use_stderr=*/true, level, log_pattern); |
517 | 538 | output_dest = "stderr (console)"; |
518 | 539 | } else { |
519 | | - // Create the log directory only if it does not already exist. |
| 540 | + // Create the full directory path (equivalent to mkdir -p). |
520 | 541 | #ifdef _WIN32 |
521 | | - DWORD attrs = GetFileAttributesA(log_directory.c_str()); |
522 | | - if (attrs == INVALID_FILE_ATTRIBUTES || !(attrs & FILE_ATTRIBUTE_DIRECTORY)) { |
523 | | - _mkdir(log_directory.c_str()); |
| 542 | + // Walk each component and create it if missing. |
| 543 | + for (std::size_t pos = 0; pos <= log_directory.size(); ++pos) { |
| 544 | + if (pos == log_directory.size() || |
| 545 | + log_directory[pos] == '\\' || log_directory[pos] == '/') { |
| 546 | + if (pos == 0) continue; |
| 547 | + std::string partial = log_directory.substr(0, pos); |
| 548 | + DWORD attrs = GetFileAttributesA(partial.c_str()); |
| 549 | + if (attrs == INVALID_FILE_ATTRIBUTES) { |
| 550 | + if (_mkdir(partial.c_str()) != 0 && errno != EEXIST) { |
| 551 | + std::cerr << "ze_logger: Failed to create log directory '" |
| 552 | + << partial << "': " << errnoToString(errno) << "\n"; |
| 553 | + return std::make_shared<ZeLogger>(); |
| 554 | + } |
| 555 | + } else if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) { |
| 556 | + std::cerr << "ze_logger: Log directory path component '" |
| 557 | + << partial << "' exists but is not a directory\n"; |
| 558 | + return std::make_shared<ZeLogger>(); |
| 559 | + } |
| 560 | + } |
524 | 561 | } |
525 | 562 | #else |
526 | | - struct stat st{}; |
527 | | - if (stat(log_directory.c_str(), &st) != 0 || !S_ISDIR(st.st_mode)) { |
528 | | - mkdir(log_directory.c_str(), 0755); |
| 563 | + // Walk each component and create it if missing. |
| 564 | + for (std::size_t pos = 0; pos <= log_directory.size(); ++pos) { |
| 565 | + if (pos == log_directory.size() || log_directory[pos] == '/') { |
| 566 | + if (pos == 0) continue; |
| 567 | + std::string partial = log_directory.substr(0, pos); |
| 568 | + struct stat st{}; |
| 569 | + if (stat(partial.c_str(), &st) != 0) { |
| 570 | + if (mkdir(partial.c_str(), 0755) != 0 && errno != EEXIST) { |
| 571 | + std::cerr << "ze_logger: Failed to create log directory '" |
| 572 | + << partial << "': " << errnoToString(errno) << "\n"; |
| 573 | + return std::make_shared<ZeLogger>(); |
| 574 | + } |
| 575 | + } else if (!S_ISDIR(st.st_mode)) { |
| 576 | + std::cerr << "ze_logger: Log directory path component '" |
| 577 | + << partial << "' exists but is not a directory\n"; |
| 578 | + return std::make_shared<ZeLogger>(); |
| 579 | + } |
| 580 | + } |
529 | 581 | } |
530 | 582 | #endif |
531 | 583 | logger = std::make_shared<ZeLogger>(full_log_file_path, level, log_pattern); |
|
0 commit comments