From c37b945c2ec667a3de9edd369e190e3d33bf29c9 Mon Sep 17 00:00:00 2001 From: Maciej Plewka Date: Mon, 20 Jul 2026 11:16:16 +0200 Subject: [PATCH 1/2] Add ZEL_LOADER_LOG_FILE_PATTERN to customize log filename Signed-off-by: Maciej Plewka --- README.md | 20 +++++++ source/utils/ze_logger.cpp | 114 ++++++++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1d98f871..c7474688 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ The Level Zero Loader provides built-in logging controlled via environment varia | `ZEL_LOADER_LOGGING_LEVEL` | `warn` | Log level: `trace`, `debug`, `info`, `warn`, `error`, `critical`, `off` | | `ZEL_LOADER_LOG_DIR` | `~/.oneapi_logs` | Directory to write the log file into | | `ZEL_LOADER_LOG_FILE` | `ze_loader.log` | Log filename | +| `ZEL_LOADER_LOG_FILE_PATTERN` | unset | Filename pattern with runtime tokens, overrides `ZEL_LOADER_LOG_FILE` when set | | `ZEL_LOADER_LOG_PATTERN` | see below | Custom log format pattern | ## Output destination @@ -87,6 +88,25 @@ The two flags control output as follows: The log directory (`ZEL_LOADER_LOG_DIR`) is created automatically on first use if it does not exist. +## Log file pattern + +When `ZEL_LOADER_LOG_FILE_PATTERN` is set, the loader resolves the log filename from the pattern +and uses that result instead of `ZEL_LOADER_LOG_FILE`. This makes it possible to keep separate +log files per process or per run. + +Supported filename pattern tokens: +- `%P` — process id +- `%N` — process executable base name +- `%T` — logger startup timestamp formatted as `YYYYMMDD-HHMMSS` +- `%%` — literal percent sign + +Examples: +``` +ZEL_LOADER_LOG_FILE_PATTERN=ze_loader-%P.log +ZEL_LOADER_LOG_FILE_PATTERN=%N-%P.log +ZEL_LOADER_LOG_FILE_PATTERN=%N-%T-%P.log +``` + ## Log pattern Default pattern (used when `ZEL_LOADER_LOG_PATTERN` is not set): diff --git a/source/utils/ze_logger.cpp b/source/utils/ze_logger.cpp index 1049dd6e..5715e9d5 100644 --- a/source/utils/ze_logger.cpp +++ b/source/utils/ze_logger.cpp @@ -10,6 +10,7 @@ #include "ze_util.h" #include +#include #include #include #include @@ -54,6 +55,7 @@ static bool winEnableAnsiColor(int fd) { #else #include +#include #include #include #include @@ -70,6 +72,102 @@ namespace loader { // --------------------------------------------------------------------------- namespace { +std::string baseNameFromPath(const std::string &path) { + const std::size_t pos = path.find_last_of("\\/"); + if (pos == std::string::npos) { + return path; + } + return path.substr(pos + 1); +} + +std::string sanitizeFileNameComponent(std::string value) { + if (value.empty()) { + return "process"; + } + for (char &ch : value) { + const unsigned char uch = static_cast(ch); + if (uch < 0x20 || ch == '<' || ch == '>' || ch == ':' || ch == '"' || + ch == '/' || ch == '\\' || ch == '|' || ch == '?' || ch == '*') { + ch = '_'; + } + } + return value; +} + +std::string currentProcessName() { +#ifdef _WIN32 + char module_path[MAX_PATH] = {}; + const DWORD len = GetModuleFileNameA(nullptr, module_path, MAX_PATH); + if (len != 0) { + return sanitizeFileNameComponent(baseNameFromPath(std::string(module_path, len))); + } +#else + char module_path[PATH_MAX] = {}; + const ssize_t len = readlink("/proc/self/exe", module_path, sizeof(module_path) - 1); + if (len > 0) { + module_path[len] = '\0'; + return sanitizeFileNameComponent(baseNameFromPath(module_path)); + } +#endif + return "process"; +} + +std::string startupTimestampForFileName() { + const auto now = std::chrono::system_clock::now(); + const auto now_t = std::chrono::system_clock::to_time_t(now); + std::tm tm_buf{}; +#ifdef _WIN32 + localtime_s(&tm_buf, &now_t); +#else + localtime_r(&now_t, &tm_buf); +#endif + + char timestamp[32] = {}; + std::strftime(timestamp, sizeof(timestamp), "%Y%m%d-%H%M%S", &tm_buf); + return timestamp; +} + +std::string expandLogFilePattern(const std::string &pattern) { + if (pattern.empty()) { + return pattern; + } + + const std::string pid = std::to_string(static_cast(GET_PID())); + const std::string process_name = currentProcessName(); + const std::string timestamp = startupTimestampForFileName(); + + std::string expanded; + expanded.reserve(pattern.size() + pid.size() + process_name.size()); + + for (std::size_t i = 0; i < pattern.size(); ++i) { + if (pattern[i] == '%' && i + 1 < pattern.size()) { + switch (pattern[i + 1]) { + case '%': + expanded.push_back('%'); + ++i; + continue; + case 'P': + expanded += pid; + ++i; + continue; + case 'N': + expanded += process_name; + ++i; + continue; + case 'T': + expanded += timestamp; + ++i; + continue; + default: + break; + } + } + expanded.push_back(pattern[i]); + } + + return expanded; +} + struct AnsiColor { static const char *reset() { return "\033[0m"; } static const char *trace() { return "\033[37m"; } // white @@ -575,10 +673,20 @@ std::shared_ptr createLogger(const std::string &caller) { loader_file = LOADER_LOG_FILE; } + auto loader_file_pattern = getenv_string("ZEL_LOADER_LOG_FILE_PATTERN"); + if (loader_file_pattern.empty()) { + loader_file_pattern = loader_file; + } + + std::string resolved_loader_file = expandLogFilePattern(loader_file_pattern); + if (resolved_loader_file.empty()) { + resolved_loader_file = loader_file; + } + #ifdef _WIN32 - std::string full_log_file_path = log_directory + "\\" + loader_file; + std::string full_log_file_path = log_directory + "\\" + resolved_loader_file; #else - std::string full_log_file_path = log_directory + "/" + loader_file; + std::string full_log_file_path = log_directory + "/" + resolved_loader_file; #endif const uint32_t logging_mode = getenv_tomode("ZEL_ENABLE_LOADER_LOGGING"); @@ -679,6 +787,8 @@ std::shared_ptr createLogger(const std::string &caller) { cfg += "\n ZEL_LOADER_LOGGING_LEVEL : " + log_level; cfg += "\n ZEL_LOADER_LOG_DIR : " + log_directory; cfg += "\n ZEL_LOADER_LOG_FILE : " + loader_file; + cfg += "\n ZEL_LOADER_LOG_FILE_PATTERN : " + loader_file_pattern; + cfg += "\n Resolved log filename : " + resolved_loader_file; cfg += "\n ZEL_LOADER_LOG_PATTERN : " + log_pattern; cfg += "\n Output : " + output_dest; logger->info(cfg); From 5ddab105d73c58e6497a00b35e6e9bde2bb29ff2 Mon Sep 17 00:00:00 2001 From: Maciej Plewka Date: Tue, 21 Jul 2026 07:56:07 +0200 Subject: [PATCH 2/2] Support runtime pattern tokens in ZEL_LOADER_LOG_FILE Signed-off-by: Maciej Plewka --- README.md | 18 +++++++++--------- source/utils/ze_logger.cpp | 14 +++----------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index c7474688..659c3275 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,7 @@ The Level Zero Loader provides built-in logging controlled via environment varia | `ZEL_LOADER_LOG_CONSOLE` | `0` | Set to `1` to enable console (stderr) logging, overrides file logging | | `ZEL_LOADER_LOGGING_LEVEL` | `warn` | Log level: `trace`, `debug`, `info`, `warn`, `error`, `critical`, `off` | | `ZEL_LOADER_LOG_DIR` | `~/.oneapi_logs` | Directory to write the log file into | -| `ZEL_LOADER_LOG_FILE` | `ze_loader.log` | Log filename | -| `ZEL_LOADER_LOG_FILE_PATTERN` | unset | Filename pattern with runtime tokens, overrides `ZEL_LOADER_LOG_FILE` when set | +| `ZEL_LOADER_LOG_FILE` | `ze_loader.log` | Log filename, supports runtime pattern tokens (see below) | | `ZEL_LOADER_LOG_PATTERN` | see below | Custom log format pattern | ## Output destination @@ -90,21 +89,22 @@ The log directory (`ZEL_LOADER_LOG_DIR`) is created automatically on first use i ## Log file pattern -When `ZEL_LOADER_LOG_FILE_PATTERN` is set, the loader resolves the log filename from the pattern -and uses that result instead of `ZEL_LOADER_LOG_FILE`. This makes it possible to keep separate -log files per process or per run. +`ZEL_LOADER_LOG_FILE` may contain runtime tokens that the loader expands when resolving the log +filename. This makes it possible to keep separate log files per process or per run. A filename +without tokens (the default `ze_loader.log`) is used unchanged. Supported filename pattern tokens: - `%P` — process id - `%N` — process executable base name - `%T` — logger startup timestamp formatted as `YYYYMMDD-HHMMSS` -- `%%` — literal percent sign + +A `%` not followed by `P`, `N`, or `T` is kept verbatim in the filename. Examples: ``` -ZEL_LOADER_LOG_FILE_PATTERN=ze_loader-%P.log -ZEL_LOADER_LOG_FILE_PATTERN=%N-%P.log -ZEL_LOADER_LOG_FILE_PATTERN=%N-%T-%P.log +ZEL_LOADER_LOG_FILE=ze_loader-%P.log +ZEL_LOADER_LOG_FILE=%N-%P.log +ZEL_LOADER_LOG_FILE=%N-%T-%P.log ``` ## Log pattern diff --git a/source/utils/ze_logger.cpp b/source/utils/ze_logger.cpp index 5715e9d5..2cb53aef 100644 --- a/source/utils/ze_logger.cpp +++ b/source/utils/ze_logger.cpp @@ -142,10 +142,6 @@ std::string expandLogFilePattern(const std::string &pattern) { for (std::size_t i = 0; i < pattern.size(); ++i) { if (pattern[i] == '%' && i + 1 < pattern.size()) { switch (pattern[i + 1]) { - case '%': - expanded.push_back('%'); - ++i; - continue; case 'P': expanded += pid; ++i; @@ -673,12 +669,9 @@ std::shared_ptr createLogger(const std::string &caller) { loader_file = LOADER_LOG_FILE; } - auto loader_file_pattern = getenv_string("ZEL_LOADER_LOG_FILE_PATTERN"); - if (loader_file_pattern.empty()) { - loader_file_pattern = loader_file; - } - - std::string resolved_loader_file = expandLogFilePattern(loader_file_pattern); + // Expand filename pattern tokens (%P, %N, %T, %%) within ZEL_LOADER_LOG_FILE. + // A filename without tokens is returned unchanged, preserving existing behaviour. + std::string resolved_loader_file = expandLogFilePattern(loader_file); if (resolved_loader_file.empty()) { resolved_loader_file = loader_file; } @@ -787,7 +780,6 @@ std::shared_ptr createLogger(const std::string &caller) { cfg += "\n ZEL_LOADER_LOGGING_LEVEL : " + log_level; cfg += "\n ZEL_LOADER_LOG_DIR : " + log_directory; cfg += "\n ZEL_LOADER_LOG_FILE : " + loader_file; - cfg += "\n ZEL_LOADER_LOG_FILE_PATTERN : " + loader_file_pattern; cfg += "\n Resolved log filename : " + resolved_loader_file; cfg += "\n ZEL_LOADER_LOG_PATTERN : " + log_pattern; cfg += "\n Output : " + output_dest;