--[[ https://github.com/sundream/ltrace --]] local ltrace_getenv local getstack_from local getstack local dumpvalue local dumpkey local dumptable local dumpframe local dumpstack local getfulltrace local mTABLE_MAX_DEEP = 3 local mVALUE_MAX_LEN = 5120 local traceback = {} -- local setfenv = -- _G.setfenv or -- function(f, t) -- f = (type(f) == "function" and f or debug.getinfo(f + 1, "f").func) -- local name -- local up = 0 -- repeat -- up = up + 1 -- name = debug.getupvalue(f, up) -- until name == "_ENV" or name == nil -- if name then -- debug.upvaluejoin( -- f, -- up, -- function() -- return name -- end, -- 1 -- ) -- use unique upvalue -- debug.setupvalue(f, up, t) -- end -- return f -- end local getfenv = _G.getfenv or function(f) f = (type(f) == "function" and f or debug.getinfo(f + 1, "f").func) local name, val local up = 0 repeat up = up + 1 name, val = debug.getupvalue(f, up) until name == "_ENV" or name == nil return val end local function canPrint(k, v) if type(v) ~= "table" then return true end if v == _G then return false end return true end ltrace_getenv = function(realframe) local env = {} local indexmap = {} local i = 1 local funcinfo = debug.getinfo(realframe, "nlSf") if not funcinfo then return end local k, v = debug.getlocal(realframe, i) while k do indexmap[k] = i env[k] = v i = i + 1 k, v = debug.getlocal(realframe, i) end setmetatable(env, {__index = getfenv(funcinfo.func)}) return env, funcinfo, indexmap end getstack_from = function(callfile, callline, maxframesz) assert(callfile and callline) local realframe = 0 local framestack = {} local env, funcinfo, indexmap = ltrace_getenv(realframe) while funcinfo do if funcinfo.currentline == callline and funcinfo.short_src == callfile then break end realframe = realframe + 1 env, funcinfo, indexmap = ltrace_getenv(realframe) end while funcinfo do realframe = realframe + 1 env, funcinfo, indexmap = ltrace_getenv(realframe) if not funcinfo then break end table.insert( framestack, { realframe = realframe, env = env, funcinfo = funcinfo, indexmap = indexmap } ) if maxframesz and #framestack >= maxframesz then break end end return framestack end local function get_params(func) local info = debug.getinfo(func, "u") if not info then return end local argNameList = {} for i = 1, info.nparams do local name = debug.getlocal(func, i) table.insert(argNameList, name) end if info.isvararg then table.insert(argNameList, "...") end return argNameList, info.nparams, info.isvararg end local uncomparedTypeMap = { ["table"] = true, ["userdata"] = true, ["function"] = true, ["boolean"] = true, ["thread"] = true } local function compare(a, b) local ta = type(a) local tb = type(b) if ta ~= tb or uncomparedTypeMap[ta] or uncomparedTypeMap[tb] then return tostring(a) < tostring(b) end return a < b end local function pairs_orderly(t, comp) comp = comp or compare local keys = {} for k, v in pairs(t) do table.insert(keys, k) end local size = #keys table.sort(keys, comp) local i = 0 return function(tbl, k) i = i + 1 if i > size then return end return keys[i], t[keys[i]] end end local ignoreMap = {} dumptable = function(value, depth) assert(type(value) == "table") local tostr local meta = getmetatable(value) if meta and meta.__tostring then tostr = meta.__tostring elseif value.__tostring then tostr = value.__tostring end if tostr then return tostr(value) end local rettbl = {} depth = (depth or 0) + 1 if depth > mTABLE_MAX_DEEP then return "{...}" end table.insert(rettbl, "{") local content = {} for k, v in pairs_orderly(value) do if not ignoreMap[k] and canPrint(k, v) then local line = {} table.insert(line, dumpkey(k, depth) .. "=") table.insert(line, dumpvalue(v, depth)) table.insert(content, table.concat(line)) end end table.insert(rettbl, table.concat(content, ", ")) table.insert(rettbl, "}") return table.concat(rettbl) end dumpkey = function(key, depth) local vtype = type(key) if vtype == "table" then return "[" .. dumptable(key, depth) .. "]" elseif vtype == "string" then if key:match("^%d") or key:match("%w+") ~= key then return "[" .. string.format("%q", key) .. "]" end return tostring(key) end return "[" .. tostring(key) .. "]" end dumpvalue = function(v, depth) local vtype = type(v) if vtype == "table" then return dumptable(v, depth) elseif vtype == "string" then return string.format("%q", v) elseif vtype == "number" or vtype == "boolean" then return tostring(v) end return string.format("<%s>", tostring(v)) end dumpframe = function(frameidx, env, funcinfo, indexmap) local fix = -1 if funcinfo.what ~= "Lua" then return string.format("%d[C] : in <%s>", frameidx + fix, funcinfo.name or funcinfo.what or funcinfo.namewhat) end local out = {} local args = get_params(funcinfo.func) local funcstr = "" if funcinfo.name then funcstr = string.format("in <%s(%s)>", funcinfo.name, table.concat(args, ",")) end table.insert( out, string.format("%d @%s:%d %s", frameidx + fix, funcinfo.short_src or "", funcinfo.currentline, funcstr) ) local valuelines = {} local function compare(k1, k2) return indexmap[k1] < indexmap[k2] end for k, v in pairs_orderly(env, compare) do if canPrint(k, v) then local vs = dumpvalue(v) if #vs > mVALUE_MAX_LEN then vs = vs:sub(1, mVALUE_MAX_LEN) .. "..." end -- table.insert(valuelines, string.format("\t%s<%s>:%s", k, indexmap[k] or "", vs)) table.insert(valuelines, string.format("\t%s:%s", k, vs)) end end table.insert(out, table.concat(valuelines, "\n")) return table.concat(out, "\n") end dumpstack = function(stack) local list = {} for i, v in pairs(stack) do local s = dumpframe(i, v.env, v.funcinfo, v.indexmap) table.insert(list, s) end return list end getstack = function(level, maxframesz) level = level or 2 local info = debug.getinfo(level, "nlSf") return getstack_from(info.short_src, info.currentline, maxframesz) end getfulltrace = function(level, maxframesz) local fullstack = getstack(level, maxframesz) local list = dumpstack(fullstack) local ret = {} for _, v in pairs(list) do table.insert(ret, v) end return table.concat(ret, "\n") end local function settdmaxdeep(depth) mTABLE_MAX_DEEP = depth or mTABLE_MAX_DEEP end local function setvdmaxlen(len) mVALUE_MAX_LEN = len or mVALUE_MAX_LEN end traceback.setvdmaxlen = setvdmaxlen traceback.settdmaxdeep = settdmaxdeep traceback.getstack_from = getstack_from traceback.getstack = getstack traceback.dumpvalue = dumpvalue traceback.dumptable = dumptable traceback.dumpframe = dumpframe traceback.dumpstack = dumpstack traceback.getfulltrace = getfulltrace return traceback