#line 1623 "/home/ubuntu/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 """
variant 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);
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;
}
}