added logging support
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
--- love_file_handler.lua — LÖVE2D file handler for logger.lua
|
||||
--
|
||||
-- Uses love.filesystem to write logs into LÖVE's save directory, which works
|
||||
-- correctly on every platform LÖVE supports (Windows, macOS, Linux, Android).
|
||||
--
|
||||
-- Requires:
|
||||
-- logger.lua in the same directory (or on package.path)
|
||||
-- LÖVE2D 11.x+ (love.filesystem must be available)
|
||||
--
|
||||
-- Usage inside main.lua / conf.lua:
|
||||
--
|
||||
-- local Logger = require("logger")
|
||||
-- local LoveHandler = require("love_file_handler")
|
||||
--
|
||||
-- local log = Logger.new("game", {
|
||||
-- handlers = {
|
||||
-- Logger.handlers.ConsoleHandler(), -- print() fallback
|
||||
-- LoveHandler.new("logs/game.log"), -- love.filesystem
|
||||
-- },
|
||||
-- })
|
||||
--
|
||||
-- function love.load()
|
||||
-- log:info("Game started — LÖVE %s", love.getVersion())
|
||||
-- end
|
||||
|
||||
-- ── Sanity check ──────────────────────────────────────────────────────────
|
||||
|
||||
assert(love and love.filesystem,
|
||||
"love_file_handler requires the LÖVE2D runtime (love.filesystem)")
|
||||
|
||||
-- ── Module table ──────────────────────────────────────────────────────────
|
||||
|
||||
local LoveHandler = {}
|
||||
|
||||
-- ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
--- Ensure every directory component in `path` exists.
|
||||
-- love.filesystem.createDirectory is recursive on LÖVE 11+.
|
||||
local function ensure_dir(path)
|
||||
local dir = path:match("^(.*)/[^/]+$")
|
||||
if dir and dir ~= "" then
|
||||
love.filesystem.createDirectory(dir)
|
||||
end
|
||||
end
|
||||
|
||||
--- Return current file size in bytes, or 0 on error / missing file.
|
||||
local function file_size(path)
|
||||
local info = love.filesystem.getInfo(path)
|
||||
return info and info.size or 0
|
||||
end
|
||||
|
||||
--- Shift rotated files: .4→.5, .3→.4, …, base→.1
|
||||
local function rotate(path, max_files)
|
||||
for i = max_files - 1, 1, -1 do
|
||||
local src = path .. "." .. i
|
||||
local dest = path .. "." .. (i + 1)
|
||||
if love.filesystem.getInfo(src) then
|
||||
-- love.filesystem has no rename; copy + delete
|
||||
local data = love.filesystem.read(src)
|
||||
if data then
|
||||
love.filesystem.write(dest, data)
|
||||
love.filesystem.remove(src)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- move base → .1
|
||||
if love.filesystem.getInfo(path) then
|
||||
local data = love.filesystem.read(path)
|
||||
if data then
|
||||
love.filesystem.write(path .. ".1", data)
|
||||
love.filesystem.remove(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ── Constructor ───────────────────────────────────────────────────────────
|
||||
|
||||
--- Create a new LÖVE2D file handler.
|
||||
--
|
||||
-- @param path string Path inside the LÖVE save directory.
|
||||
-- Subdirectories are created automatically.
|
||||
-- Default: "logs/game.log"
|
||||
--
|
||||
-- @param opts table Optional settings:
|
||||
-- max_size (number) Rotate when the log file reaches this many bytes.
|
||||
-- Default: 1 MB (1 048 576). Set to 0 to disable.
|
||||
-- max_files (number) How many rotated files to keep. Default: 5.
|
||||
-- buffered (bool) Accumulate lines in memory and flush in batches.
|
||||
-- Reduces I/O overhead on mobile / web exports.
|
||||
-- Default: false.
|
||||
-- flush_size (number) Lines to buffer before auto-flushing (buffered mode).
|
||||
-- Default: 20.
|
||||
-- on_error (func) Called with (err_string) on any I/O error.
|
||||
-- Default: prints a warning to love.graphics console.
|
||||
--
|
||||
-- @return handler, control_table
|
||||
-- handler — function accepted by Logger:add_handler()
|
||||
-- control — table with :flush(), :close(), :path(), :size() methods
|
||||
function LoveHandler.new(path, opts)
|
||||
path = path or "logs/game.log"
|
||||
opts = opts or {}
|
||||
|
||||
local max_size = (opts.max_size == nil) and (1024 * 1024) or opts.max_size
|
||||
local max_files = opts.max_files or 5
|
||||
local buffered = opts.buffered or false
|
||||
local flush_size = opts.flush_size or 20
|
||||
local on_error = opts.on_error or function(err)
|
||||
if love.graphics and love.graphics.print then
|
||||
print("[love_file_handler] I/O error: " .. tostring(err))
|
||||
end
|
||||
end
|
||||
|
||||
-- State
|
||||
local buffer = {} -- pending lines (buffered mode)
|
||||
local closed = false
|
||||
|
||||
ensure_dir(path)
|
||||
|
||||
-- ── Internal write ────────────────────────────────────────────────────
|
||||
|
||||
local function raw_write(lines)
|
||||
if #lines == 0 then return end
|
||||
local text = table.concat(lines, "\n") .. "\n"
|
||||
|
||||
-- Rotate if needed
|
||||
if max_size and max_size > 0 and file_size(path) + #text >= max_size then
|
||||
rotate(path, max_files)
|
||||
end
|
||||
|
||||
-- Append
|
||||
local ok, err = love.filesystem.append(path, text)
|
||||
if not ok then
|
||||
on_error(tostring(err))
|
||||
end
|
||||
end
|
||||
|
||||
-- ── Public control table ──────────────────────────────────────────────
|
||||
|
||||
local control = {}
|
||||
|
||||
--- Flush buffered lines to disk immediately.
|
||||
function control:flush()
|
||||
if #buffer > 0 then
|
||||
raw_write(buffer)
|
||||
buffer = {}
|
||||
end
|
||||
end
|
||||
|
||||
--- Close the handler (flushes first). After close() the handler is a no-op.
|
||||
function control:close()
|
||||
self:flush()
|
||||
closed = true
|
||||
end
|
||||
|
||||
--- Full path as seen by love.filesystem.
|
||||
function control:path()
|
||||
return path
|
||||
end
|
||||
|
||||
--- Real filesystem path to the save directory file (for external tools).
|
||||
function control:real_path()
|
||||
local save_dir = love.filesystem.getSaveDirectory()
|
||||
return save_dir .. "/" .. path
|
||||
end
|
||||
|
||||
--- Current log file size in bytes.
|
||||
function control:size()
|
||||
return file_size(path)
|
||||
end
|
||||
|
||||
--- List rotated files that exist (e.g. {"logs/game.log.1", ...})
|
||||
function control:rotated_files()
|
||||
local result = {}
|
||||
for i = 1, max_files do
|
||||
local p = path .. "." .. i
|
||||
if love.filesystem.getInfo(p) then
|
||||
table.insert(result, p)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
--- Read the entire current log file as a string (nil on error).
|
||||
function control:read()
|
||||
self:flush()
|
||||
return love.filesystem.read(path)
|
||||
end
|
||||
|
||||
-- ── Handler function ──────────────────────────────────────────────────
|
||||
|
||||
local function handler(record)
|
||||
if closed then return end
|
||||
|
||||
if buffered then
|
||||
table.insert(buffer, record.formatted)
|
||||
if #buffer >= flush_size then
|
||||
raw_write(buffer)
|
||||
buffer = {}
|
||||
end
|
||||
else
|
||||
raw_write({ record.formatted })
|
||||
end
|
||||
end
|
||||
|
||||
return handler, control
|
||||
end
|
||||
|
||||
-- ── Convenience: hook love.quit to flush ──────────────────────────────────
|
||||
|
||||
--- Wrap love.quit so a control table's flush() is called automatically.
|
||||
-- Call once per handler after Logger.new(); safe to call multiple times.
|
||||
--
|
||||
-- @param control The control table returned by LoveHandler.new()
|
||||
function LoveHandler.auto_flush_on_quit(control)
|
||||
local original_quit = love.quit or function() end
|
||||
love.quit = function()
|
||||
control:flush()
|
||||
return original_quit()
|
||||
end
|
||||
end
|
||||
|
||||
return LoveHandler
|
||||
Reference in New Issue
Block a user