#include "engine/framework/debug/trace.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace engine::debug {
namespace {
struct LoggerState {
std::optional<:string> file_path;
std::unique_ptr<:ofstream> file_stream;
};
LoggerState & logger_state() {
static LoggerState state;
return state;
}
std::mutex & logger_mutex() {
static std::mutex mutex;
return mutex;
}
std::atomic_bool & logger_enabled_flag() {
static std::atomic_bool enabled{false};
return enabled;
}
std::ostream & logger_output_locked(LoggerState & state) {
if (!state.file_path.has_value() || state.file_path->empty()) {
return std::cout;
}
if (!state.file_stream || !state.file_stream->is_open()) {
state.file_stream = std::make_unique<:ofstream>(*state.file_path, std::ios::app);
if (!state.file_stream->is_open()) {
throw std::runtime_error("Failed to open log file: " + *state.file_path);
}
}
return *state.file_stream;
}
std::string timestamp_seconds_local() {
const auto now = std::chrono::system_clock::now();
const std::time_t now_time = std::chrono::system_clock::to_time_t(now);
std::tm local_tm{};
#if defined(_WIN32)
localtime_s(&local_tm, &now_time);
#else
localtime_r(&now_time, &local_tm);
#endif
std::ostringstream out;
out << std::put_time(&local_tm, "%Y%m%d-%H%M%S");
return out.str();
}
std::string format_scalar_double(double value) {
char buffer[64];
const auto result = std::to_chars(buffer, buffer + sizeof(buffer), value, std::chars_format::general);
if (result.ec == std::errc()) {
return std::string(buffer, result.ptr);
}
std::ostringstream out;
out.precision(17);
out << value;
return out.str();
}
} // namespace
std::string_view to_string(LogLevel level) noexcept {
switch (level) {
case LogLevel::Debug:
return "debug";
case LogLevel::Info:
return "info";
case LogLevel::Warning:
return "warning";
case LogLevel::Error:
return "error";
}
return "unknown";
}
void configure_logging(const LoggingConfig & config) {
std::lock_guard<:mutex> lock(logger_mutex());
auto & state = logger_state();
logger_enabled_flag().store(false, std::memory_order_release);
if (state.file_path != config.file_path) {
state.file_stream.reset();
}
state.file_path = config.file_path;
if (config.enabled && state.file_path.has_value() && !state.file_path->empty()) {
(void)logger_output_locked(state);
}
logger_enabled_flag().store(config.enabled, std::memory_order_release);
}
void reset_logging() {
configure_logging(LoggingConfig{});
}
bool log_enabled() {
return logger_enabled_flag().load(std::memory_order_acquire);
}
void log_message(std::string_view line) {
if (!log_enabled()) {
return;
}
std::lock_guard<:mutex> lock(logger_mutex());
if (!log_enabled()) {
return;
}
auto & state = logger_state();
auto & output = logger_output_locked(state);
output << line << "\n";
output.flush();
}
void log_message(LogLevel level, std::string_view category, std::string_view message) {
if (!log_enabled()) {
return;
}
std::ostringstream out;
out << "[" << to_string(level) << "]";
if (!category.empty()) {
out << "[" << category << "]";
}
out << " " << message;
log_message(out.str());
}
bool trace_log_enabled() {
return log_enabled();
}
bool timing_log_enabled() {
return log_enabled();
}
std::string format_dims(const std::vector & dims) {
std::ostringstream oss;
oss << "[";
for (size_t i = 0; i < dims.size(); ++i) {
if (i != 0) {
oss << ",";
}
oss << dims[i];
}
oss << "]";
return oss.str();
}
std::vector sample_point_indices(size_t count, size_t target) {
if (count == 0) {
return {};
}
if (count <= target) {
std::vector all(count);
for (size_t i = 0; i < count; ++i) {
all[i] = i;
}
return all;
}
const size_t first_count = 14;
const size_t middle_count = 12;
const size_t last_count = 14;
const size_t first_end = count / 3;
const size_t middle_end = (count * 2) / 3;
auto append_range = [](std::vector & dst, size_t begin, size_t end, size_t samples) {
if (begin >= end || samples == 0) {
return;
}
const size_t span = end - begin;
if (span <= samples) {
for (size_t i = begin; i < end; ++i) {
if (dst.empty() || dst.back() != i) {
dst.push_back(i);
}
}
return;
}
for (size_t i = 0; i < samples; ++i) {
const double pos = samples == 1 ? 0.0 : static_cast(i) / static_cast(samples - 1);
const size_t offset = static_cast(pos * static_cast(span - 1));
const size_t index = begin + offset;
if (dst.empty() || dst.back() != index) {
dst.push_back(index);
}
}
};
std::vector points;
points.reserve(target);
append_range(points, 0, first_end, first_count);
append_range(points, first_end, middle_end, middle_count);
append_range(points, middle_end, count, last_count);
if (points.empty() || points.front() != 0) {
points.insert(points.begin(), 0);
}
if (points.back() != count - 1) {
points.push_back(count - 1);
}
points.erase(std::unique(points.begin(), points.end()), points.end());
return points;
}
std::string sample_points_f32(const std::vector & values) {
if (values.empty()) {
return "[]";
}
const std::vector points = sample_point_indices(values.size());
std::ostringstream oss;
oss << "[";
for (size_t i = 0; i < points.size(); ++i) {
if (i != 0) {
oss << ",";
}
const size_t index = points[i];
oss << index << ":" << values[index];
}
oss << "]";
return oss.str();
}
std::string sample_points_i32(const std::vector & values) {
if (values.empty()) {
return "[]";
}
const std::vector points = sample_point_indices(values.size());
std::ostringstream oss;
oss << "[";
for (size_t i = 0; i < points.size(); ++i) {
if (i != 0) {
oss << ",";
}
const size_t index = points[i];
oss << index << ":" << values[index];
}
oss << "]";
return oss.str();
}
void trace_log_f32(const std::string & name, const std::vector & dims, const std::vector & values) {
if (!trace_log_enabled()) {
return;
}
std::ostringstream output;
output << "[TRACE ts=" << timestamp_seconds_local() << "] " << name
<< " shape=" << format_dims(dims)
<< " size=" << values.size()
<< " samples=" << sample_points_f32(values);
log_message(output.str());
}
void trace_log_i32(const std::string & name, const std::vector & dims, const std::vector & values) {
if (!trace_log_enabled()) {
return;
}
std::ostringstream output;
output << "[TRACE ts=" << timestamp_seconds_local() << "] " << name
<< " shape=" << format_dims(dims)
<< " size=" << values.size()
<< " samples=" << sample_points_i32(values);
log_message(output.str());
}
void trace_log_scalar(const std::string & name, std::string_view value) {
if (!trace_log_enabled()) {
return;
}
std::ostringstream output;
output << "[TRACE ts=" << timestamp_seconds_local() << "] " << name << " " << value;
log_message(output.str());
}
void trace_log_scalar(const std::string & name, double value) {
if (!trace_log_enabled()) {
return;
}
trace_log_scalar(name, format_scalar_double(value));
}
void trace_log_scalar(const std::string & name, int64_t value) {
if (!trace_log_enabled()) {
return;
}
trace_log_scalar(name, std::to_string(value));
}
void trace_log_scalar(const std::string & name, uint64_t value) {
if (!trace_log_enabled()) {
return;
}
trace_log_scalar(name, std::to_string(value));
}
void trace_log_scalar(const std::string & name, bool value) {
if (!trace_log_enabled()) {
return;
}
trace_log_scalar(name, std::string_view(value ? "1" : "0"));
}
void timing_log_scalar(const std::string & name, std::string_view value) {
if (!timing_log_enabled()) {
return;
}
std::ostringstream output;
output << "[TIMING ts=" << timestamp_seconds_local() << "] " << name << " " << value;
log_message(output.str());
}
void timing_log_scalar(const std::string & name, double value) {
if (!timing_log_enabled()) {
return;
}
timing_log_scalar(name, format_scalar_double(value));
}
void timing_log_scalar(const std::string & name, int64_t value) {
if (!timing_log_enabled()) {
return;
}
timing_log_scalar(name, std::to_string(value));
}
void timing_log_scalar(const std::string & name, uint64_t value) {
if (!timing_log_enabled()) {
return;
}
timing_log_scalar(name, std::to_string(value));
}
void timing_log_scalar(const std::string & name, bool value) {
if (!timing_log_enabled()) {
return;
}
timing_log_scalar(name, std::string_view(value ? "1" : "0"));
}
} // namespace engine::debug