cc-utilities/logging.lua
2024-10-14 06:06:07 +02:00

206 lines
4.7 KiB
Lua

local Logging = {}
local LogLevel = {
TRACE = 0,
DEBUG = 1,
INFO = 2,
WARNING = 3,
CRITICAL = 4,
ERROR = 5
}
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 = {}
Logger.__index = Logger
function Logger:new(o)
local logger = {}
local output = print
if type(o) == "table" then
if type(o.level) == "number" and LogLevel.isValid(o.level) then
logger.level = o.level
end
if type(o.output) == "function" then
output = o.output
end
end
logger.output = output
setmetatable(logger, self)
logger.__index = logger
return logger
end
local RecursionSentinel = {}
function RecursionSentinel.make(table)
local obj = { table = table }
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)
local sentinel = knownTables[value]
if sentinel == nil then
sentinel = knownTables[value]
knownTables[value] = RecursionSentinel.make(value)
end
return sentinel
end
function RecursionSentinel.add(knownTables, value)
local sentinel = RecursionSentinel.make(value)
knownTables[value] = sentinel
return sentinel
end
function RecursionSentinel.remove(knownTables, value)
knownTables[value] = nil
end
local function cloneNonRecursive(value, sentinels)
if type(value) == "table" then
if RecursionSentinel.isKnown(sentinels, value) then
return RecursionSentinel.getSentinel(sentinels, value)
end
local clone = {}
local sentinel = RecursionSentinel.add(sentinels, value)
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, value)
sentinel.value = clone
return sentinel.value
else
return value
end
end
local function _simpleStringify(value, builder)
if type(value) == "table" then
table.insert(builder, "<")
if RecursionSentinel.isSentinel(value) then
table.insert(builder, "recurse ")
table.insert(builder, tostring(value.value))
else
table.insert(builder, tostring(value))
table.insert(builder, ">{")
local first = true
for i,v in ipairs(value) do
if not first then
table.insert(builder, ",")
else
first = false
end
_simpleStringify(v, builder)
end
first = #value == 0
for k,v in pairs(value) do
if not first then
table.insert(builder, ",")
else
first = false
end
_simpleStringify(k, builder)
table.insert(builder, "=")
_simpleStringify(v, builder)
end
table.insert(builder, "}")
end
table.insert(builder, ">")
else
local isString = type(value) == "string"
if isString then
table.insert(builder, "\"")
end
table.insert(builder, tostring(value))
if isString then
table.insert(builder, "\"")
end
end
end
function Logger:_doPrint(level, message, ...)
if LogLevel.isGreaterOrEqual(level, self.level) then
local result = { tostring(LogLevel[level]), message }
for _,v in ipairs({...}) do
local collect = {}
_simpleStringify(cloneNonRecursive(v, {}), collect)
table.insert(result, table.concat(collect))
end
self.output(table.concat(result, " "))
end
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(output)
self.output = output
end
function Logger:setLevel(level)
if LogLevel.isValid(level) then
self.level = LogLevel.asNumber(level)
end
end
Logging.Logger = Logger
Logging.LogLevel = LogLevel
function Logging.getGlobalLogger()
if _GLOBAL_LOGGER == nil then
_GLOBAL_LOGGER = Logger:new{ level = LogLevel.DEBUG, output = print }
end
return _GLOBAL_LOGGER
end
return Logging