local fs = require 'bee.filesystem' local time = require 'bee.time' local monotonic = time.monotonic local osDate = os.date local ioOpen = io.open local tablePack = table.pack local tableConcat = table.concat local tostring = tostring local debugTraceBack = debug.traceback local mathModf = math.modf local debugGetInfo = debug.getinfo local ioStdErr = io.stderr local m = {} m.file = nil m.startTime = time.time() - monotonic() m.size = 0 m.maxSize = 100 * 1024 * 1024 m.level = 'info' m.levelMap = { ['trace'] = 1, ['debug'] = 2, ['info'] = 3, ['warn'] = 4, ['error'] = 5, } local function trimSrc(src) if src:sub(1, 1) == '@' then src = src:sub(2) end return src end local function init_log_file() if not m.file then m.file = ioOpen(m.path, 'w') if not m.file then return end m.file:write('') m.file:close() m.file = ioOpen(m.path, 'ab') if not m.file then return end m.file:setvbuf 'no' end end local function pushLog(level, ...) if not m.path then return end local t = tablePack(...) for i = 1, t.n do t[i] = tostring(t[i]) end local str = tableConcat(t, '\t', 1, t.n) if level == 'error' then str = str .. '\n' .. debugTraceBack(nil, 3) end local info = debugGetInfo(3, 'Sl') local text = m.raw(0, level, str, info.source, info.currentline, monotonic()) return text end function m.trace(...) pushLog('trace', ...) end function m.debug(...) pushLog('debug', ...) end function m.info(...) pushLog('info', ...) end function m.warn(...) pushLog('warn', ...) end function m.error(...) return pushLog('error', ...) end function m.raw(thd, level, msg, source, currentline, clock) if m.levelMap[level] < (m.levelMap[m.level] or m.levelMap['info']) then return msg end if level == 'error' then ioStdErr:write(msg .. '\n') if not m.firstError then m.firstError = msg end end if m.size > m.maxSize then return msg end init_log_file() local sec, ms = mathModf((m.startTime + clock) / 1000) local timestr = osDate('%H:%M:%S', sec) local agl = '' if #level < 5 then agl = (' '):rep(5 - #level) end local buf if currentline == -1 then buf = ('[%s.%03.f][%s]%s[#%d]: %s\n'):format(timestr, ms * 1000, level, agl, thd, msg) else buf = ('[%s.%03.f][%s]%s[#%d:%s:%s]: %s\n'):format(timestr, ms * 1000, level, agl, thd, trimSrc(source), currentline, msg) end m.size = m.size + #buf if m.file then if m.size > m.maxSize then m.file:write(buf:sub(1, m.size - m.maxSize)) m.file:write('[REACH MAX SIZE]') else m.file:write(buf) end end if log.print then print(buf) end return buf end function m.init(root, path) local lastBuf if m.file then m.file:close() m.file = nil local file = ioOpen(m.path, 'rb') if file then lastBuf = file:read(m.maxSize) file:close() end end m.path = path:string() m.prefixLen = #root:string() m.size = 0 pcall(function () if not fs.exists(path:parent_path()) then fs.create_directories(path:parent_path()) end end) if lastBuf then init_log_file() if m.file then m.file:write(lastBuf) m.size = m.size + #lastBuf end end end return m