diff options
author | Dominik Picheta <dominikpicheta@googlemail.com> | 2014-01-27 18:20:08 +0000 |
---|---|---|
committer | Dominik Picheta <dominikpicheta@googlemail.com> | 2014-01-27 18:20:08 +0000 |
commit | 9344e9fbf7d4c056710c152b8e811632914f8043 (patch) | |
tree | a5f57917821175ef1b0a054f42b065881f01e306 | |
parent | 92f7c479b9c8db6fdf5044a61025dd88529ce8b6 (diff) | |
download | Nim-9344e9fbf7d4c056710c152b8e811632914f8043.tar.gz |
Finished logging module.
-rw-r--r-- | devel/logging.nim | 210 | ||||
-rw-r--r-- | doc/lib.txt | 3 | ||||
-rw-r--r-- | lib/pure/logging.nim | 267 | ||||
-rw-r--r-- | web/nimrod.ini | 2 |
4 files changed, 271 insertions, 211 deletions
diff --git a/devel/logging.nim b/devel/logging.nim deleted file mode 100644 index a10478dab..000000000 --- a/devel/logging.nim +++ /dev/null @@ -1,210 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements a simple logger. It is based on the following design: -## * Runtime log formating is a bug: Sooner or later every log file is parsed. -## * Keep it simple: If this library does not fullfill your needs, write your -## own. Trying to support every logging feature just leads to bloat. -## -## Format is:: -## -## DEBUG|INFO|... (2009-11-02 00:00:00)? (Component: )? Message -## -## - -import strutils, os, times - -type - TLevel* = enum ## logging level - lvlAll, ## all levels active - lvlDebug, ## debug level (and any above) active - lvlInfo, ## info level (and any above) active - lvlWarn, ## warn level (and any above) active - lvlError, ## error level (and any above) active - lvlFatal, ## fatal level (and any above) active - lvlNone - -const - LevelNames*: array [TLevel, string] = [ - "DEBUG", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NONE" - ] - - defaultFmtStr = "" ## default string between log level and message per logger - verboseFmtStr = "$date $time " - -type - TLogger* = object of TObject ## abstract logger; the base type of all loggers - levelThreshold*: TLevel ## only messages of level >= levelThreshold - ## should be processed - fmtStr: string ## = defaultFmtStr by default, see substituteLog for $date etc. - - TConsoleLogger* = object of TLogger ## logger that writes the messages to the - ## console - - TFileLogger* = object of TLogger ## logger that writes the messages to a file - f: TFile - - # TODO: implement rolling log, will produce filename.1, filename.2 etc. - TRollingFileLogger* = object of TFileLogger ## logger that writes the - ## message to a file - maxLines: int # maximum number of lines - curLine : int - baseName: string # initial filename - logFiles: int # how many log files already created, e.g. basename.1, basename.2... - - - - -proc substituteLog*(frmt: string): string = - ## converts $date to the current date - ## converts $time to the current time - ## converts $app to getAppFilename() - ## converts - result = newStringOfCap(frmt.len + 20) - var i = 0 - while i < frmt.len: - if frmt[i] != '$': - result.add(frmt[i]) - inc(i) - else: - inc(i) - var v = "" - var app = getAppFilename() - while frmt[i] in IdentChars: - v.add(toLower(frmt[i])) - inc(i) - case v - of "date": result.add(getDateStr()) - of "time": result.add(getClockStr()) - of "app": result.add(app) - of "appdir": result.add(app.splitFile.dir) - of "appname": result.add(app.splitFile.name) - - - -method log*(L: ref TLogger, level: TLevel, - frmt: string, args: varargs[string, `$`]) = - ## override this method in custom loggers. Default implementation does - ## nothing. - nil - -method log*(L: ref TConsoleLogger, level: TLevel, - frmt: string, args: varargs[string, `$`]) = - Writeln(stdout, LevelNames[level], " ", substituteLog(L.fmtStr), frmt % args) - -method log*(L: ref TFileLogger, level: TLevel, - frmt: string, args: varargs[string, `$`]) = - Writeln(L.f, LevelNames[level], " ", substituteLog(L.fmtStr), frmt % args) - -proc defaultFilename*(): string = - ## returns the default filename for a logger - var (path, name, ext) = splitFile(getAppFilename()) - result = changeFileExt(path / name & "_" & getDateStr(), "log") - - - - -proc newConsoleLogger*(levelThreshold = lvlAll) : ref TConsoleLogger = - new result - result.fmtStr = defaultFmtStr - result.levelThreshold = levelThreshold - -proc newFileLogger*(filename = defaultFilename(), - mode: TFileMode = fmAppend, - levelThreshold = lvlAll): ref TFileLogger = - new(result) - result.levelThreshold = levelThreshold - result.f = open(filename, mode) - result.fmtStr = defaultFmtStr - -# ------ - -proc readLogLines(logger : ref TRollingFileLogger) = nil - #f.readLine # TODO read all lines, update curLine - - -proc newRollingFileLogger*(filename = defaultFilename(), - mode: TFileMode = fmReadWrite, - levelThreshold = lvlAll, - maxLines = 1000): ref TRollingFileLogger = - new(result) - result.levelThreshold = levelThreshold - result.fmtStr = defaultFmtStr - result.maxLines = maxLines - result.f = open(filename, mode) - result.curLine = 0 - - # TODO count all number files - # count lines in existing filename file - # if >= maxLines then rename to next numbered file and create new file - - #if mode in {fmReadWrite, fmReadWriteExisting}: - # readLogLines(result) - - - -method log*(L: ref TRollingFileLogger, level: TLevel, - frmt: string, args: varargs[string, `$`]) = - # TODO - # if more than maxlines, then set cursor to zero - - Writeln(L.f, LevelNames[level], " ", frmt % args) - -# -------- - -var - level* = lvlAll ## global log filter - handlers*: seq[ref TLogger] = @[] ## handlers with their own log levels - -proc logLoop(level: TLevel, frmt: string, args: varargs[string, `$`]) = - for logger in items(handlers): - if level >= logger.levelThreshold: - log(logger, level, frmt, args) - -template log*(level: TLevel, frmt: string, args: varargs[string, `$`]) = - ## logs a message of the given level - bind logLoop - bind `%` - bind logging.Level - - if level >= logging.Level: - logLoop(level, frmt, args) - -template debug*(frmt: string, args: varargs[string, `$`]) = - ## logs a debug message - log(lvlDebug, frmt, args) - -template info*(frmt: string, args: varargs[string, `$`]) = - ## logs an info message - log(lvlInfo, frmt, args) - -template warn*(frmt: string, args: varargs[string, `$`]) = - ## logs a warning message - log(lvlWarn, frmt, args) - -template error*(frmt: string, args: varargs[string, `$`]) = - ## logs an error message - log(lvlError, frmt, args) - -template fatal*(frmt: string, args: varargs[string, `$`]) = - ## logs a fatal error message - log(lvlFatal, frmt, args) - - -# -------------- - -when isMainModule: - var L = newConsoleLogger() - var fL = newFileLogger("test.log") - fL.fmtStr = verboseFmtStr - handlers.add(L) - handlers.add(fL) - info("hello", []) - - diff --git a/doc/lib.txt b/doc/lib.txt index ba0cb0a90..3214cdae2 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -341,6 +341,9 @@ Miscellaneous * `endians <endians.html>`_ This module contains helpers that deal with different byte orders. +* `logging <logging.html>`_ + This module implements a simple logger. + Database support ---------------- diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim new file mode 100644 index 000000000..8158cbc2a --- /dev/null +++ b/lib/pure/logging.nim @@ -0,0 +1,267 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Andreas Rumpf, Dominik Picheta +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements a simple logger. It has been designed to be as simple +## as possible to avoid bloat, if this library does not fullfill your needs, +## write your own. +## +## Format strings support the following variables which must be prefixed with +## the dollar operator (``$``): +## +## ============ ======================= +## Operator Output +## ============ ======================= +## $date Current date +## $time Current time +## $app ``os.getAppFilename()`` +## ============ ======================= +## +## +## The following example demonstrates logging to three different handlers +## simultaneously: +## +## .. code-block:: nimrod +## +## var L = newConsoleLogger() +## var fL = newFileLogger("test.log", fmtStr = verboseFmtStr) +## var rL = newRollingFileLogger("rolling.log", fmtStr = verboseFmtStr) +## handlers.add(L) +## handlers.add(fL) +## handlers.add(rL) +## info("920410:52 accepted") +## warn("4 8 15 16 23 4-- Error") +## error("922044:16 SYSTEM FAILURE") +## fatal("SYSTEM FAILURE SYSTEM FAILURE") + +import strutils, os, times + +type + TLevel* = enum ## logging level + lvlAll, ## all levels active + lvlDebug, ## debug level (and any above) active + lvlInfo, ## info level (and any above) active + lvlWarn, ## warn level (and any above) active + lvlError, ## error level (and any above) active + lvlFatal, ## fatal level (and any above) active + lvlNone ## no levels active + +const + LevelNames*: array [TLevel, string] = [ + "DEBUG", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NONE" + ] + + defaultFmtStr* = "" ## default string between log level and message per logger + verboseFmtStr* = "$date $time " + +type + PLogger* = ref object of PObject ## abstract logger; the base type of all loggers + levelThreshold*: TLevel ## only messages of level >= levelThreshold + ## should be processed + fmtStr: string ## = defaultFmtStr by default, see substituteLog for $date etc. + + PConsoleLogger* = ref object of PLogger ## logger that writes the messages to the + ## console + + PFileLogger* = ref object of PLogger ## logger that writes the messages to a file + f: TFile + + PRollingFileLogger* = ref object of PFileLogger ## logger that writes the + ## messages to a file and + ## performs log rotation + maxLines: int # maximum number of lines + curLine : int + baseName: string # initial filename + baseMode: TFileMode # initial file mode + logFiles: int # how many log files already created, e.g. basename.1, basename.2... + +proc substituteLog(frmt: string): string = + ## converts $date to the current date + ## converts $time to the current time + ## converts $app to getAppFilename() + ## converts + result = newStringOfCap(frmt.len + 20) + var i = 0 + while i < frmt.len: + if frmt[i] != '$': + result.add(frmt[i]) + inc(i) + else: + inc(i) + var v = "" + var app = getAppFilename() + while frmt[i] in IdentChars: + v.add(toLower(frmt[i])) + inc(i) + case v + of "date": result.add(getDateStr()) + of "time": result.add(getClockStr()) + of "app": result.add(app) + of "appdir": result.add(app.splitFile.dir) + of "appname": result.add(app.splitFile.name) + +method log*(logger: PLogger, level: TLevel, + frmt: string, args: varargs[string, `$`]) = + ## Override this method in custom loggers. Default implementation does + ## nothing. + nil + +method log*(logger: PConsoleLogger, level: TLevel, + frmt: string, args: varargs[string, `$`]) = + ## Logs to the console using ``logger`` only. + if level >= logger.levelThreshold: + writeln(stdout, LevelNames[level], " ", substituteLog(logger.fmtStr), + frmt % args) + +method log*(logger: PFileLogger, level: TLevel, + frmt: string, args: varargs[string, `$`]) = + ## Logs to a file using ``logger`` only. + if level >= logger.levelThreshold: + writeln(logger.f, LevelNames[level], " ", + substituteLog(logger.fmtStr), frmt % args) + +proc defaultFilename*(): string = + ## Returns the default filename for a logger. + var (path, name, ext) = splitFile(getAppFilename()) + result = changeFileExt(path / name, "log") + +proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): PConsoleLogger = + ## Creates a new console logger. This logger logs to the console. + new result + result.fmtStr = fmtStr + result.levelThreshold = levelThreshold + +proc newFileLogger*(filename = defaultFilename(), + mode: TFileMode = fmAppend, + levelThreshold = lvlAll, + fmtStr = defaultFmtStr): PFileLogger = + ## Creates a new file logger. This logger logs to a file. + new(result) + result.levelThreshold = levelThreshold + result.f = open(filename, mode) + result.fmtStr = fmtStr + +# ------ + +proc countLogLines(logger: PRollingFileLogger): int = + result = 0 + for line in logger.f.lines(): + result.inc() + +proc countFiles(filename: string): int = + # Example: file.log.1 + result = 0 + let (dir, name, ext) = splitFile(filename) + for kind, path in walkDir(dir): + if kind == pcFile: + let llfn = name & ext & ExtSep + if path.extractFilename.startsWith(llfn): + let numS = path.extractFilename[llfn.len .. -1] + try: + let num = parseInt(numS) + if num > result: + result = num + except EInvalidValue: discard + +proc newRollingFileLogger*(filename = defaultFilename(), + mode: TFileMode = fmReadWrite, + levelThreshold = lvlAll, + fmtStr = defaultFmtStr, + maxLines = 1000): PRollingFileLogger = + ## Creates a new rolling file logger. Once a file reaches ``maxLines`` lines + ## a new log file will be started and the old will be renamed. + new(result) + result.levelThreshold = levelThreshold + result.fmtStr = defaultFmtStr + result.maxLines = maxLines + result.f = open(filename, mode) + result.curLine = 0 + result.baseName = filename + result.baseMode = mode + + result.logFiles = countFiles(filename) + + if mode == fmAppend: + # We need to get a line count because we will be appending to the file. + result.curLine = countLogLines(result) + +proc rotate(logger: PRollingFileLogger) = + let (dir, name, ext) = splitFile(logger.baseName) + for i in countdown(logger.logFiles, 0): + let srcSuff = if i != 0: ExtSep & $i else: "" + moveFile(dir / (name & ext & srcSuff), + dir / (name & ext & ExtSep & $(i+1))) + +method log*(logger: PRollingFileLogger, level: TLevel, + frmt: string, args: varargs[string, `$`]) = + ## Logs to a file using rolling ``logger`` only. + if level >= logger.levelThreshold: + if logger.curLine >= logger.maxLines: + logger.f.close() + rotate(logger) + logger.logFiles.inc + logger.curLine = 0 + logger.f = open(logger.baseName, logger.baseMode) + + writeln(logger.f, LevelNames[level], " ", frmt % args) + logger.curLine.inc + +# -------- + +var + level* = lvlAll ## global log filter + handlers*: seq[PLogger] = @[] ## handlers with their own log levels + +proc logLoop(level: TLevel, frmt: string, args: varargs[string, `$`]) = + for logger in items(handlers): + if level >= logger.levelThreshold: + log(logger, level, frmt, args) + +template log*(level: TLevel, frmt: string, args: varargs[string, `$`]) = + ## Logs a message to all registered handlers at the given level. + bind logLoop + bind `%` + bind logging.Level + + if level >= logging.Level: + logLoop(level, frmt, args) + +template debug*(frmt: string, args: varargs[string, `$`]) = + ## Logs a debug message to all registered handlers. + log(lvlDebug, frmt, args) + +template info*(frmt: string, args: varargs[string, `$`]) = + ## Logs an info message to all registered handlers. + log(lvlInfo, frmt, args) + +template warn*(frmt: string, args: varargs[string, `$`]) = + ## Logs a warning message to all registered handlers. + log(lvlWarn, frmt, args) + +template error*(frmt: string, args: varargs[string, `$`]) = + ## Logs an error message to all registered handlers. + log(lvlError, frmt, args) + +template fatal*(frmt: string, args: varargs[string, `$`]) = + ## Logs a fatal error message to all registered handlers. + log(lvlFatal, frmt, args) + + +# -------------- + +when isMainModule: + var L = newConsoleLogger() + var fL = newFileLogger("test.log", fmtStr = verboseFmtStr) + var rL = newRollingFileLogger("rolling.log", fmtStr = verboseFmtStr) + handlers.add(L) + handlers.add(fL) + handlers.add(rL) + for i in 0 .. 25: + info("hello" & $i, []) + + diff --git a/web/nimrod.ini b/web/nimrod.ini index 9af3bc226..71e36dcdc 100644 --- a/web/nimrod.ini +++ b/web/nimrod.ini @@ -62,7 +62,7 @@ srcdoc2: "pure/ftpclient;pure/memfiles;pure/subexes;pure/collections/critbits" srcdoc2: "pure/asyncio;pure/actors;core/locks;pure/oids;pure/endians;pure/uri" srcdoc2: "pure/nimprof;pure/unittest;packages/docutils/highlite" srcdoc2: "packages/docutils/rst;packages/docutils/rstast" -srcdoc2: "packages/docutils/rstgen" +srcdoc2: "packages/docutils/rstgen;pure/logging" webdoc: "wrappers/libcurl;pure/md5;wrappers/mysql;wrappers/iup" webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc" |