logging

This module implements a simple logger.

It has been designed to be as simple as possible to avoid bloat. If this library does not fulfill your needs, write your own.

Basic usage

To get started, first create a logger:

import logging

var logger = newConsoleLogger()

The logger that was created above logs to the console, but this module also provides loggers that log to files, such as the FileLogger. Creating custom loggers is also possible by inheriting from the Logger type.

Once a logger has been created, call its log proc to log a message:

logger.log(lvlInfo, "a log message")
# Output: INFO a log message

The INFO within the output is the result of a format string being prepended to the message, and it will differ depending on the message's level. Format strings are explained in more detail here.

There are six logging levels: debug, info, notice, warn, error, and fatal. They are described in more detail within the Level enum's documentation. A message is logged if its level is at or above both the logger's levelThreshold field and the global log filter. The latter can be changed with the setLogFilter proc.

Warning:

  • For loggers that log to a console or to files, only error and fatal messages will cause their output buffers to be flushed immediately. Use the flushFile proc to flush the buffer manually if needed.

Handlers

When using multiple loggers, calling the log proc for each logger can become repetitive. Instead of doing that, register each logger that will be used with the addHandler proc, which is demonstrated in the following example:

import logging

var consoleLog = newConsoleLogger()
var fileLog = newFileLogger("errors.log", levelThreshold=lvlError)
var rollingLog = newRollingFileLogger("rolling.log")

addHandler(consoleLog)
addHandler(fileLog)
addHandler(rollingLog)

After doing this, use either the log template or one of the level-specific templates, such as the error template, to log messages to all registered handlers at once.

# This example uses the loggers created above
log(lvlError, "an error occurred")
error("an error occurred")  # Equivalent to the above line
info("something normal happened")  # Will not be written to errors.log

Note that a message's level is still checked against each handler's levelThreshold and the global log filter.

Format strings

Log messages are prefixed with format strings. These strings contain placeholders for variables, such as $time, that are replaced with their corresponding values, such as the current time, before they are prepended to a log message. Characters that are not part of variables are unaffected.

The format string used by a logger can be specified by providing the fmtStr argument when creating the logger or by setting its fmtStr field afterward. If not specified, the default format string is used.

The following variables, which must be prefixed with a dollar sign ($), are available:

VariableOutput
$dateCurrent date
$timeCurrent time
$datetime$dateT$time
$appos.getAppFilename()
$appnameBase name of $app
$appdirDirectory name of $app
$levelidFirst letter of log level
$levelnameLog level name

Note that $app, $appname, and $appdir are not supported when using the JavaScript backend.

The following example illustrates how to use format strings:

import logging

var logger = newConsoleLogger(fmtStr="[$time] - $levelname: ")
logger.log(lvlInfo, "this is a message")
# Output: [19:50:13] - INFO: this is a message

Notes when using multiple threads

There are a few details to keep in mind when using this module within multiple threads:

  • The global log filter is actually a thread-local variable, so it needs to be set in each thread that uses this module.
  • The list of registered handlers is also a thread-local variable. If a handler will be used in multiple threads, it needs to be registered in each of those threads.

See also

Types

Level = enum
  lvlAll,                   ## All levels active
  lvlDebug,                 ## Debug level and above are active
  lvlInfo,                  ## Info level and above are active
  lvlNotice,                ## Notice level and above are active
  lvlWarn,                  ## Warn level and above are active
  lvlError,                 ## Error level and above are active
  lvlFatal,                 ## Fatal level and above are active
  lvlNone                    ## No levels active; nothing is logged

Enumeration of logging levels.

Debug messages represent the lowest logging level, and fatal error messages represent the highest logging level. lvlAll can be used to enable all messages, while lvlNone can be used to disable all messages.

Typical usage for each logging level, from lowest to highest, is described below:

  • Debug - debugging information helpful only to developers
  • Info - anything associated with normal operation and without any particular importance
  • Notice - more important information that users should be notified about
  • Warn - impending problems that require some attention
  • Error - error conditions that the application can recover from
  • Fatal - fatal errors that prevent the application from continuing

