Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add sentry for CrashLogger #1616

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ jobs:
# with:
# xmake-version: branch@master

- uses: matbour/setup-sentry-cli@v1
with:
token: ${{ SECRETS.SENTRY_TOKEN }}
organization: ${{ SECRETS.SENTRY_ORG }}
project: ${{ SECRETS.SENTRY_PROJECT }}

- uses: actions/cache@v4
with:
path: |
Expand All @@ -58,7 +64,10 @@ jobs:
xmake f -a x64 -m ${{ matrix.mode }} -p windows -v -y --target_type=${{ matrix.target_type }} --tests=${{ matrix.tests }}

- run: |
xmake -v -w -y
xmake -v -y

- run: |
sentry-cli debug-files upload --include-sources bin/LeviLamina

- uses: actions/upload-artifact@v4
with:
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ jobs:
# with:
# xmake-version: branch@master

- uses: matbour/setup-sentry-cli@v1
with:
token: ${{ SECRETS.SENTRY_TOKEN }}
organization: ${{ SECRETS.SENTRY_ORG }}
project: ${{ SECRETS.SENTRY_PROJECT }}

- uses: actions/cache@v4
with:
path: |
Expand All @@ -38,7 +44,10 @@ jobs:
xmake f -a x64 -m ${{ matrix.mode }} -p windows -v -y
- run: |
xmake -v -w -y
xmake -v -y
- run: |
sentry-cli debug-files upload --include-sources bin/LeviLamina
# - run: |
# xmake package -v -y
Expand Down
4 changes: 3 additions & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
"name": "${modName}",
"entry": "${modFile}",
"version": "${modVersion}",
"type": "preload-native"
"type": "preload-native",
"sentry-dsn": "https://43a888504c33385bfd2e570c9ac939aa@o4508652421906432.ingest.us.sentry.io/4508652563398656",
"sentry-force-upload": true
}
30 changes: 5 additions & 25 deletions src-server/ll/core/Statistics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,31 +129,11 @@ struct Statistics::Impl {
}

