Release v0.2.0 with incident diagnostics and in-app log viewer
This commit is contained in:
+100
-4
@@ -527,16 +527,112 @@ std::wstring lastError(const std::wstring &context)
|
||||
{
|
||||
return context + L" (Windows error " + std::to_wstring(GetLastError()) + L")";
|
||||
}
|
||||
std::wstring diagnosticText(const std::wstring &text)
|
||||
{
|
||||
// Escape UTF-16 code units so invisible characters and malformed surrogate pairs
|
||||
// can be reproduced without introducing fake log lines. Bound large selections.
|
||||
std::wstring out = L"\"";
|
||||
constexpr wchar_t hex[] = L"0123456789abcdef";
|
||||
const auto count = std::min<size_t>(text.size(), 512);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
auto c = static_cast<unsigned short>(text[i]);
|
||||
if (c == L'"' || c == L'\\')
|
||||
{
|
||||
out += L'\\';
|
||||
out += c;
|
||||
}
|
||||
else if (c < 32 || c >= 127)
|
||||
{
|
||||
out += L"\\u";
|
||||
for (int shift = 12; shift >= 0; shift -= 4)
|
||||
out += hex[(c >> shift) & 15];
|
||||
}
|
||||
else
|
||||
out += c;
|
||||
}
|
||||
return out + L"\" units=" + std::to_wstring(text.size()) +
|
||||
L" truncated=" + std::to_wstring(count != text.size());
|
||||
}
|
||||
namespace
|
||||
{
|
||||
std::recursive_mutex logGate;
|
||||
bool loggingEnabled = false;
|
||||
DiagnosticBuffer diagnosticBuffer;
|
||||
}
|
||||
std::wstring DiagnosticBuffer::record(const std::wstring &message, bool problem, ULONGLONG now)
|
||||
{
|
||||
while (!entries.empty() && (entries.size() >= 40 || now - entries.front().first > 30000))
|
||||
entries.pop_front();
|
||||
entries.emplace_back(now, message);
|
||||
if (!problem)
|
||||
return {};
|
||||
std::wstring out = L"Diagnostic incident (preceding context, up to 30 seconds):";
|
||||
for (const auto &[at, entry] : entries)
|
||||
out += L"\n ageMs=" + std::to_wstring(now - at) + L" " + entry;
|
||||
entries.clear();
|
||||
return out;
|
||||
}
|
||||
void setLoggingEnabled(bool enabled)
|
||||
{
|
||||
std::lock_guard lock(logGate);
|
||||
loggingEnabled = enabled;
|
||||
if (!enabled)
|
||||
diagnosticBuffer.clear();
|
||||
}
|
||||
std::wstring readDiagnosticLog(bool previous)
|
||||
{
|
||||
std::lock_guard lock(logGate);
|
||||
auto path = dataDirectory() / (previous ? L"kswitcher.log.1" : L"kswitcher.log");
|
||||
if (!std::filesystem::exists(path))
|
||||
return previous ? L"No previous log is available yet." : L"No log yet. Entries will appear when a problem is detected.";
|
||||
auto bytes = readFile(path, 2 * 1024 * 1024);
|
||||
auto text = wide(std::string(bytes.begin(), bytes.end()));
|
||||
std::wstring display;
|
||||
for (size_t i = 0; i < text.size(); ++i)
|
||||
{
|
||||
if (text[i] == L'\n' && (i == 0 || text[i - 1] != L'\r'))
|
||||
display += L'\r';
|
||||
display += text[i];
|
||||
}
|
||||
return display.empty() ? L"The log is empty." : display;
|
||||
}
|
||||
void clearDiagnosticLog(bool previous)
|
||||
{
|
||||
// Serialize clearing with writes and rotation. Never delete the other log.
|
||||
std::lock_guard lock(logGate);
|
||||
auto path = dataDirectory() / (previous ? L"kswitcher.log.1" : L"kswitcher.log");
|
||||
std::filesystem::remove(path);
|
||||
if (!previous)
|
||||
diagnosticBuffer.clear();
|
||||
}
|
||||
void diagnosticEvent(const std::wstring &message, bool problem)
|
||||
{
|
||||
std::lock_guard lock(logGate);
|
||||
if (!loggingEnabled)
|
||||
return;
|
||||
auto incident = diagnosticBuffer.record(message, problem, GetTickCount64());
|
||||
if (!incident.empty())
|
||||
log(incident);
|
||||
}
|
||||
void log(const std::wstring &message)
|
||||
{
|
||||
static std::mutex gate;
|
||||
std::lock_guard lock(gate);
|
||||
std::lock_guard lock(logGate);
|
||||
if (!loggingEnabled)
|
||||
return;
|
||||
try
|
||||
{
|
||||
auto path = dataDirectory() / L"kswitcher.log";
|
||||
std::error_code ec;
|
||||
if (std::filesystem::file_size(path, ec) > 256 * 1024)
|
||||
std::filesystem::remove(path, ec);
|
||||
auto size = std::filesystem::file_size(path, ec);
|
||||
if (!ec && size > 256 * 1024)
|
||||
{
|
||||
auto previous = path;
|
||||
previous += L".1";
|
||||
std::filesystem::remove(previous, ec);
|
||||
ec.clear();
|
||||
std::filesystem::rename(path, previous, ec);
|
||||
}
|
||||
std::ofstream f(path, std::ios::app);
|
||||
SYSTEMTIME t;
|
||||
GetLocalTime(&t);
|
||||
|
||||
Reference in New Issue
Block a user