Software /
code /
prosody
Comparison
tools/modtrace.lua @ 11195:c4cb536b67b5
tools.modtrace: Library for tracing/debugging Lua module and method calls
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Fri, 30 Oct 2020 13:53:24 +0000 |
child | 11197:50f182931bdd |
comparison
equal
deleted
inserted
replaced
11194:9d1ce6f28401 | 11195:c4cb536b67b5 |
---|---|
1 -- Trace module calls and method calls on created objects | |
2 -- | |
3 -- Very rough and for debugging purposes only. It makes many | |
4 -- assumptions and there are many ways it could fail. | |
5 -- | |
6 -- Example use: | |
7 -- | |
8 -- local dbuffer = require "tools.modtrace".trace("util.dbuffer"); | |
9 -- | |
10 | |
11 local t_pack = require "util.table".pack; | |
12 local serialize = require "util.serialization".serialize; | |
13 local unpack = table.unpack or unpack; --luacheck: ignore 113 | |
14 local set = require "util.set"; | |
15 | |
16 local function stringify_value(v) | |
17 if type(v) == "string" and #v > 20 then | |
18 return ("<string(%d)>"):format(#v); | |
19 elseif type(v) == "function" then | |
20 return tostring(v); | |
21 end | |
22 return serialize(v, "debug"); | |
23 end | |
24 | |
25 local function stringify_params(...) | |
26 local n = select("#", ...); | |
27 local r = {}; | |
28 for i = 1, n do | |
29 table.insert(r, stringify_value((select(i, ...)))); | |
30 end | |
31 return table.concat(r, ", "); | |
32 end | |
33 | |
34 local function stringify_result(ret) | |
35 local r = {}; | |
36 for i = 1, ret.n do | |
37 table.insert(r, stringify_value(ret[i])); | |
38 end | |
39 return table.concat(r, ", "); | |
40 end | |
41 | |
42 local function stringify_call(method_name, ...) | |
43 return ("%s(%s)"):format(method_name, stringify_params(...)); | |
44 end | |
45 | |
46 local function wrap_method(original_obj, original_method, method_name) | |
47 method_name = ("<%s>:%s"):format(getmetatable(original_obj).__name or "object", method_name); | |
48 return function (new_obj_self, ...) | |
49 local opts = new_obj_self._modtrace_opts; | |
50 local f = opts.output or io.stderr; | |
51 f:write(stringify_call(method_name, ...)); | |
52 local ret = t_pack(original_method(original_obj, ...)); | |
53 if ret.n > 0 then | |
54 f:write(" = ", stringify_result(ret), "\n"); | |
55 else | |
56 f:write("\n"); | |
57 end | |
58 return unpack(ret, 1, ret.n); | |
59 end; | |
60 end | |
61 | |
62 local function wrap_function(original_function, function_name, opts) | |
63 local f = opts.output or io.stderr; | |
64 return function (...) | |
65 f:write(stringify_call(function_name, ...)); | |
66 local ret = t_pack(original_function(...)); | |
67 if ret.n > 0 then | |
68 f:write(" = ", stringify_result(ret), "\n"); | |
69 else | |
70 f:write("\n"); | |
71 end | |
72 return unpack(ret, 1, ret.n); | |
73 end; | |
74 end | |
75 | |
76 local function wrap_metamethod(name, method) | |
77 if name == "__index" then | |
78 return function (new_obj, k) | |
79 local original_method; | |
80 if type(method) == "table" then | |
81 original_method = new_obj._modtrace_original_obj[k]; | |
82 else | |
83 original_method = method(new_obj._modtrace_original_obj, k); | |
84 end | |
85 if original_method == nil then | |
86 return nil; | |
87 end | |
88 return wrap_method(new_obj._modtrace_original_obj, original_method, k); | |
89 end; | |
90 end | |
91 return function (new_obj, ...) | |
92 return method(new_obj._modtrace_original_obj, ...); | |
93 end; | |
94 end | |
95 | |
96 local function wrap_mt(original_mt) | |
97 local new_mt = {}; | |
98 for k, v in pairs(original_mt) do | |
99 new_mt[k] = wrap_metamethod(k, v); | |
100 end | |
101 return new_mt; | |
102 end | |
103 | |
104 local function wrap_obj(original_obj, opts) | |
105 local new_mt = wrap_mt(getmetatable(original_obj)); | |
106 return setmetatable({_modtrace_original_obj = original_obj, _modtrace_opts = opts}, new_mt); | |
107 end | |
108 | |
109 local function wrap_new(original_new, function_name, opts) | |
110 local f = opts.output or io.stderr; | |
111 return function (...) | |
112 f:write(stringify_call(function_name, ...)); | |
113 local ret = t_pack(original_new(...)); | |
114 local obj = ret[1]; | |
115 | |
116 if ret.n == 1 and type(ret[1]) == "table" then | |
117 f:write(" = <", getmetatable(ret[1]).__name or "object", ">", "\n"); | |
118 elseif ret.n > 0 then | |
119 f:write(" = ", stringify_result(ret), "\n"); | |
120 else | |
121 f:write("\n"); | |
122 end | |
123 | |
124 if obj then | |
125 ret[1] = wrap_obj(obj, opts); | |
126 end | |
127 return unpack(ret, 1, ret.n); | |
128 end; | |
129 end | |
130 | |
131 local function trace(module, opts) | |
132 if type(module) == "string" then | |
133 module = require(module); | |
134 end | |
135 opts = opts or {}; | |
136 local new_methods = set.new(opts.new_methods or {"new"}); | |
137 local fake_module = setmetatable({}, { | |
138 __index = function (_, k) | |
139 if new_methods:contains(k) then | |
140 return wrap_new(module[k], k, opts); | |
141 else | |
142 return wrap_function(module[k], k, opts); | |
143 end | |
144 end; | |
145 }); | |
146 return fake_module; | |
147 end | |
148 | |
149 return { | |
150 wrap = trace; | |
151 trace = trace; | |
152 } |