From 7bc9c01333401e2620b9ad63784dbc55fe59fa44 Mon Sep 17 00:00:00 2001 From: xianyubb <2149656630@qq.com> Date: Mon, 17 Aug 2026 04:02:24 +0800 Subject: [PATCH 1/3] Print script source locations for runtime errors --- src-client/lse/PluginManager.cpp | 2 + src-server/lse/PluginManager.cpp | 2 + src/legacy/api/APIHelp.h | 7 +- src/legacy/api/EventAPI.cpp | 2 +- src/legacy/api/EventAPI.h | 2 +- src/legacy/api/LegacyCommandAPI.cpp | 2 +- src/legacy/api/NetworkAPI.cpp | 6 +- src/legacy/api/SystemAPI.cpp | 2 +- src/legacy/engine/MessageSystem.cpp | 26 +-- src/legacy/engine/RemoteCall.cpp | 4 +- src/legacy/engine/TimeTaskSystem.cpp | 6 +- src/legacy/main/BuiltinCommands.cpp | 4 +- src/legacy/main/NodeJsHelper.cpp | 5 +- src/legacy/main/PythonHelper.cpp | 3 +- src/legacy/utils/ScriptErrorPrinter.cpp | 201 ++++++++++++++++++++++++ src/legacy/utils/ScriptErrorPrinter.h | 13 ++ src/lse/Entry.cpp | 3 +- 17 files changed, 256 insertions(+), 34 deletions(-) create mode 100644 src/legacy/utils/ScriptErrorPrinter.cpp create mode 100644 src/legacy/utils/ScriptErrorPrinter.h diff --git a/src-client/lse/PluginManager.cpp b/src-client/lse/PluginManager.cpp index 002d7212..d5d19368 100644 --- a/src-client/lse/PluginManager.cpp +++ b/src-client/lse/PluginManager.cpp @@ -2,6 +2,7 @@ #include "legacy/engine/EngineManager.h" #include "legacy/engine/EngineOwnData.h" +#include "legacy/utils/ScriptErrorPrinter.h" #include "ll/api/io/FileUtils.h" // IWYU pragma: keep #include "ll/api/mod/Mod.h" #include "ll/api/mod/ModManager.h" @@ -242,6 +243,7 @@ ll::Expected<> PluginManager::enableScriptPlugin(std::string_view name, bool isH if (scriptEngine) { auto error = [&] { EngineScope engineScope(scriptEngine.get()); + ::legacy::script_error::printException(e, LegacyScriptEngine::getLogger()); return ll::makeStringError( "Failed to enable plugin {0}: {1}\n{2}"_tr(manifest.name, e.message(), e.stacktrace()) ); diff --git a/src-server/lse/PluginManager.cpp b/src-server/lse/PluginManager.cpp index 86ff50f5..da831528 100644 --- a/src-server/lse/PluginManager.cpp +++ b/src-server/lse/PluginManager.cpp @@ -2,6 +2,7 @@ #include "legacy/engine/EngineManager.h" #include "legacy/engine/EngineOwnData.h" +#include "legacy/utils/ScriptErrorPrinter.h" #include "ll/api/io/FileUtils.h" // IWYU pragma: keep #include "ll/api/mod/Mod.h" #include "ll/api/mod/ModManager.h" @@ -219,6 +220,7 @@ ll::Expected<> PluginManager::load(ll::mod::Manifest manifest) { if (scriptEngine) { auto error = [&] { EngineScope engineScope(scriptEngine.get()); + ::legacy::script_error::printException(e, LegacyScriptEngine::getLogger()); return ll::makeStringError( "Failed to load plugin {0}: {1}\n{2}"_tr(manifest.name, e.message(), e.stacktrace()) ); diff --git a/src/legacy/api/APIHelp.h b/src/legacy/api/APIHelp.h index a560906c..26f8f601 100644 --- a/src/legacy/api/APIHelp.h +++ b/src/legacy/api/APIHelp.h @@ -1,6 +1,7 @@ -#pragma once +#pragma once #include "legacy/engine/EngineOwnData.h" +#include "legacy/utils/ScriptErrorPrinter.h" #include "legacy/utils/JsonHelper.h" #include "legacy/utils/UsingScriptX.h" #include "ll/api/utils/ErrorUtils.h" @@ -69,14 +70,14 @@ inline Exception WrongArgsCountException(std::string const& func) { #define CATCH \ catch (...) { \ - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); \ + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); \ LogErrorWithInfo(__FUNCTION__); \ } #define CATCH_WITH_MESSAGE(...) \ catch (...) { \ lse::LegacyScriptEngine::getLogger().error(__VA_ARGS__); \ - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); \ + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); \ LogErrorWithInfo(__FUNCTION__); \ } diff --git a/src/legacy/api/EventAPI.cpp b/src/legacy/api/EventAPI.cpp index 0151ff8b..eb350a95 100644 --- a/src/legacy/api/EventAPI.cpp +++ b/src/legacy/api/EventAPI.cpp @@ -903,7 +903,7 @@ void InitBasicEventListeners() { } } catch (...) { lse::LegacyScriptEngine::getLogger().error("Error occurred in Engine Message Loop!"); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } #endif diff --git a/src/legacy/api/EventAPI.h b/src/legacy/api/EventAPI.h index 9cffd8d7..5f9a2892 100644 --- a/src/legacy/api/EventAPI.h +++ b/src/legacy/api/EventAPI.h @@ -206,6 +206,6 @@ void FakeCallEventImpl(EventListener& listener, ScriptEngine* engine, EVENT_TYPE #define IF_LISTENED_END(TYPE) \ catch (...) { \ lse::LegacyScriptEngine::getLogger().error("Event Callback Failed! In Event: {}", EventTypeToString(TYPE)); \ - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); \ + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); \ } \ } diff --git a/src/legacy/api/LegacyCommandAPI.cpp b/src/legacy/api/LegacyCommandAPI.cpp index 383ee56f..301d2a1d 100644 --- a/src/legacy/api/LegacyCommandAPI.cpp +++ b/src/legacy/api/LegacyCommandAPI.cpp @@ -124,7 +124,7 @@ void registerLegacyCommands() { registerLegacyCommand(name, data.description, data.level, data.engine, data.playerFunc, data.consoleFunc); } catch (...) { LegacyScriptEngine::getLogger().error("Failed to register legacy command: {}"_tr(name)); - ll::error_utils::printCurrentException(LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(LegacyScriptEngine::getLogger()); } } localShareData->fakeCommandsMap.clear(); diff --git a/src/legacy/api/NetworkAPI.cpp b/src/legacy/api/NetworkAPI.cpp index e3a88e22..f944d22b 100644 --- a/src/legacy/api/NetworkAPI.cpp +++ b/src/legacy/api/NetworkAPI.cpp @@ -21,7 +21,7 @@ using namespace ll::coro; catch (...) { \ EngineScope enterCoro(engine); \ lse::LegacyScriptEngine::getLogger().error(LOG); \ - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); \ + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); \ LogErrorWithInfo(__FUNCTION__); \ co_return; \ } @@ -345,7 +345,7 @@ Local WSClientClass::connectAsync(Arguments const& args) { "WSClientClass::connectAsync Failed! In plugin: {}", pluginName ); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } }).detach(); return Boolean::newBoolean(true); @@ -761,7 +761,7 @@ Local HttpServerClass::listen(Arguments const& args) const { svr->listen(addr, port); } catch (...) { lse::LegacyScriptEngine::getLogger().error("Failed to listen {}:{}", addr, port); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } }).detach(); return this->getScriptObject(); // return self diff --git a/src/legacy/api/SystemAPI.cpp b/src/legacy/api/SystemAPI.cpp index ebf6373b..44ce01b2 100644 --- a/src/legacy/api/SystemAPI.cpp +++ b/src/legacy/api/SystemAPI.cpp @@ -85,7 +85,7 @@ bool NewProcess( if (callback) callback(static_cast(exitCode), std::move(strOutput)); } catch (...) { lse::LegacyScriptEngine::getLogger().error("NewProcess Callback Failed!"); - ll::utils::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } }).detach(); diff --git a/src/legacy/engine/MessageSystem.cpp b/src/legacy/engine/MessageSystem.cpp index a4767ccd..5439f765 100644 --- a/src/legacy/engine/MessageSystem.cpp +++ b/src/legacy/engine/MessageSystem.cpp @@ -114,14 +114,14 @@ ModuleMessageResult ModuleMessage::broadcastLocal(MessageType type, string const "Fail to post message to plugin {}", getEngineData(engine)->pluginName ); - ll::error_utils::printException(e, lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printException(e, lse::LegacyScriptEngine::getLogger()); } catch (...) { EngineScope scope(engine.get()); lse::LegacyScriptEngine::getLogger().error( "Fail to post message to plugin {}", getEngineData(engine)->pluginName ); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } } return ModuleMessageResult(msgId, engineList); @@ -145,14 +145,14 @@ ModuleMessageResult ModuleMessage::broadcastGlobal(MessageType type, string cons "Fail to post message to plugin {}", getEngineData(engine)->pluginName ); - ll::error_utils::printException(e, lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printException(e, lse::LegacyScriptEngine::getLogger()); } catch (...) { EngineScope scope(engine.get()); lse::LegacyScriptEngine::getLogger().error( "Fail to post message to plugin {}", getEngineData(engine)->pluginName ); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } } return ModuleMessageResult(msgId, engineList); @@ -178,14 +178,14 @@ ModuleMessage::broadcastTo(std::string const& toModuleType, MessageType type, st "Fail to post message to plugin {}", getEngineData(engine)->pluginName ); - ll::error_utils::printException(e, lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printException(e, lse::LegacyScriptEngine::getLogger()); } catch (...) { EngineScope scope(engine.get()); lse::LegacyScriptEngine::getLogger().error( "Fail to post message to plugin {}", getEngineData(engine)->pluginName ); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } } } @@ -209,14 +209,14 @@ ModuleMessage::sendTo(std::shared_ptr engine, MessageType type, st "Fail to post message to plugin {}", getEngineData(engine)->pluginName ); - ll::error_utils::printException(e, lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printException(e, lse::LegacyScriptEngine::getLogger()); } catch (...) { EngineScope scope(engine.get()); lse::LegacyScriptEngine::getLogger().error( "Fail to post message to plugin {}", getEngineData(engine)->pluginName ); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } return ModuleMessageResult(msgId, {}); } @@ -240,14 +240,14 @@ ModuleMessage::sendToRandom(std::string const& toModuleType, MessageType type, s "Fail to post message to plugin {}", getEngineData(engine)->pluginName ); - ll::error_utils::printException(e, lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printException(e, lse::LegacyScriptEngine::getLogger()); } catch (...) { EngineScope scope(engine.get()); lse::LegacyScriptEngine::getLogger().error( "Fail to post message to plugin {}", getEngineData(engine)->pluginName ); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } } } @@ -270,7 +270,7 @@ bool ModuleMessage::sendResult(MessageType typ, std::string const& dat, int64_t "Fail to post message to plugin {}", getEngineData(engine)->pluginName ); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } return false; } @@ -338,10 +338,10 @@ void MessageSystemLoopOnce() { "Error occurred in Engine Message Loop! In plugin: {}", getEngineOwnData()->pluginName ); - ll::error_utils::printException(e, lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printException(e, lse::LegacyScriptEngine::getLogger()); } catch (...) { lse::LegacyScriptEngine::getLogger().error("Error occurred in Engine Message Loop!"); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } } } diff --git a/src/legacy/engine/RemoteCall.cpp b/src/legacy/engine/RemoteCall.cpp index 2b531365..3a1d5f9d 100644 --- a/src/legacy/engine/RemoteCall.cpp +++ b/src/legacy/engine/RemoteCall.cpp @@ -55,7 +55,7 @@ void RemoteSyncCallRequest(ModuleMessage& msg) { if (engine) { EngineScope enter(engine); lse::LegacyScriptEngine::getLogger().error("In plugin: {}", getEngineOwnData()->pluginName); - ll::error_utils::printException(e, lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printException(e, lse::LegacyScriptEngine::getLogger()); } // Feedback @@ -73,7 +73,7 @@ void RemoteSyncCallRequest(ModuleMessage& msg) { } } catch (...) { lse::LegacyScriptEngine::getLogger().error("Error occurred in remote engine!"); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); // Feedback if (!msg.sendResult(ModuleMessage::MessageType::RemoteSyncCallReturn, "[null]")) { diff --git a/src/legacy/engine/TimeTaskSystem.cpp b/src/legacy/engine/TimeTaskSystem.cpp index 46d28fc7..5256ff56 100644 --- a/src/legacy/engine/TimeTaskSystem.cpp +++ b/src/legacy/engine/TimeTaskSystem.cpp @@ -28,7 +28,7 @@ std::unordered_map timeTaskMap; EngineScope scope(data.engine); \ lse::LegacyScriptEngine::getLogger() \ .error("Error occurred in {}, in plugin: {}", TASK_TYPE, getEngineData(data.engine)->pluginName); \ - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); \ + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); \ } int NewTimeout(const Local& func, const std::vector>& paras, int timeout) { @@ -199,7 +199,7 @@ bool ClearTimeTask(unsigned int const& id) { } } catch (...) { lse::LegacyScriptEngine::getLogger().error("Fail in ClearTimeTask"); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } return true; } @@ -218,6 +218,6 @@ void LLSERemoveTimeTaskData(std::shared_ptr const& engine) { } } catch (...) { lse::LegacyScriptEngine::getLogger().info("Fail in LLSERemoveTimeTaskData"); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } } diff --git a/src/legacy/main/BuiltinCommands.cpp b/src/legacy/main/BuiltinCommands.cpp index ebcd8485..0e026ec2 100644 --- a/src/legacy/main/BuiltinCommands.cpp +++ b/src/legacy/main/BuiltinCommands.cpp @@ -40,7 +40,7 @@ bool ProcessDebugEngine(std::string const& cmd) { logger.info(sout.str()); PrintDebugSign(); } catch (...) { - ll::error_utils::printCurrentException(logger); + ::legacy::script_error::printCurrentException(logger); PrintDebugSign(); } return false; @@ -72,7 +72,7 @@ void RegisterDebugCommand() { PrintValue(sout, result); output.success(sout.str()); } catch (...) { - ll::error_utils::printCurrentException(logger); + ::legacy::script_error::printCurrentException(logger); } } else { if (InConsoleDebugMode) { diff --git a/src/legacy/main/NodeJsHelper.cpp b/src/legacy/main/NodeJsHelper.cpp index 3ac928fa..b64cb010 100644 --- a/src/legacy/main/NodeJsHelper.cpp +++ b/src/legacy/main/NodeJsHelper.cpp @@ -2,6 +2,7 @@ #include "fmt/format.h" #include "legacy/engine/EngineOwnData.h" +#include "legacy/utils/ScriptErrorPrinter.h" #include "legacy/utils/Utils.h" #include "ll/api/Expected.h" #include "ll/api/base/Containers.h" @@ -397,7 +398,7 @@ bool stopEngine(node::Environment* env) { return true; } catch (...) { lse::LegacyScriptEngine::getLogger().error("Fail to stop engine {}", static_cast(env)); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); return false; } } @@ -597,7 +598,7 @@ int executeNpmCommand(std::vector npmArgs, std::string workingDir) exit_code = node::SpinEventLoop(env).FromMaybe(exit_code); } catch (...) { lse::LegacyScriptEngine::getLogger().error("Fail to execute NPM command. Error occurs"); - ll::error_utils::printCurrentException(lse::LegacyScriptEngine::getLogger()); + ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); } node::Stop(env); } diff --git a/src/legacy/main/PythonHelper.cpp b/src/legacy/main/PythonHelper.cpp index eb3788ac..2e42fde0 100644 --- a/src/legacy/main/PythonHelper.cpp +++ b/src/legacy/main/PythonHelper.cpp @@ -4,6 +4,7 @@ #include "legacy/api/CommandAPI.h" #include "legacy/engine/EngineManager.h" #include "legacy/engine/TimeTaskSystem.h" +#include "legacy/utils/ScriptErrorPrinter.h" #include "legacy/utils/Utils.h" #include "ll/api/utils/StringUtils.h" #include "lse/Entry.h" @@ -213,7 +214,7 @@ bool processPythonDebugEngine(std::string const& cmd) { } catch (...) { isInsideCodeBlock = false; codeBuffer.clear(); - ll::error_utils::printCurrentException(logger); + ::legacy::script_error::printCurrentException(logger); } } PrintPyDebugSign(); diff --git a/src/legacy/utils/ScriptErrorPrinter.cpp b/src/legacy/utils/ScriptErrorPrinter.cpp new file mode 100644 index 00000000..c63aa04f --- /dev/null +++ b/src/legacy/utils/ScriptErrorPrinter.cpp @@ -0,0 +1,201 @@ +#include "legacy/utils/ScriptErrorPrinter.h" + +#include "legacy/engine/EngineOwnData.h" +#include "ll/api/utils/ErrorUtils.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace legacy::script_error { +namespace { + +struct ScriptFrame { + std::string file; + size_t line = 0; + size_t column = 0; +}; + +bool isInternalFrame(std::string const& file) { + if (file.empty()) return true; + if (file == "" || file == "[native code]") return true; + if (file.starts_with("native ") || file.starts_with("internal/")) return true; + return false; +} + +std::optional parseLine(std::string const& line) { + static std::regex const pythonPattern{R"re(File "([^"]+)", line ([0-9]+))re"}; + static std::regex const v8Pattern{R"((?:at\s+(?:.+\s+\()?)([^()\s]+):([0-9]+):([0-9]+)\)?)"}; + static std::regex const luaPattern{R"re(^\s*(?:\[string "([^"]+)"\]|([^:\s]+)):([0-9]+):)re"}; + static std::regex const qjsPattern{R"(([^@\s()]+):([0-9]+):([0-9]+))"}; + + std::smatch match; + if (std::regex_search(line, match, pythonPattern)) { + return ScriptFrame{match[1].str(), static_cast(std::stoull(match[2].str())), 0}; + } + if (std::regex_search(line, match, v8Pattern)) { + return ScriptFrame{ + match[1].str(), + static_cast(std::stoull(match[2].str())), + static_cast(std::stoull(match[3].str())) + }; + } + if (std::regex_search(line, match, qjsPattern)) { + return ScriptFrame{ + match[1].str(), + static_cast(std::stoull(match[2].str())), + static_cast(std::stoull(match[3].str())) + }; + } + if (std::regex_search(line, match, luaPattern)) { + auto file = match[1].matched ? match[1].str() : match[2].str(); + return ScriptFrame{file, static_cast(std::stoull(match[3].str())), 0}; + } + return std::nullopt; +} + +std::vector parseFrames(std::string const& stacktrace) { + std::vector frames; + std::istringstream input(stacktrace); + std::string line; + while (std::getline(input, line)) { + auto frame = parseLine(line); + if (!frame || frame->line == 0 || isInternalFrame(frame->file)) continue; + frames.emplace_back(std::move(*frame)); + } + return frames; +} + +std::filesystem::path pluginRoot() { + try { + auto data = getEngineOwnData(); + if (data && data->plugin) return data->plugin->getModDir(); + } catch (...) { + } + return {}; +} + +std::optional findSourceFile(std::string const& rawFile) { + if (rawFile.empty()) return std::nullopt; + + std::filesystem::path filePath{rawFile}; + std::error_code ec; + if (filePath.is_absolute() && std::filesystem::is_regular_file(filePath, ec)) { + return filePath; + } + + auto root = pluginRoot(); + if (root.empty()) return std::nullopt; + + auto direct = root / filePath; + if (std::filesystem::is_regular_file(direct, ec)) return direct; + + auto filename = filePath.filename(); + if (filename.empty()) return std::nullopt; + + size_t visited = 0; + for (std::filesystem::recursive_directory_iterator it{ + root, + std::filesystem::directory_options::skip_permission_denied, + ec + }, + end; + !ec && it != end && visited < 5000; + it.increment(ec), ++visited) { + if (!it->is_regular_file(ec)) continue; + if (it->path().filename() == filename) return it->path(); + } + return std::nullopt; +} + +std::optional readLine(std::filesystem::path const& path, size_t wantedLine) { + if (wantedLine == 0) return std::nullopt; + std::ifstream input(path); + if (!input) return std::nullopt; + + std::string line; + for (size_t current = 1; std::getline(input, line); ++current) { + if (current == wantedLine) return line; + } + return std::nullopt; +} + +std::string displayPath(std::filesystem::path const& path) { + auto root = pluginRoot(); + std::error_code ec; + if (!root.empty()) { + auto relative = std::filesystem::relative(path, root, ec); + if (!ec && !relative.empty()) return relative.generic_string(); + } + return path.generic_string(); +} + +void printFrame(ScriptFrame const& frame, ll::io::Logger& logger) { + auto source = findSourceFile(frame.file); + if (!source) { + logger.error( + "Script error location: {}:{}{} (source file not found)", + frame.file, + frame.line, + frame.column ? fmt::format(":{}", frame.column) : "" + ); + return; + } + + logger.error( + "Script error location: {}:{}{}", + displayPath(*source), + frame.line, + frame.column ? fmt::format(":{}", frame.column) : "" + ); + + auto before = readLine(*source, frame.line > 1 ? frame.line - 1 : 0); + auto exact = readLine(*source, frame.line); + auto after = readLine(*source, frame.line + 1); + + if (before) logger.error("{:>6} | {}", frame.line - 1, *before); + if (exact) { + logger.error("> {:>4} | {}", frame.line, *exact); + if (frame.column > 0) { + auto caretColumn = std::max(frame.column, 1); + logger.error(" | {}^", std::string(caretColumn - 1, ' ')); + } + } + if (after) logger.error("{:>6} | {}", frame.line + 1, *after); +} + +void printScriptLocation(script::Exception const& exception, ll::io::Logger& logger) { + auto frames = parseFrames(exception.stacktrace()); + if (frames.empty()) return; + printFrame(frames.front(), logger); +} + +} // namespace + +void printException(script::Exception const& exception, ll::io::Logger& logger) { + printScriptLocation(exception, logger); + ll::error_utils::printException(exception, logger); +} + +void printCurrentException(ll::io::Logger& logger) { + auto current = std::current_exception(); + if (!current) return; + + try { + std::rethrow_exception(current); + } catch (script::Exception const& exception) { + printException(exception, logger); + } catch (...) { + ll::error_utils::printCurrentException(logger); + } +} + +} // namespace legacy::script_error diff --git a/src/legacy/utils/ScriptErrorPrinter.h b/src/legacy/utils/ScriptErrorPrinter.h new file mode 100644 index 00000000..a2deed9a --- /dev/null +++ b/src/legacy/utils/ScriptErrorPrinter.h @@ -0,0 +1,13 @@ +#pragma once + +#include "legacy/utils/UsingScriptX.h" + +#include + +namespace legacy::script_error { + +void printException(script::Exception const& exception, ll::io::Logger& logger); + +void printCurrentException(ll::io::Logger& logger); + +} // namespace legacy::script_error diff --git a/src/lse/Entry.cpp b/src/lse/Entry.cpp index a4f75682..dbab16ba 100644 --- a/src/lse/Entry.cpp +++ b/src/lse/Entry.cpp @@ -4,6 +4,7 @@ #include "legacy/engine/EngineOwnData.h" #include "legacy/main/BindAPIs.h" #include "legacy/main/EconomicSystem.h" +#include "legacy/utils/ScriptErrorPrinter.h" #include "ll/api/Config.h" #include "ll/api/event/EventBus.h" #include "ll/api/event/command/ServerCommandRegisterEvent.h" @@ -126,7 +127,7 @@ bool LegacyScriptEngine::load() { return true; } catch (...) { logger.error("Failed to load: {0}"_tr(getSelf().getName())); - ll::error_utils::printCurrentException(logger); + ::legacy::script_error::printCurrentException(logger); return false; } } From 36b940a27f51fdcff69b2d99a475aa628f964947 Mon Sep 17 00:00:00 2001 From: xianyubb <2149656630@qq.com> Date: Mon, 17 Aug 2026 04:17:48 +0800 Subject: [PATCH 2/3] Report script API names for event callbacks --- src/legacy/api/APIHelp.h | 6 ++++-- src/legacy/api/EventAPI.h | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/legacy/api/APIHelp.h b/src/legacy/api/APIHelp.h index 26f8f601..a2f42d66 100644 --- a/src/legacy/api/APIHelp.h +++ b/src/legacy/api/APIHelp.h @@ -74,13 +74,15 @@ inline Exception WrongArgsCountException(std::string const& func) { LogErrorWithInfo(__FUNCTION__); \ } -#define CATCH_WITH_MESSAGE(...) \ +#define CATCH_WITH_API_AND_MESSAGE(API_NAME, ...) \ catch (...) { \ lse::LegacyScriptEngine::getLogger().error(__VA_ARGS__); \ ::legacy::script_error::printCurrentException(lse::LegacyScriptEngine::getLogger()); \ - LogErrorWithInfo(__FUNCTION__); \ + LogErrorWithInfo(API_NAME); \ } +#define CATCH_WITH_MESSAGE(...) CATCH_WITH_API_AND_MESSAGE(__FUNCTION__, __VA_ARGS__) + // 截获回调函数异常 #define CATCH_IN_CALLBACK(...) CATCH_WITH_MESSAGE("In callback for", __VA_ARGS__) diff --git a/src/legacy/api/EventAPI.h b/src/legacy/api/EventAPI.h index 5f9a2892..fcc5203f 100644 --- a/src/legacy/api/EventAPI.h +++ b/src/legacy/api/EventAPI.h @@ -179,7 +179,7 @@ void CallEventImpl(EventListener& listener, bool& returnValue, EVENT_TYPES type, returnValue = false; } } - CATCH_WITH_MESSAGE("CallEvent Callback Failed! In Event: {}", EventTypeToString(type)) + CATCH_WITH_API_AND_MESSAGE("listen", "CallEvent Callback Failed! In Event: {}", EventTypeToString(type)) } #define FakeCallEvent(ENGINE, TYPE, ...) \ @@ -195,7 +195,7 @@ void FakeCallEventImpl(EventListener& listener, ScriptEngine* engine, EVENT_TYPE try { listener.func.get().call({}, args...); } - CATCH_WITH_MESSAGE("FakeCallEvent Callback Failed!") + CATCH_WITH_API_AND_MESSAGE("listen", "FakeCallEvent Callback Failed!") } } From 81d053fd1e6762e353941786b47e2944d12da23e Mon Sep 17 00:00:00 2001 From: xianyubb <2149656630@qq.com> Date: Mon, 17 Aug 2026 04:49:59 +0800 Subject: [PATCH 3/3] Improve JavaScript error source locations --- src/legacy/main/NodeJsHelper.cpp | 5 +- src/legacy/utils/ScriptErrorPrinter.cpp | 129 +++++++++++++++++++++++- src/legacy/utils/ScriptErrorPrinter.h | 2 + 3 files changed, 131 insertions(+), 5 deletions(-) diff --git a/src/legacy/main/NodeJsHelper.cpp b/src/legacy/main/NodeJsHelper.cpp index b64cb010..11cbfd12 100644 --- a/src/legacy/main/NodeJsHelper.cpp +++ b/src/legacy/main/NodeJsHelper.cpp @@ -176,6 +176,7 @@ std::shared_ptr newEngine() { } v8::Isolate* isolate = setup->isolate(); node::Environment* env = setup->env(); + isolate->SetCaptureStackTraceForUncaughtExceptions(true); v8::Locker locker(isolate); v8::Isolate::Scope isolate_scope(isolate); @@ -326,7 +327,9 @@ bool loadPluginCode( } if (errorMsg->IsString()) { v8::String::Utf8Value value{it->second->isolate(), errorMsg}; - logger.error(std::string_view{*value, static_cast(value.length())}); + auto error = std::string{*value, static_cast(value.length())}; + ::legacy::script_error::printRawError(error, logger); + logger.error(error); loadFailed = true; } } diff --git a/src/legacy/utils/ScriptErrorPrinter.cpp b/src/legacy/utils/ScriptErrorPrinter.cpp index c63aa04f..9caf1450 100644 --- a/src/legacy/utils/ScriptErrorPrinter.cpp +++ b/src/legacy/utils/ScriptErrorPrinter.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -128,6 +129,107 @@ std::optional readLine(std::filesystem::path const& path, size_t wa return std::nullopt; } +bool isIdentifierChar(char ch) { + return std::isalnum(static_cast(ch)) || ch == '_' || ch == '$'; +} + +std::optional +findIdentifierInSource(std::filesystem::path const& path, std::string const& identifier, size_t preferredStartLine) { + if (identifier.empty()) return std::nullopt; + + std::ifstream input(path); + if (!input) return std::nullopt; + + std::optional firstMatch; + std::string line; + for (size_t current = 1; std::getline(input, line); ++current) { + size_t pos = 0; + while ((pos = line.find(identifier, pos)) != std::string::npos) { + auto beforeOk = pos == 0 || !isIdentifierChar(line[pos - 1]); + auto afterPos = pos + identifier.size(); + auto afterOk = afterPos >= line.size() || !isIdentifierChar(line[afterPos]); + if (beforeOk && afterOk) { + ScriptFrame frame{"", current, pos + 1}; + if (current >= preferredStartLine) return frame; + if (!firstMatch) firstMatch = frame; + break; + } + ++pos; + } + } + return firstMatch; +} + +bool isLikelyScriptFile(std::filesystem::path const& path) { + auto ext = path.extension().string(); + std::ranges::transform(ext, ext.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); + return ext == ".js" || ext == ".mjs" || ext == ".cjs" || ext == ".lua" || ext == ".py"; +} + +bool isIgnoredSourceDir(std::filesystem::path const& path) { + for (auto const& part : path) { + auto name = part.string(); + if (name == "node_modules" || name == ".git" || name == "__pycache__") return true; + } + return false; +} + +std::optional findIdentifierInPluginSource(std::string const& identifier) { + auto root = pluginRoot(); + if (root.empty() || identifier.empty()) return std::nullopt; + + std::error_code ec; + size_t visited = 0; + for (std::filesystem::recursive_directory_iterator it{ + root, + std::filesystem::directory_options::skip_permission_denied, + ec + }, + end; + !ec && it != end && visited < 5000; + it.increment(ec), ++visited) { + auto const& path = it->path(); + if (it->is_directory(ec) && isIgnoredSourceDir(std::filesystem::relative(path, root, ec))) { + it.disable_recursion_pending(); + continue; + } + if (!it->is_regular_file(ec) || !isLikelyScriptFile(path)) continue; + + auto frame = findIdentifierInSource(path, identifier, 1); + if (!frame) continue; + + frame->file = path.string(); + return frame; + } + return std::nullopt; +} + +std::optional undefinedIdentifierFromMessage(std::string const& message) { + static std::regex const patterns[]{ + std::regex{R"((?:ReferenceError:\s*)?([A-Za-z_$][A-Za-z0-9_$]*) is not defined)"}, + std::regex{R"(Can't find variable:\s*([A-Za-z_$][A-Za-z0-9_$]*))"} + }; + + std::smatch match; + for (auto const& pattern : patterns) { + if (std::regex_search(message, match, pattern)) { + return match[1].str(); + } + } + return std::nullopt; +} + +void refineFrameFromMessage(ScriptFrame& frame, std::filesystem::path const& source, std::string const& message) { + auto identifier = undefinedIdentifierFromMessage(message); + if (!identifier) return; + + auto better = findIdentifierInSource(source, *identifier, frame.line); + if (!better) return; + + frame.line = better->line; + frame.column = better->column; +} + std::string displayPath(std::filesystem::path const& path) { auto root = pluginRoot(); std::error_code ec; @@ -138,7 +240,7 @@ std::string displayPath(std::filesystem::path const& path) { return path.generic_string(); } -void printFrame(ScriptFrame const& frame, ll::io::Logger& logger) { +void printFrame(ScriptFrame frame, ll::io::Logger& logger, std::string const& message) { auto source = findSourceFile(frame.file); if (!source) { logger.error( @@ -150,6 +252,8 @@ void printFrame(ScriptFrame const& frame, ll::io::Logger& logger) { return; } + refineFrameFromMessage(frame, *source, message); + logger.error( "Script error location: {}:{}{}", displayPath(*source), @@ -172,10 +276,25 @@ void printFrame(ScriptFrame const& frame, ll::io::Logger& logger) { if (after) logger.error("{:>6} | {}", frame.line + 1, *after); } +void printScriptLocation(std::string const& stacktrace, std::string const& message, ll::io::Logger& logger) { + auto frames = parseFrames(stacktrace); + if (frames.empty()) { + auto identifier = undefinedIdentifierFromMessage(message); + if (!identifier) return; + + auto frame = findIdentifierInPluginSource(*identifier); + if (!frame) return; + + printFrame(*frame, logger, message); + return; + } + printFrame(frames.front(), logger, message); +} + void printScriptLocation(script::Exception const& exception, ll::io::Logger& logger) { - auto frames = parseFrames(exception.stacktrace()); - if (frames.empty()) return; - printFrame(frames.front(), logger); + auto stacktrace = exception.stacktrace(); + auto message = exception.message(); + printScriptLocation(stacktrace.empty() ? message : stacktrace, message, logger); } } // namespace @@ -185,6 +304,8 @@ void printException(script::Exception const& exception, ll::io::Logger& logger) ll::error_utils::printException(exception, logger); } +void printRawError(std::string const& error, ll::io::Logger& logger) { printScriptLocation(error, error, logger); } + void printCurrentException(ll::io::Logger& logger) { auto current = std::current_exception(); if (!current) return; diff --git a/src/legacy/utils/ScriptErrorPrinter.h b/src/legacy/utils/ScriptErrorPrinter.h index a2deed9a..51cc704e 100644 --- a/src/legacy/utils/ScriptErrorPrinter.h +++ b/src/legacy/utils/ScriptErrorPrinter.h @@ -8,6 +8,8 @@ namespace legacy::script_error { void printException(script::Exception const& exception, ll::io::Logger& logger); +void printRawError(std::string const& error, ll::io::Logger& logger); + void printCurrentException(ll::io::Logger& logger); } // namespace legacy::script_error