// // 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 }