Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +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` | `ze_loader.log` | Log filename, supports runtime pattern tokens (see below) |
| `ZEL_LOADER_LOG_PATTERN` | see below | Custom log format pattern |

## Output destination
Expand All @@ -87,6 +87,26 @@ 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

`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`

A `%` not followed by `P`, `N`, or `T` is kept verbatim in the filename.

Examples:
```
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

Default pattern (used when `ZEL_LOADER_LOG_PATTERN` is not set):
Expand Down
106 changes: 104 additions & 2 deletions source/utils/ze_logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "ze_util.h"

#include <cerrno>
#include <cctype>
#include <chrono>
#include <cstdio>
#include <cstring>
Expand Down Expand Up @@ -54,6 +55,7 @@ static bool winEnableAnsiColor(int fd) {

#else
#include <unistd.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
Expand All @@ -70,6 +72,98 @@ 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<unsigned char>(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<long long>(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 '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
Expand Down Expand Up @@ -575,10 +669,17 @@ std::shared_ptr<ZeLogger> createLogger(const std::string &caller) {
loader_file = LOADER_LOG_FILE;
}

// 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;
}

#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");
Expand Down Expand Up @@ -679,6 +780,7 @@ std::shared_ptr<ZeLogger> 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 Resolved log filename : " + resolved_loader_file;
cfg += "\n ZEL_LOADER_LOG_PATTERN : " + log_pattern;
cfg += "\n Output : " + output_dest;
logger->info(cfg);
Expand Down