logging
This commit is contained in:
@@ -308,6 +308,8 @@ set(RENDER_CADENCE_APP_SOURCES
|
|||||||
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.cpp"
|
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.cpp"
|
||||||
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.h"
|
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameTypes.h"
|
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameTypes.h"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/logging/Logger.cpp"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/logging/Logger.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/platform/HiddenGlWindow.cpp"
|
"${RENDER_CADENCE_APP_DIR}/platform/HiddenGlWindow.cpp"
|
||||||
"${RENDER_CADENCE_APP_DIR}/platform/HiddenGlWindow.h"
|
"${RENDER_CADENCE_APP_DIR}/platform/HiddenGlWindow.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/render/Bgra8ReadbackPipeline.cpp"
|
"${RENDER_CADENCE_APP_DIR}/render/Bgra8ReadbackPipeline.cpp"
|
||||||
@@ -349,6 +351,7 @@ target_include_directories(RenderCadenceCompositor PRIVATE
|
|||||||
"${RENDER_CADENCE_APP_DIR}"
|
"${RENDER_CADENCE_APP_DIR}"
|
||||||
"${RENDER_CADENCE_APP_DIR}/app"
|
"${RENDER_CADENCE_APP_DIR}/app"
|
||||||
"${RENDER_CADENCE_APP_DIR}/frames"
|
"${RENDER_CADENCE_APP_DIR}/frames"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/logging"
|
||||||
"${RENDER_CADENCE_APP_DIR}/platform"
|
"${RENDER_CADENCE_APP_DIR}/platform"
|
||||||
"${RENDER_CADENCE_APP_DIR}/render"
|
"${RENDER_CADENCE_APP_DIR}/render"
|
||||||
"${RENDER_CADENCE_APP_DIR}/runtime"
|
"${RENDER_CADENCE_APP_DIR}/runtime"
|
||||||
@@ -803,6 +806,21 @@ endif()
|
|||||||
|
|
||||||
add_test(NAME RenderCadenceCompositorRuntimeShaderParamsTests COMMAND RenderCadenceCompositorRuntimeShaderParamsTests)
|
add_test(NAME RenderCadenceCompositorRuntimeShaderParamsTests COMMAND RenderCadenceCompositorRuntimeShaderParamsTests)
|
||||||
|
|
||||||
|
add_executable(RenderCadenceCompositorLoggerTests
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/logging/Logger.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorLoggerTests.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(RenderCadenceCompositorLoggerTests PRIVATE
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(RenderCadenceCompositorLoggerTests PRIVATE /W3)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME RenderCadenceCompositorLoggerTests COMMAND RenderCadenceCompositorLoggerTests)
|
||||||
|
|
||||||
add_executable(SystemOutputFramePoolTests
|
add_executable(SystemOutputFramePoolTests
|
||||||
"${APP_DIR}/videoio/SystemOutputFramePool.cpp"
|
"${APP_DIR}/videoio/SystemOutputFramePool.cpp"
|
||||||
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ Included now:
|
|||||||
- render-thread-only GL commit once the artifact is ready
|
- render-thread-only GL commit once the artifact is ready
|
||||||
- manifest-driven stateless single-pass shader packages
|
- manifest-driven stateless single-pass shader packages
|
||||||
- default float, vec2, color, boolean, enum, and trigger parameters
|
- default float, vec2, color, boolean, enum, and trigger parameters
|
||||||
|
- background logging with `log`, `warning`, and `error` levels
|
||||||
- compact telemetry
|
- compact telemetry
|
||||||
- non-GL frame-exchange tests
|
- non-GL frame-exchange tests
|
||||||
|
|
||||||
@@ -100,6 +101,10 @@ Use `--no-shader` to keep the simple motion fallback only.
|
|||||||
|
|
||||||
## Expected Telemetry
|
## Expected Telemetry
|
||||||
|
|
||||||
|
Startup, shutdown, shader-build, and render-thread event messages are written through the app logger. Telemetry is intentionally separate and remains a compact once-per-second cadence line.
|
||||||
|
|
||||||
|
The logger writes to the console, `OutputDebugStringA`, and `logs/render-cadence-compositor.log` by default. Render-thread log calls use the non-blocking path so diagnostics do not become cadence blockers.
|
||||||
|
|
||||||
The app prints one line per second:
|
The app prints one line per second:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
#include "app/AppConfig.h"
|
#include "app/AppConfig.h"
|
||||||
#include "app/RenderCadenceApp.h"
|
#include "app/RenderCadenceApp.h"
|
||||||
#include "frames/SystemFrameExchange.h"
|
#include "frames/SystemFrameExchange.h"
|
||||||
|
#include "logging/Logger.h"
|
||||||
#include "render/RenderThread.h"
|
#include "render/RenderThread.h"
|
||||||
#include "VideoIOFormat.h"
|
#include "VideoIOFormat.h"
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -38,17 +40,22 @@ private:
|
|||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
|
RenderCadenceCompositor::AppConfig appConfig = RenderCadenceCompositor::DefaultAppConfig();
|
||||||
|
RenderCadenceCompositor::Logger::Instance().Start(appConfig.logging);
|
||||||
|
RenderCadenceCompositor::Log(
|
||||||
|
"app",
|
||||||
|
"RenderCadenceCompositor starting. Starts render cadence, system-memory exchange, DeckLink scheduled output, and telemetry. Press Enter to stop.");
|
||||||
|
|
||||||
ComInitGuard com;
|
ComInitGuard com;
|
||||||
if (!com.Initialize())
|
if (!com.Initialize())
|
||||||
{
|
{
|
||||||
std::cerr << "COM initialization failed: 0x" << std::hex << com.Result() << std::dec << "\n";
|
std::ostringstream message;
|
||||||
|
message << "COM initialization failed: 0x" << std::hex << com.Result();
|
||||||
|
RenderCadenceCompositor::LogError("app", message.str());
|
||||||
|
RenderCadenceCompositor::Logger::Instance().Stop();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "RenderCadenceCompositor\n"
|
|
||||||
<< " Starts render cadence, system-memory exchange, DeckLink scheduled output, and telemetry.\n"
|
|
||||||
<< " Press Enter to stop.\n";
|
|
||||||
|
|
||||||
SystemFrameExchangeConfig frameExchangeConfig;
|
SystemFrameExchangeConfig frameExchangeConfig;
|
||||||
frameExchangeConfig.width = 1920;
|
frameExchangeConfig.width = 1920;
|
||||||
frameExchangeConfig.height = 1080;
|
frameExchangeConfig.height = 1080;
|
||||||
@@ -66,7 +73,6 @@ int main(int argc, char** argv)
|
|||||||
|
|
||||||
RenderThread renderThread(frameExchange, renderConfig);
|
RenderThread renderThread(frameExchange, renderConfig);
|
||||||
|
|
||||||
RenderCadenceCompositor::AppConfig appConfig = RenderCadenceCompositor::DefaultAppConfig();
|
|
||||||
for (int index = 1; index < argc; ++index)
|
for (int index = 1; index < argc; ++index)
|
||||||
{
|
{
|
||||||
const std::string argument = argv[index];
|
const std::string argument = argv[index];
|
||||||
@@ -87,12 +93,15 @@ int main(int argc, char** argv)
|
|||||||
std::string error;
|
std::string error;
|
||||||
if (!app.Start(error))
|
if (!app.Start(error))
|
||||||
{
|
{
|
||||||
std::cerr << "RenderCadenceCompositor start failed: " << error << "\n";
|
RenderCadenceCompositor::LogError("app", "RenderCadenceCompositor start failed: " + error);
|
||||||
|
RenderCadenceCompositor::Logger::Instance().Stop();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string line;
|
std::string line;
|
||||||
std::getline(std::cin, line);
|
std::getline(std::cin, line);
|
||||||
app.Stop();
|
app.Stop();
|
||||||
|
RenderCadenceCompositor::Log("app", "RenderCadenceCompositor stopped.");
|
||||||
|
RenderCadenceCompositor::Logger::Instance().Stop();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ AppConfig DefaultAppConfig()
|
|||||||
config.deckLink.outputAlphaRequired = false;
|
config.deckLink.outputAlphaRequired = false;
|
||||||
config.outputThread.targetBufferedFrames = 4;
|
config.outputThread.targetBufferedFrames = 4;
|
||||||
config.telemetry.interval = std::chrono::seconds(1);
|
config.telemetry.interval = std::chrono::seconds(1);
|
||||||
|
config.logging.minimumLevel = LogLevel::Log;
|
||||||
|
config.logging.writeToConsole = true;
|
||||||
|
config.logging.writeToDebugOutput = true;
|
||||||
|
config.logging.writeToFile = true;
|
||||||
|
config.logging.filePath = "logs/render-cadence-compositor.log";
|
||||||
|
config.logging.maxQueuedMessages = 1024;
|
||||||
config.warmupCompletedFrames = 4;
|
config.warmupCompletedFrames = 4;
|
||||||
config.warmupTimeout = std::chrono::seconds(3);
|
config.warmupTimeout = std::chrono::seconds(3);
|
||||||
config.prerollTimeout = std::chrono::seconds(3);
|
config.prerollTimeout = std::chrono::seconds(3);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "../logging/Logger.h"
|
||||||
#include "../telemetry/TelemetryPrinter.h"
|
#include "../telemetry/TelemetryPrinter.h"
|
||||||
#include "../video/DeckLinkOutput.h"
|
#include "../video/DeckLinkOutput.h"
|
||||||
#include "../video/DeckLinkOutputThread.h"
|
#include "../video/DeckLinkOutputThread.h"
|
||||||
@@ -15,6 +16,7 @@ struct AppConfig
|
|||||||
DeckLinkOutputConfig deckLink;
|
DeckLinkOutputConfig deckLink;
|
||||||
DeckLinkOutputThreadConfig outputThread;
|
DeckLinkOutputThreadConfig outputThread;
|
||||||
TelemetryPrinterConfig telemetry;
|
TelemetryPrinterConfig telemetry;
|
||||||
|
LoggerConfig logging;
|
||||||
std::size_t warmupCompletedFrames = 4;
|
std::size_t warmupCompletedFrames = 4;
|
||||||
std::chrono::milliseconds warmupTimeout = std::chrono::seconds(3);
|
std::chrono::milliseconds warmupTimeout = std::chrono::seconds(3);
|
||||||
std::chrono::milliseconds prerollTimeout = std::chrono::seconds(3);
|
std::chrono::milliseconds prerollTimeout = std::chrono::seconds(3);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "AppConfig.h"
|
#include "AppConfig.h"
|
||||||
|
#include "../logging/Logger.h"
|
||||||
#include "../runtime/RuntimeShaderBridge.h"
|
#include "../runtime/RuntimeShaderBridge.h"
|
||||||
#include "../telemetry/TelemetryPrinter.h"
|
#include "../telemetry/TelemetryPrinter.h"
|
||||||
#include "../video/DeckLinkOutput.h"
|
#include "../video/DeckLinkOutput.h"
|
||||||
#include "../video/DeckLinkOutputThread.h"
|
#include "../video/DeckLinkOutputThread.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -65,6 +65,7 @@ public:
|
|||||||
|
|
||||||
bool Start(std::string& error)
|
bool Start(std::string& error)
|
||||||
{
|
{
|
||||||
|
Log("app", "Initializing DeckLink output.");
|
||||||
if (!mOutput.Initialize(
|
if (!mOutput.Initialize(
|
||||||
mConfig.deckLink,
|
mConfig.deckLink,
|
||||||
[this](const VideoIOCompletion& completion) {
|
[this](const VideoIOCompletion& completion) {
|
||||||
@@ -72,44 +73,56 @@ public:
|
|||||||
},
|
},
|
||||||
error))
|
error))
|
||||||
{
|
{
|
||||||
|
LogError("app", "DeckLink output initialization failed: " + error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log("app", "Starting render thread.");
|
||||||
if (!detail::StartRenderThread(mRenderThread, error, 0))
|
if (!detail::StartRenderThread(mRenderThread, error, 0))
|
||||||
{
|
{
|
||||||
|
LogError("app", "Render thread start failed: " + error);
|
||||||
Stop();
|
Stop();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
StartRuntimeShaderBuild();
|
StartRuntimeShaderBuild();
|
||||||
|
|
||||||
|
Log("app", "Waiting for rendered warmup frames.");
|
||||||
if (!mFrameExchange.WaitForCompletedDepth(mConfig.warmupCompletedFrames, mConfig.warmupTimeout))
|
if (!mFrameExchange.WaitForCompletedDepth(mConfig.warmupCompletedFrames, mConfig.warmupTimeout))
|
||||||
{
|
{
|
||||||
error = "Timed out waiting for rendered warmup frames.";
|
error = "Timed out waiting for rendered warmup frames.";
|
||||||
|
LogError("app", error);
|
||||||
Stop();
|
Stop();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log("app", "Starting DeckLink output thread.");
|
||||||
if (!mOutputThread.Start())
|
if (!mOutputThread.Start())
|
||||||
{
|
{
|
||||||
error = "DeckLink output thread failed to start.";
|
error = "DeckLink output thread failed to start.";
|
||||||
|
LogError("app", error);
|
||||||
Stop();
|
Stop();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log("app", "Waiting for DeckLink preroll frames.");
|
||||||
if (!WaitForPreroll())
|
if (!WaitForPreroll())
|
||||||
{
|
{
|
||||||
error = "Timed out waiting for DeckLink preroll frames.";
|
error = "Timed out waiting for DeckLink preroll frames.";
|
||||||
|
LogError("app", error);
|
||||||
Stop();
|
Stop();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log("app", "Starting DeckLink scheduled playback.");
|
||||||
if (!mOutput.StartScheduledPlayback(error))
|
if (!mOutput.StartScheduledPlayback(error))
|
||||||
{
|
{
|
||||||
|
LogError("app", "DeckLink scheduled playback failed: " + error);
|
||||||
Stop();
|
Stop();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mTelemetry.Start(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
mTelemetry.Start(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
||||||
|
Log("app", "RenderCadenceCompositor started.");
|
||||||
mStarted = true;
|
mStarted = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -122,6 +135,8 @@ public:
|
|||||||
StopRuntimeShaderBuild();
|
StopRuntimeShaderBuild();
|
||||||
mRenderThread.Stop();
|
mRenderThread.Stop();
|
||||||
mOutput.ReleaseResources();
|
mOutput.ReleaseResources();
|
||||||
|
if (mStarted)
|
||||||
|
Log("app", "RenderCadenceCompositor shutdown complete.");
|
||||||
mStarted = false;
|
mStarted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,13 +158,20 @@ private:
|
|||||||
|
|
||||||
void StartRuntimeShaderBuild()
|
void StartRuntimeShaderBuild()
|
||||||
{
|
{
|
||||||
|
if (mConfig.runtimeShaderId.empty())
|
||||||
|
{
|
||||||
|
Log("runtime-shader", "Runtime shader build disabled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log("runtime-shader", "Starting background Slang build for shader '" + mConfig.runtimeShaderId + "'.");
|
||||||
mShaderBridge.Start(
|
mShaderBridge.Start(
|
||||||
mConfig.runtimeShaderId,
|
mConfig.runtimeShaderId,
|
||||||
[this](const RuntimeShaderArtifact& artifact) {
|
[this](const RuntimeShaderArtifact& artifact) {
|
||||||
mRenderThread.SubmitRuntimeShaderArtifact(artifact);
|
mRenderThread.SubmitRuntimeShaderArtifact(artifact);
|
||||||
},
|
},
|
||||||
[](const std::string& message) {
|
[](const std::string& message) {
|
||||||
std::cout << "Runtime Slang build failed: " << message << "\n";
|
LogError("runtime-shader", "Runtime Slang build failed: " + message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
283
apps/RenderCadenceCompositor/logging/Logger.cpp
Normal file
283
apps/RenderCadenceCompositor/logging/Logger.cpp
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
#include "Logger.h"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <ctime>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace RenderCadenceCompositor
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
int Rank(LogLevel level)
|
||||||
|
{
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case LogLevel::Log:
|
||||||
|
return 0;
|
||||||
|
case LogLevel::Warning:
|
||||||
|
return 1;
|
||||||
|
case LogLevel::Error:
|
||||||
|
return 2;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FormatTimestamp(std::chrono::system_clock::time_point timestamp)
|
||||||
|
{
|
||||||
|
const std::time_t time = std::chrono::system_clock::to_time_t(timestamp);
|
||||||
|
std::tm localTime = {};
|
||||||
|
localtime_s(&localTime, &time);
|
||||||
|
|
||||||
|
std::ostringstream stream;
|
||||||
|
stream << std::put_time(&localTime, "%Y-%m-%d %H:%M:%S");
|
||||||
|
return stream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FormatRecord(const Logger::Record& record)
|
||||||
|
{
|
||||||
|
std::ostringstream stream;
|
||||||
|
stream << FormatTimestamp(record.timestamp)
|
||||||
|
<< " [" << LogLevelName(record.level) << "]"
|
||||||
|
<< " [" << record.subsystem << "] "
|
||||||
|
<< record.message;
|
||||||
|
return stream.str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* LogLevelName(LogLevel level)
|
||||||
|
{
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case LogLevel::Log:
|
||||||
|
return "log";
|
||||||
|
case LogLevel::Warning:
|
||||||
|
return "warning";
|
||||||
|
case LogLevel::Error:
|
||||||
|
return "error";
|
||||||
|
default:
|
||||||
|
return "log";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger& Logger::Instance()
|
||||||
|
{
|
||||||
|
static Logger logger;
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::~Logger()
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Start(LoggerConfig config)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
|
if (mRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mConfig = config;
|
||||||
|
mStopping = false;
|
||||||
|
mRunning = true;
|
||||||
|
OpenFileSink();
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> countersLock(mCountersMutex);
|
||||||
|
mCounters = LoggerCounters();
|
||||||
|
}
|
||||||
|
mThread = std::thread([this]() { ThreadMain(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Stop()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
|
if (!mRunning && !mThread.joinable())
|
||||||
|
return;
|
||||||
|
mStopping = true;
|
||||||
|
}
|
||||||
|
mCondition.notify_all();
|
||||||
|
|
||||||
|
if (mThread.joinable())
|
||||||
|
mThread.join();
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
|
CloseFileSink();
|
||||||
|
mRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Write(LogLevel level, const std::string& subsystem, const std::string& message)
|
||||||
|
{
|
||||||
|
Enqueue(Record{ std::chrono::system_clock::now(), std::this_thread::get_id(), level, subsystem, message }, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Logger::TryWrite(LogLevel level, const std::string& subsystem, const std::string& message)
|
||||||
|
{
|
||||||
|
return Enqueue(Record{ std::chrono::system_clock::now(), std::this_thread::get_id(), level, subsystem, message }, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Log(const std::string& subsystem, const std::string& message)
|
||||||
|
{
|
||||||
|
Write(LogLevel::Log, subsystem, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Warning(const std::string& subsystem, const std::string& message)
|
||||||
|
{
|
||||||
|
Write(LogLevel::Warning, subsystem, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Error(const std::string& subsystem, const std::string& message)
|
||||||
|
{
|
||||||
|
Write(LogLevel::Error, subsystem, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerCounters Logger::Counters() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mCountersMutex);
|
||||||
|
return mCounters;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Logger::IsRunning() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
|
return mRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Logger::ShouldWrite(LogLevel level) const
|
||||||
|
{
|
||||||
|
return Rank(level) >= Rank(mConfig.minimumLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Logger::Enqueue(Record record, bool block)
|
||||||
|
{
|
||||||
|
if (!ShouldWrite(record.level))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(mMutex, std::defer_lock);
|
||||||
|
if (block)
|
||||||
|
{
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
else if (!lock.try_lock())
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> countersLock(mCountersMutex);
|
||||||
|
++mCounters.dropped;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mRunning)
|
||||||
|
{
|
||||||
|
lock.unlock();
|
||||||
|
WriteRecord(record);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mQueue.size() >= mConfig.maxQueuedMessages)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> countersLock(mCountersMutex);
|
||||||
|
++mCounters.dropped;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mQueue.push_back(std::move(record));
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> countersLock(mCountersMutex);
|
||||||
|
++mCounters.queued;
|
||||||
|
}
|
||||||
|
lock.unlock();
|
||||||
|
mCondition.notify_one();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::ThreadMain()
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
Record record;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
mCondition.wait(lock, [this]() {
|
||||||
|
return mStopping || !mQueue.empty();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mQueue.empty())
|
||||||
|
{
|
||||||
|
if (mStopping)
|
||||||
|
return;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
record = std::move(mQueue.front());
|
||||||
|
mQueue.pop_front();
|
||||||
|
}
|
||||||
|
WriteRecord(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::WriteRecord(const Record& record)
|
||||||
|
{
|
||||||
|
const std::string line = FormatRecord(record);
|
||||||
|
|
||||||
|
if (mConfig.writeToDebugOutput)
|
||||||
|
OutputDebugStringA((line + "\n").c_str());
|
||||||
|
|
||||||
|
if (mConfig.writeToConsole)
|
||||||
|
{
|
||||||
|
if (record.level == LogLevel::Error)
|
||||||
|
std::cerr << line << "\n";
|
||||||
|
else
|
||||||
|
std::cout << line << "\n";
|
||||||
|
}
|
||||||
|
if (mFile.is_open())
|
||||||
|
{
|
||||||
|
mFile << line << "\n";
|
||||||
|
mFile.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mCountersMutex);
|
||||||
|
++mCounters.written;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::OpenFileSink()
|
||||||
|
{
|
||||||
|
CloseFileSink();
|
||||||
|
if (!mConfig.writeToFile || mConfig.filePath.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::error_code error;
|
||||||
|
const std::filesystem::path logPath(mConfig.filePath);
|
||||||
|
if (logPath.has_parent_path())
|
||||||
|
std::filesystem::create_directories(logPath.parent_path(), error);
|
||||||
|
mFile.open(logPath, std::ios::out | std::ios::app);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::CloseFileSink()
|
||||||
|
{
|
||||||
|
if (mFile.is_open())
|
||||||
|
mFile.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TryLog(LogLevel level, const std::string& subsystem, const std::string& message)
|
||||||
|
{
|
||||||
|
return Logger::Instance().TryWrite(level, subsystem, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Log(const std::string& subsystem, const std::string& message)
|
||||||
|
{
|
||||||
|
Logger::Instance().Log(subsystem, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWarning(const std::string& subsystem, const std::string& message)
|
||||||
|
{
|
||||||
|
Logger::Instance().Warning(subsystem, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogError(const std::string& subsystem, const std::string& message)
|
||||||
|
{
|
||||||
|
Logger::Instance().Error(subsystem, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
101
apps/RenderCadenceCompositor/logging/Logger.h
Normal file
101
apps/RenderCadenceCompositor/logging/Logger.h
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <deque>
|
||||||
|
#include <fstream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace RenderCadenceCompositor
|
||||||
|
{
|
||||||
|
enum class LogLevel
|
||||||
|
{
|
||||||
|
Log,
|
||||||
|
Warning,
|
||||||
|
Error
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoggerConfig
|
||||||
|
{
|
||||||
|
LogLevel minimumLevel = LogLevel::Log;
|
||||||
|
bool writeToConsole = true;
|
||||||
|
bool writeToDebugOutput = true;
|
||||||
|
bool writeToFile = true;
|
||||||
|
std::string filePath = "logs/render-cadence-compositor.log";
|
||||||
|
std::size_t maxQueuedMessages = 1024;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoggerCounters
|
||||||
|
{
|
||||||
|
uint64_t queued = 0;
|
||||||
|
uint64_t written = 0;
|
||||||
|
uint64_t dropped = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* LogLevelName(LogLevel level);
|
||||||
|
|
||||||
|
class Logger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Record
|
||||||
|
{
|
||||||
|
std::chrono::system_clock::time_point timestamp;
|
||||||
|
std::thread::id threadId;
|
||||||
|
LogLevel level = LogLevel::Log;
|
||||||
|
std::string subsystem;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
static Logger& Instance();
|
||||||
|
|
||||||
|
Logger(const Logger&) = delete;
|
||||||
|
Logger& operator=(const Logger&) = delete;
|
||||||
|
|
||||||
|
~Logger();
|
||||||
|
|
||||||
|
void Start(LoggerConfig config = LoggerConfig());
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
void Write(LogLevel level, const std::string& subsystem, const std::string& message);
|
||||||
|
bool TryWrite(LogLevel level, const std::string& subsystem, const std::string& message);
|
||||||
|
|
||||||
|
void Log(const std::string& subsystem, const std::string& message);
|
||||||
|
void Warning(const std::string& subsystem, const std::string& message);
|
||||||
|
void Error(const std::string& subsystem, const std::string& message);
|
||||||
|
|
||||||
|
LoggerCounters Counters() const;
|
||||||
|
bool IsRunning() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Logger() = default;
|
||||||
|
|
||||||
|
bool ShouldWrite(LogLevel level) const;
|
||||||
|
bool Enqueue(Record record, bool block);
|
||||||
|
void ThreadMain();
|
||||||
|
void WriteRecord(const Record& record);
|
||||||
|
void OpenFileSink();
|
||||||
|
void CloseFileSink();
|
||||||
|
|
||||||
|
LoggerConfig mConfig;
|
||||||
|
std::ofstream mFile;
|
||||||
|
std::thread mThread;
|
||||||
|
mutable std::mutex mMutex;
|
||||||
|
std::condition_variable mCondition;
|
||||||
|
std::deque<Record> mQueue;
|
||||||
|
bool mStopping = false;
|
||||||
|
bool mRunning = false;
|
||||||
|
|
||||||
|
mutable std::mutex mCountersMutex;
|
||||||
|
LoggerCounters mCounters;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool TryLog(LogLevel level, const std::string& subsystem, const std::string& message);
|
||||||
|
void Log(const std::string& subsystem, const std::string& message);
|
||||||
|
void LogWarning(const std::string& subsystem, const std::string& message);
|
||||||
|
void LogError(const std::string& subsystem, const std::string& message);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "../frames/SystemFrameExchange.h"
|
#include "../frames/SystemFrameExchange.h"
|
||||||
#include "../frames/SystemFrameTypes.h"
|
#include "../frames/SystemFrameTypes.h"
|
||||||
|
#include "../logging/Logger.h"
|
||||||
#include "../platform/HiddenGlWindow.h"
|
#include "../platform/HiddenGlWindow.h"
|
||||||
#include "Bgra8ReadbackPipeline.h"
|
#include "Bgra8ReadbackPipeline.h"
|
||||||
#include "GLExtensions.h"
|
#include "GLExtensions.h"
|
||||||
@@ -10,7 +11,6 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
RenderThread::RenderThread(SystemFrameExchange& frameExchange, Config config) :
|
RenderThread::RenderThread(SystemFrameExchange& frameExchange, Config config) :
|
||||||
mFrameExchange(frameExchange),
|
mFrameExchange(frameExchange),
|
||||||
@@ -71,6 +71,7 @@ RenderThread::Metrics RenderThread::GetMetrics() const
|
|||||||
|
|
||||||
void RenderThread::ThreadMain()
|
void RenderThread::ThreadMain()
|
||||||
{
|
{
|
||||||
|
RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Log, "render-thread", "Render thread starting.");
|
||||||
HiddenGlWindow window;
|
HiddenGlWindow window;
|
||||||
std::string error;
|
std::string error;
|
||||||
if (!window.Create(mConfig.width, mConfig.height, error) || !window.MakeCurrent())
|
if (!window.Create(mConfig.width, mConfig.height, error) || !window.MakeCurrent())
|
||||||
@@ -156,6 +157,7 @@ void RenderThread::ThreadMain()
|
|||||||
renderer.ShutdownGl();
|
renderer.ShutdownGl();
|
||||||
window.ClearCurrent();
|
window.ClearCurrent();
|
||||||
mRunning.store(false, std::memory_order_release);
|
mRunning.store(false, std::memory_order_release);
|
||||||
|
RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Log, "render-thread", "Render thread stopped.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderThread::SignalStarted()
|
void RenderThread::SignalStarted()
|
||||||
@@ -167,6 +169,7 @@ void RenderThread::SignalStarted()
|
|||||||
|
|
||||||
void RenderThread::SignalStartupFailure(const std::string& error)
|
void RenderThread::SignalStartupFailure(const std::string& error)
|
||||||
{
|
{
|
||||||
|
RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Error, "render-thread", error);
|
||||||
std::lock_guard<std::mutex> lock(mStartupMutex);
|
std::lock_guard<std::mutex> lock(mStartupMutex);
|
||||||
mStartupError = error;
|
mStartupError = error;
|
||||||
mStartupCondition.notify_all();
|
mStartupCondition.notify_all();
|
||||||
@@ -221,13 +224,19 @@ void RenderThread::TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeSha
|
|||||||
std::string commitError;
|
std::string commitError;
|
||||||
if (!runtimeShaderRenderer.CommitShaderArtifact(artifact, commitError))
|
if (!runtimeShaderRenderer.CommitShaderArtifact(artifact, commitError))
|
||||||
{
|
{
|
||||||
OutputDebugStringA(("Runtime shader GL commit failed: " + commitError + "\n").c_str());
|
RenderCadenceCompositor::TryLog(
|
||||||
|
RenderCadenceCompositor::LogLevel::Error,
|
||||||
|
"render-thread",
|
||||||
|
"Runtime shader GL commit failed: " + commitError);
|
||||||
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
||||||
++mMetrics.shaderBuildFailures;
|
++mMetrics.shaderBuildFailures;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputDebugStringA(("Runtime shader committed: " + artifact.shaderId + ". " + artifact.message + "\n").c_str());
|
RenderCadenceCompositor::TryLog(
|
||||||
|
RenderCadenceCompositor::LogLevel::Log,
|
||||||
|
"render-thread",
|
||||||
|
"Runtime shader committed: " + artifact.shaderId + ". " + artifact.message);
|
||||||
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
||||||
++mMetrics.shaderBuildsCommitted;
|
++mMetrics.shaderBuildsCommitted;
|
||||||
}
|
}
|
||||||
|
|||||||
88
tests/RenderCadenceCompositorLoggerTests.cpp
Normal file
88
tests/RenderCadenceCompositorLoggerTests.cpp
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#include "Logger.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
int gFailures = 0;
|
||||||
|
|
||||||
|
void Expect(bool condition, const std::string& message)
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
++gFailures;
|
||||||
|
std::cerr << "FAILED: " << message << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestLevelNames()
|
||||||
|
{
|
||||||
|
using namespace RenderCadenceCompositor;
|
||||||
|
Expect(std::string(LogLevelName(LogLevel::Log)) == "log", "log level name");
|
||||||
|
Expect(std::string(LogLevelName(LogLevel::Warning)) == "warning", "warning level name");
|
||||||
|
Expect(std::string(LogLevelName(LogLevel::Error)) == "error", "error level name");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestLevelFiltering()
|
||||||
|
{
|
||||||
|
using namespace RenderCadenceCompositor;
|
||||||
|
|
||||||
|
LoggerConfig config;
|
||||||
|
config.minimumLevel = LogLevel::Warning;
|
||||||
|
config.writeToConsole = false;
|
||||||
|
config.writeToDebugOutput = false;
|
||||||
|
config.writeToFile = false;
|
||||||
|
config.maxQueuedMessages = 16;
|
||||||
|
|
||||||
|
Logger& logger = Logger::Instance();
|
||||||
|
logger.Start(config);
|
||||||
|
logger.Write(LogLevel::Log, "test", "filtered");
|
||||||
|
logger.Write(LogLevel::Warning, "test", "kept");
|
||||||
|
logger.Write(LogLevel::Error, "test", "kept");
|
||||||
|
logger.Stop();
|
||||||
|
|
||||||
|
const LoggerCounters counters = logger.Counters();
|
||||||
|
Expect(counters.queued == 2, "logger queues only messages at or above minimum level");
|
||||||
|
Expect(counters.written == 2, "logger writes queued messages before stop returns");
|
||||||
|
Expect(counters.dropped == 0, "logger does not drop under queue capacity");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestTryWriteDropsWhenQueueIsFull()
|
||||||
|
{
|
||||||
|
using namespace RenderCadenceCompositor;
|
||||||
|
|
||||||
|
LoggerConfig config;
|
||||||
|
config.minimumLevel = LogLevel::Log;
|
||||||
|
config.writeToConsole = false;
|
||||||
|
config.writeToDebugOutput = false;
|
||||||
|
config.writeToFile = false;
|
||||||
|
config.maxQueuedMessages = 0;
|
||||||
|
|
||||||
|
Logger& logger = Logger::Instance();
|
||||||
|
logger.Start(config);
|
||||||
|
|
||||||
|
const bool sawDrop = !logger.TryWrite(LogLevel::Log, "test", "message");
|
||||||
|
logger.Stop();
|
||||||
|
|
||||||
|
Expect(sawDrop || logger.Counters().dropped > 0, "try-write reports or counts queue pressure");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
TestLevelNames();
|
||||||
|
TestLevelFiltering();
|
||||||
|
TestTryWriteDropsWhenQueueIsFull();
|
||||||
|
|
||||||
|
if (gFailures != 0)
|
||||||
|
{
|
||||||
|
std::cerr << gFailures << " RenderCadenceCompositorLogger test failure(s).\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "RenderCadenceCompositorLogger tests passed.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user