Merge pull request #4705 from yglukhov/js-logging
Logging compatible with js
This commit is contained in:
commit
63851b2c91
2 changed files with 122 additions and 108 deletions
|
|
@ -47,7 +47,9 @@
|
||||||
## **Warning:** The global list of handlers is a thread var, this means that
|
## **Warning:** The global list of handlers is a thread var, this means that
|
||||||
## the handlers must be re-added in each thread.
|
## the handlers must be re-added in each thread.
|
||||||
|
|
||||||
import strutils, os, times
|
import strutils, times
|
||||||
|
when not defined(js):
|
||||||
|
import os
|
||||||
|
|
||||||
type
|
type
|
||||||
Level* = enum ## logging level
|
Level* = enum ## logging level
|
||||||
|
|
@ -77,21 +79,24 @@ type
|
||||||
ConsoleLogger* = ref object of Logger ## logger that writes the messages to the
|
ConsoleLogger* = ref object of Logger ## logger that writes the messages to the
|
||||||
## console
|
## console
|
||||||
|
|
||||||
FileLogger* = ref object of Logger ## logger that writes the messages to a file
|
when not defined(js):
|
||||||
file*: File ## the wrapped file.
|
type
|
||||||
|
FileLogger* = ref object of Logger ## logger that writes the messages to a file
|
||||||
|
file*: File ## the wrapped file.
|
||||||
|
|
||||||
RollingFileLogger* = ref object of FileLogger ## logger that writes the
|
RollingFileLogger* = ref object of FileLogger ## logger that writes the
|
||||||
## messages to a file and
|
## messages to a file and
|
||||||
## performs log rotation
|
## performs log rotation
|
||||||
maxLines: int # maximum number of lines
|
maxLines: int # maximum number of lines
|
||||||
curLine : int
|
curLine : int
|
||||||
baseName: string # initial filename
|
baseName: string # initial filename
|
||||||
baseMode: FileMode # initial file mode
|
baseMode: FileMode # initial file mode
|
||||||
logFiles: int # how many log files already created, e.g. basename.1, basename.2...
|
logFiles: int # how many log files already created, e.g. basename.1, basename.2...
|
||||||
bufSize: int # size of output buffer (-1: use system defaults, 0: unbuffered, >0: fixed buffer size)
|
bufSize: int # size of output buffer (-1: use system defaults, 0: unbuffered, >0: fixed buffer size)
|
||||||
|
|
||||||
{.deprecated: [TLevel: Level, PLogger: Logger, PConsoleLogger: ConsoleLogger,
|
{.deprecated: [PFileLogger: FileLogger, PRollingFileLogger: RollingFileLogger].}
|
||||||
PFileLogger: FileLogger, PRollingFileLogger: RollingFileLogger].}
|
|
||||||
|
{.deprecated: [TLevel: Level, PLogger: Logger, PConsoleLogger: ConsoleLogger].}
|
||||||
|
|
||||||
var
|
var
|
||||||
level {.threadvar.}: Level ## global log filter
|
level {.threadvar.}: Level ## global log filter
|
||||||
|
|
@ -112,7 +117,7 @@ proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): str
|
||||||
else:
|
else:
|
||||||
inc(i)
|
inc(i)
|
||||||
var v = ""
|
var v = ""
|
||||||
var app = getAppFilename()
|
let app = when defined(js): "" else: getAppFilename()
|
||||||
while frmt[i] in IdentChars:
|
while frmt[i] in IdentChars:
|
||||||
v.add(toLower(frmt[i]))
|
v.add(toLower(frmt[i]))
|
||||||
inc(i)
|
inc(i)
|
||||||
|
|
@ -121,8 +126,10 @@ proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): str
|
||||||
of "time": result.add(getClockStr())
|
of "time": result.add(getClockStr())
|
||||||
of "datetime": result.add(getDateStr() & "T" & getClockStr())
|
of "datetime": result.add(getDateStr() & "T" & getClockStr())
|
||||||
of "app": result.add(app)
|
of "app": result.add(app)
|
||||||
of "appdir": result.add(app.splitFile.dir)
|
of "appdir":
|
||||||
of "appname": result.add(app.splitFile.name)
|
when not defined(js): result.add(app.splitFile.dir)
|
||||||
|
of "appname":
|
||||||
|
when not defined(js): result.add(app.splitFile.name)
|
||||||
of "levelid": result.add(LevelNames[level][0])
|
of "levelid": result.add(LevelNames[level][0])
|
||||||
of "levelname": result.add(LevelNames[level])
|
of "levelname": result.add(LevelNames[level])
|
||||||
else: discard
|
else: discard
|
||||||
|
|
@ -139,19 +146,13 @@ method log*(logger: Logger, level: Level, args: varargs[string, `$`]) {.
|
||||||
method log*(logger: ConsoleLogger, level: Level, args: varargs[string, `$`]) =
|
method log*(logger: ConsoleLogger, level: Level, args: varargs[string, `$`]) =
|
||||||
## Logs to the console using ``logger`` only.
|
## Logs to the console using ``logger`` only.
|
||||||
if level >= logging.level and level >= logger.levelThreshold:
|
if level >= logging.level and level >= logger.levelThreshold:
|
||||||
writeLine(stdout, substituteLog(logger.fmtStr, level, args))
|
let ln = substituteLog(logger.fmtStr, level, args)
|
||||||
if level in {lvlError, lvlFatal}: flushFile(stdout)
|
when defined(js):
|
||||||
|
let cln: cstring = ln
|
||||||
method log*(logger: FileLogger, level: Level, args: varargs[string, `$`]) =
|
{.emit: "console.log(`cln`);".}
|
||||||
## Logs to a file using ``logger`` only.
|
else:
|
||||||
if level >= logging.level and level >= logger.levelThreshold:
|
writeLine(stdout, ln)
|
||||||
writeLine(logger.file, substituteLog(logger.fmtStr, level, args))
|
if level in {lvlError, lvlFatal}: flushFile(stdout)
|
||||||
if level in {lvlError, lvlFatal}: flushFile(logger.file)
|
|
||||||
|
|
||||||
proc defaultFilename*(): string =
|
|
||||||
## Returns the default filename for a logger.
|
|
||||||
var (path, name, _) = splitFile(getAppFilename())
|
|
||||||
result = changeFileExt(path / name, "log")
|
|
||||||
|
|
||||||
proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): ConsoleLogger =
|
proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): ConsoleLogger =
|
||||||
## Creates a new console logger. This logger logs to the console.
|
## Creates a new console logger. This logger logs to the console.
|
||||||
|
|
@ -159,87 +160,99 @@ proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): Console
|
||||||
result.fmtStr = fmtStr
|
result.fmtStr = fmtStr
|
||||||
result.levelThreshold = levelThreshold
|
result.levelThreshold = levelThreshold
|
||||||
|
|
||||||
proc newFileLogger*(filename = defaultFilename(),
|
when not defined(js):
|
||||||
mode: FileMode = fmAppend,
|
method log*(logger: FileLogger, level: Level, args: varargs[string, `$`]) =
|
||||||
levelThreshold = lvlAll,
|
## Logs to a file using ``logger`` only.
|
||||||
fmtStr = defaultFmtStr,
|
if level >= logging.level and level >= logger.levelThreshold:
|
||||||
bufSize: int = -1): FileLogger =
|
writeLine(logger.file, substituteLog(logger.fmtStr, level, args))
|
||||||
## Creates a new file logger. This logger logs to a file.
|
if level in {lvlError, lvlFatal}: flushFile(logger.file)
|
||||||
## Use ``bufSize`` as size of the output buffer when writing the file
|
|
||||||
## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size).
|
|
||||||
new(result)
|
|
||||||
result.levelThreshold = levelThreshold
|
|
||||||
result.file = open(filename, mode, bufSize = bufSize)
|
|
||||||
result.fmtStr = fmtStr
|
|
||||||
|
|
||||||
# ------
|
proc defaultFilename*(): string =
|
||||||
|
## Returns the default filename for a logger.
|
||||||
|
var (path, name, _) = splitFile(getAppFilename())
|
||||||
|
result = changeFileExt(path / name, "log")
|
||||||
|
|
||||||
proc countLogLines(logger: RollingFileLogger): int =
|
proc newFileLogger*(filename = defaultFilename(),
|
||||||
result = 0
|
mode: FileMode = fmAppend,
|
||||||
for line in logger.file.lines():
|
levelThreshold = lvlAll,
|
||||||
result.inc()
|
fmtStr = defaultFmtStr,
|
||||||
|
bufSize: int = -1): FileLogger =
|
||||||
|
## Creates a new file logger. This logger logs to a file.
|
||||||
|
## Use ``bufSize`` as size of the output buffer when writing the file
|
||||||
|
## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size).
|
||||||
|
new(result)
|
||||||
|
result.levelThreshold = levelThreshold
|
||||||
|
result.file = open(filename, mode, bufSize = bufSize)
|
||||||
|
result.fmtStr = fmtStr
|
||||||
|
|
||||||
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 ValueError: discard
|
|
||||||
|
|
||||||
proc newRollingFileLogger*(filename = defaultFilename(),
|
proc countLogLines(logger: RollingFileLogger): int =
|
||||||
mode: FileMode = fmReadWrite,
|
result = 0
|
||||||
levelThreshold = lvlAll,
|
for line in logger.file.lines():
|
||||||
fmtStr = defaultFmtStr,
|
result.inc()
|
||||||
maxLines = 1000,
|
|
||||||
bufSize: int = -1): RollingFileLogger =
|
|
||||||
## 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.
|
|
||||||
## Use ``bufSize`` as size of the output buffer when writing the file
|
|
||||||
## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size).
|
|
||||||
new(result)
|
|
||||||
result.levelThreshold = levelThreshold
|
|
||||||
result.fmtStr = fmtStr
|
|
||||||
result.maxLines = maxLines
|
|
||||||
result.bufSize = bufSize
|
|
||||||
result.file = open(filename, mode, bufSize=result.bufSize)
|
|
||||||
result.curLine = 0
|
|
||||||
result.baseName = filename
|
|
||||||
result.baseMode = mode
|
|
||||||
|
|
||||||
result.logFiles = countFiles(filename)
|
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 ValueError: discard
|
||||||
|
|
||||||
if mode == fmAppend:
|
proc newRollingFileLogger*(filename = defaultFilename(),
|
||||||
# We need to get a line count because we will be appending to the file.
|
mode: FileMode = fmReadWrite,
|
||||||
result.curLine = countLogLines(result)
|
levelThreshold = lvlAll,
|
||||||
|
fmtStr = defaultFmtStr,
|
||||||
|
maxLines = 1000,
|
||||||
|
bufSize: int = -1): RollingFileLogger =
|
||||||
|
## 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.
|
||||||
|
## Use ``bufSize`` as size of the output buffer when writing the file
|
||||||
|
## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size).
|
||||||
|
new(result)
|
||||||
|
result.levelThreshold = levelThreshold
|
||||||
|
result.fmtStr = fmtStr
|
||||||
|
result.maxLines = maxLines
|
||||||
|
result.bufSize = bufSize
|
||||||
|
result.file = open(filename, mode, bufSize=result.bufSize)
|
||||||
|
result.curLine = 0
|
||||||
|
result.baseName = filename
|
||||||
|
result.baseMode = mode
|
||||||
|
|
||||||
proc rotate(logger: RollingFileLogger) =
|
result.logFiles = countFiles(filename)
|
||||||
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: RollingFileLogger, level: Level, args: varargs[string, `$`]) =
|
if mode == fmAppend:
|
||||||
## Logs to a file using rolling ``logger`` only.
|
# We need to get a line count because we will be appending to the file.
|
||||||
if level >= logging.level and level >= logger.levelThreshold:
|
result.curLine = countLogLines(result)
|
||||||
if logger.curLine >= logger.maxLines:
|
|
||||||
logger.file.close()
|
|
||||||
rotate(logger)
|
|
||||||
logger.logFiles.inc
|
|
||||||
logger.curLine = 0
|
|
||||||
logger.file = open(logger.baseName, logger.baseMode, bufSize = logger.bufSize)
|
|
||||||
|
|
||||||
writeLine(logger.file, substituteLog(logger.fmtStr, level, args))
|
proc rotate(logger: RollingFileLogger) =
|
||||||
if level in {lvlError, lvlFatal}: flushFile(logger.file)
|
let (dir, name, ext) = splitFile(logger.baseName)
|
||||||
logger.curLine.inc
|
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: RollingFileLogger, level: Level, args: varargs[string, `$`]) =
|
||||||
|
## Logs to a file using rolling ``logger`` only.
|
||||||
|
if level >= logging.level and level >= logger.levelThreshold:
|
||||||
|
if logger.curLine >= logger.maxLines:
|
||||||
|
logger.file.close()
|
||||||
|
rotate(logger)
|
||||||
|
logger.logFiles.inc
|
||||||
|
logger.curLine = 0
|
||||||
|
logger.file = open(logger.baseName, logger.baseMode, bufSize = logger.bufSize)
|
||||||
|
|
||||||
|
writeLine(logger.file, substituteLog(logger.fmtStr, level, args))
|
||||||
|
if level in {lvlError, lvlFatal}: flushFile(logger.file)
|
||||||
|
logger.curLine.inc
|
||||||
|
|
||||||
# --------
|
# --------
|
||||||
|
|
||||||
|
|
@ -323,10 +336,11 @@ proc getLogFilter*(): Level =
|
||||||
|
|
||||||
when not defined(testing) and isMainModule:
|
when not defined(testing) and isMainModule:
|
||||||
var L = newConsoleLogger()
|
var L = newConsoleLogger()
|
||||||
var fL = newFileLogger("test.log", fmtStr = verboseFmtStr)
|
when not defined(js):
|
||||||
var rL = newRollingFileLogger("rolling.log", fmtStr = verboseFmtStr)
|
var fL = newFileLogger("test.log", fmtStr = verboseFmtStr)
|
||||||
|
var rL = newRollingFileLogger("rolling.log", fmtStr = verboseFmtStr)
|
||||||
|
addHandler(fL)
|
||||||
|
addHandler(rL)
|
||||||
addHandler(L)
|
addHandler(L)
|
||||||
addHandler(fL)
|
|
||||||
addHandler(rL)
|
|
||||||
for i in 0 .. 25:
|
for i in 0 .. 25:
|
||||||
info("hello", i)
|
info("hello", i)
|
||||||
|
|
|
||||||
|
|
@ -228,8 +228,8 @@ proc jsTests(r: var TResults, cat: Category, options: string) =
|
||||||
"varres/tvartup", "misc/tints", "misc/tunsignedinc"]:
|
"varres/tvartup", "misc/tints", "misc/tunsignedinc"]:
|
||||||
test "tests/" & testfile & ".nim"
|
test "tests/" & testfile & ".nim"
|
||||||
|
|
||||||
for testfile in ["pure/strutils", "pure/json", "pure/random", "pure/times"]:
|
for testfile in ["strutils", "json", "random", "times", "logging"]:
|
||||||
test "lib/" & testfile & ".nim"
|
test "lib/pure/" & testfile & ".nim"
|
||||||
|
|
||||||
# ------------------------- manyloc -------------------------------------------
|
# ------------------------- manyloc -------------------------------------------
|
||||||
#proc runSpecialTests(r: var TResults, options: string) =
|
#proc runSpecialTests(r: var TResults, options: string) =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue