//======================================================================== //Copyright 1997-2006 Mort Bay Consulting Pty. Ltd. //------------------------------------------------------------------------ //Licensed under the Apache License, Version 2.0 (the "License"); //you may not use this file except in compliance with the License. //You may obtain a copy of the License at //http://www.apache.org/licenses/LICENSE-2.0 //Unless required by applicable law or agreed to in writing, software //distributed under the License is distributed on an "AS IS" BASIS, //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //See the License for the specific language governing permissions and //limitations under the License. //======================================================================== package net.i2p.jetty; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Locale; import java.util.TimeZone; import javax.servlet.http.Cookie; import org.eclipse.jetty.http.PathMap; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.DateCache; import org.eclipse.jetty.util.RolloverFileOutputStream; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.log.Log; /** * This {@link RequestLog} implementation outputs logs in the pseudo-standard NCSA common log format. * Configuration options allow a choice between the standard Common Log Format (as used in the 3 log format) * and the Combined Log Format (single log format). * This log format can be output by most web servers, and almost all web log analysis software can understand * these formats. * * ** I2P Mods ** * * For Jetty 5, this extended NCSARequestLog to * override log() to put in the requestor's destination hash, * instead of 127.0.0.1, * which is placed in the X-I2P-DestHash field in the request headers * by I2PTunnelHTTPServer. * But we also had to modify NCSARequestLog to do so, to change private * fields to protected. * * So that we will work with system Jetty 6 packages, we just copy the whole thing * and modify log() as required. * * @author Greg Wilkins * @author Nigel Canonizado * */ public class I2PRequestLog extends AbstractLifeCycle implements RequestLog { private String _filename; private boolean _extended; private boolean _append; private int _retainDays; private boolean _closeOut; private boolean _preferProxiedForAddress; private String _logDateFormat="dd/MMM/yyyy:HH:mm:ss Z"; private String _filenameDateFormat = null; private Locale _logLocale = Locale.getDefault(); private String _logTimeZone = "GMT"; private String[] _ignorePaths; private boolean _logLatency = false; private boolean _logCookies = false; private boolean _logServer = false; private boolean _b64; private transient OutputStream _out; private transient OutputStream _fileOut; private transient DateCache _logDateCache; private transient PathMap<String> _ignorePathMap; private transient Writer _writer; private transient ArrayList<Utf8StringBuilder> _buffers; private transient char[] _copy; public I2PRequestLog() { _extended = true; _append = true; _retainDays = 31; } /* ------------------------------------------------------------ */ /** * @param filename The filename for the request log. This may be in the format expected by {@link RolloverFileOutputStream} */ public I2PRequestLog(String filename) { _extended = true; _append = true; _retainDays = 31; setFilename(filename); } /* ------------------------------------------------------------ */ /** * @param filename The filename for the request log. This may be in the format expected by {@link RolloverFileOutputStream} */ public void setFilename(String filename) { if (filename != null) { filename = filename.trim(); if (filename.length() == 0) filename = null; } _filename = filename; } public String getFilename() { return _filename; } public String getDatedFilename() { if (_fileOut instanceof RolloverFileOutputStream) return ((RolloverFileOutputStream)_fileOut).getDatedFilename(); return null; } /* ------------------------------------------------------------ */ /** * @param format Format for the timestamps in the log file. If not set, * the pre-formated request timestamp is used. */ public void setLogDateFormat(String format) { _logDateFormat = format; } public String getLogDateFormat() { return _logDateFormat; } public void setLogLocale(Locale logLocale) { _logLocale = logLocale; } public Locale getLogLocale() { return _logLocale; } public void setLogTimeZone(String tz) { _logTimeZone = tz; } public String getLogTimeZone() { return _logTimeZone; } public void setRetainDays(int retainDays) { _retainDays = retainDays; } public int getRetainDays() { return _retainDays; } public void setExtended(boolean extended) { _extended = extended; } public boolean isExtended() { return _extended; } public void setAppend(boolean append) { _append = append; } public boolean isAppend() { return _append; } public void setIgnorePaths(String[] ignorePaths) { _ignorePaths = ignorePaths; } public String[] getIgnorePaths() { return _ignorePaths; } public void setLogCookies(boolean logCookies) { _logCookies = logCookies; } public boolean getLogCookies() { return _logCookies; } public boolean getLogServer() { return _logServer; } public void setLogServer(boolean logServer) { _logServer=logServer; } public void setLogLatency(boolean logLatency) { _logLatency = logLatency; } public boolean getLogLatency() { return _logLatency; } public void setPreferProxiedForAddress(boolean preferProxiedForAddress) { _preferProxiedForAddress = preferProxiedForAddress; } /** * @param b64 true to enable base 64 logging. False for base 32 logging. Default false. * @since 0.9.24 */ public void setB64(boolean b64) { _b64 = b64; } /* ------------------------------------------------------------ */ public void log(Request request, Response response) { if (!isStarted()) return; try { if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null) return; if (_fileOut == null) return; Utf8StringBuilder u8buf; StringBuilder buf; synchronized(_writer) { int size=_buffers.size(); u8buf = size==0?new Utf8StringBuilder(160):_buffers.remove(size-1); buf = u8buf.getStringBuilder(); } synchronized(buf) // for efficiency until we can use StringBuilder { if (_logServer) { buf.append(request.getServerName()); buf.append(' '); } String addr = null; if (_preferProxiedForAddress) { addr = request.getHeader("X-Forwarded-For"); } if (addr == null) { if (_b64) { addr = request.getHeader("X-I2P-DestHash"); if (addr != null) addr += ".i2p"; } else { // 52chars.b32.i2p addr = request.getHeader("X-I2P-DestB32"); } if (addr == null) addr = request.getRemoteAddr(); } buf.append(addr); buf.append(" - "); String user = request.getRemoteUser(); buf.append((user == null)? " - " : user); buf.append(" ["); if (_logDateCache!=null) buf.append(_logDateCache.format(request.getTimeStamp())); else //buf.append(request.getTimeStampBuffer().toString()); // TODO SimpleDateFormat or something buf.append(request.getTimeStamp()); buf.append("] \""); buf.append(request.getMethod()); buf.append(' '); request.getUri().writeTo(u8buf); buf.append(' '); buf.append(request.getProtocol()); buf.append("\" "); int status = response.getStatus(); if (status<=0) status=404; buf.append((char)('0'+((status/100)%10))); buf.append((char)('0'+((status/10)%10))); buf.append((char)('0'+(status%10))); long responseLength=response.getContentCount(); if (responseLength >=0) { buf.append(' '); if (responseLength > 99999) buf.append(Long.toString(responseLength)); else { if (responseLength > 9999) buf.append((char)('0' + ((responseLength / 10000)%10))); if (responseLength > 999) buf.append((char)('0' + ((responseLength /1000)%10))); if (responseLength > 99) buf.append((char)('0' + ((responseLength / 100)%10))); if (responseLength > 9) buf.append((char)('0' + ((responseLength / 10)%10))); buf.append((char)('0' + (responseLength)%10)); } buf.append(' '); } else buf.append(" - "); } if (!_extended && !_logCookies && !_logLatency) { synchronized(_writer) { buf.append(System.getProperty("line.separator", "\n")); int l=buf.length(); if (l>_copy.length) l=_copy.length; buf.getChars(0,l,_copy,0); _writer.write(_copy,0,l); _writer.flush(); u8buf.reset(); _buffers.add(u8buf); } } else { synchronized(_writer) { int l=buf.length(); if (l>_copy.length) l=_copy.length; buf.getChars(0,l,_copy,0); _writer.write(_copy,0,l); u8buf.reset(); _buffers.add(u8buf); // TODO do outside synchronized scope if (_extended) logExtended(request, response, _writer); // TODO do outside synchronized scope if (_logCookies) { Cookie[] cookies = request.getCookies(); if (cookies == null || cookies.length == 0) _writer.write(" -"); else { _writer.write(" \""); for (int i = 0; i < cookies.length; i++) { if (i != 0) _writer.write(';'); _writer.write(cookies[i].getName()); _writer.write('='); _writer.write(cookies[i].getValue()); } _writer.write('\"'); } } if (_logLatency) { _writer.write(' '); _writer.write(Long.toString(System.currentTimeMillis() - request.getTimeStamp())); } _writer.write(System.getProperty("line.separator", "\n")); _writer.flush(); } } } catch (IOException e) { Log.getLogger((String)null).warn(e); } } /* ------------------------------------------------------------ */ protected void logExtended(Request request, Response response, Writer writer) throws IOException { String referer = request.getHeader("Referer"); if (referer == null) writer.write("\"-\" "); else { writer.write('"'); writer.write(referer); writer.write("\" "); } String agent = request.getHeader("User-Agent"); if (agent == null) writer.write("\"-\" "); else { writer.write('"'); writer.write(agent); writer.write('"'); } } /* ------------------------------------------------------------ */ protected void doStart() throws Exception { if (_logDateFormat!=null) { _logDateCache = new DateCache(_logDateFormat, _logLocale, _logTimeZone); } if (_filename != null) { _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null); _closeOut = true; Log.getLogger((String)null).info("Opened "+getDatedFilename()); } else _fileOut = System.err; _out = _fileOut; if (_ignorePaths != null && _ignorePaths.length > 0) { _ignorePathMap = new PathMap<String>(); for (int i = 0; i < _ignorePaths.length; i++) _ignorePathMap.put(_ignorePaths[i], _ignorePaths[i]); } else _ignorePathMap = null; _writer = new OutputStreamWriter(_out, "UTF-8"); _buffers = new ArrayList<Utf8StringBuilder>(); _copy = new char[1024]; super.doStart(); } /* ------------------------------------------------------------ */ protected void doStop() throws Exception { super.doStop(); try {if (_writer != null) _writer.flush();} catch (IOException e) {Log.getLogger((String)null).ignore(e);} if (_out != null && _closeOut) try {_out.close();} catch (IOException e) {Log.getLogger((String)null).ignore(e);} _out = null; _fileOut = null; _closeOut = false; _logDateCache = null; _writer = null; _buffers = null; _copy = null; } /* ------------------------------------------------------------ */ /** * @return the log File Date Format */ public String getFilenameDateFormat() { return _filenameDateFormat; } /* ------------------------------------------------------------ */ /** Set the log file date format. * see RolloverFileOutputStream(String, boolean, int, TimeZone, String, String) * @param logFileDateFormat the logFileDateFormat to pass to RolloverFileOutputStream */ public void setFilenameDateFormat(String logFileDateFormat) { _filenameDateFormat=logFileDateFormat; } }