Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F160514680
D53316.id164947.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
18 KB
Referenced Files
None
Subscribers
None
D53316.id164947.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D53316: Add Lua Logging module to FreeBSD ports tree and introduce Lua functions and modules to ports
Attached
Detach File
Event Timeline
Log In to Comment