logging.nim 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Andreas Rumpf, Dominik Picheta
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements a simple logger.
  10. ##
  11. ## It has been designed to be as simple as possible to avoid bloat.
  12. ## If this library does not fulfill your needs, write your own.
  13. ##
  14. ## Basic usage
  15. ## ===========
  16. ##
  17. ## To get started, first create a logger:
  18. ##
  19. ## ```Nim
  20. ## import std/logging
  21. ##
  22. ## var logger = newConsoleLogger()
  23. ## ```
  24. ##
  25. ## The logger that was created above logs to the console, but this module
  26. ## also provides loggers that log to files, such as the
  27. ## `FileLogger<#FileLogger>`_. Creating custom loggers is also possible by
  28. ## inheriting from the `Logger<#Logger>`_ type.
  29. ##
  30. ## Once a logger has been created, call its `log proc
  31. ## <#log.e,ConsoleLogger,Level,varargs[string,]>`_ to log a message:
  32. ##
  33. ## ```Nim
  34. ## logger.log(lvlInfo, "a log message")
  35. ## # Output: INFO a log message
  36. ## ```
  37. ##
  38. ## The ``INFO`` within the output is the result of a format string being
  39. ## prepended to the message, and it will differ depending on the message's
  40. ## level. Format strings are `explained in more detail
  41. ## here<#basic-usage-format-strings>`_.
  42. ##
  43. ## There are six logging levels: debug, info, notice, warn, error, and fatal.
  44. ## They are described in more detail within the `Level enum's documentation
  45. ## <#Level>`_. A message is logged if its level is at or above both the logger's
  46. ## ``levelThreshold`` field and the global log filter. The latter can be changed
  47. ## with the `setLogFilter proc<#setLogFilter,Level>`_.
  48. ##
  49. ## .. warning::
  50. ## For loggers that log to a console or to files, only error and fatal
  51. ## messages will cause their output buffers to be flushed immediately by default.
  52. ## set ``flushThreshold`` when creating the logger to change this.
  53. ##
  54. ## Handlers
  55. ## --------
  56. ##
  57. ## When using multiple loggers, calling the log proc for each logger can
  58. ## become repetitive. Instead of doing that, register each logger that will be
  59. ## used with the `addHandler proc<#addHandler,Logger>`_, which is demonstrated
  60. ## in the following example:
  61. ##
  62. ## ```Nim
  63. ## import std/logging
  64. ##
  65. ## var consoleLog = newConsoleLogger()
  66. ## var fileLog = newFileLogger("errors.log", levelThreshold=lvlError)
  67. ## var rollingLog = newRollingFileLogger("rolling.log")
  68. ##
  69. ## addHandler(consoleLog)
  70. ## addHandler(fileLog)
  71. ## addHandler(rollingLog)
  72. ## ```
  73. ##
  74. ## After doing this, use either the `log template
  75. ## <#log.t,Level,varargs[string,]>`_ or one of the level-specific templates,
  76. ## such as the `error template<#error.t,varargs[string,]>`_, to log messages
  77. ## to all registered handlers at once.
  78. ##
  79. ## ```Nim
  80. ## # This example uses the loggers created above
  81. ## log(lvlError, "an error occurred")
  82. ## error("an error occurred") # Equivalent to the above line
  83. ## info("something normal happened") # Will not be written to errors.log
  84. ## ```
  85. ##
  86. ## Note that a message's level is still checked against each handler's
  87. ## ``levelThreshold`` and the global log filter.
  88. ##
  89. ## Format strings
  90. ## --------------
  91. ##
  92. ## Log messages are prefixed with format strings. These strings contain
  93. ## placeholders for variables, such as ``$time``, that are replaced with their
  94. ## corresponding values, such as the current time, before they are prepended to
  95. ## a log message. Characters that are not part of variables are unaffected.
  96. ##
  97. ## The format string used by a logger can be specified by providing the `fmtStr`
  98. ## argument when creating the logger or by setting its `fmtStr` field afterward.
  99. ## If not specified, the `default format string<#defaultFmtStr>`_ is used.
  100. ##
  101. ## The following variables, which must be prefixed with a dollar sign (``$``),
  102. ## are available:
  103. ##
  104. ## ============ =======================
  105. ## Variable Output
  106. ## ============ =======================
  107. ## $date Current date
  108. ## $time Current time
  109. ## $datetime $dateT$time
  110. ## $app `os.getAppFilename()<os.html#getAppFilename>`_
  111. ## $appname Base name of ``$app``
  112. ## $appdir Directory name of ``$app``
  113. ## $levelid First letter of log level
  114. ## $levelname Log level name
  115. ## ============ =======================
  116. ##
  117. ## Note that ``$app``, ``$appname``, and ``$appdir`` are not supported when
  118. ## using the JavaScript backend.
  119. ##
  120. ## The following example illustrates how to use format strings:
  121. ##
  122. ## ```Nim
  123. ## import std/logging
  124. ##
  125. ## var logger = newConsoleLogger(fmtStr="[$time] - $levelname: ")
  126. ## logger.log(lvlInfo, "this is a message")
  127. ## # Output: [19:50:13] - INFO: this is a message
  128. ## ```
  129. ##
  130. ## Notes when using multiple threads
  131. ## ---------------------------------
  132. ##
  133. ## There are a few details to keep in mind when using this module within
  134. ## multiple threads:
  135. ## * The global log filter is actually a thread-local variable, so it needs to
  136. ## be set in each thread that uses this module.
  137. ## * The list of registered handlers is also a thread-local variable. If a
  138. ## handler will be used in multiple threads, it needs to be registered in
  139. ## each of those threads.
  140. ##
  141. ## See also
  142. ## ========
  143. ## * `strutils module<strutils.html>`_ for common string functions
  144. ## * `strformat module<strformat.html>`_ for string interpolation and formatting
  145. ## * `strscans module<strscans.html>`_ for ``scanf`` and ``scanp`` macros, which
  146. ## offer easier substring extraction than regular expressions
  147. import std/[strutils, times]
  148. when not defined(js):
  149. import std/os
  150. when defined(nimPreviewSlimSystem):
  151. import std/syncio
  152. type
  153. Level* = enum ## \
  154. ## Enumeration of logging levels.
  155. ##
  156. ## Debug messages represent the lowest logging level, and fatal error
  157. ## messages represent the highest logging level. ``lvlAll`` can be used
  158. ## to enable all messages, while ``lvlNone`` can be used to disable all
  159. ## messages.
  160. ##
  161. ## Typical usage for each logging level, from lowest to highest, is
  162. ## described below:
  163. ##
  164. ## * **Debug** - debugging information helpful only to developers
  165. ## * **Info** - anything associated with normal operation and without
  166. ## any particular importance
  167. ## * **Notice** - more important information that users should be
  168. ## notified about
  169. ## * **Warn** - impending problems that require some attention
  170. ## * **Error** - error conditions that the application can recover from
  171. ## * **Fatal** - fatal errors that prevent the application from continuing
  172. ##
  173. ## It is completely up to the application how to utilize each level.
  174. ##
  175. ## Individual loggers have a ``levelThreshold`` field that filters out
  176. ## any messages with a level lower than the threshold. There is also
  177. ## a global filter that applies to all log messages, and it can be changed
  178. ## using the `setLogFilter proc<#setLogFilter,Level>`_.
  179. lvlAll, ## All levels active
  180. lvlDebug, ## Debug level and above are active
  181. lvlInfo, ## Info level and above are active
  182. lvlNotice, ## Notice level and above are active
  183. lvlWarn, ## Warn level and above are active
  184. lvlError, ## Error level and above are active
  185. lvlFatal, ## Fatal level and above are active
  186. lvlNone ## No levels active; nothing is logged
  187. const
  188. LevelNames*: array[Level, string] = [
  189. "DEBUG", "DEBUG", "INFO", "NOTICE", "WARN", "ERROR", "FATAL", "NONE"
  190. ] ## Array of strings representing each logging level.
  191. defaultFmtStr* = "$levelname " ## The default format string.
  192. verboseFmtStr* = "$levelid, [$datetime] -- $appname: " ## \
  193. ## A more verbose format string.
  194. ##
  195. ## This string can be passed as the ``frmStr`` argument to procs that create
  196. ## new loggers, such as the `newConsoleLogger proc<#newConsoleLogger>`_.
  197. ##
  198. ## If a different format string is preferred, refer to the
  199. ## `documentation about format strings<#basic-usage-format-strings>`_
  200. ## for more information, including a list of available variables.
  201. defaultFlushThreshold = when NimMajor >= 2:
  202. when defined(nimV1LogFlushBehavior): lvlError else: lvlAll
  203. else:
  204. when defined(nimFlushAllLogs): lvlAll else: lvlError
  205. ## The threshold above which log messages to file-like loggers
  206. ## are automatically flushed.
  207. ##
  208. ## By default, only error and fatal messages are logged,
  209. ## but defining ``-d:nimFlushAllLogs`` will make all levels be flushed
  210. type
  211. Logger* = ref object of RootObj
  212. ## The abstract base type of all loggers.
  213. ##
  214. ## Custom loggers should inherit from this type. They should also provide
  215. ## their own implementation of the
  216. ## `log method<#log.e,Logger,Level,varargs[string,]>`_.
  217. ##
  218. ## See also:
  219. ## * `ConsoleLogger<#ConsoleLogger>`_
  220. ## * `FileLogger<#FileLogger>`_
  221. ## * `RollingFileLogger<#RollingFileLogger>`_
  222. levelThreshold*: Level ## Only messages that are at or above this
  223. ## threshold will be logged
  224. fmtStr*: string ## Format string to prepend to each log message;
  225. ## defaultFmtStr is the default
  226. ConsoleLogger* = ref object of Logger
  227. ## A logger that writes log messages to the console.
  228. ##
  229. ## Create a new ``ConsoleLogger`` with the `newConsoleLogger proc
  230. ## <#newConsoleLogger>`_.
  231. ##
  232. ## See also:
  233. ## * `FileLogger<#FileLogger>`_
  234. ## * `RollingFileLogger<#RollingFileLogger>`_
  235. useStderr*: bool ## If true, writes to stderr; otherwise, writes to stdout
  236. flushThreshold*: Level ## Only messages that are at or above this
  237. ## threshold will be flushed immediately
  238. when not defined(js):
  239. type
  240. FileLogger* = ref object of Logger
  241. ## A logger that writes log messages to a file.
  242. ##
  243. ## Create a new ``FileLogger`` with the `newFileLogger proc
  244. ## <#newFileLogger,File>`_.
  245. ##
  246. ## **Note:** This logger is not available for the JavaScript backend.
  247. ##
  248. ## See also:
  249. ## * `ConsoleLogger<#ConsoleLogger>`_
  250. ## * `RollingFileLogger<#RollingFileLogger>`_
  251. file*: File ## The wrapped file
  252. flushThreshold*: Level ## Only messages that are at or above this
  253. ## threshold will be flushed immediately
  254. RollingFileLogger* = ref object of FileLogger
  255. ## A logger that writes log messages to a file while performing log
  256. ## rotation.
  257. ##
  258. ## Create a new ``RollingFileLogger`` with the `newRollingFileLogger proc
  259. ## <#newRollingFileLogger,FileMode,Positive,int>`_.
  260. ##
  261. ## **Note:** This logger is not available for the JavaScript backend.
  262. ##
  263. ## See also:
  264. ## * `ConsoleLogger<#ConsoleLogger>`_
  265. ## * `FileLogger<#FileLogger>`_
  266. maxLines: int # maximum number of lines
  267. curLine: int
  268. baseName: string # initial filename
  269. baseMode: FileMode # initial file mode
  270. logFiles: int # how many log files already created, e.g. basename.1, basename.2...
  271. bufSize: int # size of output buffer (-1: use system defaults, 0: unbuffered, >0: fixed buffer size)
  272. var
  273. level {.threadvar.}: Level ## global log filter
  274. handlers {.threadvar.}: seq[Logger] ## handlers with their own log levels
  275. proc substituteLog*(frmt: string, level: Level,
  276. args: varargs[string, `$`]): string =
  277. ## Formats a log message at the specified level with the given format string.
  278. ##
  279. ## The `format variables<#basic-usage-format-strings>`_ present within
  280. ## ``frmt`` will be replaced with the corresponding values before being
  281. ## prepended to ``args`` and returned.
  282. ##
  283. ## Unless you are implementing a custom logger, there is little need to call
  284. ## this directly. Use either a logger's log method or one of the logging
  285. ## templates.
  286. ##
  287. ## See also:
  288. ## * `log method<#log.e,ConsoleLogger,Level,varargs[string,]>`_
  289. ## for the ConsoleLogger
  290. ## * `log method<#log.e,FileLogger,Level,varargs[string,]>`_
  291. ## for the FileLogger
  292. ## * `log method<#log.e,RollingFileLogger,Level,varargs[string,]>`_
  293. ## for the RollingFileLogger
  294. ## * `log template<#log.t,Level,varargs[string,]>`_
  295. runnableExamples:
  296. doAssert substituteLog(defaultFmtStr, lvlInfo, "a message") == "INFO a message"
  297. doAssert substituteLog("$levelid - ", lvlError, "an error") == "E - an error"
  298. doAssert substituteLog("$levelid", lvlDebug, "error") == "Derror"
  299. var msgLen = 0
  300. for arg in args:
  301. msgLen += arg.len
  302. result = newStringOfCap(frmt.len + msgLen + 20)
  303. var i = 0
  304. while i < frmt.len:
  305. if frmt[i] != '$':
  306. result.add(frmt[i])
  307. inc(i)
  308. else:
  309. inc(i)
  310. var v = ""
  311. let app = when defined(js): "" else: getAppFilename()
  312. while i < frmt.len and frmt[i] in IdentChars:
  313. v.add(toLowerAscii(frmt[i]))
  314. inc(i)
  315. case v
  316. of "date": result.add(getDateStr())
  317. of "time": result.add(getClockStr())
  318. of "datetime": result.add(getDateStr() & "T" & getClockStr())
  319. of "app": result.add(app)
  320. of "appdir":
  321. when not defined(js): result.add(app.splitFile.dir)
  322. of "appname":
  323. when not defined(js): result.add(app.splitFile.name)
  324. of "levelid": result.add(LevelNames[level][0])
  325. of "levelname": result.add(LevelNames[level])
  326. else: discard
  327. for arg in args:
  328. result.add(arg)
  329. method log*(logger: Logger, level: Level, args: varargs[string, `$`]) {.
  330. raises: [Exception], gcsafe,
  331. tags: [RootEffect], base.} =
  332. ## Override this method in custom loggers. The default implementation does
  333. ## nothing.
  334. ##
  335. ## See also:
  336. ## * `log method<#log.e,ConsoleLogger,Level,varargs[string,]>`_
  337. ## for the ConsoleLogger
  338. ## * `log method<#log.e,FileLogger,Level,varargs[string,]>`_
  339. ## for the FileLogger
  340. ## * `log method<#log.e,RollingFileLogger,Level,varargs[string,]>`_
  341. ## for the RollingFileLogger
  342. ## * `log template<#log.t,Level,varargs[string,]>`_
  343. discard
  344. method log*(logger: ConsoleLogger, level: Level, args: varargs[string, `$`]) =
  345. ## Logs to the console with the given `ConsoleLogger<#ConsoleLogger>`_ only.
  346. ##
  347. ## This method ignores the list of registered handlers.
  348. ##
  349. ## Whether the message is logged depends on both the ConsoleLogger's
  350. ## ``levelThreshold`` field and the global log filter set using the
  351. ## `setLogFilter proc<#setLogFilter,Level>`_.
  352. ##
  353. ## **Note:** Only error and fatal messages will cause the output buffer
  354. ## to be flushed immediately by default. Set ``flushThreshold`` when creating
  355. ## the logger to change this.
  356. ##
  357. ## See also:
  358. ## * `log method<#log.e,FileLogger,Level,varargs[string,]>`_
  359. ## for the FileLogger
  360. ## * `log method<#log.e,RollingFileLogger,Level,varargs[string,]>`_
  361. ## for the RollingFileLogger
  362. ## * `log template<#log.t,Level,varargs[string,]>`_
  363. ##
  364. ## **Examples:**
  365. ##
  366. ## ```Nim
  367. ## var consoleLog = newConsoleLogger()
  368. ## consoleLog.log(lvlInfo, "this is a message")
  369. ## consoleLog.log(lvlError, "error code is: ", 404)
  370. ## ```
  371. if level >= logging.level and level >= logger.levelThreshold:
  372. let ln = substituteLog(logger.fmtStr, level, args)
  373. when defined(js):
  374. let cln = ln.cstring
  375. case level
  376. of lvlDebug: {.emit: "console.debug(`cln`);".}
  377. of lvlInfo: {.emit: "console.info(`cln`);".}
  378. of lvlWarn: {.emit: "console.warn(`cln`);".}
  379. of lvlError: {.emit: "console.error(`cln`);".}
  380. else: {.emit: "console.log(`cln`);".}
  381. else:
  382. try:
  383. var handle = stdout
  384. if logger.useStderr:
  385. handle = stderr
  386. writeLine(handle, ln)
  387. if level >= logger.flushThreshold: flushFile(handle)
  388. except IOError:
  389. discard
  390. proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr,
  391. useStderr = false, flushThreshold = defaultFlushThreshold): ConsoleLogger =
  392. ## Creates a new `ConsoleLogger<#ConsoleLogger>`_.
  393. ##
  394. ## By default, log messages are written to ``stdout``. If ``useStderr`` is
  395. ## true, they are written to ``stderr`` instead.
  396. ##
  397. ## For the JavaScript backend, log messages are written to the console,
  398. ## and ``useStderr`` is ignored.
  399. ##
  400. ## See also:
  401. ## * `newFileLogger proc<#newFileLogger,File>`_ that uses a file handle
  402. ## * `newFileLogger proc<#newFileLogger,FileMode,int>`_
  403. ## that accepts a filename
  404. ## * `newRollingFileLogger proc<#newRollingFileLogger,FileMode,Positive,int>`_
  405. ##
  406. ## **Examples:**
  407. ##
  408. ## ```Nim
  409. ## var normalLog = newConsoleLogger()
  410. ## var formatLog = newConsoleLogger(fmtStr=verboseFmtStr)
  411. ## var errorLog = newConsoleLogger(levelThreshold=lvlError, useStderr=true)
  412. ## ```
  413. new result
  414. result.fmtStr = fmtStr
  415. result.levelThreshold = levelThreshold
  416. result.flushThreshold = flushThreshold
  417. result.useStderr = useStderr
  418. when not defined(js):
  419. method log*(logger: FileLogger, level: Level, args: varargs[string, `$`]) =
  420. ## Logs a message at the specified level using the given
  421. ## `FileLogger<#FileLogger>`_ only.
  422. ##
  423. ## This method ignores the list of registered handlers.
  424. ##
  425. ## Whether the message is logged depends on both the FileLogger's
  426. ## ``levelThreshold`` field and the global log filter set using the
  427. ## `setLogFilter proc<#setLogFilter,Level>`_.
  428. ##
  429. ## **Notes:**
  430. ## * Only error and fatal messages will cause the output buffer
  431. ## to be flushed immediately by default. Set ``flushThreshold`` when creating
  432. ## the logger to change this.
  433. ## * This method is not available for the JavaScript backend.
  434. ##
  435. ## See also:
  436. ## * `log method<#log.e,ConsoleLogger,Level,varargs[string,]>`_
  437. ## for the ConsoleLogger
  438. ## * `log method<#log.e,RollingFileLogger,Level,varargs[string,]>`_
  439. ## for the RollingFileLogger
  440. ## * `log template<#log.t,Level,varargs[string,]>`_
  441. ##
  442. ## **Examples:**
  443. ##
  444. ## ```Nim
  445. ## var fileLog = newFileLogger("messages.log")
  446. ## fileLog.log(lvlInfo, "this is a message")
  447. ## fileLog.log(lvlError, "error code is: ", 404)
  448. ## ```
  449. if level >= logging.level and level >= logger.levelThreshold:
  450. writeLine(logger.file, substituteLog(logger.fmtStr, level, args))
  451. if level >= logger.flushThreshold: flushFile(logger.file)
  452. proc defaultFilename*(): string =
  453. ## Returns the filename that is used by default when naming log files.
  454. ##
  455. ## **Note:** This proc is not available for the JavaScript backend.
  456. var (path, name, _) = splitFile(getAppFilename())
  457. result = changeFileExt(path / name, "log")
  458. proc newFileLogger*(file: File,
  459. levelThreshold = lvlAll,
  460. fmtStr = defaultFmtStr,
  461. flushThreshold = defaultFlushThreshold): FileLogger =
  462. ## Creates a new `FileLogger<#FileLogger>`_ that uses the given file handle.
  463. ##
  464. ## **Note:** This proc is not available for the JavaScript backend.
  465. ##
  466. ## See also:
  467. ## * `newConsoleLogger proc<#newConsoleLogger>`_
  468. ## * `newFileLogger proc<#newFileLogger,FileMode,int>`_
  469. ## that accepts a filename
  470. ## * `newRollingFileLogger proc<#newRollingFileLogger,FileMode,Positive,int>`_
  471. ##
  472. ## **Examples:**
  473. ##
  474. ## ```Nim
  475. ## var messages = open("messages.log", fmWrite)
  476. ## var formatted = open("formatted.log", fmWrite)
  477. ## var errors = open("errors.log", fmWrite)
  478. ##
  479. ## var normalLog = newFileLogger(messages)
  480. ## var formatLog = newFileLogger(formatted, fmtStr=verboseFmtStr)
  481. ## var errorLog = newFileLogger(errors, levelThreshold=lvlError)
  482. ## ```
  483. new(result)
  484. result.file = file
  485. result.levelThreshold = levelThreshold
  486. result.flushThreshold = flushThreshold
  487. result.fmtStr = fmtStr
  488. proc newFileLogger*(filename = defaultFilename(),
  489. mode: FileMode = fmAppend,
  490. levelThreshold = lvlAll,
  491. fmtStr = defaultFmtStr,
  492. bufSize: int = -1,
  493. flushThreshold = defaultFlushThreshold): FileLogger =
  494. ## Creates a new `FileLogger<#FileLogger>`_ that logs to a file with the
  495. ## given filename.
  496. ##
  497. ## ``bufSize`` controls the size of the output buffer that is used when
  498. ## writing to the log file. The following values can be provided:
  499. ## * ``-1`` - use system defaults
  500. ## * ``0`` - unbuffered
  501. ## * ``> 0`` - fixed buffer size
  502. ##
  503. ## **Note:** This proc is not available for the JavaScript backend.
  504. ##
  505. ## See also:
  506. ## * `newConsoleLogger proc<#newConsoleLogger>`_
  507. ## * `newFileLogger proc<#newFileLogger,File>`_ that uses a file handle
  508. ## * `newRollingFileLogger proc<#newRollingFileLogger,FileMode,Positive,int>`_
  509. ##
  510. ## **Examples:**
  511. ##
  512. ## ```Nim
  513. ## var normalLog = newFileLogger("messages.log")
  514. ## var formatLog = newFileLogger("formatted.log", fmtStr=verboseFmtStr)
  515. ## var errorLog = newFileLogger("errors.log", levelThreshold=lvlError)
  516. ## ```
  517. let file = open(filename, mode, bufSize = bufSize)
  518. newFileLogger(file, levelThreshold, fmtStr, flushThreshold)
  519. # ------
  520. proc countLogLines(logger: RollingFileLogger): int =
  521. let fp = open(logger.baseName, fmRead)
  522. for line in fp.lines():
  523. result.inc()
  524. fp.close()
  525. proc countFiles(filename: string): int =
  526. # Example: file.log.1
  527. result = 0
  528. var (dir, name, ext) = splitFile(filename)
  529. if dir == "":
  530. dir = "."
  531. for kind, path in walkDir(dir):
  532. if kind == pcFile:
  533. let llfn = name & ext & ExtSep
  534. if path.extractFilename.startsWith(llfn):
  535. let numS = path.extractFilename[llfn.len .. ^1]
  536. try:
  537. let num = parseInt(numS)
  538. if num > result:
  539. result = num
  540. except ValueError: discard
  541. proc newRollingFileLogger*(filename = defaultFilename(),
  542. mode: FileMode = fmReadWrite,
  543. levelThreshold = lvlAll,
  544. fmtStr = defaultFmtStr,
  545. maxLines: Positive = 1000,
  546. bufSize: int = -1,
  547. flushThreshold = defaultFlushThreshold): RollingFileLogger =
  548. ## Creates a new `RollingFileLogger<#RollingFileLogger>`_.
  549. ##
  550. ## Once the current log file being written to contains ``maxLines`` lines,
  551. ## a new log file will be created, and the old log file will be renamed.
  552. ##
  553. ## ``bufSize`` controls the size of the output buffer that is used when
  554. ## writing to the log file. The following values can be provided:
  555. ## * ``-1`` - use system defaults
  556. ## * ``0`` - unbuffered
  557. ## * ``> 0`` - fixed buffer size
  558. ##
  559. ## **Note:** This proc is not available in the JavaScript backend.
  560. ##
  561. ## See also:
  562. ## * `newConsoleLogger proc<#newConsoleLogger>`_
  563. ## * `newFileLogger proc<#newFileLogger,File>`_ that uses a file handle
  564. ## * `newFileLogger proc<#newFileLogger,FileMode,int>`_
  565. ## that accepts a filename
  566. ##
  567. ## **Examples:**
  568. ##
  569. ## ```Nim
  570. ## var normalLog = newRollingFileLogger("messages.log")
  571. ## var formatLog = newRollingFileLogger("formatted.log", fmtStr=verboseFmtStr)
  572. ## var shortLog = newRollingFileLogger("short.log", maxLines=200)
  573. ## var errorLog = newRollingFileLogger("errors.log", levelThreshold=lvlError)
  574. ## ```
  575. new(result)
  576. result.levelThreshold = levelThreshold
  577. result.fmtStr = fmtStr
  578. result.maxLines = maxLines
  579. result.bufSize = bufSize
  580. result.file = open(filename, mode, bufSize = result.bufSize)
  581. result.curLine = 0
  582. result.baseName = filename
  583. result.baseMode = mode
  584. result.flushThreshold = flushThreshold
  585. result.logFiles = countFiles(filename)
  586. if mode == fmAppend:
  587. # We need to get a line count because we will be appending to the file.
  588. result.curLine = countLogLines(result)
  589. proc rotate(logger: RollingFileLogger) =
  590. let (dir, name, ext) = splitFile(logger.baseName)
  591. for i in countdown(logger.logFiles, 0):
  592. let srcSuff = if i != 0: ExtSep & $i else: ""
  593. moveFile(dir / (name & ext & srcSuff),
  594. dir / (name & ext & ExtSep & $(i+1)))
  595. method log*(logger: RollingFileLogger, level: Level, args: varargs[string, `$`]) =
  596. ## Logs a message at the specified level using the given
  597. ## `RollingFileLogger<#RollingFileLogger>`_ only.
  598. ##
  599. ## This method ignores the list of registered handlers.
  600. ##
  601. ## Whether the message is logged depends on both the RollingFileLogger's
  602. ## ``levelThreshold`` field and the global log filter set using the
  603. ## `setLogFilter proc<#setLogFilter,Level>`_.
  604. ##
  605. ## **Notes:**
  606. ## * Only error and fatal messages will cause the output buffer
  607. ## to be flushed immediately by default. Set ``flushThreshold`` when creating
  608. ## the logger to change this.
  609. ## * This method is not available for the JavaScript backend.
  610. ##
  611. ## See also:
  612. ## * `log method<#log.e,ConsoleLogger,Level,varargs[string,]>`_
  613. ## for the ConsoleLogger
  614. ## * `log method<#log.e,FileLogger,Level,varargs[string,]>`_
  615. ## for the FileLogger
  616. ## * `log template<#log.t,Level,varargs[string,]>`_
  617. ##
  618. ## **Examples:**
  619. ##
  620. ## ```Nim
  621. ## var rollingLog = newRollingFileLogger("messages.log")
  622. ## rollingLog.log(lvlInfo, "this is a message")
  623. ## rollingLog.log(lvlError, "error code is: ", 404)
  624. ## ```
  625. if level >= logging.level and level >= logger.levelThreshold:
  626. if logger.curLine >= logger.maxLines:
  627. logger.file.close()
  628. rotate(logger)
  629. logger.logFiles.inc
  630. logger.curLine = 0
  631. logger.file = open(logger.baseName, logger.baseMode,
  632. bufSize = logger.bufSize)
  633. writeLine(logger.file, substituteLog(logger.fmtStr, level, args))
  634. if level >= logger.flushThreshold: flushFile(logger.file)
  635. logger.curLine.inc
  636. # --------
  637. proc logLoop(level: Level, args: varargs[string, `$`]) =
  638. for logger in items(handlers):
  639. if level >= logger.levelThreshold:
  640. log(logger, level, args)
  641. template log*(level: Level, args: varargs[string, `$`]) =
  642. ## Logs a message at the specified level to all registered handlers.
  643. ##
  644. ## Whether the message is logged depends on both the FileLogger's
  645. ## `levelThreshold` field and the global log filter set using the
  646. ## `setLogFilter proc<#setLogFilter,Level>`_.
  647. ##
  648. ## **Examples:**
  649. ##
  650. ## ```Nim
  651. ## var logger = newConsoleLogger()
  652. ## addHandler(logger)
  653. ##
  654. ## log(lvlInfo, "This is an example.")
  655. ## ```
  656. ##
  657. ## See also:
  658. ## * `debug template<#debug.t,varargs[string,]>`_
  659. ## * `info template<#info.t,varargs[string,]>`_
  660. ## * `notice template<#notice.t,varargs[string,]>`_
  661. ## * `warn template<#warn.t,varargs[string,]>`_
  662. ## * `error template<#error.t,varargs[string,]>`_
  663. ## * `fatal template<#fatal.t,varargs[string,]>`_
  664. bind logLoop
  665. bind `%`
  666. bind logging.level
  667. if level >= logging.level:
  668. logLoop(level, args)
  669. template debug*(args: varargs[string, `$`]) =
  670. ## Logs a debug message to all registered handlers.
  671. ##
  672. ## Debug messages are typically useful to the application developer only,
  673. ## and they are usually disabled in release builds, although this template
  674. ## does not make that distinction.
  675. ##
  676. ## **Examples:**
  677. ##
  678. ## ```Nim
  679. ## var logger = newConsoleLogger()
  680. ## addHandler(logger)
  681. ##
  682. ## debug("myProc called with arguments: foo, 5")
  683. ## ```
  684. ##
  685. ## See also:
  686. ## * `log template<#log.t,Level,varargs[string,]>`_
  687. ## * `info template<#info.t,varargs[string,]>`_
  688. ## * `notice template<#notice.t,varargs[string,]>`_
  689. log(lvlDebug, args)
  690. template info*(args: varargs[string, `$`]) =
  691. ## Logs an info message to all registered handlers.
  692. ##
  693. ## Info messages are typically generated during the normal operation
  694. ## of an application and are of no particular importance. It can be useful to
  695. ## aggregate these messages for later analysis.
  696. ##
  697. ## **Examples:**
  698. ##
  699. ## ```Nim
  700. ## var logger = newConsoleLogger()
  701. ## addHandler(logger)
  702. ##
  703. ## info("Application started successfully.")
  704. ## ```
  705. ##
  706. ## See also:
  707. ## * `log template<#log.t,Level,varargs[string,]>`_
  708. ## * `debug template<#debug.t,varargs[string,]>`_
  709. ## * `notice template<#notice.t,varargs[string,]>`_
  710. log(lvlInfo, args)
  711. template notice*(args: varargs[string, `$`]) =
  712. ## Logs an notice to all registered handlers.
  713. ##
  714. ## Notices are semantically very similar to info messages, but they are meant
  715. ## to be messages that the user should be actively notified about, depending
  716. ## on the application.
  717. ##
  718. ## **Examples:**
  719. ##
  720. ## ```Nim
  721. ## var logger = newConsoleLogger()
  722. ## addHandler(logger)
  723. ##
  724. ## notice("An important operation has completed.")
  725. ## ```
  726. ##
  727. ## See also:
  728. ## * `log template<#log.t,Level,varargs[string,]>`_
  729. ## * `debug template<#debug.t,varargs[string,]>`_
  730. ## * `info template<#info.t,varargs[string,]>`_
  731. log(lvlNotice, args)
  732. template warn*(args: varargs[string, `$`]) =
  733. ## Logs a warning message to all registered handlers.
  734. ##
  735. ## A warning is a non-error message that may indicate impending problems or
  736. ## degraded performance.
  737. ##
  738. ## **Examples:**
  739. ##
  740. ## ```Nim
  741. ## var logger = newConsoleLogger()
  742. ## addHandler(logger)
  743. ##
  744. ## warn("The previous operation took too long to process.")
  745. ## ```
  746. ##
  747. ## See also:
  748. ## * `log template<#log.t,Level,varargs[string,]>`_
  749. ## * `error template<#error.t,varargs[string,]>`_
  750. ## * `fatal template<#fatal.t,varargs[string,]>`_
  751. log(lvlWarn, args)
  752. template error*(args: varargs[string, `$`]) =
  753. ## Logs an error message to all registered handlers.
  754. ##
  755. ## Error messages are for application-level error conditions, such as when
  756. ## some user input generated an exception. Typically, the application will
  757. ## continue to run, but with degraded functionality or loss of data, and
  758. ## these effects might be visible to users.
  759. ##
  760. ## **Examples:**
  761. ##
  762. ## ```Nim
  763. ## var logger = newConsoleLogger()
  764. ## addHandler(logger)
  765. ##
  766. ## error("An exception occurred while processing the form.")
  767. ## ```
  768. ##
  769. ## See also:
  770. ## * `log template<#log.t,Level,varargs[string,]>`_
  771. ## * `warn template<#warn.t,varargs[string,]>`_
  772. ## * `fatal template<#fatal.t,varargs[string,]>`_
  773. log(lvlError, args)
  774. template fatal*(args: varargs[string, `$`]) =
  775. ## Logs a fatal error message to all registered handlers.
  776. ##
  777. ## Fatal error messages usually indicate that the application cannot continue
  778. ## to run and will exit due to a fatal condition. This template only logs the
  779. ## message, and it is the application's responsibility to exit properly.
  780. ##
  781. ## **Examples:**
  782. ##
  783. ## ```Nim
  784. ## var logger = newConsoleLogger()
  785. ## addHandler(logger)
  786. ##
  787. ## fatal("Can't open database -- exiting.")
  788. ## ```
  789. ##
  790. ## See also:
  791. ## * `log template<#log.t,Level,varargs[string,]>`_
  792. ## * `warn template<#warn.t,varargs[string,]>`_
  793. ## * `error template<#error.t,varargs[string,]>`_
  794. log(lvlFatal, args)
  795. proc addHandler*(handler: Logger) =
  796. ## Adds a logger to the list of registered handlers.
  797. ##
  798. ## .. warning:: The list of handlers is a thread-local variable. If the given
  799. ## handler will be used in multiple threads, this proc should be called in
  800. ## each of those threads.
  801. ##
  802. ## See also:
  803. ## * `removeHandler proc`_
  804. ## * `getHandlers proc<#getHandlers>`_
  805. runnableExamples:
  806. var logger = newConsoleLogger()
  807. addHandler(logger)
  808. doAssert logger in getHandlers()
  809. handlers.add(handler)
  810. proc removeHandler*(handler: Logger) =
  811. ## Removes a logger from the list of registered handlers.
  812. ##
  813. ## Note that for n times a logger is registered, n calls to this proc
  814. ## are required to remove that logger.
  815. for i, hnd in handlers:
  816. if hnd == handler:
  817. handlers.delete(i)
  818. return
  819. proc getHandlers*(): seq[Logger] =
  820. ## Returns a list of all the registered handlers.
  821. ##
  822. ## See also:
  823. ## * `addHandler proc<#addHandler,Logger>`_
  824. return handlers
  825. proc setLogFilter*(lvl: Level) =
  826. ## Sets the global log filter.
  827. ##
  828. ## Messages below the provided level will not be logged regardless of an
  829. ## individual logger's ``levelThreshold``. By default, all messages are
  830. ## logged.
  831. ##
  832. ## .. warning:: The global log filter is a thread-local variable. If logging
  833. ## is being performed in multiple threads, this proc should be called in each
  834. ## thread unless it is intended that different threads should log at different
  835. ## logging levels.
  836. ##
  837. ## See also:
  838. ## * `getLogFilter proc<#getLogFilter>`_
  839. runnableExamples:
  840. setLogFilter(lvlError)
  841. doAssert getLogFilter() == lvlError
  842. level = lvl
  843. proc getLogFilter*(): Level =
  844. ## Gets the global log filter.
  845. ##
  846. ## See also:
  847. ## * `setLogFilter proc<#setLogFilter,Level>`_
  848. return level
  849. # --------------
  850. when not defined(testing) and isMainModule:
  851. var L = newConsoleLogger()
  852. when not defined(js):
  853. var fL = newFileLogger("test.log", fmtStr = verboseFmtStr)
  854. var rL = newRollingFileLogger("rolling.log", fmtStr = verboseFmtStr)
  855. addHandler(fL)
  856. addHandler(rL)
  857. addHandler(L)
  858. for i in 0 .. 25:
  859. info("hello", i)
  860. var nilString: string
  861. info "hello ", nilString