//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 8 Apr 08 Brian Frank Creation
//
using concurrent
**
** FileLogger appends Str log entries to a file. You
** can add a FileLogger as a Log handler:
**
** sysLogger := FileLogger
** {
** dir = scriptDir
** filename = "sys-{YYMM}.log"
** }
** Log.addHandler |rec| { sysLogger.writeLogRec(rec) }
**
** See `filename` for specifying a datetime pattern for your log files.
**
const class FileLogger : ActorPool
{
**
** Constructor must set `dir` and `filename`
**
new make(|This|? f := null) : super(f) {}
**
** Directory used to store log file(s).
**
const File dir
**
** Log filename pattern. The name may contain a pattern between
** '{}' using the pattern format of `sys::DateTime.toLocale`. For
** example to maintain a log file per month, use a filename such
** as "mylog-{YYYY-MM}.log".
**
const Str filename
**
** Callback called each time the file logger opens an existing
** or new log file. Callback should write any header information
** to the given output stream. The callback will occur on the logger's
** actor, so take care not incur additional actor messaging.
**
const |OutStream|? onOpen
**
** Append string log message to file.
**
Void writeLogRec(LogRec rec)
{
actor.send(rec)
}
**
** Append string log message to file.
**
Void writeStr(Str msg)
{
actor.send(msg)
}
**
** Run the script
**
internal Obj? receive(Obj msg)
{
try
{
// get or initialize current state
state := Actor.locals["state"] as FileLoggerState
if (state == null)
Actor.locals["state"] = state = FileLoggerState(this)
// append to current file
if (msg is LogRec)
{
rec := (LogRec)msg
state.out.printLine(rec)
if (rec.err != null) rec.err.trace(state.out)
state.out.flush
}
else
{
state.out.printLine(msg).flush
}
}
catch (Err e)
{
log.err("FileLogger.receive", e)
}
return null
}
private const static Log log := Log.get("logger")
private const Actor actor := Actor(this) |msg| { receive(msg) }
}
internal class FileLoggerState
{
new make(FileLogger logger)
{
this.logger = logger
this.dir = logger.dir
this.filename = logger.filename
i := filename.index("{")
if (i != null)
this.pattern = filename[i+1 ..< filename.index("}")]
else
open(dir + filename.toUri)
}
OutStream out()
{
// check if we need to open a new file
if (pattern != null && DateTime.now.toLocale(pattern) != curPattern)
{
// if we currently have a file open, then close it
curOut?.close
// open new file with new pattern
curPattern = DateTime.now.toLocale(pattern)
newName := filename[0..<filename.index("{")] +
curPattern +
filename[filename.index("}")+1..-1]
curFile := dir + newName.toUri
return open(curFile)
}
// current output stream
return curOut
}
OutStream open(File curFile)
{
try
{
this.curOut = curFile.out(true)
}
catch (Err e)
{
echo("ERROR: Cannot open log file: $curFile")
e.trace
this.curOut = NilOutStream()
}
try
logger.onOpen?.call(this.curOut)
catch (Err e)
curOut.printLine("ERROR: FileLogger.onOpen\n${e.traceToStr}")
return this.curOut
}
const FileLogger logger
const Str filename
const File dir
Str? pattern
Str curPattern := ""
OutStream? curOut
}
internal class NilOutStream : OutStream
{
new make() : super(null) {}
override This write(Int byte) { this }
override This writeBuf(Buf buf, Int n := buf.remaining) { this }
override This flush() { this }
override This sync() { this }
override Bool close() { true }
}