Skip to content

Commit 0b05bc5

Browse files
committed
Added stacktrace vars support
Sends locals and upvalues to Sentry for each stack level
1 parent 12131fc commit 0b05bc5

File tree

1 file changed

+76
-2
lines changed

1 file changed

+76
-2
lines changed

lua/includes/modules/sentry.lua

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,12 +346,53 @@ end
346346
-- Stack Reverse Engineering
347347
--
348348

349+
local NIL_REPLACEMENT = "<nil>"; -- We need some way of representing nil values without them disappearing from tables
350+
351+
---
352+
-- Recursively formats a table of variables into a dictionary of key -> values
353+
-- @param vars The table of keys and values to process, recursively deals with table values
354+
-- @param out The output dictionary
355+
-- @param[opt] prefix What to prefix the variable names with, used for the recursion of tables
356+
-- @param[opt] done A list of tables that have already been processed to prevent infinite recursion
357+
local function formatStackVariables(vars, out, prefix, done)
358+
prefix = prefix or ""
359+
done = done or {}
360+
done[vars] = true
361+
362+
for k,v in pairs(vars) do
363+
local vType = type(v)
364+
if (vType == "table" and v.r and v.g and v.b and v.a) then
365+
out[prefix .. k] = ("Color(%.0f, %.0f, %.0f, %.0f)"):format(v.r, v.g, v.b, v.a)
366+
elseif (vType == "table" and not done[v]) then
367+
formatStackVariables(v, out, prefix .. k .. ".", done)
368+
elseif (vType == "number" or vType == "bool") then
369+
out[prefix .. k] = tostring(v)
370+
elseif (vType == "string") then -- nil values are included here aswell
371+
out[prefix .. k] = v
372+
elseif (vType == "Vector") then
373+
out[prefix .. k] = ("Vector(%.3f, %.3f, %.3f)"):format(v.x, v.y, v.z)
374+
elseif (vType == "Angle") then
375+
out[prefix .. k] = ("Angle(%.3f, %.3f, %.3f)"):format(v.p, v.y, v.r)
376+
elseif (vType == "Player" and IsValid(v)) then
377+
out[prefix .. k] = ("Player[%q, %s]"):format(v:Nick(), v:SteamID())
378+
elseif (vType == "NPC" and IsValid(v)) then
379+
out[prefix .. k] = ("NPC[%i, %s]"):format(v:EntIndex(), v:GetClass())
380+
elseif (vType == "Weapon" and IsValid(v)) then
381+
out[prefix .. k] = ("Weapon[%i, %s]"):format(v:EntIndex(), v:GetClass())
382+
elseif (vType == "Entity" and IsValid(v)) then
383+
out[prefix .. k] = ("Entity[%i, %s]"):format(v:EntIndex(), v:GetClass())
384+
elseif (vType ~= "function") then -- Catch-all except functions cause they are boring
385+
out[prefix .. k] = tostring(v)
386+
end
387+
end
388+
end
389+
349390
---
350391
-- Turns a lua stacktrace into a Sentry stacktrace
351392
-- @param stack Lua stacktrace in debug.getinfo style
352393
-- @return A reversed stacktrace with different field names
353394
local function sentrifyStack(stack)
354-
-- Sentry likes stacks in the oposite order to lua
395+
-- Sentry likes stacks in the opposite order to lua
355396
stack = table.Reverse(stack);
356397

357398
-- The first entry from LuaError is sometimes useless
@@ -365,11 +406,16 @@ local function sentrifyStack(stack)
365406

366407
local ret = {}
367408
for i, frame in ipairs(stack) do
409+
local vars = {};
410+
formatStackVariables(frame["upvalues"], vars);
411+
formatStackVariables(frame["locals"], vars);
412+
368413
ret[i] = {
369414
filename = frame["source"]:sub(2),
370415
["function"] = frame["name"] or "<unknown>",
371416
module = modulify(frame["source"]),
372417
lineno = frame["currentline"],
418+
vars = vars,
373419
}
374420
end
375421
return { frames = ret };
@@ -383,11 +429,37 @@ local function getStack()
383429

384430
local stack = {};
385431
while true do
386-
local info = debug.getinfo(level, "Sln");
432+
local info = debug.getinfo(level, "fSlnu");
387433
if (not info) then
388434
break;
389435
end
390436

437+
local locals = {}
438+
local upvalues = {}
439+
440+
if (info.what == "Lua") then
441+
-- Capture locals
442+
local i = 1
443+
while true do
444+
local name, value = debug.getlocal(level, i);
445+
if (not isstring(name)) then break; end
446+
447+
if (#name > 0 and name[1] ~= "(") then -- Some locals are internal with names like "(*temporary)"
448+
locals[name] = value == nil and NIL_REPLACEMENT or value;
449+
end
450+
i = i + 1
451+
end
452+
453+
-- Capture upvalues
454+
for j = 1, info.nups do
455+
local name, value = debug.getupvalue(info.func, j);
456+
upvalues[name] = value == nil and NIL_REPLACEMENT or value;
457+
end
458+
end
459+
460+
info.locals = locals;
461+
info.upvalues = upvalues;
462+
391463
stack[level - 2] = info;
392464

393465
level = level + 1;
@@ -868,6 +940,8 @@ function ExecuteTransaction(name, txn, func, ...)
868940

869941
local success = table.remove(res, 1);
870942
if (not success) then
943+
--xpcallCB("Test") -- Uncomment this if you get "error in error handling" errors, useful for debugging
944+
871945
local err = res[1];
872946
SkipNext(err);
873947
-- Boom

0 commit comments

Comments
 (0)