It is completely up to the application how to utilize each level.

Individual loggers have a levelThreshold field that filters out any messages with a level lower than the threshold. There is also a global filter that applies to all log messages, and it can be changed using the setLogFilter proc.

  Source Edit
Logger = ref object of RootObj
  levelThreshold*: Level     ## Only messages that are at or above this
                             ## threshold will be logged
  fmtStr*: string            ## Format string to prepend to each log message;
                             ## defaultFmtStr is the default
  

The abstract base type of all loggers.

Custom loggers should inherit from this type. They should also provide their own implementation of the log method.

See also:

  Source Edit
ConsoleLogger = ref object of Logger
  useStderr*: bool           ## If true, writes to stderr; otherwise, writes to stdout
  

A logger that writes log messages to the console.

Create a new ConsoleLogger with the newConsoleLogger proc.

See also:

  Source Edit
FileLogger = ref object of Logger
  file*: File                ## The wrapped file
  

A logger that writes log messages to a file.

Create a new FileLogger with the newFileLogger proc.

Note: This logger is not available for the JavaScript backend.

See also:

  Source Edit
RollingFileLogger = ref object of FileLogger
  maxLines: int
  curLine: int
  baseName: string
  baseMode: FileMode
  logFiles: int
  bufSize: int

A logger that writes log messages to a file while performing log rotation.

Create a new RollingFileLogger with the newRollingFileLogger proc.

Note: This logger is not available for the JavaScript backend.

See also:

  Source Edit

Consts

LevelNames: array[Level, string] = ["DEBUG", "DEBUG", "INFO", "NOTICE", "WARN",
                                    "ERROR", "FATAL", "NONE"]
Array of strings representing each logging level.   Source Edit
defaultFmtStr = "$levelname "
The default format string.   Source Edit
verboseFmtStr = "$levelid, [$datetime] -- $appname: "

A more verbose format string.

This string can be passed as the frmStr argument to procs that create new loggers, such as the newConsoleLogger proc.

If a different format string is preferred, refer to the documentation about format strings for more information, including a list of available variables.

  Source Edit

Procs

proc substituteLog(frmt: string; level: Level; args: varargs[string, `$`]): string {...}{.
    raises: [], tags: [ReadIOEffect, TimeEffect].}

Formats a log message at the specified level with the given format string.

The format variables present within frmt will be replaced with the corresponding values before being prepended to args and returned.

Unless you are implementing a custom logger, there is little need to call this directly. Use either a logger's log method or one of the logging templates.

See also:

Example:

doAssert substituteLog(defaultFmtStr, lvlInfo, "a message") == "INFO a message"
doAssert substituteLog("$levelid - ", lvlError, "an error") == "E - an error"
doAssert substituteLog("$levelid", lvlDebug, "error") == "Derror"
  Source Edit
proc newConsoleLogger(levelThreshold = lvlAll; fmtStr = defaultFmtStr;
                      useStderr = false): ConsoleLogger {...}{.raises: [], tags: [].}

Creates a new ConsoleLogger.

By default, log messages are written to stdout. If useStderr is true, they are written to stderr instead.

For the JavaScript backend, log messages are written to the console, and useStderr is ignored.

See also:

Examples:

var normalLog = newConsoleLogger()
var formatLog = newConsoleLogger(fmtStr=verboseFmtStr)
var errorLog = newConsoleLogger(levelThreshold=lvlError, useStderr=true)
  Source Edit
proc defaultFilename(): string {...}{.raises: [], tags: [ReadIOEffect].}

Returns the filename that is used by default when naming log files.

Note: This proc is not available for the JavaScript backend.

  Source Edit
proc newFileLogger(file: File; levelThreshold = lvlAll; fmtStr = defaultFmtStr): FileLogger {...}{.
    raises: [], tags: [].}

Creates a new FileLogger that uses the given file handle.

Note: This proc is not available for the JavaScript backend.

See also:

Examples:

var messages = open("messages.log", fmWrite)
var formatted = open("formatted.log", fmWrite)
var errors = open("errors.log", fmWrite)

