diff --git a/Changelog.md b/Changelog.md index b38e3f3..9390e5c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ # v2.3.0 -- ADDED logging of errors to the backend +- ADDED logging of various errors, crashes and exceptions to the backend +- ADDED `[CHAT]` messages to server console # v2.2.0 diff --git a/include/Common.h b/include/Common.h index 483ac9b..619aab0 100644 --- a/include/Common.h +++ b/include/Common.h @@ -126,6 +126,8 @@ void RegisterThread(const std::string str); } \ } while (false) +void LogChatMessage(const std::string& name, int id, const std::string& msg); + #define Biggest 30000 std::string Comp(std::string Data); std::string DeComp(std::string Compressed); diff --git a/include/CustomAssert.h b/include/CustomAssert.h index 7bf3622..5a04e5e 100644 --- a/include/CustomAssert.h +++ b/include/CustomAssert.h @@ -59,10 +59,18 @@ inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const ch #define AssertNotReachable() _assert(__FILE__, __func__, __LINE__, "reached unreachable code", false) #else // In release build, these macros turn into NOPs. The compiler will optimize these out. -#define Assert(x) \ - do { \ - } while (false) -#define AssertNotReachable() \ - do { \ - } while (false) +#define Assert(cond) \ + do { \ + if (!result) { \ + Sentry.LogAssert(#cond, _file_basename, _line, __func__); \ + } +} +while (false) +#define AssertNotReachable() \ + do { \ + if (!result) { \ + Sentry.LogAssert("code is unreachable", _file_basename, _line, __func__); \ + } +} +while (false) #endif // DEBUG diff --git a/include/TSentry.h b/include/TSentry.h index 5cb83ba..36593ba 100644 --- a/include/TSentry.h +++ b/include/TSentry.h @@ -3,8 +3,9 @@ #include -#include #include +#include +#include // TODO possibly use attach_stacktrace @@ -18,9 +19,9 @@ public: void SetupUser(); void Log(sentry_level_t level, const std::string& logger, const std::string& text); void LogError(const std::string& text, const std::string& file, const std::string& line); - void SetExtra(const std::string& key, const sentry_value_t& value); - void SetExtra(const std::string& key, const std::string& value); + void SetContext(const std::string& context_name, const std::unordered_map& map); void LogException(const std::exception& e, const std::string& file, const std::string& line); + void LogAssert(const std::string& condition_string, const std::string& file, const std::string& line, const std::string& function); void AddErrorBreadcrumb(const std::string& msg, const std::string& file, const std::string& line); // cleared when Logged void SetTransaction(const std::string& id); @@ -29,6 +30,7 @@ public: private: bool mValid { true }; std::mutex mMutex; + sentry_value_t mContext; }; #endif // SENTRY_H diff --git a/src/Common.cpp b/src/Common.cpp index 32b5905..64b7278 100644 --- a/src/Common.cpp +++ b/src/Common.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -86,3 +87,15 @@ std::string ThreadName() { void RegisterThread(const std::string str) { threadNameMap[std::this_thread::get_id()] = str; } + +void LogChatMessage(const std::string& name, int id, const std::string& msg) { + std::stringstream ss; + ss << "[CHAT] "; + if (id != -1) { + ss << "(" << id << ") <" << name << ">"; + } else { + ss << name << ""; + } + ss << msg; + Application::Console().Write(ss.str()); +} diff --git a/src/Http.cpp b/src/Http.cpp index 85a5ee7..fbb034b 100644 --- a/src/Http.cpp +++ b/src/Http.cpp @@ -124,15 +124,16 @@ std::string Http::POST(const std::string& host, const std::string& target, const http::read(stream, buffer, response); - Sentry.SetExtra("reponse-code", std::to_string(response.result_int())); - + std::unordered_map response_data; + response_data["reponse-code"] = std::to_string(response.result_int()); for (const auto& header : response.base()) { // need to do explicit casts to convert string_view to string // since string_view may not be null-terminated (and in fact isn't, here) std::string KeyString(header.name_string()); std::string ValueString(header.value()); - Sentry.SetExtra(KeyString, ValueString); + response_data[KeyString] = ValueString; } + Sentry.SetContext("https-post-data", response_data); std::stringstream result; result << response; diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp index de54b81..dd9ecf9 100644 --- a/src/THeartbeatThread.cpp +++ b/src/THeartbeatThread.cpp @@ -43,8 +43,9 @@ void THeartbeatThread::operator()() { if (T.size() > std::string("YOU_SHALL_NOT_PASS").size() && Application::Settings.Key.size() == 36) { auto Lock = Sentry.CreateExclusiveContext(); - Sentry.SetExtra("response-body", T); - Sentry.SetExtra("request-body", Body); + Sentry.SetContext("heartbeat", + { { "response-body", T }, + { "request-body", Body } }); Sentry.SetTransaction(transaction); Sentry.Log(SENTRY_LEVEL_ERROR, "default", "wrong backend response format"); } diff --git a/src/TLuaFile.cpp b/src/TLuaFile.cpp index d6b1f33..0d78b67 100644 --- a/src/TLuaFile.cpp +++ b/src/TLuaFile.cpp @@ -118,7 +118,7 @@ bool CheckLua(lua_State* L, int r) { warn(a + " | " + msg); return false; } - // What the fuck, what do we do?! + // This should never happen since it's not directly called from "userspace" Lua. AssertNotReachable(); } return true; @@ -127,7 +127,10 @@ bool CheckLua(lua_State* L, int r) { int lua_RegisterEvent(lua_State* L) { int Args = lua_gettop(L); auto MaybeScript = Engine().GetScript(L); - Assert(MaybeScript.has_value()); + if (!MaybeScript.has_value()) { + error("RegisterEvent: There is no script associated with this lua_State."); + return 0; + } TLuaFile& Script = MaybeScript.value(); if (Args == 2 && lua_isstring(L, 1) && lua_isstring(L, 2)) { Script.RegisterEvent(lua_tostring(L, 1), lua_tostring(L, 2)); @@ -139,7 +142,10 @@ int lua_RegisterEvent(lua_State* L) { int lua_TriggerEventL(lua_State* L) { int Args = lua_gettop(L); auto MaybeScript = Engine().GetScript(L); - Assert(MaybeScript.has_value()); + if (!MaybeScript.has_value()) { + error("TriggerEvent: There is no script associated with this lua_State."); + return 0; + } TLuaFile& Script = MaybeScript.value(); if (Args > 0) { if (lua_isstring(L, 1)) { @@ -155,7 +161,10 @@ int lua_TriggerEventL(lua_State* L) { int lua_TriggerEventG(lua_State* L) { int Args = lua_gettop(L); auto MaybeScript = Engine().GetScript(L); - Assert(MaybeScript.has_value()); + if (!MaybeScript.has_value()) { + error("TriggerGlobalEvent: There is no script associated with this lua_State."); + return 0; + } TLuaFile& Script = MaybeScript.value(); if (Args > 0) { if (lua_isstring(L, 1)) { @@ -194,8 +203,11 @@ void CallAsync(TLuaFile* lua, const std::string& Func, int U) { int lua_StopThread(lua_State* L) { auto MaybeScript = Engine().GetScript(L); - Assert(MaybeScript.has_value()); - // ugly, but whatever, this is safe as fuck + if (!MaybeScript.has_value()) { + error("StopThread: There is no script associated with this lua_State."); + return 0; + } + // ugly, but whatever, this is very safe MaybeScript.value().get().SetStopThread(true); return 0; } @@ -209,7 +221,10 @@ int lua_CreateThread(lua_State* L) { int U = int(lua_tointeger(L, 2)); if (U > 0 && U < 501) { auto MaybeScript = Engine().GetScript(L); - Assert(MaybeScript.has_value()); + if (!MaybeScript.has_value()) { + error("CreateThread: There is no script associated with this lua_State."); + return 0; + } TLuaFile& Script = MaybeScript.value(); std::thread t1(CallAsync, &Script, STR, U); t1.detach(); @@ -397,7 +412,9 @@ int lua_sendChat(lua_State* L) { if (lua_isstring(L, 2)) { int ID = int(lua_tointeger(L, 1)); if (ID == -1) { - std::string Packet = "C:Server: " + std::string(lua_tostring(L, 2)); + auto msg = std::string(lua_tostring(L, 2)); + LogChatMessage(" (to everyone) ", -1, msg); + std::string Packet = "C:Server: " + msg; Engine().Network().SendToAll(nullptr, Packet, true, true); } else { auto MaybeClient = GetClient(Engine().Server(), ID); @@ -405,7 +422,9 @@ int lua_sendChat(lua_State* L) { auto c = MaybeClient.value().lock(); if (!c->IsSynced()) return 0; - std::string Packet = "C:Server: " + std::string(lua_tostring(L, 2)); + auto msg = std::string(lua_tostring(L, 2)); + LogChatMessage(" (to \"" + c->GetName() + "\")", -1, msg); + std::string Packet = "C:Server: " + msg; Engine().Network().Respond(*c, Packet, true); } else SendError(Engine(), L, ("SendChatMessage invalid argument [1] invalid ID")); diff --git a/src/TNetwork.cpp b/src/TNetwork.cpp index 4dc012c..6977c60 100644 --- a/src/TNetwork.cpp +++ b/src/TNetwork.cpp @@ -298,15 +298,17 @@ void TNetwork::Authentication(SOCKET TCPSock) { ClientKick(*Client, "Backend returned invalid auth response format."); error("Backend returned invalid auth response format. This should never happen."); auto Lock = Sentry.CreateExclusiveContext(); - Sentry.SetExtra("response-body", Rc); - Sentry.SetExtra("key", RequestString); + Sentry.SetContext("auth", + { { "response-body", Rc }, + { "key", RequestString } }); Sentry.SetTransaction(Application::GetBackendUrlForAuth() + Target); Sentry.Log(SENTRY_LEVEL_ERROR, "default", "auth: wrong backend response format"); return; } else if (Rc == "0") { auto Lock = Sentry.CreateExclusiveContext(); - Sentry.SetExtra("response-body", Rc); - Sentry.SetExtra("key", RequestString); + Sentry.SetContext("auth", + { { "response-body", Rc }, + { "key", RequestString } }); Sentry.SetTransaction(Application::GetBackendUrlForAuth() + Target); Sentry.Log(SENTRY_LEVEL_INFO, "default", "backend returned 0 instead of json"); } diff --git a/src/TSentry.cpp b/src/TSentry.cpp index 0287c65..cacd096 100644 --- a/src/TSentry.cpp +++ b/src/TSentry.cpp @@ -49,18 +49,20 @@ void TSentry::LogError(const std::string& text, const std::string& file, const s Log(SENTRY_LEVEL_ERROR, "default", file + ": " + text); } -void TSentry::SetExtra(const std::string& key, const sentry_value_t& value) { +void TSentry::SetContext(const std::string& context_name, const std::unordered_map& map) { if (!mValid) { return; } - sentry_set_extra(key.c_str(), value); -} - -void TSentry::SetExtra(const std::string& key, const std::string& value) { - if (!mValid) { - return; + mContext = sentry_value_new_object(); + for (const auto& pair : map) { + std::string key = pair.first; + if (key == "type") { + // `type` is reserved + key = "_type"; + } + sentry_value_set_by_key(mContext, key.c_str(), sentry_value_new_string(pair.second.c_str())); } - SetExtra(key.c_str(), sentry_value_new_string(value.c_str())); + sentry_set_context(context_name.c_str(), mContext); } void TSentry::LogException(const std::exception& e, const std::string& file, const std::string& line) { @@ -68,7 +70,17 @@ void TSentry::LogException(const std::exception& e, const std::string& file, con return; } SetTransaction(file + ":" + line); - Log(SENTRY_LEVEL_ERROR, "exceptions", std::string(e.what()) + " @ " + file + ":" + line); + Log(SENTRY_LEVEL_FATAL, "exceptions", std::string(e.what()) + " @ " + file + ":" + line); +} + +void TSentry::LogAssert(const std::string& condition_string, const std::string& file, const std::string& line, const std::string& function) { + if (!mValid) { + return; + } + SetTransaction(file + ":" + line + ":" + function); + std::stringstream ss; + ss << "\"" << condition_string << "\" failed @ " << file << ":" << line; + Log(SENTRY_LEVEL_FATAL, "asserts", ss.str()); } void TSentry::AddErrorBreadcrumb(const std::string& msg, const std::string& file, const std::string& line) { diff --git a/src/TServer.cpp b/src/TServer.cpp index d2e4d58..f7531e6 100644 --- a/src/TServer.cpp +++ b/src/TServer.cpp @@ -7,7 +7,7 @@ #include #include -#undef GetObject //Fixes Windows +#undef GetObject // Fixes Windows #include "Json.h" @@ -126,6 +126,7 @@ void TServer::GlobalParser(const std::weak_ptr& Client, std::string Pac if (Packet.length() < 4 || Packet.find(':', 3) == std::string::npos) break; Res = TriggerLuaEvent("onChatMessage", false, nullptr, std::make_unique(TLuaArg { { LockedClient->GetID(), LockedClient->GetName(), Packet.substr(Packet.find(':', 3) + 1) } }), true); + LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), Packet.substr(Packet.find(':', 3) + 1)); // FIXME: this needs to be adjusted once lua is merged if (std::any_cast(Res)) break; Network.SendToAll(nullptr, Packet, true, true); @@ -324,9 +325,10 @@ void TServer::Apply(TClient& c, int VID, const std::string& pckt) { if (VD.empty()) { error("Tried to apply change to vehicle that does not exist"); auto Lock = Sentry.CreateExclusiveContext(); - Sentry.SetExtra("packet", Packet); - Sentry.SetExtra("vehicle-id", std::to_string(VID)); - Sentry.SetExtra("client-car-count", std::to_string(c.GetCarCount())); + Sentry.SetContext("vehicle-change", + { { "packet", Packet }, + { "vehicle-id", std::to_string(VID) }, + { "client-car-count", std::to_string(c.GetCarCount()) } }); Sentry.LogError("attempt to apply change to nonexistent vehicle", _file_basename, _line); return; } @@ -335,7 +337,8 @@ void TServer::Apply(TClient& c, int VID, const std::string& pckt) { FoundPos = VD.find('{'); if (FoundPos == std::string::npos) { auto Lock = Sentry.CreateExclusiveContext(); - Sentry.SetExtra("packet", VD); + Sentry.SetContext("vehicle-change-packet", + { { "packet", VD } }); Sentry.LogError("malformed packet", _file_basename, _line); error("Malformed packet received, no '{' found"); return;