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 _simpleStringify(cloneNonRecursive(v, {}), result) 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