var normalLog = newFileLogger(messages)
var formatLog = newFileLogger(formatted, fmtStr=verboseFmtStr)
var errorLog = newFileLogger(errors, levelThreshold=lvlError)
  Source Edit
proc newFileLogger(filename = defaultFilename(); mode: FileMode = fmAppend;
                   levelThreshold = lvlAll; fmtStr = defaultFmtStr;
                   bufSize: int = -1): FileLogger {...}{.raises: [IOError], tags: [].}

Creates a new FileLogger that logs to a file with the given filename.

bufSize controls the size of the output buffer that is used when writing to the log file. The following values can be provided:

  • -1 - use system defaults
  • 0 - unbuffered
  • > 0 - fixed buffer size

Note: This proc is not available for the JavaScript backend.

See also:

Examples:

var normalLog = newFileLogger("messages.log")
var formatLog = newFileLogger("formatted.log", fmtStr=verboseFmtStr)
var errorLog = newFileLogger("errors.log", levelThreshold=lvlError)
  Source Edit
proc newRollingFileLogger(filename = defaultFilename();
                          mode: FileMode = fmReadWrite; levelThreshold = lvlAll;
                          fmtStr = defaultFmtStr; maxLines: Positive = 1000;
                          bufSize: int = -1): RollingFileLogger {...}{.
    raises: [IOError, OSError], tags: [ReadDirEffect, ReadIOEffect].}

Creates a new RollingFileLogger.

Once the current log file being written to contains maxLines lines, a new log file will be created, and the old log file will be renamed.

bufSize controls the size of the output buffer that is used when writing to the log file. The following values can be provided:

  • -1 - use system defaults
  • 0 - unbuffered
  • > 0 - fixed buffer size

Note: This proc is not available in the JavaScript backend.

See also:

Examples:

var normalLog = newRollingFileLogger("messages.log")
var formatLog = newRollingFileLogger("formatted.log", fmtStr=verboseFmtStr)
var shortLog = newRollingFileLogger("short.log", maxLines=200)
var errorLog = newRollingFileLogger("errors.log", levelThreshold=lvlError)
  Source Edit
proc addHandler(handler: Logger) {...}{.raises: [], tags: [].}

Adds a logger to the list of registered handlers.

Warning: The list of handlers is a thread-local variable. If the given handler will be used in multiple threads, this proc should be called in each of those threads.

See also:

Example:

var logger = newConsoleLogger()
addHandler(logger)
doAssert logger in getHandlers()
  Source Edit
proc getHandlers(): seq[Logger] {...}{.raises: [], tags: [].}

Returns a list of all the registered handlers.

See also:

  Source Edit
proc setLogFilter(lvl: Level) {...}{.raises: [], tags: [].}

Sets the global log filter.

Messages below the provided level will not be logged regardless of an individual logger's levelThreshold. By default, all messages are logged.

Warning: The global log filter is a thread-local variable. If logging is being performed in multiple threads, this proc should be called in each thread unless it is intended that different threads should log at different logging levels.

See also:

Example:

setLogFilter(lvlError)
doAssert getLogFilter() == lvlError
  Source Edit
proc getLogFilter(): Level {...}{.raises: [], tags: [].}

Gets the global log filter.

See also:

  Source Edit

Methods

method log(logger: Logger; level: Level; args: varargs[string, `$`]) {...}{.
    raises: [Exception], gcsafe, tags: [RootEffect], base.}

Override this method in custom loggers. The default implementation does nothing.

See also:

  Source Edit
method log(logger: ConsoleLogger; level: Level; args: varargs[string, `$`]) {...}{.
    raises: [], tags: [ReadIOEffect, TimeEffect, WriteIOEffect].}

Logs to the console with the given ConsoleLogger only.

This method ignores the list of registered handlers.

Whether the message is logged depends on both the ConsoleLogger's levelThreshold field and the global log filter set using the setLogFilter proc.

Note: Only error and fatal messages will cause the output buffer to be flushed immediately. Use the flushFile proc to flush the buffer manually if needed.

See also:

Examples:

var consoleLog = newConsoleLogger()
consoleLog.log(lvlInfo, "this is a message")
consoleLog.log(lvlError, "error code is: ", 404)
  Source Edit
