339 lines
8.3 KiB
Lua
339 lines
8.3 KiB
Lua
local Logging = {}
|
|
|
|
local LogLevel = {
|
|
TRACE = 1,
|
|
DEBUG = 2,
|
|
INFO = 3,
|
|
WARNING = 4,
|
|
CRITICAL = 5,
|
|
ERROR = 6
|
|
}
|
|
|
|
LogLevel._DEFAULT = LogLevel.TRACE
|
|
|
|
for k,v in pairs(LogLevel) do
|
|
LogLevel[v] = k
|
|
end
|
|
|
|
function LogLevel.isValid(value)
|
|
return LogLevel[LogLevel[value]] ~= nil
|
|
end
|
|
|
|
function LogLevel.asNumber(value)
|
|
return type(value) == "number" and value or LogLevel[value]
|
|
end
|
|
|
|
function LogLevel.isGreaterOrEqual(a, b)
|
|
return a >= b
|
|
end
|
|
|
|
local Logger = {
|
|
ignoreGlobals = true
|
|
}
|
|
Logger.__index = Logger
|
|
|
|
|
|
local function combineOutputs(...)
|
|
local args = {...}
|
|
if #args == 0 then
|
|
return function() end
|
|
elseif #args == 1 then
|
|
return args[1]
|
|
end
|
|
return function(text, level)
|
|
for _,v in ipairs(args) do
|
|
v(text, level)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- TODO: Split logger outputs and specify LogLevel for each
|
|
function Logger:new(...)
|
|
local logger = { output = combineOutputs(...) or function() end }
|
|
setmetatable(logger, self)
|
|
logger.__index = logger
|
|
|
|
return logger
|
|
end
|
|
|
|
function Logger:setIgnoreGlobals(ignoreGlobals)
|
|
self.ignoreGlobals = ignoreGlobals
|
|
end
|
|
|
|
local LogPlain = {}
|
|
function LogPlain.of(value, deep)
|
|
local obj = { value, deep = deep }
|
|
setmetatable(obj, LogPlain)
|
|
return obj
|
|
end
|
|
|
|
function LogPlain.is(value)
|
|
return type(value) == "table" and getmetatable(value) == LogPlain
|
|
end
|
|
|
|
function LogPlain.isDeep(value)
|
|
return LogPlain.is(value) and value.deep
|
|
end
|
|
|
|
function LogPlain.getValue(value)
|
|
return LogPlain.is(value) and value[1] or value
|
|
end
|
|
|
|
local RecursionSentinel = {}
|
|
function RecursionSentinel.make(table, index)
|
|
local obj = { table = table, index = index }
|
|
setmetatable(obj, RecursionSentinel)
|
|
return obj
|
|
end
|
|
|
|
function RecursionSentinel.isSentinel(value)
|
|
return type(value) == "table" and getmetatable(value) == RecursionSentinel
|
|
end
|
|
|
|
function RecursionSentinel.isKnown(knownTables, value)
|
|
return knownTables[value] ~= nil
|
|
end
|
|
|
|
function RecursionSentinel.getSentinel(knownTables, value)
|
|
return knownTables[value] or RecursionSentinel.add(knownTables, value)
|
|
end
|
|
|
|
function RecursionSentinel.add(knownTables, value)
|
|
local sentinel = RecursionSentinel.make(value, #knownTables)
|
|
knownTables[value] = sentinel
|
|
return sentinel
|
|
end
|
|
|
|
function RecursionSentinel.remove(knownTables, value)
|
|
local sentinel = knownTables[value]
|
|
if sentinel then
|
|
sentinel.index = nil
|
|
end
|
|
knownTables[value] = nil
|
|
end
|
|
|
|
local function cloneNonRecursive(inValue, sentinels)
|
|
local value = LogPlain.getValue(inValue)
|
|
if type(value) == "table" then
|
|
local wrapLogPlain = LogPlain.is(inValue) and function(v) return LogPlain.of(v, inValue.deep) end or function(v) return v end
|
|
if RecursionSentinel.isKnown(sentinels, value) then
|
|
return wrapLogPlain(RecursionSentinel.getSentinel(sentinels, value))
|
|
end
|
|
|
|
local sentinel = RecursionSentinel.getSentinel(sentinels, value)
|
|
local clone = {}
|
|
for i,v in ipairs(value) do
|
|
clone[i] = cloneNonRecursive(v, sentinels)
|
|
end
|
|
for k,v in pairs(value) do
|
|
clone[cloneNonRecursive(k, sentinels)] = cloneNonRecursive(v, sentinels)
|
|
end
|
|
RecursionSentinel.remove(sentinels, sentinel.table)
|
|
|
|
sentinel.value = clone
|
|
return wrapLogPlain(sentinel.value)
|
|
else
|
|
return inValue
|
|
end
|
|
end
|
|
|
|
local G_ = {}
|
|
for k,v in pairs(_G) do
|
|
G_[v] = k
|
|
end
|
|
|
|
local function _simpleStringify(inValue, builder, ignoreGlobals, skipFunctions, plain)
|
|
local value = LogPlain.getValue(inValue)
|
|
local isPlain = plain or LogPlain.is(inValue)
|
|
local isDeep = plain or LogPlain.isDeep(inValue)
|
|
if type(value) == "table" then
|
|
if not isPlain then
|
|
table.insert(builder, "<")
|
|
end
|
|
if ignoreGlobals and G_[value] ~= nil then
|
|
table.insert(builder, "_G.")
|
|
table.insert(builder, G_[value])
|
|
elseif RecursionSentinel.isSentinel(value) then
|
|
if isPlain then
|
|
table.insert(builder, "<recurse_")
|
|
table.insert(builder, tostring(value.index))
|
|
else
|
|
table.insert(builder, "recurse ")
|
|
table.insert(builder, tostring(value.value))
|
|
end
|
|
table.insert(builder, ">")
|
|
else
|
|
if not isPlain then
|
|
table.insert(builder, tostring(value))
|
|
table.insert(builder, ">")
|
|
end
|
|
table.insert(builder, "{")
|
|
local first = true
|
|
for _,v in ipairs(value) do
|
|
if not first then
|
|
table.insert(builder, ",")
|
|
else
|
|
first = false
|
|
end
|
|
_simpleStringify(v, builder, ignoreGlobals, skipFunctions, isDeep)
|
|
end
|
|
first = #value == 0
|
|
local len = #value
|
|
for k,v in pairs(value) do
|
|
-- Array elements already printed
|
|
if type(k) == "number" and k <= len then
|
|
goto continue
|
|
end
|
|
if not first then
|
|
table.insert(builder, ",")
|
|
else
|
|
first = false
|
|
end
|
|
-- String key values are always plain-printed
|
|
_simpleStringify(k, builder, ignoreGlobals, skipFunctions, isDeep or type(k) == "string")
|
|
table.insert(builder, "=")
|
|
_simpleStringify(v, builder, ignoreGlobals, skipFunctions, isDeep)
|
|
::continue::
|
|
end
|
|
table.insert(builder, "}")
|
|
end
|
|
elseif type(value) == "string" then
|
|
if not isPlain then
|
|
table.insert(builder, "\"")
|
|
end
|
|
table.insert(builder, tostring(value))
|
|
if not isPlain then
|
|
table.insert(builder, "\"")
|
|
end
|
|
elseif type(value) == "function" then
|
|
if not skipFunctions then
|
|
local info = debug.getinfo(value, "u")
|
|
if not isPlain then
|
|
table.insert(builder, tostring(value))
|
|
end
|
|
table.insert(builder, "(")
|
|
table.insert(builder, tostring(info.nparams))
|
|
if info.isvararg then
|
|
table.insert(builder, "*")
|
|
end
|
|
table.insert(builder, ")")
|
|
end
|
|
elseif not skipFunctions or type(value) ~= "function" then
|
|
table.insert(builder, tostring(value))
|
|
end
|
|
end
|
|
|
|
function Logging.deepStringify(value)
|
|
local collect = {}
|
|
_simpleStringify(cloneNonRecursive(value, {}), collect, false, false)
|
|
return table.concat(collect)
|
|
end
|
|
|
|
function Logger:_doPrint(level, message, ...)
|
|
local result = { tostring(LogLevel[level]), Logging.deepStringify(message) }
|
|
for _,v in ipairs({...}) do
|
|
table.insert(result, Logging.deepStringify(v))
|
|
end
|
|
self.output(table.concat(result, " "), level)
|
|
end
|
|
|
|
function Logger:trace(message, ...)
|
|
self:_doPrint(LogLevel.TRACE, message, ...)
|
|
end
|
|
|
|
function Logger:debug(message, ...)
|
|
self:_doPrint(LogLevel.DEBUG, message, ...)
|
|
end
|
|
|
|
function Logger:info(message, ...)
|
|
self:_doPrint(LogLevel.INFO, message, ...)
|
|
end
|
|
|
|
function Logger:warn(message, ...)
|
|
self:_doPrint(LogLevel.WARNING, message, ...)
|
|
end
|
|
|
|
function Logger:critical(message, ...)
|
|
self:_doPrint(LogLevel.CRITICAL, message, ...)
|
|
end
|
|
|
|
function Logger:error(message, ...)
|
|
self:_doPrint(LogLevel.ERROR, message, ...)
|
|
end
|
|
|
|
function Logger:setOutput(...)
|
|
self.output = combineOutputs(...)
|
|
end
|
|
|
|
function Logger.plain(value, deep)
|
|
return LogPlain.of(value, deep)
|
|
end
|
|
|
|
Logging.Logger = Logger
|
|
Logging.LogLevel = LogLevel
|
|
|
|
function Logging.getGlobalLogger()
|
|
if _G._GLOBAL_LOGGER == nil then
|
|
_G._GLOBAL_LOGGER = Logger:new{ output = function() end }
|
|
end
|
|
return _G._GLOBAL_LOGGER
|
|
end
|
|
|
|
function Logging.firstLoad(...)
|
|
Logging.resetAll()
|
|
local logger = Logging.getGlobalLogger()
|
|
logger:setOutput(...)
|
|
return logger
|
|
end
|
|
|
|
Logging.OUTPUTS = {}
|
|
function Logging.OUTPUTS.file(name, logLevel)
|
|
logLevel = logLevel or LogLevel._DEFAULT
|
|
|
|
local doWrite = function(file, text)
|
|
file.write(text)
|
|
file.write("\n")
|
|
file.flush()
|
|
end
|
|
local file = fs.open(name, "w+")
|
|
return function(text, level)
|
|
if level < logLevel then
|
|
return
|
|
end
|
|
|
|
if not pcall(doWrite, file, text) then
|
|
local dRes, dRet = pcall(fs.delete, name)
|
|
if not dRes then
|
|
print("Error deleting logfile ("..name.."): "..tostring(dRet))
|
|
else
|
|
file = fs.open(name, "w+")
|
|
local wRes, wRet = pcall(doWrite, file, text)
|
|
if not wRes then
|
|
print("Error writing to logfile ("..name.."): "..tostring(wRet))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Logging.OUTPUTS.transmit(modem, channel, logLevel)
|
|
logLevel = logLevel or LogLevel._DEFAULT
|
|
end
|
|
|
|
function Logging.OUTPUTS.stdout(logLevel)
|
|
logLevel = logLevel or LogLevel._DEFAULT
|
|
|
|
return function(text, level)
|
|
if level < logLevel then
|
|
return
|
|
end
|
|
|
|
print(text)
|
|
end
|
|
end
|
|
|
|
function Logging.resetAll()
|
|
_G._GLOBAL_LOGGER = nil
|
|
end
|
|
|
|
return Logging |