Page MenuHomeFreeBSD

D53316.id164947.diff
No OneTemporary

D53316.id164947.diff

diff --git a/Mk/LuaScripts/logging.lua b/Mk/LuaScripts/logging.lua
new file mode 100644
--- /dev/null
+++ b/Mk/LuaScripts/logging.lua
@@ -0,0 +1,583 @@
+-------------------------------------------------------------------------------
+-- SPDX-License-Identifier: MIT
+-- SPDX-License-Identifier: BSD-2-Clause
+--
+-- This is based on LunarModules logger and it's ported for be used with
+-- FreeBSD FLua version 5.4
+--
+-- @author Danilo Tuler (tuler@ideais.com.br)
+-- @author Andre Carregal (info@keplerproject.org)
+-- @author Thiago Costa Ponte (thiago@ideais.com.br)
+--
+-- @copyright 2004-2010 Kepler Project, 2011-2013 Neopallium, 2020-2023 Thijs Schreijer
+-- @copyright (c) 2025 The FreeBSD Foundation
+--
+-- Portions of this software were developed by Tuukka Pasanen <tuukka.pasanen@ilmi.fi>
+-- under sponsorship from the FreeBSD Foundation
+--
+-- Basic usage:
+-- local Logging = require "logging"
+-- local logger = Logging.new(nil, "INFO", true)
+-- logger:log(logger.INFO, "This is INFO level and should be printed")
+--
+-- logger:setLevel(logger.WARN)
+-- logger:info("This is INFO level and should not be printed")
+-- logger:warn("This is WARN level and should be printed")
+-- logger:error("This is ERROR level and should be printed")
+--
+-- local table = { a = 1, b = 2 }
+-- logger:debug(table)
+-- logger:info("val1='%s', val2=%d", "string value", 1234)
+--
+-- Beyond basic usage:
+-- local Logging = require "logging"
+-- local logger = Logging.new(nil, "DEBUG", true)
+--
+-- local function log_callback(val1, val2)
+-- return string.format("val1='%s', val2=%d", val1, val2)
+-- end
+--
+-- logger:debug(log_callback, "string value", 1234)
+--
+-- logger:setLevel (logger.INFO)
+-- local debug_print = logger:getPrint(logger.INFO)
+-- debug_print("hello\nthere!")
+-------------------------------------------------------------------------------
+
+local _tostring = tostring
+
+local select = select
+local error = error
+local format = string.format
+local floor = math.floor
+local pairs = pairs
+local ipairs = ipairs
+
+local logging = {
+ -- Meta information
+ _COPYRIGHT = "Copyright (C) 2004-2010 Kepler Project, 2011-2013 Neopallium, 2020-2023 Thijs Schreijer, 2025 The FreeBSD Foundation",
+ _DESCRIPTION = "A simple Lua logging API for FreeBSD ports",
+ _VERSION = "FreeBSD Ports Logger 1.0.0",
+}
+
+local LEVELS = { "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "OFF" }
+local MAX_LEVELS = #LEVELS
+for i, level in ipairs(LEVELS) do
+ LEVELS[level] = i
+ logging[level] = level
+end
+
+local defaultLevel = LEVELS[1]
+local defaultLogPattern = "%date %level %message\n"
+local defaultLogPatterns = nil
+local defaultTimestampPattern = nil
+local defaultLogger = nil
+
+local function rewrite_stacktrace()
+ -- prettify stack-trace, remove lualogging entries and reformat to 1 line
+ local result = ""
+ local trace = debug and debug.traceback() or ""
+ for entry in trace:gmatch("%s*(.-)\n") do
+ if entry:match("%:%d+%:") and not entry:find("logging.lua") then
+ result = result .. " | " .. entry
+ end
+ end
+ return result
+end
+
+-- private log function, with support for formating a complex log message.
+local function LOG_MSG(self, level, fmt, ...)
+ local f_type = type(fmt)
+ if f_type == "string" then
+ if select("#", ...) > 0 then
+ local status, msg = pcall(format, fmt, ...)
+ if status then
+ return self:append(level, msg)
+ else
+ return self:append(level, "Error formatting log message: " .. msg .. rewrite_stacktrace())
+ end
+ else
+ -- only a single string, no formating needed.
+ return self:append(level, fmt)
+ end
+ elseif f_type == "function" then
+ -- fmt should be a callable function which returns the message to log
+ return self:append(level, fmt(...))
+ end
+ -- fmt is not a string and not a function, just call tostring() on it.
+ return self:append(level, logging.tostring(fmt))
+end
+
+-- do nothing function for disabled levels.
+local function disable_level() end
+
+-- a generic print function that prints to the log
+local function print_to_log(logger, level, ...)
+ local args = { n = select("#", ...), ... }
+ for i = 1, args.n do
+ args[i] = _tostring(args[i])
+ end
+ args = table.concat(args, " ") .. "\n"
+ for line in args:gmatch("(.-)\n") do
+ logger:log(level, line)
+ end
+end
+
+-- improved assertion function.
+local function assert(exp, ...)
+ -- if exp is true, we are finished so don't do any processing of the parameters
+ if exp then
+ return exp, ...
+ end
+ -- assertion failed, raise error
+ error(format(...), 2)
+end
+
+-------------------------------------------------------------------------------
+-- Default appender if custom one is not provided
+-- Color table:
+-- OFF Console color
+-- TRACE Blue
+-- DEBUG Bold White
+-- INFO green
+-- WARN Yellow
+-- ERROR Underline Red
+-- FATAL Bold Purple
+-- @param self Logger object
+-- @param level Level of log
+-- @param message Debug message
+-- @return Always true that everything went fine
+-------------------------------------------------------------------------------
+local function default_appender(self, level, message)
+ local modes = {
+ OFF = "\27[0m",
+ TRACE = "\27[0;34m",
+ DEBUG = "\27[1;37m",
+ INFO = "\27[0;32m",
+ WARN = "\27[1;33m",
+ ERROR = "\27[4;31m",
+ FATAL = "\27[1;35m",
+ }
+
+ io.write(self.usecolor and modes[level] or "")
+ io.write(level)
+ io.write(self.usecolor and modes["OFF"] or "")
+ print("", message)
+ return true
+end
+
+-------------------------------------------------------------------------------
+-- Creates a new logger object
+-- @param append Function used by the logger to append a message with a
+-- log-level to the log stream.
+-- @param startLevel log-level to start with
+-- @param colorOutput Use colors in output (true means color output)
+-- @return Table representing the new logger object.
+-- @return String if there was any error setting the custom levels if provided
+-------------------------------------------------------------------------------
+function logging.new(append, startLevel, colorOutput)
+ if type(append) ~= "function" then
+ append = default_appender
+ end
+
+ assert(type(append) == "function", "Appender must be a function, got: %s.", type(append))
+
+ startLevel = startLevel or defaultLevel
+
+ isColor = true
+
+ if type(colorOutput) == "boolean" and colorOutput == false then
+ isColor = false
+ end
+
+ assert(LEVELS[startLevel], "startLevel must be a valid log-level constant if given")
+
+ local LEVEL_FUNCS = {}
+
+ local logger = {}
+ logger.append = append
+
+ -------------------------------------------------------------------------------
+ -- set log level
+ -- @param self Current logger object
+ -- @param level log level string (TRACE,DEBUG,INFO,WARN,ERROR or FATAL)
+ -------------------------------------------------------------------------------
+ logger.setLevel = function(self, level)
+ local order = LEVELS[level]
+ assert(order, "undefined level '%s'", _tostring(level))
+ local old_level = self.level
+ self.level = level
+ self.level_order = order
+ -- enable/disable levels
+ for i = 1, MAX_LEVELS do
+ local name = LEVELS[i]:lower()
+ if i >= order and i ~= MAX_LEVELS then
+ self[name] = LEVEL_FUNCS[i]
+ else
+ self[name] = disable_level
+ end
+ end
+
+ if old_level and old_level ~= level then
+ self:log(LEVELS[1], "Logger: changing loglevel from %s to %s", old_level, level)
+ end
+ end
+
+ -------------------------------------------------------------------------------
+ -- Whether to use color output
+ -- @param self Current logger object
+ -- @param useColor true use ANSI color output false don't
+ -------------------------------------------------------------------------------
+ logger.setUseColor = function(self, useColor)
+ self.usecolor = useColor
+ end
+
+ -------------------------------------------------------------------------------
+ -- Log function logger:log(logger.DEBUG, "this is debug string")
+ -- @param self Current object
+ -- @param level level log level (TRACE,DEBUG,INFO,WARN,ERROR or FATAL)
+ -- @param ... Other parameters
+ -------------------------------------------------------------------------------
+ logger.log = function(self, level, ...)
+ local order = LEVELS[level]
+ assert(order, "undefined level `%s'", _tostring(level))
+
+ if order < self.level_order then
+ return
+ end
+ return LOG_MSG(self, level, ...)
+ end
+
+ -------------------------------------------------------------------------------
+ -- set log level
+ -- @param self Current object
+ -- @param level log level string (TRACE,DEBUG,INFO,WARN,ERROR,FATAL)
+ -------------------------------------------------------------------------------
+ -- a print function generator
+ logger.getPrint = function(self, level)
+ local order = LEVELS[level]
+ assert(order, "undefined level `%s'", _tostring(level))
+ return function(...)
+ if order >= self.level_order then
+ print_to_log(self, level, ...)
+ end
+ end
+ end
+
+ -- create the proxy functions for each log level.
+ for i = 1, MAX_LEVELS do
+ local level = LEVELS[i]
+ if logger[level:lower()] then
+ return nil,
+ "'"
+ .. level
+ .. "' is not a proper level name since there is already a property '"
+ .. level:lower()
+ .. "'"
+ end
+ LEVEL_FUNCS[i] = function(self, ...)
+ -- no level checking needed here, this function will only be called if it's level is active.
+ return LOG_MSG(self, level, ...)
+ end
+ end
+
+ -- insert log level constants
+ for i = 1, MAX_LEVELS do
+ logger[LEVELS[i]] = LEVELS[i]
+ end
+
+ -- initialize log level.
+ logger:setLevel(startLevel)
+ logger:setUseColor(isColor)
+ return logger
+end
+
+local sourceDebugLevel = 1 -- this will be set dynamically below
+local getDebugInfoLine = debug and "local info = debug.getinfo(%d)"
+ or "local info = { short_src = '?', currentline = -1 }"
+
+-------------------------------------------------------------------------------
+-- Prepares the log message
+-------------------------------------------------------------------------------
+function logging.compilePattern(pattern)
+ pattern = string.format("%q", pattern)
+
+ -- replace %source by its components first
+ pattern = pattern:gsub("%%source", "%%file:%%line in function '%%function'")
+
+ local placeholders = {
+ ["date"] = false,
+ ["level"] = false,
+ ["message"] = false,
+ -- truthy: requires debug info to be fetched first
+ ["file"] = "info.short_src",
+ ["line"] = "tostring(info.currentline)",
+ ["function"] = '(info.name or "unknown function")',
+ }
+ local inject_info = false
+ for placeholder, needs_info in pairs(placeholders) do
+ local count
+ pattern, count = pattern:gsub("%%" .. placeholder, '"..' .. (needs_info or placeholder) .. '.."')
+ inject_info = inject_info or (count > 0 and needs_info)
+ end
+ -- cleanup start & end
+ if pattern:sub(1, 4) == '""..' then
+ pattern = pattern:sub(5, -1)
+ end
+ if pattern:sub(-4, -1) == '..""' then
+ pattern = pattern:sub(1, -5)
+ end
+ -- build function
+ local func = [[
+ return function(date, level, message)
+ ]] .. (inject_info and getDebugInfoLine:format(sourceDebugLevel) or "") .. [[
+
+ return ]] .. pattern .. [[
+
+ end]]
+
+ return (loadstring or load)(func, "lualogging_generated_formatter")()
+end
+
+local clearCompiledCache
+do
+ local cache = setmetatable({}, {
+ __index = function(self, pattern)
+ -- pattern wasn't found in cache, compile now, and cache format-function
+ self[pattern] = logging.compilePattern(pattern)
+ return self[pattern]
+ end,
+ })
+
+ function clearCompiledCache()
+ for k in pairs(cache) do
+ cache[k] = nil
+ end
+ end
+
+ function logging.prepareLogMsg(lpattern, dpattern, level, message)
+ return cache[lpattern](dpattern, level, message)
+ end
+end
+
+-------------------------------------------------------------------------------
+-- os.date replacement with milliseconds if supported
+-- ms placeholder = %q or %xq (where x is number of decimals)
+-------------------------------------------------------------------------------
+
+do
+ local gettime = os.time
+ local ok, socket = pcall(require, "socket") -- load luasocket if available
+ if ok then
+ gettime = socket.gettime
+ end
+
+ -- use a pattern cache to know if we even need ms to format
+ local patternCache = setmetatable({}, {
+ __index = function(self, patt)
+ local placeholder = patt:match("(%%%d*q)")
+ if not placeholder then
+ self[patt] = false
+ return false
+ end
+
+ local size = tonumber(placeholder:sub(2, -2)) or 3
+ assert(size >= 1 and size <= 6, "millisecond format %q quantifier range is 1 to 6")
+ self[patt] = ("0"):rep(size) -- a string to grab trailing "0"'s from
+ return self[patt]
+ end,
+ })
+ function logging.date(fmt, t)
+ fmt = fmt or "%c"
+ t = t or gettime()
+ local pad = patternCache[fmt]
+ local ms
+ if pad then
+ -- ms required
+ ms = math.fmod(t, 1)
+ local mss = (tostring(ms) .. pad):sub(3, -1)
+
+ fmt = fmt:gsub("(%%%d*q)", function(placeholder)
+ return mss:sub(1, #pad)
+ end)
+ end
+
+ local res, err = os.date(fmt, floor(t)) -- 5.3+ requires t to be an integer
+ if type(res) == "table" then
+ res.secf = ms or math.fmod(t, 1)
+ end
+ return res, err
+ end
+end
+
+-------------------------------------------------------------------------------
+-- Converts a Lua value to a string
+--
+-- Converts Table fields in alphabetical order
+-------------------------------------------------------------------------------
+local function tostring(value)
+ local str = ""
+
+ if type(value) ~= "table" then
+ if type(value) == "string" then
+ str = string.format("%q", value)
+ else
+ str = _tostring(value)
+ end
+ else
+ local auxTable = {}
+ for key in pairs(value) do
+ if tonumber(key) ~= key then
+ table.insert(auxTable, key)
+ else
+ table.insert(auxTable, tostring(key))
+ end
+ end
+ table.sort(auxTable)
+
+ str = str .. "{"
+ local separator = ""
+ local entry
+ for _, fieldName in ipairs(auxTable) do
+ if (tonumber(fieldName)) and (tonumber(fieldName) > 0) then
+ entry = tostring(value[tonumber(fieldName)])
+ else
+ entry = fieldName .. " = " .. tostring(value[fieldName])
+ end
+ str = str .. separator .. entry
+ separator = ", "
+ end
+ str = str .. "}"
+ end
+ return str
+end
+
+logging.tostring = tostring
+
+-------------------------------------------------------------------------------
+-- Application level defaults
+-------------------------------------------------------------------------------
+function logging.defaultLogPatterns(patt)
+ if patt then
+ if type(patt) == "string" then
+ patt = logging.buildLogPatterns({}, patt)
+ end
+ assert(type(patt) == "table", "logPatterns must be a string or a table, got: %s", type(patt))
+ for _, level in ipairs(LEVELS) do
+ if level ~= "OFF" then
+ assert(
+ type(patt[level]) == "string",
+ "the patterns contains a '%s' value (instead of a string) for level '%s'",
+ type(patt[level]),
+ level
+ )
+ end
+ end
+ defaultLogPatterns = patt
+ end
+ return defaultLogPatterns
+end
+
+function logging.defaultTimestampPattern(patt)
+ if patt then
+ if type(patt) ~= "string" then
+ error("timestampPattern must be a string", 2)
+ end
+ defaultTimestampPattern = patt
+ end
+ return defaultTimestampPattern
+end
+
+function logging.defaultLevel(level)
+ if level then
+ if not LEVELS[level] then
+ assert(LEVELS[level], "undefined level '%s'", _tostring(level))
+ end
+ defaultLevel = level
+ end
+ return defaultLevel
+end
+
+function logging.defaultLogger(logger)
+ if logger then
+ -- check getPrint to protect against accidental call using colon-notation
+ if type(logger) ~= "table" or type(logger.getPrint) ~= "function" then
+ error("expected a logger object", 2)
+ end
+ defaultLogger = logger
+ end
+
+ if not defaultLogger then
+ -- no default logger yet, go create it, using the current defaults
+ defaultLogger = require("logging.console")({ destination = "stderr" })
+ end
+
+ return defaultLogger
+end
+
+--- Returns a table of patterns, indexed by loglevel.
+-- @param patterns (table, optional) table containing logPattern strings per level, defaults to `{}`
+-- @param default (string, optional) the logPattern to be used for levels not yet present in 'patterns'.
+-- @return table, with a logPattern for every log-level constant
+function logging.buildLogPatterns(patterns, default)
+ patterns = patterns or {}
+ assert(
+ -- type(default) == "string" or type(default) == "nil",
+ "expected default logPattern (2nd argument) to be a string or nil, got: %s",
+ tostring(default)
+ )
+ --assert(
+ -- typeof(patterns) == "table",
+ -- "expected patterns (1st argument) to be a table or nil, got: %s",
+ -- tostring(patterns)
+ --)
+ local target = {}
+ for _, level in ipairs(LEVELS) do
+ if level ~= "OFF" then
+ target[level] = patterns[level] or default or defaultLogPatterns[level]
+ end
+ end
+ return target
+end
+
+defaultLogPatterns = logging.buildLogPatterns({}, defaultLogPattern)
+
+-------------------------------------------------------------------------------
+-- dynamically detect proper source debug level, since this can vary by Lua versions
+-------------------------------------------------------------------------------
+if debug then
+ local detection_logger, test_msg
+
+ local function detect_func()
+ detection_logger:debug("message")
+ end -- This function MUST be on a single line!!
+ local detect_func_info = debug.getinfo(detect_func)
+ local detect_func_match = detect_func_info.short_src .. ":" .. tostring(detect_func_info.linedefined or -999)
+
+ detection_logger = logging.new(function(self, level, message)
+ test_msg = logging.prepareLogMsg("%source", "", level, message)
+ end)
+
+ while true do
+ if not pcall(detect_func) then
+ -- cannot detect debug level, so set the function to fetch debug info to
+ -- return a table that always returns "na" for each lookup
+ getDebugInfoLine = "local info = setmetatable({}, { __index = function() return 'na' end })"
+ break
+ end
+
+ if test_msg:find(detect_func_match, 1, true) then
+ break -- found correct level, done
+ end
+ -- move to next level
+ sourceDebugLevel = sourceDebugLevel + 1
+ clearCompiledCache()
+ end
+end
+
+if _VERSION < "Lua 5.2" then
+ -- still create 'logging' global for Lua versions < 5.2
+ _G.logging = logging
+end
+
+return logging

File Metadata

Mime Type
text/plain
Expires
Fri, Jun 26, 6:46 AM (13 h, 35 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
34336205
Default Alt Text
D53316.id164947.diff (18 KB)

Event Timeline