method log(logger: FileLogger; level: Level; args: varargs[string, `$`]) {...}{.
    raises: [IOError], tags: [WriteIOEffect, ReadIOEffect, TimeEffect].}

Logs a message at the specified level using the given FileLogger only.

This method ignores the list of registered handlers.

Whether the message is logged depends on both the FileLogger's levelThreshold field and the global log filter set using the setLogFilter proc.

Notes:

  • Only error and fatal messages will cause the output buffer to be flushed immediately. Use the flushFile proc to flush the buffer manually if needed.
  • This method is not available for the JavaScript backend.

See also:

Examples:

var fileLog = newFileLogger("messages.log")
fileLog.log(lvlInfo, "this is a message")
fileLog.log(lvlError, "error code is: ", 404)
  Source Edit
method log(logger: RollingFileLogger; level: Level; args: varargs[string, `$`]) {...}{.
    raises: [OSError, IOError, Exception],
    tags: [ReadIOEffect, WriteIOEffect, TimeEffect].}

Logs a message at the specified level using the given RollingFileLogger only.

This method ignores the list of registered handlers.

Whether the message is logged depends on both the RollingFileLogger's levelThreshold field and the global log filter set using the setLogFilter proc.

Notes:

  • Only error and fatal messages will cause the output buffer to be flushed immediately. Use the flushFile proc to flush the buffer manually if needed.
  • This method is not available for the JavaScript backend.

See also:

Examples:

var rollingLog = newRollingFileLogger("messages.log")
rollingLog.log(lvlInfo, "this is a message")
rollingLog.log(lvlError, "error code is: ", 404)
  Source Edit

Templates

template log(level: Level; args: varargs[string, `$`])

Logs a message at the specified level to all registered handlers.

Whether the message is logged depends on both the FileLogger's levelThreshold field and the global log filter set using the setLogFilter proc.

Examples:

var logger = newConsoleLogger()
addHandler(logger)

log(lvlInfo, "This is an example.")

See also:

  Source Edit
template debug(args: varargs[string, `$`])

Logs a debug message to all registered handlers.

Debug messages are typically useful to the application developer only, and they are usually disabled in release builds, although this template does not make that distinction.

Examples:

var logger = newConsoleLogger()
addHandler(logger)

debug("myProc called with arguments: foo, 5")

See also:

  Source Edit
template info(args: varargs[string, `$`])

Logs an info message to all registered handlers.

Info messages are typically generated during the normal operation of an application and are of no particular importance. It can be useful to aggregate these messages for later analysis.

Examples:

var logger = newConsoleLogger()
addHandler(logger)

info("Application started successfully.")

See also:

  Source Edit
template notice(args: varargs[string, `$`])

Logs an notice to all registered handlers.

Notices are semantically very similar to info messages, but they are meant to be messages that the user should be actively notified about, depending on the application.

Examples:

var logger = newConsoleLogger()
addHandler(logger)

notice("An important operation has completed.")

See also:

  Source Edit
template warn(args: varargs[string, `$`])

Logs a warning message to all registered handlers.

A warning is a non-error message that may indicate impending problems or degraded performance.

Examples:

var logger = newConsoleLogger()
addHandler(logger)

warn("The previous operation took too long to process.")

See also:

  Source Edit
template error(args: varargs[string, `$`])

Logs an error message to all registered handlers.

Error messages are for application-level error conditions, such as when some user input generated an exception. Typically, the application will continue to run, but with degraded functionality or loss of data, and these effects might be visible to users.

Examples:

var logger = newConsoleLogger()
addHandler(logger)

error("An exception occurred while processing the form.")

See also:

  Source Edit
template fatal(args: varargs[string, `$`])

Logs a fatal error message to all registered handlers.

Fatal error messages usually indicate that the application cannot continue to run and will exit due to a fatal condition. This template only logs the message, and it is the application's responsibility to exit properly.

Examples:

var logger = newConsoleLogger()
addHandler(logger)

fatal("Can't open database -- exiting.")

See also:

  Source Edit