Files
UnrealEngineUWP/Engine/Source/Programs/Unsync/Private/UnsyncLog.cpp
Yuriy ODonnell b5709042fb Import Unsync into the main source tree
This is a binary patching and incremental downloading tool, similar to rsync or zsync. It aims to improve the large binary download processes that previously were served by robocopy (i.e. full packages produced by the build farm).

The original code can be found in `//depot/usr/yuriy.odonnell/unsync`. This commit is a branch from the original location to preserve history.

While the codebase is designed to be self-contained and does not depend on any engine libraries, it mostly follows the UE coding guidelines and can be built with UBT.

Currently only Windows is supported, however the tool is expected to also work on Mac and Linux in the future.

#rb Martin.Ridgers
#preflight skip

[CL 18993571 by Yuriy ODonnell in ue5-main branch]
2022-02-15 04:30:27 -05:00

404 lines
7.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UnsyncLog.h"
#include "UnsyncCore.h"
#include "UnsyncError.h"
#include "UnsyncFile.h"
#include "UnsyncUtil.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <atomic>
#include <mutex>
#include <vector>
#if UNSYNC_PLATFORM_WINDOWS
# include <Dbghelp.h>
# include <Windows.h>
# pragma comment(lib, "Dbghelp.lib")
#endif // UNSYNC_PLATFORM_WINDOWS
namespace unsync {
#if UNSYNC_PLATFORM_WINDOWS
bool
IsDebuggerPresent()
{
return ::IsDebuggerPresent();
}
#else
bool
IsDebuggerPresent()
{
return false; // TODO
}
#endif
thread_local bool GLogVerbose = false;
bool GLogVeryVerbose = false;
bool GBreakOnError = IsDebuggerPresent();
bool GBreakOnWarning = false;
thread_local uint32 GLogIndent = 0;
bool GLogProgress = false;
std::mutex GLogMutex;
std::atomic<uint32> GLogThreadIndexCounter;
thread_local uint32 GLogThreadIndex = ~0u;
FTimePoint GNextFlushTime = TimePointNow();
static void
LogConditionalFlush(FILE* Stream)
{
FTimePoint NextFlushTime = GNextFlushTime;
FTimePoint CurrentTime = TimePointNow();
if (CurrentTime > NextFlushTime || GLogProgress)
{
GNextFlushTime = CurrentTime + std::chrono::milliseconds(1000);
fflush(Stream);
}
}
static uint32
GetLogThreadIndex()
{
if (GLogThreadIndex == ~0u)
{
GLogThreadIndex = GLogThreadIndexCounter++;
}
return GLogThreadIndex;
}
struct FLogFile
{
FLogFile(const wchar_t* InFilename) : Filename(InFilename)
{
#if UNSYNC_PLATFORM_WINDOWS
Handle = _wfopen(InFilename, L"wt, ccs=UNICODE");
#else
// TODO
#endif
}
~FLogFile()
{
if (Handle)
{
fclose(Handle);
}
}
std::wstring Filename;
FILE* Handle = nullptr;
};
std::unique_ptr<FLogFile> GLogFile;
void
LogSetFileInternal(const wchar_t* Filename)
{
GLogFile = {};
if (Filename)
{
GLogFile = std::make_unique<FLogFile>(Filename);
}
}
std::vector<std::unique_ptr<FLogFile>> GLogFileStack;
FLogFileScope::FLogFileScope(const wchar_t* Filename)
{
{
std::lock_guard<std::mutex> LockGuard(GLogMutex);
GLogFileStack.push_back(std::move(GLogFile));
LogSetFileInternal(Filename);
}
UNSYNC_VERBOSE2(L"UNSYNC %hs started logging to file '%ls'", GetVersionString().c_str(), Filename);
}
FLogFileScope::~FLogFileScope()
{
UNSYNC_VERBOSE2(L"Finished logging to file");
std::lock_guard<std::mutex> LockGuard(GLogMutex);
std::swap(GLogFile, GLogFileStack.back());
GLogFileStack.pop_back();
}
void
LogProgress(const wchar_t* ItemName, uint64 Current, uint64 Total)
{
if (!GLogProgress)
{
return;
}
std::lock_guard<std::mutex> LockGuard(GLogMutex);
if (ItemName == nullptr)
{
ItemName = L"*";
}
wprintf(L"@progress [%ls] %llu / %llu\n", ItemName, Current, Total);
LogConditionalFlush(stdout);
}
void
LogStatus(const wchar_t* InItemName, const wchar_t* Status)
{
if (!GLogProgress)
{
return;
}
std::lock_guard<std::mutex> LockGuard(GLogMutex);
const wchar_t* ItemName = InItemName ? InItemName : L"*";
wprintf(L"@status [%ls] %ls\n", ItemName, Status);
if (InItemName)
{
LogConditionalFlush(stdout);
}
else
{
fflush(stdout);
}
}
void
LogFlush()
{
std::lock_guard<std::mutex> LockGuard(GLogMutex);
fflush(stdout);
if (GLogFile && GLogFile->Handle)
{
FILE* F = GLogFile->Handle;
fflush(F);
}
}
void
LogPrintf(ELogLevel Level, const wchar_t* Str, ...)
{
std::lock_guard<std::mutex> LockGuard(GLogMutex);
bool bShouldIndent = false;
const wchar_t* Prefix = nullptr;
if (Level == ELogLevel::Error)
{
Prefix = L"ERROR: ";
}
else if (Level == ELogLevel::Warning)
{
Prefix = L"WARNING: ";
}
else if (GLogIndent)
{
// Indent the log only for info/debug/trace levels
bShouldIndent = true;
}
ELogLevel MaxDisplayLevel = ELogLevel::Info;
if (GLogVerbose)
{
if (GLogVeryVerbose)
{
MaxDisplayLevel = ELogLevel::Trace;
}
else
{
MaxDisplayLevel = ELogLevel::Debug;
}
}
if (Level <= MaxDisplayLevel)
{
if (Prefix)
{
wprintf(Prefix);
}
if (bShouldIndent)
{
wprintf(L"%*c", GLogIndent, L' ');
}
va_list Va;
va_start(Va, Str);
vwprintf(Str, Va);
va_end(Va);
LogConditionalFlush(stdout);
}
if (GLogFile && GLogFile->Handle)
{
FILE* F = GLogFile->Handle;
uint32 ThreadIndex = GetLogThreadIndex();
fwprintf(F, L"[%3d] ", ThreadIndex);
switch (Level)
{
case ELogLevel::Error:
fwprintf(F, L"[ERROR] ");
break;
case ELogLevel::Warning:
fwprintf(F, L"[WARN] ");
break;
case ELogLevel::Info:
fwprintf(F, L"[INFO] ");
break;
case ELogLevel::Debug:
fwprintf(F, L"[DEBUG] ");
break;
case ELogLevel::Trace:
fwprintf(F, L"[TRACE] ");
break;
default:
break;
}
va_list Va;
va_start(Va, Str);
vfwprintf(F, Str, Va);
va_end(Va);
if (Level == ELogLevel::Error || Level == ELogLevel::Warning)
{
fflush(F);
}
}
}
extern const char* HttpStatusToString(int32 Code);
void
LogError(const FError& E)
{
const char* ErrorKindStr = nullptr;
const char* ErrorDescStr = nullptr;
switch (E.Kind)
{
default:
case EErrorKind::Unknown:
ErrorKindStr = "Unknown";
break;
case EErrorKind::Http:
ErrorDescStr = HttpStatusToString(E.Code);
ErrorKindStr = "HTTP";
break;
case EErrorKind::System:
ErrorKindStr = "System";
break;
case EErrorKind::App:
ErrorKindStr = "Application";
break;
}
if (ErrorDescStr)
{
LogPrintf(ELogLevel::Error,
L"%hs code: %d (%hs). Context: %ls\n",
ErrorKindStr,
E.Code,
ErrorDescStr,
E.Context.empty() ? L"<none>" : E.Context.c_str());
}
else
{
LogPrintf(ELogLevel::Error,
L"%hs code: %d. Context: %ls\n",
ErrorKindStr,
E.Code,
E.Context.empty() ? L"<none>" : E.Context.c_str());
}
}
static fs::path GCrashDumpPath;
void
SetCrashDumpPath(const fs::path& Path)
{
std::lock_guard<std::mutex> LockGuard(GLogMutex);
GCrashDumpPath = Path;
}
bool
get_crash_dump_path(fs::path& OutPath)
{
std::lock_guard<std::mutex> LockGuard(GLogMutex);
if (GCrashDumpPath.empty())
{
return false;
}
else
{
OutPath = GCrashDumpPath;
return true;
}
}
bool
LogWriteCrashDump(void* InExceptionPointers)
{
#if UNSYNC_PLATFORM_WINDOWS
_EXCEPTION_POINTERS* ExceptionPointers = (_EXCEPTION_POINTERS*)InExceptionPointers;
fs::path CrashDumpPath;
if (!get_crash_dump_path(CrashDumpPath))
{
return false;
}
wchar_t DumpFilename[4096] = {};
uint64 Timestamp = TimePointNow().time_since_epoch().count();
swprintf_s(DumpFilename, L"unsync-%016llX.dmp", Timestamp);
fs::path CrashDumpFilename = CrashDumpPath / fs::path(DumpFilename);
LogPrintf(ELogLevel::Info, L"!!! Writing crash dump to '%ls'\n", CrashDumpFilename.wstring().c_str());
MINIDUMP_EXCEPTION_INFORMATION ExceptionInfo;
ExceptionInfo.ThreadId = GetCurrentThreadId();
ExceptionInfo.ExceptionPointers = ExceptionPointers;
ExceptionInfo.ClientPointers = true;
HANDLE DumpFile =
CreateFileW(CrashDumpFilename.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
if (DumpFile != INVALID_HANDLE_VALUE)
{
BOOL Ok =
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), DumpFile, MiniDumpWithDataSegs, &ExceptionInfo, nullptr, nullptr);
if (Ok)
{
LogPrintf(ELogLevel::Info, L"!!! Crash dump file written.\n");
return true;
}
else
{
LogPrintf(ELogLevel::Error, L"!!! Failed to generate crash dump. Error code: %d.\n", GetLastError());
}
}
else
{
LogPrintf(ELogLevel::Error, L"!!! Failed to open output file. Error code: %d.\n", GetLastError());
}
LogPrintf(ELogLevel::Error, L"!!! Failed to write dump file.\n");
#endif // UNSYNC_PLATFORM_WINDOWS
return false;
}
FLogFlushScope::~FLogFlushScope()
{
LogFlush();
}
} // namespace unsync