Impl() {
namespace fs = std::filesystem;

auto& dataDir = getSelfModIns()->getDataDir();

if (!fs::exists(dataDir)) {
fs::create_directory(dataDir);
}
auto uuidPath = dataDir / u8"statisticsUuid";
if (!fs::exists(uuidPath)) {
std::string uuid = mce::UUID::random().asString();
json["serverUUID"] = uuid;
file_utils::writeFile(uuidPath, uuid);
} else {
auto uuidFile = file_utils::readFile(uuidPath);
if (uuidFile.has_value()) {
json["serverUUID"] = uuidFile.value();
} else {
std::string uuid = mce::UUID::random().asString();
file_utils::writeFile(uuidPath, uuid);
}
}
json["osName"] = sys_utils::isWine() ? "Linux(wine)" : "Windows";
json["osArch"] = "amd64";
json["osVersion"] = "";
json["coreCount"] = std::thread::hardware_concurrency();
json["serverUUID"] = getServiceUuid();
json["osName"] = sys_utils::isWine() ? "Linux(wine)" : "Windows";
json["osArch"] = "amd64";
json["osVersion"] = "";
json["coreCount"] = std::thread::hardware_concurrency();

coro::keepThis([&]() -> coro::CoroTask<> {
co_await (1.0min * random_utils::rand(3.0, 6.0));
Expand Down
2 changes: 2 additions & 0 deletions src/ll/api/utils/SystemUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ LLNDAPI bool isStdoutSupportAnsi();

LLNDAPI bool isWine();

LLNDAPI std::string getSystemVersion();

class DynamicLibrary {
HandleT lib = nullptr;

Expand Down
30 changes: 30 additions & 0 deletions src/ll/api/utils/SystemUtils_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,36 @@ bool isWine() {
return result;
}

std::string getSystemVersion() {
RTL_OSVERSIONINFOW osVersionInfoW = [] {
RTL_OSVERSIONINFOW osVersionInfoW{};
HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll");
if (hMod) {
using RtlGetVersionPtr = uint(WINAPI*)(PRTL_OSVERSIONINFOW);
auto fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion");
if (fxPtr != nullptr) {
osVersionInfoW.dwOSVersionInfoSize = sizeof(osVersionInfoW);
if (0 == fxPtr(&osVersionInfoW)) {
return osVersionInfoW;
}
}
}
return osVersionInfoW;
}();
if (osVersionInfoW.dwMajorVersion == 0) {
return "Unknown";
}
std::string osVersion =
std::to_string(osVersionInfoW.dwMajorVersion) + '.' + std::to_string(osVersionInfoW.dwMinorVersion);
if (osVersionInfoW.dwBuildNumber != 0) {
osVersion += '.' + std::to_string(osVersionInfoW.dwBuildNumber);
}
if (osVersionInfoW.szCSDVersion[0] != 0) {
osVersion += ' ' + wstr2str(osVersionInfoW.szCSDVersion);
}
return osVersion;
}

std::span<std::byte> getImageRange(std::string_view name) {
static auto process = GetCurrentProcess();
HMODULE rangeStart;
Expand Down
7 changes: 4 additions & 3 deletions src/ll/core/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace ll {

struct LeviConfig {

int version = 31;
int version = 32;

std::string language = "system";
struct {
Expand All @@ -31,8 +31,9 @@ struct LeviConfig {
CmdSetting modManageCommand{true, CommandPermissionLevel::Admin};
} command{};
struct {
bool enabled = true;
bool useBuiltin = true;
bool enabled = true;
bool useBuiltin = false;
bool uploadToSentry = true;
std::optional<std::string> externalpath;
} crashLogger{};

Expand Down
116 changes: 77 additions & 39 deletions src/ll/core/CrashLogger_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
#include "ll/api/io/Logger.h"
#include "ll/api/io/LoggerRegistry.h"
#include "ll/api/io/PatternFormatter.h"
#include "ll/api/mod/Mod.h"
#include "ll/api/utils/ErrorUtils.h"
#include "ll/api/utils/StacktraceUtils.h"
#include "ll/api/utils/StringUtils.h"
#include "ll/api/utils/SystemUtils.h"
#include "ll/core/Config.h"
#include "ll/core/LeviLamina.h"
#include "ll/core/SentryUploader.h"
#include "ll/core/io/Output.h"

#include "pl/Config.h"
Expand Down Expand Up @@ -43,7 +45,6 @@ class CrashLoggerNew {

static std::unique_ptr<CrashLoggerNew> cln;


std::string toString(_CONTEXT const& c) {
return fmt::format("RAX: 0x{:016X} RBX: 0x{:016X} RCX: 0x{:016X}\n", c.Rax, c.Rbx, c.Rcx)
+ fmt::format("RDX: 0x{:016X} RSI: 0x{:016X} RDI: 0x{:016X}\n", c.Rdx, c.Rsi, c.Rdi)
Expand All @@ -64,6 +65,50 @@ std::string toString(_CONTEXT const& c) {
);
}

bool saveCrashInfo(
const std::filesystem::path& logPath,
const std::string& minidumpName,
const std::string& traceName
) {
auto path = logPath / fmt::format("sentry_{:%Y-%m-%d_%H-%M-%S}", fmt::localtime(_time64(nullptr)));
return file_utils::writeFile(path, fmt::format("{}\n{}", minidumpName, traceName));
}

// Read log and dump file path from logs/crash/sentry_xxx, then submit them to sentry
void submitCrashInfo() {
auto path = file_utils::u8path(pl::pl_log_path) / u8"crash";
for (auto& entry : std::filesystem::directory_iterator(path)) {
if (entry.path().filename().string().starts_with("sentry_")) {
auto content = file_utils::readFile(entry.path());
if (!content) {
continue;
}
auto paths = string_utils::splitByPattern(*content, "\n");
if (paths.size() != 2) {
continue;
}
auto dmpFilePath = file_utils::u8path(pl::pl_log_path) / u8"crash" / paths[0];
auto logFilePath = file_utils::u8path(pl::pl_log_path) / u8"crash" / paths[1];
SentryUploader sentryUploader{
std::string(getServiceUuid()),
dmpFilePath.filename().string(),
u8str2str(dmpFilePath.u8string()),
logFilePath.filename().string(),
u8str2str(logFilePath.u8string()),
ll::getLoaderVersion().to_string().find('+') != std::string::npos,
ll::getLoaderVersion().to_string()
};
sentryUploader.addModSentryInfo(
ll::getSelfModIns()->getName(),
"https://43a888504c33385bfd2e570c9ac939aa@o4508652421906432.ingest.us.sentry.io/4508652563398656",
ll::getLoaderVersion().to_string()
);
sentryUploader.uploadAll();
std::filesystem::remove(entry.path());
}
}
}

void CrashLogger::init() {
auto& config = getLeviConfig();

Expand All @@ -76,6 +121,9 @@ void CrashLogger::init() {
}
if (config.modules.crashLogger.useBuiltin) {
cln = std::make_unique<CrashLoggerNew>();
if (config.modules.crashLogger.uploadToSentry) {
submitCrashInfo();
}
return;
}

Expand All @@ -90,22 +138,32 @@ void CrashLogger::init() {
sa.nLength = sizeof(SECURITY_ATTRIBUTES);

std::wstring cmd = string_utils::str2wstr(fmt::format(
"{} {} \"{}\"",
R"({} -p {} -b "{}" --lv "{}" --isdev {} --username "{}" --moddir "{}" --enablesentry {})",
getSelfModIns()->getModDir() / sv2u8sv(config.modules.crashLogger.externalpath.value_or("CrashLogger.exe")),
GetCurrentProcessId(),
ll::getGameVersion().to_string()
ll::getGameVersion().to_string(),
ll::getLoaderVersion().to_string(),
ll::getLoaderVersion().to_string().find('+') != std::string::npos,
getServiceUuid(),
mod::getModsRoot(),
config.modules.crashLogger.uploadToSentry
));

if (!CreateProcess(nullptr, cmd.data(), &sa, &sa, true, 0, nullptr, nullptr, &si, &pi)) {
crashLoggerPtr->error("Couldn't Create CrashLogger Daemon Process"_tr());
error_utils::printException(error_utils::getLastSystemError(), *crashLoggerPtr);
// If failed to create CrashLogger Daemon Process, use built-in CrashLogger
cln = std::make_unique<CrashLoggerNew>();
if (config.modules.crashLogger.uploadToSentry) {
submitCrashInfo();
}
return;
}

CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

crashLoggerPtr->info("CrashLogger enabled successfully"_tr());
return;
}

static struct CrashInfo {
Expand All @@ -123,35 +181,11 @@ static struct CrashInfo {

static void dumpSystemInfo() {
crashInfo.logger.info("System Info:");
crashInfo.logger.info(" |OS Version: {} {}", sys_utils::getSystemName(), []() -> std::string {
RTL_OSVERSIONINFOW osVersionInfoW = [] {
RTL_OSVERSIONINFOW osVersionInfoW{};
HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll");
if (hMod) {
using RtlGetVersionPtr = uint(WINAPI*)(PRTL_OSVERSIONINFOW);
auto fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion");
if (fxPtr != nullptr) {
osVersionInfoW.dwOSVersionInfoSize = sizeof(osVersionInfoW);
if (0 == fxPtr(&osVersionInfoW)) {
return osVersionInfoW;
}
}
}
return osVersionInfoW;
}();
if (osVersionInfoW.dwMajorVersion == 0) {
return "Unknown";
}
std::string osVersion =
std::to_string(osVersionInfoW.dwMajorVersion) + '.' + std::to_string(osVersionInfoW.dwMinorVersion);
if (osVersionInfoW.dwBuildNumber != 0) {
osVersion += '.' + std::to_string(osVersionInfoW.dwBuildNumber);
}
if (osVersionInfoW.szCSDVersion[0] != 0) {
osVersion += ' ' + wstr2str(osVersionInfoW.szCSDVersion);
}
return osVersion;
}() + (sys_utils::isWine() ? " (wine)" : ""));
crashInfo.logger.info(
" |OS Version: {} {}",
sys_utils::getSystemName(),
sys_utils::getSystemVersion() + (sys_utils::isWine() ? " (wine)" : "")
);
crashInfo.logger.info(" |CPU: {}", []() -> std::string {
int cpuInfo[4] = {-1};
__cpuid(cpuInfo, (int)0x80000000);
Expand Down Expand Up @@ -228,10 +262,7 @@ static BOOL CALLBACK dumpModules(
return TRUE;
}

static bool genMiniDumpFile(PEXCEPTION_POINTERS e) {

auto dumpFilePath = crashInfo.path / string_utils::str2u8str("minidump_" + crashInfo.date + ".dmp");

static bool genMiniDumpFile(PEXCEPTION_POINTERS e, std::filesystem::path& dumpFilePath) {
std::error_code ec;
if (auto c = std::filesystem::canonical(dumpFilePath, ec); ec.value() == 0) {
dumpFilePath = c;
Expand Down Expand Up @@ -274,6 +305,13 @@ static LONG unhandledExceptionFilter(_In_ struct _EXCEPTION_POINTERS* e) {
crashInfo.date = fmt::format("{:%Y-%m-%d_%H-%M-%S}", fmt::localtime(_time64(nullptr)));
crashInfo.settings = ll::getLeviConfig().modules.crashLogger;
crashInfo.path = file_utils::u8path(pl::pl_log_path) / u8"crash";
auto logFilePath = crashInfo.path / ("trace_" + crashInfo.date + ".log");
auto dumpFilePath = crashInfo.path / string_utils::str2u8str("minidump_" + crashInfo.date + ".dmp");
// Save log and dump file path to logs/crash/sentry_xxx for later use
if (getLeviConfig().modules.crashLogger.uploadToSentry) {
saveCrashInfo(crashInfo.path, dumpFilePath.filename().string(), logFilePath.filename().string());
}

if (!std::filesystem::is_directory(crashInfo.path)) {
std::filesystem::create_directory(crashInfo.path);
}
Expand All @@ -297,7 +335,7 @@ static LONG unhandledExceptionFilter(_In_ struct _EXCEPTION_POINTERS* e) {
};
crashInfo.logger.getSink(0)->setFormatter(std::move(formatter));
crashInfo.logger.addSink(std::make_shared<io::FileSink>(
crashInfo.path / ("trace_" + crashInfo.date + ".log"),
logFilePath,
makePolymorphic<io::PatternFormatter>("{tm:.3%F %T.} [{lvl}] {msg}", false)
));
crashInfo.logger.setLevel(io::LogLevel::Trace);
Expand All @@ -312,7 +350,7 @@ static LONG unhandledExceptionFilter(_In_ struct _EXCEPTION_POINTERS* e) {

crashInfo.logger.info("Process Crashed! Generating Stacktrace and MiniDump...");
try {
genMiniDumpFile(e);
genMiniDumpFile(e, dumpFilePath);
} catch (...) {
crashInfo.logger.error("!!! Error In GenMiniDumpFile !!!");
ll::error_utils::printCurrentException(crashInfo.logger);
Expand Down
Loading
Loading