logline

logline

function logline( level [, ...] ) --> line, err

Description

This function adds common useful information to the data that you want to output.

When called with a single argument, it will set the global verbosity level. When called with additional arguments it will generate the log string. However the string will be generated only if the first argument, the line log level, is smaller than the global verbosity level. In this way you can dinamically enable or disable log messages in critical part of the code.

The verbosity level can be given in two way: as an integer or as a string representing the verbosity class.

The allowed verbosity classes are:

  • ERROR <= 25

  • DEBUG <= 50

  • INFO <= 75

  • VERBOSE <= 99

Each class will be considered to cantain any integer level just below it, e.g. 26, 30 and 50 all belongs to the DEBUG class. When specifying the verbosity level as a class name, the higher belonging integer will be used.

Parameters

level

Integer verbosity level or string verbosity class name. When called without any other argument this is the global verbosity level to be set. If other arguments are present, it is the verbosity level of the log line. Note that the output will be generated only if the line have a level smaller than the global verbosity.

Additional information that will be appended to the output.

Return Values

line

It return nil if called with one arguments o if the global verbosity level is not high enough. Otherwise it will return a string containing:

  • Date

  • Time

  • os.clock() result

  • Incremental number

  • Verbosity level of the log line

  • Source position of function call

  • Additional info in the arguments

Note 1: The verbosity level will be reported both as number that as the symbolic class name.

Note 2: if the caller is a tail call or a function with a name that starts or ends with log, the position used will be the one of the caller of the caller (and so on).

err

A message if an error occurse, otherwise nil.

Code

local skip_lower_level = 25
local log_count = 0

local level_list =  {
   { 25, "ERROR" },
   { 50, "DEBUG" },
   { 75, "INFO"} ,
   { 99, "VERBOSE" }
}

local level_map
local function update_level_map()
   level_map = {}
   for k,v in ipairs( level_list ) do
      level_map[ v[ 2 ] ] = v
   end
end

update_level_map()

--ZFUNC-logline-v0
local function logline( level, ... ) --> line
   -- Classify log level
   local level_class
   if "string" == type( level ) then
      level_class = level_map[ level:upper() ]
      if level_class then level = level_class[ 1 ] end
   elseif "number" == type( level ) then
      local level_num = #level_list
      for k = 1, level_num do
         if k == level_num or level <= level_list[k][1] then
            level_class = level_list[k]
            break
         end
      end
   else
      return nil, "Invalid type for argument #1"
   end

   if not level_class then
      return nil, "Invalid symbolic log level"
   end

   local n = select( "#", ... )
   --  Single argument mode: set log level
   if n == 0 then
      skip_lower_level = level
      return
   end

   -- Multiple argument mode: generate log line

   -- Skip if the current log level is too small
   if skip_lower_level < level then
      return
   end
   log_count = log_count + 1

   -- Get info about the function in the correct stack position
   local d = debug.getinfo( 2 )
   local td = d
   local stackup = 2
   while true do
      local n = td.name
      if not n then break end
      n = n:lower()
      if  not n:match( "log$" )
      and not n:match( "^log" )
      and n ~= "" then
         break
      end
      stackup = stackup + 1
      td = debug.getinfo(stackup)
   end
   if td then d = td end

   -- Log line common part
   local line = os.date( "%Y/%m/%d %H:%M:%S" ).." "..os.clock().." "
                ..log_count.." "..level_class[ 1 ].."."..level_class[ 2 ].." "
                ..d.short_src:match( "([^/\\]*)$" )..":"..d.currentline.." | "

   -- Append additional log info from arguments
   for m = 1,n do
      line = line..tostring( select( m, ... ) ).." | "
   end

   return line
end

return logline

Examples

local t = require "taptest"
local logline = require "logline"

-- The checker: when verifing strings use patern matchin
-- Otherwise fallback to ==
local function pcheck( got, exp )
   if type( exp ) ~= type( got ) then return false end
   if "string" ~= type( got ) then return ( got == exp ) end
   return ( nil ~= got:match( exp ) )
end

-- Default log level is 25 a.k.a. ERROR
-- Only logline with smaller level will generate a message

t( logline( 10, "test" ), "|", pcheck )
t( logline( 25, "test" ), "|", pcheck )
t( logline( 26, "test" ), nil, pcheck )
t( logline( 99, "test" ), nil, pcheck )

-- Change log level
logline( 60 )
t( logline( 10, "test" ), "|", pcheck )
t( logline( 60, "test" ), "|", pcheck )
t( logline( 61, "test" ), nil, pcheck )
t( logline( 99, "test" ), nil, pcheck )

-- Symbolic log level name

logline( "error" )
t( logline( 25, "test" ),        "|", pcheck )
t( logline( 26, "test" ),        nil, pcheck )
t( logline( "error", "test" ),   "|", pcheck )
t( logline( "debug", "test" ),   nil, pcheck )
t( logline( "info", "test" ),    nil, pcheck )
t( logline( "verbose", "test" ), nil, pcheck )

logline( "debug" )
t( logline( 50, "test" ),        "|", pcheck )
t( logline( 51, "test" ),        nil, pcheck )
t( logline( "error", "test" ),   "|", pcheck )
t( logline( "debug", "test" ),   "|", pcheck )
t( logline( "info", "test" ),    nil, pcheck )
t( logline( "verbose", "test" ), nil, pcheck )

logline( "info" )
t( logline( 75, "test" ),        "|", pcheck )
t( logline( 76, "test" ),        nil, pcheck )
t( logline( "error", "test" ),   "|", pcheck )
t( logline( "debug", "test" ),   "|", pcheck )
t( logline( "info", "test" ),    "|", pcheck )
t( logline( "verbose", "test" ), nil, pcheck )

logline( "verbose" )
t( logline( 99, "test" ),        "|", pcheck )
t( logline( 100, "test" ),       nil, pcheck )
t( logline( "error", "test" ),   "|", pcheck )
t( logline( "debug", "test" ),   "|", pcheck )
t( logline( "info", "test" ),    "|", pcheck )
t( logline( "verbose", "test" ), "|", pcheck )

-- Message contains source position
t( logline( 99, "test" ), "logline%.ex1%.lua:62", pcheck ) -- he line 62 is this one

-- In some case the caller source position is used:
-- - Tail calls
-- - Functions with names that start or end with "log"

function wraplog( ... ) return logline( 99, ... ) end
function wraplogfakebarrier( ... ) return logline( 99, ... ) end
function wraplogbarrier( ... )
   local res = logline( 99, ... ) -- line 72
   return res
end

t( wraplog( "test" ),            "logline%.ex1%.lua:75", pcheck ) -- line 75
t( wraplogfakebarrier( "test" ), "logline%.ex1%.lua:76", pcheck ) -- line 76
t( wraplogbarrier( "test" ),     "logline%.ex1%.lua:71", pcheck )

-- The argument are appended to the result string
t( logline( 99, "a", 1), "| a | 1 | $", pcheck )

-- OTHER STUFF IN THE LOG: --TODO : TEST ?
-- - date
-- - time
-- - os.clock() result
-- - incremental number
-- - log level of the line

print( "# "..logline( 80, "ok" ) )

t( )