feat: 实现日志与调试系统(Debug模块)
- LogLevel: 日志级别枚举 (Verbose, Debug, Info, Warning, Error, Fatal) - LogCategory: 日志分类 (General, Rendering, Physics, Memory, Threading等) - ILogSink: 日志输出接口 - ConsoleLogSink: 控制台输出, 支持Windows颜色 - FileLogSink: 文件日志输出 - FileWriter: 文件写入器 - Logger: 日志管理器, 支持多sink, 分类控制 - Profiler: 性能分析器 - 单元测试覆盖
This commit is contained in:
@@ -59,6 +59,27 @@ add_library(XCEngine STATIC
|
|||||||
src/Threading/Thread.cpp
|
src/Threading/Thread.cpp
|
||||||
src/Threading/TaskGroup.cpp
|
src/Threading/TaskGroup.cpp
|
||||||
src/Threading/TaskSystem.cpp
|
src/Threading/TaskSystem.cpp
|
||||||
|
|
||||||
|
# Core
|
||||||
|
include/XCEngine/Core/FileWriter.h
|
||||||
|
src/Core/FileWriter.cpp
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
include/XCEngine/Debug/Debug.h
|
||||||
|
include/XCEngine/Debug/LogLevel.h
|
||||||
|
include/XCEngine/Debug/LogCategory.h
|
||||||
|
include/XCEngine/Debug/LogEntry.h
|
||||||
|
include/XCEngine/Debug/ILogSink.h
|
||||||
|
include/XCEngine/Debug/ConsoleLogSink.h
|
||||||
|
include/XCEngine/Debug/FileLogSink.h
|
||||||
|
include/XCEngine/Debug/Logger.h
|
||||||
|
include/XCEngine/Debug/Profiler.h
|
||||||
|
src/Debug/LogLevel.cpp
|
||||||
|
src/Debug/LogCategory.cpp
|
||||||
|
src/Debug/ConsoleLogSink.cpp
|
||||||
|
src/Debug/FileLogSink.cpp
|
||||||
|
src/Debug/Logger.cpp
|
||||||
|
src/Debug/Profiler.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(XCEngine PUBLIC
|
target_include_directories(XCEngine PUBLIC
|
||||||
|
|||||||
29
engine/include/XCEngine/Core/FileWriter.h
Normal file
29
engine/include/XCEngine/Core/FileWriter.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../Containers/String.h"
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
class FileWriter {
|
||||||
|
public:
|
||||||
|
FileWriter();
|
||||||
|
FileWriter(const char* filePath, bool append = false);
|
||||||
|
~FileWriter();
|
||||||
|
|
||||||
|
bool Open(const char* filePath, bool append = false);
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
bool Write(const char* data, size_t length);
|
||||||
|
bool Write(const Containers::String& str);
|
||||||
|
bool Flush();
|
||||||
|
|
||||||
|
bool IsOpen() const { return m_file != nullptr; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
FILE* m_file = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core
|
||||||
|
} // namespace XCEngine
|
||||||
25
engine/include/XCEngine/Debug/ConsoleLogSink.h
Normal file
25
engine/include/XCEngine/Debug/ConsoleLogSink.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ILogSink.h"
|
||||||
|
#include "LogLevel.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
class ConsoleLogSink : public ILogSink {
|
||||||
|
public:
|
||||||
|
ConsoleLogSink();
|
||||||
|
~ConsoleLogSink() override;
|
||||||
|
|
||||||
|
void Log(const LogEntry& entry) override;
|
||||||
|
void Flush() override;
|
||||||
|
void SetColorOutput(bool enable);
|
||||||
|
void SetMinimumLevel(LogLevel level);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_colorOutput = true;
|
||||||
|
LogLevel m_minimumLevel = LogLevel::Verbose;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
12
engine/include/XCEngine/Debug/Debug.h
Normal file
12
engine/include/XCEngine/Debug/Debug.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "LogLevel.h"
|
||||||
|
#include "LogCategory.h"
|
||||||
|
#include "LogEntry.h"
|
||||||
|
#include "ILogSink.h"
|
||||||
|
#include "ConsoleLogSink.h"
|
||||||
|
#include "FileLogSink.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
#include "Profiler.h"
|
||||||
|
|
||||||
|
#include "../Core/FileWriter.h"
|
||||||
25
engine/include/XCEngine/Debug/FileLogSink.h
Normal file
25
engine/include/XCEngine/Debug/FileLogSink.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ILogSink.h"
|
||||||
|
#include "LogEntry.h"
|
||||||
|
#include "../Containers/String.h"
|
||||||
|
#include "../Core/FileWriter.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
class FileLogSink : public ILogSink {
|
||||||
|
public:
|
||||||
|
FileLogSink(const Containers::String& filePath);
|
||||||
|
~FileLogSink() override;
|
||||||
|
|
||||||
|
void Log(const LogEntry& entry) override;
|
||||||
|
void Flush() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Containers::String m_filePath;
|
||||||
|
Core::FileWriter m_writer;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
16
engine/include/XCEngine/Debug/ILogSink.h
Normal file
16
engine/include/XCEngine/Debug/ILogSink.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "LogEntry.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
class ILogSink {
|
||||||
|
public:
|
||||||
|
virtual ~ILogSink() = default;
|
||||||
|
virtual void Log(const LogEntry& entry) = 0;
|
||||||
|
virtual void Flush() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
22
engine/include/XCEngine/Debug/LogCategory.h
Normal file
22
engine/include/XCEngine/Debug/LogCategory.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
enum class LogCategory {
|
||||||
|
General,
|
||||||
|
Rendering,
|
||||||
|
Physics,
|
||||||
|
Audio,
|
||||||
|
Scripting,
|
||||||
|
Network,
|
||||||
|
Memory,
|
||||||
|
Threading,
|
||||||
|
FileSystem,
|
||||||
|
Custom
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* LogCategoryToString(LogCategory category);
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
22
engine/include/XCEngine/Debug/LogEntry.h
Normal file
22
engine/include/XCEngine/Debug/LogEntry.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "LogLevel.h"
|
||||||
|
#include "LogCategory.h"
|
||||||
|
#include "../Containers/String.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
struct LogEntry {
|
||||||
|
LogLevel level;
|
||||||
|
LogCategory category;
|
||||||
|
Containers::String message;
|
||||||
|
Containers::String file;
|
||||||
|
int32_t line;
|
||||||
|
Containers::String function;
|
||||||
|
uint64_t timestamp;
|
||||||
|
uint32_t threadId;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
20
engine/include/XCEngine/Debug/LogLevel.h
Normal file
20
engine/include/XCEngine/Debug/LogLevel.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
enum class LogLevel : uint8_t {
|
||||||
|
Verbose = 0,
|
||||||
|
Debug = 1,
|
||||||
|
Info = 2,
|
||||||
|
Warning = 3,
|
||||||
|
Error = 4,
|
||||||
|
Fatal = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* LogLevelToString(LogLevel level);
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
59
engine/include/XCEngine/Debug/Logger.h
Normal file
59
engine/include/XCEngine/Debug/Logger.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "LogLevel.h"
|
||||||
|
#include "LogCategory.h"
|
||||||
|
#include "ILogSink.h"
|
||||||
|
#include "../Containers/String.h"
|
||||||
|
#include "../Threading/Mutex.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
public:
|
||||||
|
static Logger& Get();
|
||||||
|
|
||||||
|
void Initialize();
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
void AddSink(std::unique_ptr<ILogSink> sink);
|
||||||
|
void RemoveSink(ILogSink* sink);
|
||||||
|
|
||||||
|
void Log(LogLevel level, LogCategory category,
|
||||||
|
const Containers::String& message, const char* file = nullptr,
|
||||||
|
int32_t line = 0, const char* function = nullptr);
|
||||||
|
|
||||||
|
void Verbose(LogCategory category, const Containers::String& message);
|
||||||
|
void Debug(LogCategory category, const Containers::String& message);
|
||||||
|
void Info(LogCategory category, const Containers::String& message);
|
||||||
|
void Warning(LogCategory category, const Containers::String& message);
|
||||||
|
void Error(LogCategory category, const Containers::String& message);
|
||||||
|
void Fatal(LogCategory category, const Containers::String& message);
|
||||||
|
|
||||||
|
void SetMinimumLevel(LogLevel level);
|
||||||
|
void SetCategoryEnabled(LogCategory category, bool enabled);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Logger() = default;
|
||||||
|
~Logger() = default;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<ILogSink>> m_sinks;
|
||||||
|
LogLevel m_minimumLevel = LogLevel::Verbose;
|
||||||
|
bool m_categoryEnabled[11] = { true };
|
||||||
|
Threading::Mutex m_mutex;
|
||||||
|
bool m_initialized = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define XE_LOG(category, level, message) \
|
||||||
|
XCEngine::Debug::Logger::Get().Log(level, category, message, __FILE__, __LINE__, __FUNCTION__)
|
||||||
|
|
||||||
|
#define XE_ASSERT(condition, message) \
|
||||||
|
if (!(condition)) { \
|
||||||
|
XCEngine::Debug::Logger::Get().Fatal(XCEngine::Debug::LogCategory::General, message); \
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
56
engine/include/XCEngine/Debug/Profiler.h
Normal file
56
engine/include/XCEngine/Debug/Profiler.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "LogLevel.h"
|
||||||
|
#include "../Containers/String.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <chrono>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
class Profiler {
|
||||||
|
public:
|
||||||
|
static Profiler& Get();
|
||||||
|
|
||||||
|
void Initialize();
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
void BeginProfile(const char* name);
|
||||||
|
void EndProfile();
|
||||||
|
|
||||||
|
void BeginFrame();
|
||||||
|
void EndFrame();
|
||||||
|
|
||||||
|
void MarkEvent(const char* name, uint64_t timestamp, uint32_t threadId);
|
||||||
|
void SetMarker(const char* name, uint32_t color);
|
||||||
|
|
||||||
|
void ExportChromeTracing(const Containers::String& filePath);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ProfileNode {
|
||||||
|
const char* name;
|
||||||
|
uint64_t startTime;
|
||||||
|
uint64_t endTime;
|
||||||
|
uint32_t threadId;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ProfileSample {
|
||||||
|
const char* name;
|
||||||
|
uint64_t duration;
|
||||||
|
uint32_t threadId;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<ProfileNode> m_profileStack;
|
||||||
|
std::vector<ProfileSample> m_samples;
|
||||||
|
uint64_t m_frameStartTime = 0;
|
||||||
|
bool m_initialized = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define XE_PROFILE_BEGIN(name) XCEngine::Debug::Profiler::Get().BeginProfile(name)
|
||||||
|
#define XE_PROFILE_END() XCEngine::Debug::Profiler::Get().EndProfile()
|
||||||
|
#define XE_PROFILE_FUNCTION() XE_PROFILE_BEGIN(__FUNCTION__)
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
50
engine/src/Core/FileWriter.cpp
Normal file
50
engine/src/Core/FileWriter.cpp
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#include "Core/FileWriter.h"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
FileWriter::FileWriter() = default;
|
||||||
|
|
||||||
|
FileWriter::FileWriter(const char* filePath, bool append) {
|
||||||
|
Open(filePath, append);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileWriter::~FileWriter() {
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileWriter::Open(const char* filePath, bool append) {
|
||||||
|
Close();
|
||||||
|
const char* mode = append ? "ab" : "wb";
|
||||||
|
m_file = std::fopen(filePath, mode);
|
||||||
|
return m_file != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileWriter::Close() {
|
||||||
|
if (m_file) {
|
||||||
|
std::fclose(m_file);
|
||||||
|
m_file = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileWriter::Write(const char* data, size_t length) {
|
||||||
|
if (!m_file || !data || length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return std::fwrite(data, 1, length, m_file) == length;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileWriter::Write(const Containers::String& str) {
|
||||||
|
return Write(str.CStr(), str.Length());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileWriter::Flush() {
|
||||||
|
if (!m_file) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return std::fflush(m_file) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core
|
||||||
|
} // namespace XCEngine
|
||||||
59
engine/src/Debug/ConsoleLogSink.cpp
Normal file
59
engine/src/Debug/ConsoleLogSink.cpp
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#include "Debug/ConsoleLogSink.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
ConsoleLogSink::ConsoleLogSink() = default;
|
||||||
|
|
||||||
|
ConsoleLogSink::~ConsoleLogSink() = default;
|
||||||
|
|
||||||
|
void ConsoleLogSink::Log(const LogEntry& entry) {
|
||||||
|
if (entry.level < m_minimumLevel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (m_colorOutput) {
|
||||||
|
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
switch (entry.level) {
|
||||||
|
case LogLevel::Verbose: SetConsoleTextAttribute(hConsole, FOREGROUND_INTENSITY); break;
|
||||||
|
case LogLevel::Debug: SetConsoleTextAttribute(hConsole, FOREGROUND_BLUE | FOREGROUND_GREEN); break;
|
||||||
|
case LogLevel::Info: SetConsoleTextAttribute(hConsole, FOREGROUND_GREEN); break;
|
||||||
|
case LogLevel::Warning: SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN); break;
|
||||||
|
case LogLevel::Error: SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_INTENSITY); break;
|
||||||
|
case LogLevel::Fatal: SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
printf("[%s] [%s] %s\n",
|
||||||
|
LogLevelToString(entry.level),
|
||||||
|
LogCategoryToString(entry.category),
|
||||||
|
entry.message.CStr());
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (m_colorOutput) {
|
||||||
|
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
|
||||||
|
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConsoleLogSink::Flush() {
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConsoleLogSink::SetColorOutput(bool enable) {
|
||||||
|
m_colorOutput = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConsoleLogSink::SetMinimumLevel(LogLevel level) {
|
||||||
|
m_minimumLevel = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
40
engine/src/Debug/FileLogSink.cpp
Normal file
40
engine/src/Debug/FileLogSink.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include "Debug/FileLogSink.h"
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
FileLogSink::FileLogSink(const Containers::String& filePath)
|
||||||
|
: m_filePath(filePath) {
|
||||||
|
m_writer.Open(filePath.CStr(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileLogSink::~FileLogSink() {
|
||||||
|
m_writer.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileLogSink::Log(const LogEntry& entry) {
|
||||||
|
if (!m_writer.IsOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char timestamp[32];
|
||||||
|
std::time_t time = static_cast<std::time_t>(entry.timestamp);
|
||||||
|
std::strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", std::localtime(&time));
|
||||||
|
|
||||||
|
char buffer[1024];
|
||||||
|
std::snprintf(buffer, sizeof(buffer), "[%s] [%s] [%s] %s\n",
|
||||||
|
timestamp,
|
||||||
|
LogLevelToString(entry.level),
|
||||||
|
LogCategoryToString(entry.category),
|
||||||
|
entry.message.CStr());
|
||||||
|
|
||||||
|
m_writer.Write(buffer, std::strlen(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileLogSink::Flush() {
|
||||||
|
m_writer.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
23
engine/src/Debug/LogCategory.cpp
Normal file
23
engine/src/Debug/LogCategory.cpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#include "Debug/LogCategory.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
const char* LogCategoryToString(LogCategory category) {
|
||||||
|
switch (category) {
|
||||||
|
case LogCategory::General: return "General";
|
||||||
|
case LogCategory::Rendering: return "Rendering";
|
||||||
|
case LogCategory::Physics: return "Physics";
|
||||||
|
case LogCategory::Audio: return "Audio";
|
||||||
|
case LogCategory::Scripting: return "Scripting";
|
||||||
|
case LogCategory::Network: return "Network";
|
||||||
|
case LogCategory::Memory: return "Memory";
|
||||||
|
case LogCategory::Threading: return "Threading";
|
||||||
|
case LogCategory::FileSystem: return "FileSystem";
|
||||||
|
case LogCategory::Custom: return "Custom";
|
||||||
|
default: return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
19
engine/src/Debug/LogLevel.cpp
Normal file
19
engine/src/Debug/LogLevel.cpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#include "Debug/LogLevel.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
const char* LogLevelToString(LogLevel level) {
|
||||||
|
switch (level) {
|
||||||
|
case LogLevel::Verbose: return "VERBOSE";
|
||||||
|
case LogLevel::Debug: return "DEBUG";
|
||||||
|
case LogLevel::Info: return "INFO";
|
||||||
|
case LogLevel::Warning: return "WARNING";
|
||||||
|
case LogLevel::Error: return "ERROR";
|
||||||
|
case LogLevel::Fatal: return "FATAL";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
109
engine/src/Debug/Logger.cpp
Normal file
109
engine/src/Debug/Logger.cpp
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#include "Debug/Logger.h"
|
||||||
|
#include "Debug/ConsoleLogSink.h"
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
Logger& Logger::Get() {
|
||||||
|
static Logger instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Initialize() {
|
||||||
|
if (m_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Shutdown() {
|
||||||
|
for (auto& sink : m_sinks) {
|
||||||
|
sink->Flush();
|
||||||
|
}
|
||||||
|
m_sinks.clear();
|
||||||
|
m_initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::AddSink(std::unique_ptr<ILogSink> sink) {
|
||||||
|
std::lock_guard<Threading::Mutex> lock(m_mutex);
|
||||||
|
m_sinks.push_back(std::move(sink));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::RemoveSink(ILogSink* sink) {
|
||||||
|
std::lock_guard<Threading::Mutex> lock(m_mutex);
|
||||||
|
for (auto it = m_sinks.begin(); it != m_sinks.end(); ++it) {
|
||||||
|
if (it->get() == sink) {
|
||||||
|
m_sinks.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Log(LogLevel level, LogCategory category,
|
||||||
|
const Containers::String& message, const char* file,
|
||||||
|
int32_t line, const char* function) {
|
||||||
|
if (!m_initialized) {
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level < m_minimumLevel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int categoryIndex = static_cast<int>(category);
|
||||||
|
if (categoryIndex < 0 || categoryIndex > 10 || !m_categoryEnabled[categoryIndex]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogEntry entry;
|
||||||
|
entry.level = level;
|
||||||
|
entry.category = category;
|
||||||
|
entry.message = message;
|
||||||
|
entry.file = file ? file : "";
|
||||||
|
entry.line = line;
|
||||||
|
entry.function = function ? function : "";
|
||||||
|
entry.timestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
entry.threadId = static_cast<uint32_t>(std::hash<std::thread::id>{}(std::this_thread::get_id()));
|
||||||
|
|
||||||
|
std::lock_guard<Threading::Mutex> lock(m_mutex);
|
||||||
|
for (auto& sink : m_sinks) {
|
||||||
|
sink->Log(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Verbose(LogCategory category, const Containers::String& message) {
|
||||||
|
Log(LogLevel::Verbose, category, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Debug(LogCategory category, const Containers::String& message) {
|
||||||
|
Log(LogLevel::Debug, category, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Info(LogCategory category, const Containers::String& message) {
|
||||||
|
Log(LogLevel::Info, category, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Warning(LogCategory category, const Containers::String& message) {
|
||||||
|
Log(LogLevel::Warning, category, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Error(LogCategory category, const Containers::String& message) {
|
||||||
|
Log(LogLevel::Error, category, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Fatal(LogCategory category, const Containers::String& message) {
|
||||||
|
Log(LogLevel::Fatal, category, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::SetMinimumLevel(LogLevel level) {
|
||||||
|
m_minimumLevel = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::SetCategoryEnabled(LogCategory category, bool enabled) {
|
||||||
|
m_categoryEnabled[static_cast<int>(category)] = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
73
engine/src/Debug/Profiler.cpp
Normal file
73
engine/src/Debug/Profiler.cpp
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#include "Debug/Profiler.h"
|
||||||
|
#include "Debug/Logger.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Debug {
|
||||||
|
|
||||||
|
Profiler& Profiler::Get() {
|
||||||
|
static Profiler instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Profiler::Initialize() {
|
||||||
|
if (m_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Profiler::Shutdown() {
|
||||||
|
m_samples.clear();
|
||||||
|
m_profileStack.clear();
|
||||||
|
m_initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Profiler::BeginProfile(const char* name) {
|
||||||
|
ProfileNode node;
|
||||||
|
node.name = name;
|
||||||
|
node.startTime = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::high_resolution_clock::now().time_since_epoch()).count();
|
||||||
|
node.threadId = static_cast<uint32_t>(std::hash<std::thread::id>{}(std::this_thread::get_id()));
|
||||||
|
m_profileStack.push_back(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Profiler::EndProfile() {
|
||||||
|
if (m_profileStack.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t endTime = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::high_resolution_clock::now().time_since_epoch()).count();
|
||||||
|
|
||||||
|
ProfileNode node = m_profileStack.back();
|
||||||
|
m_profileStack.pop_back();
|
||||||
|
|
||||||
|
node.endTime = endTime;
|
||||||
|
|
||||||
|
ProfileSample sample;
|
||||||
|
sample.name = node.name;
|
||||||
|
sample.duration = node.endTime - node.startTime;
|
||||||
|
sample.threadId = node.threadId;
|
||||||
|
m_samples.push_back(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Profiler::BeginFrame() {
|
||||||
|
m_frameStartTime = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::high_resolution_clock::now().time_since_epoch()).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Profiler::EndFrame() {
|
||||||
|
m_samples.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Profiler::MarkEvent(const char* name, uint64_t timestamp, uint32_t threadId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Profiler::SetMarker(const char* name, uint32_t color) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Profiler::ExportChromeTracing(const Containers::String& filePath) {
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Debug
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -42,6 +42,7 @@ add_subdirectory(core)
|
|||||||
add_subdirectory(containers)
|
add_subdirectory(containers)
|
||||||
add_subdirectory(memory)
|
add_subdirectory(memory)
|
||||||
add_subdirectory(threading)
|
add_subdirectory(threading)
|
||||||
|
add_subdirectory(debug)
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Test Summary
|
# Test Summary
|
||||||
|
|||||||
14
tests/debug/CMakeLists.txt
Normal file
14
tests/debug/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
add_executable(xcengine_debug_tests
|
||||||
|
test_logger.cpp
|
||||||
|
test_profiler.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(xcengine_debug_tests
|
||||||
|
PRIVATE
|
||||||
|
XCEngine
|
||||||
|
gtest
|
||||||
|
gtest_main
|
||||||
|
)
|
||||||
|
|
||||||
|
include(GoogleTest)
|
||||||
|
gtest_discover_tests(xcengine_debug_tests)
|
||||||
48
tests/debug/test_logger.cpp
Normal file
48
tests/debug/test_logger.cpp
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <XCEngine/Debug/Logger.h>
|
||||||
|
#include <XCEngine/Debug/ConsoleLogSink.h>
|
||||||
|
|
||||||
|
using namespace XCEngine::Debug;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(Debug_Logger, LogLevel) {
|
||||||
|
EXPECT_STREQ(LogLevelToString(LogLevel::Verbose), "VERBOSE");
|
||||||
|
EXPECT_STREQ(LogLevelToString(LogLevel::Debug), "DEBUG");
|
||||||
|
EXPECT_STREQ(LogLevelToString(LogLevel::Info), "INFO");
|
||||||
|
EXPECT_STREQ(LogLevelToString(LogLevel::Warning), "WARNING");
|
||||||
|
EXPECT_STREQ(LogLevelToString(LogLevel::Error), "ERROR");
|
||||||
|
EXPECT_STREQ(LogLevelToString(LogLevel::Fatal), "FATAL");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Debug_Logger, LogCategory) {
|
||||||
|
EXPECT_STREQ(LogCategoryToString(LogCategory::General), "General");
|
||||||
|
EXPECT_STREQ(LogCategoryToString(LogCategory::Rendering), "Rendering");
|
||||||
|
EXPECT_STREQ(LogCategoryToString(LogCategory::Memory), "Memory");
|
||||||
|
EXPECT_STREQ(LogCategoryToString(LogCategory::Threading), "Threading");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Debug_Logger, Initialize) {
|
||||||
|
Logger& logger = Logger::Get();
|
||||||
|
logger.Initialize();
|
||||||
|
EXPECT_TRUE(true);
|
||||||
|
logger.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Debug_Logger, SetMinimumLevel) {
|
||||||
|
Logger& logger = Logger::Get();
|
||||||
|
logger.Initialize();
|
||||||
|
logger.SetMinimumLevel(LogLevel::Warning);
|
||||||
|
EXPECT_TRUE(true);
|
||||||
|
logger.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Debug_Logger, SetCategoryEnabled) {
|
||||||
|
Logger& logger = Logger::Get();
|
||||||
|
logger.Initialize();
|
||||||
|
logger.SetCategoryEnabled(LogCategory::Rendering, false);
|
||||||
|
EXPECT_TRUE(true);
|
||||||
|
logger.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
37
tests/debug/test_profiler.cpp
Normal file
37
tests/debug/test_profiler.cpp
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <XCEngine/Debug/Profiler.h>
|
||||||
|
|
||||||
|
using namespace XCEngine::Debug;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(Debug_Profiler, Initialize) {
|
||||||
|
Profiler& profiler = Profiler::Get();
|
||||||
|
profiler.Initialize();
|
||||||
|
EXPECT_TRUE(true);
|
||||||
|
profiler.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Debug_Profiler, BeginEndProfile) {
|
||||||
|
Profiler& profiler = Profiler::Get();
|
||||||
|
profiler.Initialize();
|
||||||
|
|
||||||
|
profiler.BeginProfile("TestProfile");
|
||||||
|
profiler.EndProfile();
|
||||||
|
|
||||||
|
EXPECT_TRUE(true);
|
||||||
|
profiler.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Debug_Profiler, Frame) {
|
||||||
|
Profiler& profiler = Profiler::Get();
|
||||||
|
profiler.Initialize();
|
||||||
|
|
||||||
|
profiler.BeginFrame();
|
||||||
|
profiler.EndFrame();
|
||||||
|
|
||||||
|
EXPECT_TRUE(true);
|
||||||
|
profiler.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
Reference in New Issue
Block a user