//
// 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 web
using util
**
** LogMod is used log requests according to the W3C extended log file format.
**
** See [pod doc]`pod-doc#log`
**
const class LogMod : WebMod
{
//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////
**
** Constructor with it-block.
**
new make(|This|? f := null)
{
if (f != null) f.call(this)
logger = FileLogger
{
it.dir = this.dir
it.filename = this.filename
it.onOpen = |out| { onOpen(out) }
}
}
//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////
**
** Directory used to store log file(s).
**
const File dir := noDir
private static const File noDir := File(`no-dir-configured`)
**
** 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 "web-{YYYY-MM}.log".
**
const Str filename := ""
**
** Format of the log records as a string of #Fields names.
** See [pod doc]`pod-doc#log`
**
const Str fields := "date time c-ip cs(X-Real-IP) cs-method cs-uri-stem cs-uri-query sc-status time-taken cs(User-Agent) cs(Referer)"
//////////////////////////////////////////////////////////////////////////
// Lifecycle
//////////////////////////////////////////////////////////////////////////
private Void onOpen(OutStream out)
{
// write prefix
out.printLine("#Remark ==========================================================================")
out.printLine("#Remark " + DateTime.now.toLocale)
out.printLine("#Version 1.0")
out.printLine("#Software ${Type.of(this)} ${Pod.of(this).version}")
out.printLine("#Start-Date " + DateTime.nowUtc.toLocale("YYYY-MM-DD hh:mm:ss"))
out.printLine("#Fields $fields")
}
override Void onStop()
{
logger.stop
}
override Void onService()
{
try
{
s := StrBuf(256)
fields.split.each |Str field, Int i|
{
if (i != 0) s.add(" ")
// lookup format method for field
m := formatters[field]
if (m != null)
{
s.add(m.call(req, res))
return;
}
// cs(HeaderName)
if (field.startsWith("cs("))
{
s.add(formatCsHeader(req, field[3..-2]))
return
}
// unknown field name
s.add("-")
}
logger.writeStr(s.toStr)
}
catch (Err e)
{
logger.writeStr("# $e")
}
}
//////////////////////////////////////////////////////////////////////////
// Formatters
//////////////////////////////////////////////////////////////////////////
internal static const Str:Method formatters :=
[
"date": #formatDate,
"time": #formatTime,
"c-ip": #formatCIp,
"c-port": #formatCPort,
"cs-method": #formatCsMethod,
"cs-uri": #formatCsUri,
"cs-uri-stem": #formatCsUriStem,
"cs-uri-query": #formatCsUriQuery,
"sc-status": #formatScStatus,
"time-taken": #formatTimeTaken,
]
internal static Str formatDate(WebReq req, WebRes res)
{
return DateTime.nowUtc.toLocale("YYYY-MM-DD")
}
internal static Str formatTime(WebReq req, WebRes res)
{
return DateTime.nowUtc.toLocale("hh:mm:ss")
}
internal static Str formatCIp(WebReq req, WebRes res)
{
return req.remoteAddr.numeric
}
internal static Str formatCPort(WebReq req, WebRes res)
{
return req.remotePort.toStr
}
internal static Str formatCsMethod(WebReq req, WebRes res)
{
return req.method
}
internal static Str formatCsUri(WebReq req, WebRes res)
{
return req.uri.encode
}
internal static Str formatCsUriStem(WebReq req, WebRes res)
{
return req.uri.pathOnly.encode
}
internal static Str formatCsUriQuery(WebReq req, WebRes res)
{
if (req.uri.query.isEmpty) return "-"
return Uri.encodeQuery(req.uri.query)
}
internal static Str formatScStatus(WebReq req, WebRes res)
{
return res.statusCode.toStr
}
internal static Str formatTimeTaken(WebReq req, WebRes res)
{
d := Duration.now - req.stash["web.startTime"]
return d.toMillis.toStr
}
internal static Str formatCsHeader(WebReq req, Str headerName)
{
s := req.headers[headerName]
if (s == null || s.isEmpty) return "-"
return "\"" + s.replace("\"", "\"\"") + "\""
}
//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////
private const FileLogger logger
}