diff --git a/Makefile b/Makefile index c89fe98..0b522a3 100644 --- a/Makefile +++ b/Makefile @@ -116,9 +116,6 @@ define Package/wifibox/install $(INSTALL_BIN) $(WIFIBOX_BASE_DIR)/script/d3d-updater.lua $(1)/$(TGT_LUA_DIR_SUFFIX)/script $(LN) -s /$(TGT_LUA_DIR_SUFFIX)/script/d3d-updater.lua $(1)/bin/d3d-updater - $(CP) $(WIFIBOX_BASE_DIR)/script/loglite-filters.lua $(1)/root/ - $(INSTALL_BIN) $(WIFIBOX_BASE_DIR)/script/loglite.lua $(1)/$(TGT_LUA_DIR_SUFFIX)/script - $(LN) -s /$(TGT_LUA_DIR_SUFFIX)/script/loglite.lua $(1)/bin/loglite $(INSTALL_BIN) $(WIFIBOX_BASE_DIR)/script/wifibox_init $(1)/etc/init.d/wifibox $(INSTALL_BIN) $(WIFIBOX_BASE_DIR)/script/dhcpcheck_init $(1)/etc/init.d/dhcpcheck $(INSTALL_BIN) $(WIFIBOX_BASE_DIR)/script/d3dapi $(1)/$(TGT_LUA_DIR_SUFFIX)/script diff --git a/license-spec.lua b/license-spec.lua index 7ce5907..3f451d5 100644 --- a/license-spec.lua +++ b/license-spec.lua @@ -1,6 +1,6 @@ local M = { BASE_PATH = 'src', - EXCLUDE_FILES = { 'src/util/JSON.lua', 'src/util/urlcode.lua', 'src/script/loglite%-filters.lua' }, + EXCLUDE_FILES = { 'src/util/JSON.lua', 'src/util/urlcode.lua' }, PROCESS_FILES = { ['src/[^/]*%.lua'] = 'lua', ['src/network/[^/]*%.lua'] = 'lua', diff --git a/post-install.sh b/post-install.sh index 1e20973..af32742 100644 --- a/post-install.sh +++ b/post-install.sh @@ -61,6 +61,7 @@ if [ $? -gt 0 ]; then alias encore='ulimit -c unlimited' alias wopkg='opkg -f /usr/share/lua/wifibox/opkg.conf' + # Convenience aliases for the loglite script (separate package) alias tailfw='loglite /tmp/wifibox.log firmware' tailp3d() { logfile=/tmp/print3d-ttyACM0.log diff --git a/src/script/README-loglite.md b/src/script/README-loglite.md deleted file mode 100644 index ed2b1c6..0000000 --- a/src/script/README-loglite.md +++ /dev/null @@ -1,92 +0,0 @@ -## Loglite - -The loglite script allows coloring and filtering of log files by specifying certain patterns and associating directives to them. These mainly specify colors but additionally, (non-)matched lines can be deleted from output and also all output lines can be numbered. - - -### Usage - -The script can follow an existing log file (comparable to `tail -f`), or it can follow its standard input. A file to follow is always specified as the first argument and a filter set name as the second (use '-' as file name to read from standard input). Details on filter sets can be found below. If no filter set is mentioned on the command-line, the script will attempt to use one named 'default'. - -* Example following an existing log file using a filter set named 'example': -`./loglite.lua print3d.log example`. -* Example using standard input, to filter/view a whole log file, with a filter set named 'serial' (note the '-' as file name): -`cat print3d-ttyACM0.log | ./loglite.lua - serial` -* Example using standard input, to capture both output streams from `print3d`, with a filter set named 'example' (note the '-' as file name): -`./print3d -V 2>&1 | ./loglite.lua - example`. - -#### On WiFi-Box -Loglite is already installed since version 0.10.10 as `loglite`. -Check `/root/.profile` for handy aliases like `tailfw` and `tailp3d`. - -### Filter sets - -The script looks for filter sets in the file '$HOME/loglite-filters.lua'. It looks like this: - -``` lua -local M = {} - -M.default = { - ['options'] = { mode = 'keep', count = 'none' }, - ['patterns'] = { - ['%(error%)'] = 'red', - ['%(warning%)'] = 'yellow', - ['%(bulk%)'] = 'bold,black' - } -} - -M.specialization = { - ['parent'] = 'default', - ['options'] = { mode = 'delete' } - ['patterns'] = { - ['setState%(%)'] = 'bblue,_nodelete' - } -} - -return M -``` - -Here, the declaration and returning of `M` is required for the loglite script to be able to cleanly import the file. In `M.default`, 'default' is the name of a filter set being defined (similar for 'specialization'). Definitions can contain three so-called keys: 'parent' specifies a filter set to inherit from in order to reduce code duplication, 'options' and 'patterns' are described below. - -Inheritance can be used to set new keys or to override keys from the parent set. Previously set keys cannot be removed, but they can be set to a non-existing directive (e.g., Lua's 'false' keyword) to achieve the same effect. Note that directives in inheriting sets are currently not combined with previous ones, so for instance overriding `['test'] = 'red, _delete'` with `['test'] = 'blue'` will result in only the directive 'blue' to be applied. - -#### Options - -Two options are currently available: - -* `mode`, which specifies whether to keep log lines (`keep`, the default) or to drop them (`delete`). For specific lines this can then be overridden, see 'Patterns' below. -* `count`, which can be set to `all` to prefix log lines with a counter, or `none` (default) to leave them as is. - -#### Patterns - -Pattern specifications are patterns as used in Lua: [Lua documentation on patterns](http://www.lua.org/pil/20.2.html). -The following directives can be associated with a pattern: - -* A foreground color, one of: black, red, green, yellow, blue, magenta, cyan or white. -* A background color, like foreground colors but prefixed with 'b'. -* `bold`, which usually has the effect of rendering a bright variant of the foreground color (note that `bold,black` renders as dark gray). -* `reverse` will reverse fore- and background colors. -* Also available are `blink` and `underscore` but they do currently not work in all terminal programs or might need to be enabled in the preferences. -* `_delete` or `_nodelete` to override the active mode specified in the 'options' above. - -Directives can be combined with ',' (e.g.: `'red,_nodelete'`). Finally, in any filter set, pattern rules are matched from top to bottom, the last one encountered overriding any previous conflicting directive. - -### Installation -Note: Loglite is already installed on the WiFi-Box since version 0.10.10. - -Install Lua. See: -http://lua-users.org/wiki/LuaBinaries -It's tested in Lua 5.1 and Lua 5.2. - -Loglite will check for a `loglite-filters.lua` file in your home directory. It's recommended to create a symbolic link to the latest version. -On OS X / Linux: -``` -cd -ln -s [absolute path to file]/loglite-filters.lua loglite-filters.lua -``` - -It's recommended to create a symbolic link in one of your PATH directories (`echo $PATH`) to the loglite.lua file. This allows you to run `loglite` from any directory. -On OS X / Linux: -``` -cd /usr/local/bin -ln -s [absolute path to file]/loglite.lua loglite.lua -``` diff --git a/src/script/loglite-filters.lua b/src/script/loglite-filters.lua deleted file mode 100644 index 2830da8..0000000 --- a/src/script/loglite-filters.lua +++ /dev/null @@ -1,63 +0,0 @@ -local M = {} - -M.default = { - ['options'] = { mode = 'keep', count = 'none' }, - ['patterns'] = { - ['%(error%)'] = 'red', - ['%(warning%)'] = 'yellow', - ['%(bulk%)'] = 'gray', - ['setState%(%)'] = 'bblue' - } -} - --- filter rules for firmware log (/tmp/wifibox.log) -M.firmware = { - ['parent'] = 'default', - ['patterns'] = { - ['START%-RQ'] = 'bblue', - ['END%-RQ'] = 'blue' - } -} - --- filter rules for print3d log (/tmp/print3d-*.log) -M.print3d = { - ['parent'] = 'default', - ['patterns'] = { - ['Print 3D server'] = 'byellow', - ['sendCode%(%)'] = 'green', - ['readCode%(%)'] = 'blue', - ['readResponseCode%(%)'] = 'blue' - } -} - --- filter rules for serial communcation of print3d -M.serial = { - ['options'] = { mode = 'delete', count = 'none' }, - ['patterns'] = { - ['Print 3D server'] = 'byellow,_nodelete', - ['sendCode%(%)'] = 'green,_nodelete', - ['readCode%(%)'] = 'blue,_nodelete', - ['readResponseCode%(%)'] = 'blue,_nodelete', - ['setState%(%)'] = 'bblue,_nodelete', - ['%[ABSD%]'] = 'gray,_nodelete', -- 0.10.10 - ['%[ABD%]'] = 'gray,_nodelete', -- 0.10.9 - ['%(info%)'] = 'gray,_nodelete' -- 0.10.10 - } -} - - -M.test = { -- TEST set - ['options'] = { mode = 'keep', count = 'all' }, - ['patterns'] = { - ['%(info%)'] = 'yellow' - } -} - -M.printstart = { - ['options'] = { mode = 'delete' }, - ['patterns'] = { - ['print started'] = '_uppercase,bwhite' - } -} - -return M diff --git a/src/script/loglite.lua b/src/script/loglite.lua deleted file mode 100755 index 0fdbbd2..0000000 --- a/src/script/loglite.lua +++ /dev/null @@ -1,275 +0,0 @@ -#!/usr/bin/env lua - ---[[ -For documentation on this script, see README-loglite.md. - -Ideas for improvement: -* add more directives like uppercase, prefix/suffix? -* create separate package for this script: a) since it is useful for any log file, b) this file is getting somewhat long -* for broader terminal support: detect `tput` and use it if available (http://wiki.bash-hackers.org/scripting/terminalcodes) -* pre-split keyword lists for efficiency instead of redoing this at every new line? - -FIXME: -* with deleteMode enabled, multiple matches and _nodelete in a later match, previous directives are ignored -]]-- - - ---[[========================================================================]]-- - ---Note: overview of ANSI escape codes: http://ascii-table.com/ansi-escape-sequences.php (support varies per terminal/termtype) -local ANSI_COLORS = { - ['bold'] = 1, - ['underscore'] = 4, - ['blink'] = 5, -- on osx/iterm2, this has to be enabled in preferences - ['reverse'] = 7, - ['black'] = 30, - ['red'] = 31, - ['green'] = 32, - ['yellow'] = 33, - ['blue'] = 34, - ['magenta'] = 35, - ['cyan'] = 36, - ['white'] = 37, - ['bblack'] = 40, - ['bred'] = 41, - ['bgreen'] = 42, - ['byellow'] = 43, - ['bblue'] = 44, - ['bmagenta'] = 45, - ['bcyan'] = 46, - ['bwhite'] = 47 -} - -local ESCAPE_STR = string.char(27) .. "[" -local RESET_CODE = ESCAPE_STR .. "m" - -local DFL_FILTERSET_FILE = "loglite-filters.lua" - - - ---[[========================================================================]]-- - ---- Stringifies the given object. --- From util/utils.lua --- Note that self-referencing objects will cause an endless loop with the current implementation. --- @param o The object to convert. --- @treturn string Stringified version of o. -local function dump(o) - if type(o) == 'table' then - local s = '{ ' - for k,v in pairs(o) do - if type(k) ~= 'number' then k = '"'..k..'"' end - s = s .. '['..k..'] = ' .. dump(v) .. ',' - end - return s .. '} ' - else - return tostring(o) - end -end - ---- Splits a string on a given divider character. --- From util/utils.lua --- @string[opt=':'] div The divider character to use. --- @return An array containing the resultant substrings. --- @usage local str = "a,b,c"; local parts = str:split(',') -function string:split(div) - local div, pos, arr = div or ':', 0, {} - for st,sp in function() return self:find(div, pos, true) end do - table.insert(arr, self:sub(pos, st - 1)) - pos = sp + 1 - end - table.insert(arr, self:sub(pos)) - return arr -end - ---- Determines if filename exists and can be opened for reading. --- From http://stackoverflow.com/a/4991602 --- @string filename The file to test. --- @return True if the file exists and is readable, false otherwise. -function fileExists(filename) - local f = io.open(filename, "r") - if f ~= nil then io.close(f) return true else return false end -end - ---- Converts keys of a table into a string. --- Adapted from http://stackoverflow.com/a/12674376. --- @string tbl A key/value table. --- @string[opt=','] sep Separator to use between items. --- @boolean[opt=false] sort Whether or not to sort the resulting list. --- @return A string with all keys from the given table. -local function keysToString(tbl, sep, sort) - local sep, sort = sep or ',', sort or false - local keyset, n = {}, 0 - for k,_ in pairs(tbl) do - n = n + 1 - keyset[n] = k - end - if sort then table.sort(keyset) end - return table.concat(keyset, sep) -end - ---- Merge two tables recursively (i.e., subtables also get merged). --- from: http://stackoverflow.com/a/1283608 --- @table t1 Table to merge into. --- @table t2 Table to merge into t1. --- @return The combined table (actually t1). -function mergeTables(t1, t2) - for k,v in pairs(t2) do - if type(v) == "table" then - if type(t1[k] or false) == "table" then - mergeTables(t1[k] or {}, t2[k] or {}) - else - t1[k] = v - end - else - t1[k] = v - end - end - return t1 -end - -local function hasValue(t, needle) - for k,v in pairs(t) do - if needle == v then return k end - end - return nil -end - -local function makeAnsiCode(key) - if not ANSI_COLORS[key] then return nil end - return ESCAPE_STR .. ANSI_COLORS[key] .. 'm' -end - - - ---[[========================================================================]]-- - -local function tailStream(stream, filterSet) - patterns = filterSet and filterSet.patterns or {} - options = filterSet and filterSet.options or { ['mode'] = 'keep' } - local c = 0 - - for line in stream:lines() do - --c = c + 1 -- Note: this would also count deleted lines - local embellished = line - local keepLine = (options.mode == 'keep') - local keepLineOverridden = false - - -- look for a pattern matching this line - for p,c in pairs(patterns) do - if line:match(p) then - -- print("[DEBUG] +matched rule '" .. p .. "'/'" .. c .. "' against '" .. line .. "'") - local kws = c:split(',') - - if hasValue(kws, '_delete') then keepLine = false; keepLineOverridden = true - elseif hasValue(kws, '_nodelete') then keepLine = true; keepLineOverridden = true - end - - if keepLine then - -- first collect formatting sequences - local fmt = '' - for _,kw in ipairs(kws) do - local code = makeAnsiCode(kw) - if code then fmt = fmt .. code end - end - - -- then wrap the line in formatting, if any - if fmt:len() > 0 then embellished = fmt .. embellished .. RESET_CODE end - else - -- Note: break out of loop and stop processing when line should be deleted _if_ the default has been overridden to do so - if keepLineOverridden then - embellished = nil - break - end - end - - --break -- Note: don't break, allow multiple matches per line, e.g. to mix and match fg and bg colors - end - end - - if embellished and keepLine then - c = c + 1 - - if options.count == 'all' then print(c, embellished) - else print(embellished) end - else - -- print("[DEBUG] -skipped '"..line.."'") - end - - --c = line:match 'truncated' and 0 or c -- from tail on stderr apparently - end -end - ---TODO: could be extended to look for multiple filenames in multiple paths -local function readConfigFile(filename, searchPath) - fullPath = searchPath .. '/' .. filename - if not fileExists(fullPath) then - --print("[DEBUG] config file '" .. fullPath .. "' not found") - return nil - end - - --print("[DEBUG] using config file '" .. fullPath .. "'") - -- require does not accept full paths? also, pcall does not help with dofile - return dofile(fullPath) -end - ---- Load filter set with given name from configSets, with inheritance as specified. -local function readFilterSet(configSets, setName) - local result = {} - for k,_ in pairs(configSets) do - if k == setName then - parent = configSets[setName]['parent'] - if parent ~= nil then - --print("[DEBUG] recursing for filter set '" .. parent .. "' from config") - result = mergeTables(result, readFilterSet(configSets, parent)) - end - --print("[DEBUG] using/merging filter set '" .. setName .. "' from config") - result = mergeTables(result, configSets[setName]) - break - end - end - return result -end - ---NOTE: if command-line options get any more complex, switch to a lightweight --- getopt like this one? https://attractivechaos.wordpress.com/2011/04/07/getopt-for-lua/ -local function main() - -- handle command-line arguments - local showHelp, followFile, filterSetName = false, nil, 'default' - if #arg > 0 and arg[1] == "-h" or arg[1] == "--help" then - showHelp = true - else - if #arg > 0 and arg[1] ~= '-' then followFile = arg[1] end - if #arg > 1 then filterSetName = arg[2] end - end - - -- read filter set file if available - local configSets = readConfigFile(DFL_FILTERSET_FILE, os.getenv('HOME')) or {} - local filterSet = readFilterSet(configSets, filterSetName) - -- print("[DEBUG] final filter set for '" .. filterSetName .. "' from config: " .. dump(filterSet)) - - -- if requested, display help and exit - if showHelp and showHelp == true then - print("Usage: loglite.lua [file-to-tail] [filter-set]") - print(" If no arguments are supplied, or if the first one is `-', stdin is used as input.") - print(" If no filter set is supplied, a set named `default' will be looked for.") - print(" Filter sets can be defined in a file `loglite-filters.lua' in your home directory.") - print() - print(" Available filter sets in " .. os.getenv('HOME') .. "/" .. DFL_FILTERSET_FILE .. ": " .. keysToString(configSets, ', ', true)) - os.exit(0) - end - - - ------------------------- - - --print("[DEBUG] following file: '" .. (followFile and followFile or "") .. "', with filter set '" .. filterSetName .. "'.") - - --Info on tailing a file: https://stackoverflow.com/questions/17363973/how-can-i-tail-f-a-log-filetruncate-aware-in-lua - --local tailin = io.popen('tail -F '..(...)..' 2>&1', 'r') - local tailin = followFile and io.popen('tail -f ' .. followFile, 'r') or io.stdin - - pcall(tailStream, tailin, filterSet) -- Note: protected call to suppress interrupt error thrown by lines iterator -end - -main() -os.exit(0)