summary refs log blame commit diff stats
path: root/devel/logging.nim
blob: a10478dab42236d907e33e7323d5a7e30a328477 (plain) (tree)



















                                                                               

                          






                                                       

                                                       


                                         
                                                              

   


                                                                               



                                                                               

                                                                                  




                                                                               
  
                                                                         

                                                                        
                                               
                 


                                                                                      
    
 
























                                                  



                                          
                                                       




                                                                        
                                                        


                                                                                 
                                                        






                                                                              

      





                                                                      

                                                  
                                                                


                                        
                               
 

        



                                                        
                                                         

                                                         
                                                                      

                                        
                               

                                 
                    






                                                                        
 


                                                      
                                                        

                                                  
  


                                                   

   

                                                                         
 
                                                                       

                                      
                                    
 
                                                                        

                                      


                    


                              
                                                           
                         
                           
 
                                                           
                         
                          
 
                                                           
                           
                          
 
                                                            
                          
                           
  
                                                             
                               
                           
 











                                    
#
#
#            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", [])