001: /*
002: * LogHandler.java
003: *
004: * Brazil project web application Framework,
005: * export version: 1.1
006: * Copyright (c) 2000-2001 Sun Microsystems, Inc.
007: *
008: * Sun Public License Notice
009: *
010: * The contents of this file are subject to the Sun Public License Version
011: * 1.0 (the "License"). You may not use this file except in compliance with
012: * the License. A copy of the License is included as the file "license.terms",
013: * and also available at http://www.sun.com/
014: *
015: * The Original Code is from:
016: * Brazil project web application Framework release 1.1.
017: * The Initial Developer of the Original Code is: suhler.
018: * Portions created by suhler are Copyright (C) Sun Microsystems, Inc.
019: * All Rights Reserved.
020: *
021: * Contributor(s): suhler.
022: *
023: * Version: 1.6
024: * Created by suhler on 00/12/05
025: * Last modified by suhler on 01/01/14 14:51:25
026: */
027:
028: package sunlabs.brazil.handler;
029:
030: import java.io.BufferedOutputStream;
031: import java.io.DataOutputStream;
032: import java.io.File;
033: import java.io.FileOutputStream;
034: import java.io.IOException;
035: import java.io.OutputStream;
036: import sunlabs.brazil.server.ChainHandler;
037: import sunlabs.brazil.server.Handler;
038: import sunlabs.brazil.server.Request;
039: import sunlabs.brazil.server.Server;
040: import sunlabs.brazil.util.Format;
041:
042: /**
043: * Handler for logging information about requests.
044: * Wraps another handler, and logs information
045: * about each HTTP request to a file.
046: * <p>
047: * Request properties:
048: * <dl class=props>
049: * <dt>handler
050: * <dd>The name of the handler to wrap. This can either be the token
051: * for the class, or the class name itself.
052: * <dt>fileName
053: * <dd>The name of the file to log the output to. If the file already exists,
054: * data is appended to it. If the file is removed, a new one is created.
055: * <dt>format
056: * <dd>The format of the output string. Embedded stings of the form "%X"
057: * are replaced, based on the following values for "X":
058: * <ul>
059: * <li>% <br> A single "%"
060: * <li>b <br> Bytes written to the client for this request.
061: * <li>d <br> Time to service this request (ms).
062: * <li>i <br> Client ip address.
063: * <li>m <br> Request method (GET, POST, etc)
064: * <li>M <br> Memory utilization (%).
065: * <li>q <br> query string (if any)
066: * <li>r <br> Requests used for this connection.
067: * <li>s <br> HTTP result code.
068: * <li>t <br> TimeStamp (ms since epoch).
069: * <li>T <br> Number of active threads.
070: * <li>u <br> URL for this request.
071: * <li>v <br> HTTP protocol version (10 or 11).
072: * </ul>
073: * Defaults to "%u;%t:%d:%b".
074: * <dt>props
075: * <dd>If specified This string is tacked onto the end of the "format"
076: * string. Entries in the Request Properties may be included using
077: * ${...} substitutions.
078: * <dt>headers
079: * <dd>If specified This string is tacked onto the end of the "props"
080: * string. Entries in the HTTPrequest headers may be included using
081: * ${...} substitutions.
082: * <dt>title
083: * <dd>if present, this is output as the first line of the file
084: * </dl>
085: * <p>
086: * See the {@link ChainSawHandler} for generating standard format
087: * log files.
088: *
089: * @author Stephen Uhler
090: * @version 1.16, 00/10/31
091: */
092:
093: public class LogHandler implements Handler {
094: Server server;
095: public Handler handler; // the handler to log
096: public String props; // a substitutable string for props
097: public String headers; // a substitutable string for headers
098: public String format; // substitute misc stuff
099: public String title; // title the output file
100: String handlerName; // the name of the handler
101:
102: public int flush; // log flush interval
103: public File file;
104: int count = 0; // count flushing interval
105: DataOutputStream log; // where to log the output to
106:
107: public boolean init(Server server, String prefix) {
108: this .server = server;
109: props = server.props.getProperty(prefix + "props", "");
110: title = server.props.getProperty(prefix + "title");
111: headers = server.props.getProperty(prefix + "headers", "");
112: format = server.props.getProperty(prefix + "format",
113: "%u;%t:%d:%b");
114: handlerName = server.props.getProperty(prefix + "handler");
115: handler = ChainHandler.initHandler(server, prefix, handlerName);
116:
117: String logFile = server.props.getProperty(prefix + "logFile",
118: server.hostName + "-" + server.listen.getLocalPort()
119: + ".log");
120: try {
121: flush = Integer.parseInt(server.props.getProperty(prefix
122: + "flush", "25"));
123: } catch (NumberFormatException e) {
124: }
125:
126: try {
127: file = new File(logFile);
128: log = new DataOutputStream(new BufferedOutputStream(
129: new FileOutputStream(logFile, true)));
130: if (title != null) {
131: log.writeBytes(title + "\n");
132: }
133: } catch (IOException e) {
134: server.log(Server.LOG_WARNING, prefix, e.toString());
135: return false;
136: }
137: return (handler != null);
138: }
139:
140: /**
141: * Dispatch the request to the handler. Log information if
142: * dispatched handler returns true.
143: */
144:
145: public boolean respond(Request request) throws IOException {
146: if (!handler.respond(request)) {
147: return false;
148: }
149: long duration = System.currentTimeMillis()
150: - request.startMillis;
151: String msg = subst(request, format, duration)
152: + Format.subst(request.props, props)
153: + Format.subst(request.headers, headers) + "\n";
154:
155: /*
156: * Write to the log file. If there's a problem, try to open
157: * the file again.
158: */
159:
160: if (!file.exists()) {
161: log.flush();
162: request.log(Server.LOG_WARNING, "Log file went away!");
163: log = new DataOutputStream(new BufferedOutputStream(
164: new FileOutputStream(file)));
165: if (title != null) {
166: log.writeBytes(title + "\n");
167: }
168: count = 0;
169: }
170:
171: log.writeBytes(msg);
172: request.log(Server.LOG_DIAGNOSTIC, msg);
173: if (count++ >= flush) {
174: log.flush();
175: count = 0;
176: }
177: return true;
178: }
179:
180: /**
181: * Compute built-in strings by mapping %n's to values
182: */
183:
184: static boolean format(Request request, char c, StringBuffer buff,
185: long duration) {
186: switch (c) {
187: case 'u': // URL
188: buff.append(request.url);
189: break;
190: case 't': // timestamp
191: buff.append(request.startMillis);
192: break;
193: case 'd': // service time (ms)
194: buff.append(duration);
195: break;
196: case 's': // result status
197: buff.append(request.getStatus());
198: break;
199: case 'b': // bytes written
200: buff.append(request.out.bytesWritten);
201: break;
202: case 'i': // client ip address
203: buff.append(request.getSocket().getInetAddress()
204: .getHostAddress());
205: break;
206: case 'r': // socket reuse count
207: buff.append(request.getReuseCount());
208: break;
209: case 'T': // active threads
210: buff.append(Thread.activeCount());
211: break;
212: case 'M': // memory utilization
213: long currentMem = Runtime.getRuntime().freeMemory();
214: long totalMem = Runtime.getRuntime().totalMemory();
215: buff.append(currentMem * 100 / totalMem);
216: break;
217: case 'm': // method
218: buff.append(request.method);
219: break;
220: case 'q': // query string
221: buff.append(request.query);
222: break;
223: case 'v': // protocol version (10 or 11)
224: buff.append(request.version);
225: break;
226: case '%': // insert a %
227: buff.append("%");
228: break;
229: default:
230: return false;
231: }
232: return true;
233: }
234:
235: /**
236: * Format a string.
237: * Replace %X constructs.
238: */
239:
240: public static String subst(Request request, String format,
241: long duration) {
242: int i = format.indexOf('%');
243: if (i < 0) {
244: return format;
245: }
246: int len = format.length();
247: StringBuffer result = new StringBuffer(format.substring(0, i));
248: for (; i < len; i++) {
249: try {
250: char ch = format.charAt(i);
251: if (ch == '%') {
252: i++;
253: format(request, format.charAt(i), result, duration);
254: } else {
255: result.append(ch);
256: }
257: } catch (IndexOutOfBoundsException e) {
258: /* Ignore malformed "%" sequences */
259: } catch (NullPointerException e) {
260: /* Ignore non-existent properties or array */
261: }
262: }
263: return result.toString();
264: }
265: }
|