/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.catalina.valves; import java.io.IOException; import java.io.StringReader; import java.net.InetAddress; import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.TimeZone; import javax.servlet.http.Cookie; import javax.servlet.http.HttpSession; import org.apache.catalina.Lifecycle; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.util.ServerInfo; import org.jboss.logging.Logger; import org.jboss.logging.Logger; /** * An implementation of the W3c Extended Log File Format. See * http://www.w3.org/TR/WD-logfile.html for more information about the format. * * The following fields are supported: * * * * *

* Log rotation can be on or off. This is dictated by the rotatable * property. *

* *

* For UvNIX users, another field called checkExistsis also * available. If set to true, the log file's existence will be checked before * each logging. This way an external log rotator can move the file * somewhere and tomcat will start with a new file. *

* *

* For JMX junkies, a public method called rotate has * been made available to allow you to tell this instance to move * the existing log file to somewhere else start writing a new log file. *

* *

* Conditional logging is also supported. This can be done with the * condition property. * If the value returned from ServletRequest.getAttribute(condition) * yields a non-null value. The logging will be skipped. *

* *

* For extended attributes coming from a getAttribute() call, * it is you responsibility to ensure there are no newline or * control characters. *

* * * @author Tim Funk * @author Peter Rossbach * * @version $Revision: 1.1 $ $Date: 2012/08/17 14:41:45 $ */ public class ExtendedAccessLogValve extends AccessLogValve implements Lifecycle { private static Logger log = Logger.getLogger(ExtendedAccessLogValve.class); // ----------------------------------------------------- Instance Variables /** * The descriptive information about this implementation. */ protected static final String extendedAccessLogInfo = "org.apache.catalina.valves.ExtendedAccessLogValve/2.1"; // ------------------------------------------------------------- Properties /** * Return descriptive information about this implementation. */ public String getInfo() { return (extendedAccessLogInfo); } // --------------------------------------------------------- Public Methods // -------------------------------------------------------- Private Methods /** * Wrap the incoming value into quotes and escape any inner * quotes with double quotes. * * @param value - The value to wrap quotes around * @return '-' if empty of null. Otherwise, toString() will * be called on the object and the value will be wrapped * in quotes and any quotes will be escaped with 2 * sets of quotes. */ private String wrap(Object value) { String svalue; // Does the value contain a " ? If so must encode it if (value == null || "-".equals(value)) return "-"; try { svalue = value.toString(); if ("".equals(svalue)) return "-"; } catch (Throwable e) { /* Log error */ return "-"; } /* Wrap all quotes in double quotes. */ StringBuffer buffer = new StringBuffer(svalue.length() + 2); buffer.append('\''); int i = 0; while (i < svalue.length()) { int j = svalue.indexOf('\'', i); if (j == -1) { buffer.append(svalue.substring(i)); i = svalue.length(); } else { buffer.append(svalue.substring(i, j + 1)); buffer.append('"'); i = j + 2; } } buffer.append('\''); return buffer.toString(); } /** * Open the new log file for the date specified by dateStamp. */ protected synchronized void open() { super.open(); if (currentLogFile.length()==0) { writer.println("#Fields: " + pattern); writer.println("#Version: 2.0"); writer.println("#Software: " + ServerInfo.getServerInfo()); } } // ------------------------------------------------------ Lifecycle Methods protected class DateElement implements AccessLogElement { private Date currentDate = new Date(0); private String currentDateString = null; /** * A date formatter to format a Date into a date in the format * "yyyy-MM-dd". */ private SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd"); public DateElement() { dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT")); } public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { if (currentDate != date) { synchronized (this) { if (currentDate != date) { currentDateString = dateFormatter.format(date); currentDate = date; } } } buf.append(currentDateString); } } protected class TimeElement implements AccessLogElement { private Date currentDate = new Date(0); private String currentTimeString = null; /** * A date formatter to format a Date into a time in the format * "kk:mm:ss" (kk is a 24-hour representation of the hour). */ private SimpleDateFormat timeFormatter = new SimpleDateFormat("HH:mm:ss"); public TimeElement() { timeFormatter.setTimeZone(TimeZone.getTimeZone("GMT")); } public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { if (currentDate != date) { synchronized (this) { if (currentDate != date) { currentTimeString = timeFormatter.format(date); currentDate = date; } } } buf.append(currentTimeString); } } protected class RequestHeaderElement implements AccessLogElement { private String header; public RequestHeaderElement(String header) { this.header = header; } public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getHeader(header))); } } protected class ResponseHeaderElement implements AccessLogElement { private String header; public ResponseHeaderElement(String header) { this.header = header; } public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(wrap(response.getHeader(header))); } } protected class ServletContextElement implements AccessLogElement { private String attribute; public ServletContextElement(String attribute) { this.attribute = attribute; } public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getContext().getServletContext() .getAttribute(attribute))); } } protected class CookieElement implements AccessLogElement { private String name; public CookieElement(String name) { this.name = name; } public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { Cookie[] c = request.getCookies(); for (int i = 0; c != null && i < c.length; i++) { if (name.equals(c[i].getName())) { buf.append(wrap(c[i].getValue())); } } } } /** * write a specific response header - x-O(xxx) */ protected class ResponseAllHeaderElement implements AccessLogElement { private String header; public ResponseAllHeaderElement(String header) { this.header = header; } public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { if (null != response) { String[] values = response.getHeaderValues(header); if(values.length > 0) { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < values.length; i++) { String string = values[i]; buffer.append(string) ; if(i+1 0) { whiteSpaces.append(buf); buf = new StringBuffer(); } int c = sr.read(); while (Character.isWhitespace((char) c)) { whiteSpaces.append((char) c); c = sr.read(); } if (c == -1) { ended = true; } else { buf.append((char) c); } return whiteSpaces.toString(); } public boolean isEnded() { return ended; } public String getRemains() throws IOException { StringBuffer remains = new StringBuffer(); for(int c = sr.read(); c != -1; c = sr.read()) { remains.append((char) c); } return remains.toString(); } } protected AccessLogElement[] createLogElements() { if (log.isDebugEnabled()) { log.debug("decodePattern, pattern =" + pattern); } List list = new ArrayList(); PatternTokenizer tokenizer = new PatternTokenizer(pattern); try { // Ignore leading whitespace. tokenizer.getWhiteSpaces(); if (tokenizer.isEnded()) { log.info("pattern was just empty or whitespace"); return null; } String token = tokenizer.getToken(); while (token != null) { if (log.isDebugEnabled()) { log.debug("token = " + token); } AccessLogElement element = getLogElement(token, tokenizer); if (element == null) { break; } list.add(element); String whiteSpaces = tokenizer.getWhiteSpaces(); if (whiteSpaces.length() > 0) { list.add(new StringElement(whiteSpaces)); } if (tokenizer.isEnded()) { break; } token = tokenizer.getToken(); } if (log.isDebugEnabled()) { log.debug("finished decoding with element size of: " + list.size()); } return (AccessLogElement[]) list.toArray(new AccessLogElement[0]); } catch (IOException e) { log.error("parse error", e); return null; } } protected AccessLogElement getLogElement(String token, PatternTokenizer tokenizer) throws IOException { if ("date".equals(token)) { return new DateElement(); } else if ("time".equals(token)) { if (tokenizer.hasSubToken()) { String nextToken = tokenizer.getToken(); if ("taken".equals(nextToken)) { return new ElapsedTimeElement(false); } } else { return new TimeElement(); } } else if ("bytes".equals(token)) { return new ByteSentElement(true); } else if ("cached".equals(token)) { /* I don't know how to evaluate this! */ return new StringElement("-"); } else if ("c".equals(token)) { String nextToken = tokenizer.getToken(); if ("ip".equals(nextToken)) { return new RemoteAddrElement(); } else if ("dns".equals(nextToken)) { return new HostElement(); } } else if ("s".equals(token)) { String nextToken = tokenizer.getToken(); if ("ip".equals(nextToken)) { return new LocalAddrElement(); } else if ("dns".equals(nextToken)) { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { String value; try { value = InetAddress.getLocalHost().getHostName(); } catch (Throwable e) { value = "localhost"; } buf.append(value); } }; } } else if ("cs".equals(token)) { return getClientToServerElement(tokenizer); } else if ("sc".equals(token)) { return getServerToClientElement(tokenizer); } else if ("sr".equals(token) || "rs".equals(token)) { return getProxyElement(tokenizer); } else if ("x".equals(token)) { return getXParameterElement(tokenizer); } log.error("unable to decode with rest of chars starting: " + token); return null; } protected AccessLogElement getClientToServerElement( PatternTokenizer tokenizer) throws IOException { if (tokenizer.hasSubToken()) { String token = tokenizer.getToken(); if ("method".equals(token)) { return new MethodElement(); } else if ("uri".equals(token)) { if (tokenizer.hasSubToken()) { token = tokenizer.getToken(); if ("stem".equals(token)) { return new RequestURIElement(); } else if ("query".equals(token)) { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { String query = request.getQueryString(); if (query != null) { buf.append(query); } else { buf.append('-'); } } }; } } else { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { String query = request.getQueryString(); if (query == null) { buf.append(request.getRequestURI()); } else { buf.append(request.getRequestURI()); buf.append('?'); buf.append(request.getQueryString()); } } }; } } } else if (tokenizer.hasParameter()) { String parameter = tokenizer.getParameter(); if (parameter == null) { log.error("No closing ) found for in decode"); return null; } return new RequestHeaderElement(parameter); } log.error("The next characters couldn't be decoded: " + tokenizer.getRemains()); return null; } protected AccessLogElement getServerToClientElement( PatternTokenizer tokenizer) throws IOException { if (tokenizer.hasSubToken()) { String token = tokenizer.getToken(); if ("status".equals(token)) { return new HttpStatusCodeElement(); } else if ("comment".equals(token)) { return new StringElement("?"); } } else if (tokenizer.hasParameter()) { String parameter = tokenizer.getParameter(); if (parameter == null) { log.error("No closing ) found for in decode"); return null; } return new ResponseHeaderElement(parameter); } log.error("The next characters couldn't be decoded: " + tokenizer.getRemains()); return null; } protected AccessLogElement getProxyElement(PatternTokenizer tokenizer) throws IOException { String token = null; if (tokenizer.hasSubToken()) { token = tokenizer.getToken(); return new StringElement("-"); } else if (tokenizer.hasParameter()) { tokenizer.getParameter(); return new StringElement("-"); } log.error("The next characters couldn't be decoded: " + token); return null; } protected AccessLogElement getXParameterElement(PatternTokenizer tokenizer) throws IOException { if (!tokenizer.hasSubToken()) { log.error("x param in wrong format. Needs to be 'x-#(...)' read the docs!"); return null; } String token = tokenizer.getToken(); if (!tokenizer.hasParameter()) { log.error("x param in wrong format. Needs to be 'x-#(...)' read the docs!"); return null; } String parameter = tokenizer.getParameter(); if (parameter == null) { log.error("No closing ) found for in decode"); return null; } if ("A".equals(token)) { return new ServletContextElement(parameter); } else if ("C".equals(token)) { return new CookieElement(parameter); } else if ("R".equals(token)) { return new RequestAttributeElement(parameter); } else if ("S".equals(token)) { return new SessionAttributeElement(parameter); } else if ("H".equals(token)) { return getServletRequestElement(parameter); } else if ("P".equals(token)) { return new RequestParameterElement(parameter); } else if ("O".equals(token)) { return new ResponseAllHeaderElement(parameter); } log.error("x param for servlet request, couldn't decode value: " + token); return null; } protected AccessLogElement getServletRequestElement(String parameter) { if ("authType".equals(parameter)) { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getAuthType())); } }; } else if ("remoteUser".equals(parameter)) { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getRemoteUser())); } }; } else if ("requestedSessionId".equals(parameter)) { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getRequestedSessionId())); } }; } else if ("requestedSessionIdFromCookie".equals(parameter)) { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(wrap("" + request.isRequestedSessionIdFromCookie())); } }; } else if ("requestedSessionIdValid".equals(parameter)) { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(wrap("" + request.isRequestedSessionIdValid())); } }; } else if ("contentLength".equals(parameter)) { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(wrap("" + request.getContentLength())); } }; } else if ("characterEncoding".equals(parameter)) { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getCharacterEncoding())); } }; } else if ("locale".equals(parameter)) { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getLocale())); } }; } else if ("protocol".equals(parameter)) { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getProtocol())); } }; } else if ("scheme".equals(parameter)) { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(request.getScheme()); } }; } else if ("secure".equals(parameter)) { return new AccessLogElement() { public void addElement(StringBuffer buf, Date date, Request request, Response response, long time) { buf.append(wrap("" + request.isSecure())); } }; } log.error("x param for servlet request, couldn't decode value: " + parameter); return null; } }