#line 1626 "/home/travis/build/felix-lang/felix/src/packages/web.fdoc"
  publish """
  Extensible Flexible Logger
  example:
  /* Creates two log files, my_info.log rolls over when log size exceeds 1024 bytes
     and is archived 4 times. my_debug.log does not roll over and will grow to infinite size.
     log messages with log_level INFO are routed to my_info.log.log messages with log level DEBUG
     are routed to my_debug.log */
  open Logger;
  var mylog = logger(simple_logger(
    Logger::log("log","my.log",size(1024),4ui),   INFO)+
    simple_logger(Logger::log("log","my_debug.log",size(0),0ui),  DEBUG));
  mylog(DEBUG,"Debugging enabled");
  """
  class Logger {
  
    open LowResTime;
  
    struct log {
      path:string;
      name:string;
      max_size:size;
      archives:uint;
    }
  
    publish """ Log Level definitions """
    union log_level =
      | INFO
      | WARNING
      | ERROR
      | ACCESS
      | DEBUG
      | CUSTOM1
      | CUSTOM2;
  
    publish """ Definition of log_message """
    typedef log_message = log_level*string;
  
    publish """
    Container for log handler. handles governs what log messages are sent to handles_fn
    """
    struct log_handler {
      handles: (log_message)->bool;
      handler_fn: (log_message) -> void;
    }
  
    publish """
    Simple predicate generator. Returns closusre matching message against curried
    parameter handles
    """
    fun simple_log_handles [with Eq[log_level]] (handles:log_level) (message:log_message) =>
      handles == message.(0);
  
    publish """
    Simple log handler implementation. Creates log file give log_path and log_file
    and returns clousre accepting log_message writeing to files specified
    """
    gen simple_log_handler_fn (l:log):(log_message)->void = {
      var log_handle = open_log(l); //fopen_output (l.path+"/"+l.name);
      return (proc (message:log_message)  {
                log_handle = rotate_when_larger_than_max_size(log_handle,l);
                fprintln (log_handle, "["+log_date()+"]"+" "+to_str(message));
                fflush(log_handle);
              });
    }
  
    publish """
    Simple log handler implementation for logging to console.
    """
    fun console_log_handler_fn ():(log_message)->void = {
      return (proc (message:log_message)  {
                println ("["+log_date()+"]"+" "+to_str(message));
              });
    }
  
    publish """
    Convience log_handler creator for simple logger
    """
    fun simple_logger (l:log,level:log_level):list[log_handler] =>
     list(log_handler ((simple_log_handles(level))  ,
                  simple_log_handler_fn(l)));
  
    publish """
    Convience log_handler creator for simple console logger
    """
    fun console_logger (level:log_level):list[log_handler] =>
     list(log_handler ((simple_log_handles(level))  ,
                        console_log_handler_fn()));
  
  
    publish """
    Generates logger handle used for sending messages to defined loggers.
    Accepts a list of log_handler and returns a closure accepting log_message
    writing to matching log handler
    """
    fun logger(handlers:list[log_handler]):log_message->void =  {
      var channel = mk_schannel[log_message]();
      spawn_fthread (listener(channel,handlers));
      return sender(channel);
    }
  
    publish  """Log writer runs as fthread"""
    private proc listener(chan:schannel[log_message],log_handlers:list[log_handler]) (){
      while true do
        var log_req:log_message = read chan;
        iter (proc (handler:log_handler) {
          if handler.handles log_req do
            handler.handler_fn(log_req);
          done
        }) log_handlers;
      done
      return;
    }
  
    private proc sender (log_channel:schannel[log_message]) (message:log_message) {
      write (log_channel,message);
    }
  
    instance Str[log_level] {
      fun str : log_level ->string =
        | #INFO => "[INFO]"
        | #WARNING  => "[WARNING]"
        | #ERROR  => "[ERROR]"
        | #ACCESS => "[ACCESS]"
        | #DEBUG => "[DEBUG]"
        | #CUSTOM1 => "[CUSTOM1]"
        | #CUSTOM2 => "[CUSTOM2]";
    }
  
  
    instance Eq[log_level]  {
      fun == : log_level * log_level -> bool = "$1==$2";
    }
  
    fun to_str (m:log_message):string  =>
         str(m.(0))+"\t"+m.(1);
  
    fun log_date_fmt (dt:tm) => strftime("%d/%b/%Y:%H:%M:%S %Z",dt);
  
    fun log_date () = {
      var time_epoch_seconds = time_t();
      val tm_struct =  gmtime(time_epoch_seconds);
      return log_date_fmt(tm_struct);
    }
  
    fun open_log(l:log):ofile = {
      val log_file = l.path+"/"+l.name;
      if (FileStat::fileexists log_file) and l.archives > 0ui do
        l.rotate();
      done
      var log_handle = fopen_output (log_file);
      if not valid log_handle do
        eprintln("Unable to open log at "+log_file+".\nLogging to console instead.");
        return stdout;
      else
        return log_handle;
      done
    }
  
  
    proc rotate(l:log) {
      val log_file = l.path+"/"+l.name;
      if FileStat::fileexists log_file do
        var last ="";
        for var i in l.archives downto 1ui  do
          val rlog =  log_file+"."+str(i) ;
          if FileStat::fileexists rlog and last != "" do
            if 0 != (FileSystem::rename_file (rlog, (log_file+"."+str(i+1ui)))) do
              eprintln("Unable to rotate log "+rlog+" to "+log_file+"."+str(i+1ui));
            done
          done
          last = rlog;
        done
        if 0 != (FileSystem::rename_file (log_file,(log_file+".1"))) do
          eprintln("Unable to rotate log "+log_file+" to "+log_file+".1");
        done
      done
    }
  
    fun rotate_when_larger_than_max_size(handle:ofile,l:log) = {
      if  l.max_size > size(0) and fsize(l.path+"/"+l.name) > l.max_size do
         if valid(handle) do
           fclose(handle);
         done
         return open_log(l);
      else
        return handle;
      done
    }
  
    proc fsize_: string*&size = """
      {struct stat st;
       stat($1.c_str(), &st);
       *$2 = st.st_size;}
    """;
  
    gen fsize(name:string):size = {
      var sz:size;
      fsize_(name,&sz);
      return sz;
    }
  }