KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > server > log > AccessLog


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  *
23  * Free Software Foundation, Inc.
24  * 59 Temple Place, Suite 330
25  * Boston, MA 02111-1307 USA
26  *
27  * @author Scott Ferguson
28  */

29
30 package com.caucho.server.log;
31
32 import com.caucho.config.ConfigException;
33 import com.caucho.config.types.Bytes;
34 import com.caucho.config.types.Period;
35 import com.caucho.loader.CloseListener;
36 import com.caucho.loader.Environment;
37 import com.caucho.server.connection.AbstractHttpRequest;
38 import com.caucho.server.connection.AbstractHttpResponse;
39 import com.caucho.server.util.CauchoSystem;
40 import com.caucho.util.*;
41 import com.caucho.vfs.Path;
42
43 import javax.annotation.PostConstruct;
44 import javax.servlet.ServletContext JavaDoc;
45 import javax.servlet.ServletException JavaDoc;
46 import javax.servlet.http.Cookie JavaDoc;
47 import javax.servlet.http.HttpServletRequest JavaDoc;
48 import javax.servlet.http.HttpServletResponse JavaDoc;
49 import java.io.IOException JavaDoc;
50 import java.util.ArrayList JavaDoc;
51 import java.util.logging.Logger JavaDoc;
52
53 /**
54  * Represents an log of every top-level request to the server.
55  */

56 public class AccessLog extends AbstractAccessLog implements AlarmListener
57 {
58   protected static final L10N L = new L10N(AccessLog.class);
59   protected static final Logger JavaDoc log
60     = Logger.getLogger(AccessLog.class.getName());
61   
62   // Default maximum log size = 1G
63
private static final long ROLLOVER_SIZE = 1024L * 1024L * 1024L;
64   // Milliseconds in a day
65
private static final long DAY = 24L * 3600L * 1000L;
66   // How often to check size
67
private static final long ROLLOVER_CHECK_TIME = 600L * 1000L;
68
69   public static final int BUFFER_SIZE = 65536;
70   private static final int BUFFER_GAP = 8 * 1024;
71   
72   private QDate _calendar = QDate.createLocal();
73   private String JavaDoc _timeFormat;
74   private int _timeFormatSecondOffset = -1;
75
76   private final AccessLogWriter _logWriter = new AccessLogWriter(this);
77   
78   // AccessStream
79
private Object JavaDoc _streamLock = new Object JavaDoc();
80
81   private String JavaDoc _format;
82   private Segment []_segments;
83
84   private boolean _isAutoFlush;
85   
86   private boolean _isSharedBuffer = true;
87   private Object JavaDoc _sharedBufferLock;
88
89   private final CharBuffer _cb = new CharBuffer();
90   
91   private final CharBuffer _timeCharBuffer = new CharBuffer();
92   private final ByteBuffer _timeBuffer = new ByteBuffer();
93   private long _lastTime;
94
95   private Alarm _alarm = new Alarm(this);
96   private boolean _isActive;
97
98   public AccessLog()
99   {
100     setRolloverSize(new Bytes(ROLLOVER_SIZE));
101   }
102   
103   /**
104    * Sets the access log format.
105    */

106   public void setFormat(String JavaDoc format)
107   {
108     _format = format;
109   }
110
111   /**
112    * Sets the log path
113    */

114   public void setPath(Path path)
115   {
116     super.setPath(path);
117     
118     _logWriter.setPath(path);
119   }
120
121   /**
122    * Sets the formatted path.
123    */

124   public void setPathFormat(String JavaDoc pathFormat)
125     throws ConfigException
126   {
127     super.setPathFormat(pathFormat);
128     
129     _logWriter.setPathFormat(pathFormat);
130   }
131
132   /**
133    * Sets the archive name format
134    */

135   public void setArchiveFormat(String JavaDoc format)
136   {
137     _logWriter.setArchiveFormat(format);
138   }
139
140   /**
141    * Sets the log rollover period, rounded up to the nearest hour.
142    *
143    * @param period the new rollover period in milliseconds.
144    */

145   public void setRolloverPeriod(Period period)
146   {
147     _logWriter.setRolloverPeriod(period);
148   }
149
150   /**
151    * Sets the log rollover size, rounded up to the megabyte.
152    *
153    * @param size maximum size of the log file
154    */

155   public void setRolloverSize(Bytes bytes)
156   {
157     _logWriter.setRolloverSize(bytes);
158   }
159
160   /**
161    * Sets how often the log rollover will be checked.
162    *
163    * @param period how often the log rollover will be checked.
164    */

165   public void setRolloverCheckTime(long period)
166   {
167     _logWriter.setRolloverCheckPeriod(period);
168   }
169
170   /**
171    * Sets the auto-flush attribute.
172    */

173   public void setAutoFlush(boolean isAutoFlush)
174   {
175     _isAutoFlush = isAutoFlush;
176   }
177
178   /**
179    * Sets the shared buffer attribute.
180    */

181   public void setSharedBuffer(boolean isSharedBuffer)
182   {
183     _isSharedBuffer = isSharedBuffer;
184   }
185   
186   /**
187    * Initialize the log.
188    */

189   @PostConstruct
190   public void init()
191     throws ServletException JavaDoc, IOException JavaDoc
192   {
193     _isActive = true;
194     
195     Environment.addClassLoaderListener(new CloseListener(this));
196     
197     if (_format == null)
198       _format = "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"";
199
200     ArrayList JavaDoc<Segment> segments = parseFormat(_format);
201
202     _segments = new Segment[segments.size()];
203     segments.toArray(_segments);
204     
205     if (_timeFormat == null || _timeFormat.equals("")) {
206       _timeFormat = "[%d/%b/%Y:%H:%M:%S %z]";
207       _timeFormatSecondOffset = 0;
208     }
209
210     _logWriter.init();
211     _sharedBufferLock = _logWriter.getBufferLock();
212
213     _alarm.queue(60000);
214   }
215
216   /**
217    * Parses the access log string.
218    */

219   private ArrayList JavaDoc<Segment> parseFormat(String JavaDoc format)
220   {
221     ArrayList JavaDoc<Segment> segments = new ArrayList JavaDoc<Segment>();
222     CharBuffer cb = new CharBuffer();
223
224     int i = 0;
225     while (i < _format.length()) {
226       char ch = _format.charAt(i++);
227
228       if (ch != '%' || i >= _format.length()) {
229     cb.append((char) ch);
230     continue;
231       }
232       
233       String JavaDoc arg = null;
234       ch = _format.charAt(i++);
235       if (ch == '>')
236     ch = _format.charAt(i++);
237       else if (ch == '{') {
238     if (cb.length() > 0)
239       segments.add(new Segment(this, Segment.TEXT, cb.toString()));
240     cb.clear();
241     while (i < _format.length() && _format.charAt(i++) != '}')
242       cb.append(_format.charAt(i - 1));
243     arg = cb.toString();
244     cb.clear();
245
246     ch = _format.charAt(i++);
247       }
248
249       switch (ch) {
250       case 'b': case 'c':
251       case 'h': case 'i': case 'l': case 'n':
252       case 'r': case 's':
253       case 'T': case 'D': case 'o':
254       case 'u': case 'U':
255     if (cb.length() > 0)
256       segments.add(new Segment(this, Segment.TEXT, cb.toString()));
257     cb.clear();
258     segments.add(new Segment(this, ch, arg));
259     break;
260
261       case 't':
262     if (cb.length() > 0)
263       segments.add(new Segment(this, Segment.TEXT, cb.toString()));
264     cb.clear();
265     if (arg != null)
266       _timeFormat = arg;
267     segments.add(new Segment(this, ch, arg));
268     break;
269         
270       default:
271     cb.append('%');
272     i--;
273     break;
274       }
275     }
276
277     cb.append(CauchoSystem.getNewlineString());
278     segments.add(new Segment(this, Segment.TEXT, cb.toString()));
279
280     return segments;
281   }
282
283   /**
284    * Logs a request using the current format.
285    */

286   public void log(HttpServletRequest JavaDoc req,
287           HttpServletResponse JavaDoc res,
288           ServletContext JavaDoc application)
289     throws IOException JavaDoc
290   {
291     AbstractHttpRequest request = (AbstractHttpRequest) req;
292     AbstractHttpResponse response = (AbstractHttpResponse) res;
293
294     if (_isSharedBuffer && ! _isAutoFlush) {
295       synchronized (_sharedBufferLock) {
296     byte []buffer = _logWriter.getBuffer(BUFFER_GAP);
297     int length = _logWriter.getLength();
298
299     length = log(request, response,
300              buffer, length, buffer.length - length);
301
302     _logWriter.setLength(length);
303       }
304     }
305     else {
306       byte []buffer = request.getLogBuffer();
307
308       int length = log(request, response, buffer, 0, buffer.length);
309
310       if (_isAutoFlush)
311     _logWriter.writeThrough(buffer, 0, length);
312       else
313     _logWriter.writeBuffer(buffer, 0, length);
314     }
315   }
316   
317   /**
318    * Logs a request using the current format.
319    *
320    * @param request the servlet request.
321    * @param response the servlet response.
322    * @param buffer byte buffer containing the response
323    * @param offset buffer starting offset
324    * @param length length allowed in the buffer
325    *
326    * @return the new tail of the buffer
327    */

328   private int log(AbstractHttpRequest request,
329                   AbstractHttpResponse response,
330                   byte []buffer, int offset, int length)
331     throws IOException JavaDoc
332   {
333     int len = _segments.length;
334     for (int i = 0; i < len; i++) {
335       Segment segment = _segments[i];
336       String JavaDoc value = null;
337       CharBuffer cbValue = null;
338       CharSegment csValue = null;
339
340       switch (segment._code) {
341       case Segment.TEXT:
342         int sublen = segment._data.length;
343         byte []data = segment._data;
344         for (int j = 0; j < sublen; j++)
345           buffer[offset++] = data[j];
346     break;
347         
348       case Segment.CHAR:
349         buffer[offset++] = segment._ch;
350     break;
351
352       case 'b':
353         if (response.getStatusCode() == 304)
354           buffer[offset++] = (byte) '-';
355         else
356           offset = print(buffer, offset, response.getContentLength());
357     break;
358
359         // cookie
360
case 'c':
361         Cookie JavaDoc cookie = request.getCookie(segment._string);
362         if (cookie == null)
363           cookie = response.getCookie(segment._string);
364         if (cookie == null)
365           buffer[offset++] = (byte) '-';
366         else
367           offset = print(buffer, offset, cookie.getValue());
368     break;
369         
370         // set cookie
371
case Segment.SET_COOKIE:
372         ArrayList JavaDoc cookies = response.getCookies();
373         if (cookies == null || cookies.size() == 0)
374           buffer[offset++] = (byte) '-';
375         else {
376           _cb.clear();
377           response.fillCookie(_cb, (Cookie JavaDoc) cookies.get(0), 0, 0, false);
378
379           offset = print(buffer, offset, _cb.getBuffer(), 0, _cb.getLength());
380         }
381     break;
382
383       case 'h':
384         offset = request.printRemoteAddr(buffer, offset);
385     break;
386
387         // input header
388
case 'i':
389     csValue = request.getHeaderBuffer(segment._string);
390         if (csValue == null)
391           buffer[offset++] = (byte) '-';
392         else
393           offset = print(buffer, offset, csValue);
394     break;
395
396       case 'l':
397         buffer[offset++] = (byte) '-';
398     break;
399
400         // request attribute
401
case 'n':
402         Object JavaDoc oValue = request.getAttribute(segment._string);
403         if (oValue == null)
404           buffer[offset++] = (byte) '-';
405         else
406           offset = print(buffer, offset, String.valueOf(oValue));
407     break;
408
409         // output header
410
case 'o':
411     value = response.getHeader(segment._string);
412         if (value == null)
413           buffer[offset++] = (byte) '-';
414         else
415           offset = print(buffer, offset, value);
416     break;
417
418       case 'r':
419     offset = print(buffer, offset, request.getMethod());
420         
421         buffer[offset++] = (byte) ' ';
422         
423         data = request.getUriBuffer();
424         sublen = request.getUriLength();
425     System.arraycopy(data, 0, buffer, offset, sublen);
426         offset += sublen;
427         buffer[offset++] = (byte) ' ';
428         
429     offset = print(buffer, offset, request.getProtocol());
430     break;
431
432       case 's':
433         int status = response.getStatusCode();
434         buffer[offset++] = (byte) ('0' + (status / 100) % 10);
435         buffer[offset++] = (byte) ('0' + (status / 10) % 10);
436         buffer[offset++] = (byte) ('0' + status % 10);
437     break;
438
439       case 't':
440         long date = Alarm.getCurrentTime();
441         
442         if (date / 1000 != _lastTime / 1000)
443           fillTime(date);
444
445     sublen = _timeBuffer.getLength();
446     data = _timeBuffer.getBuffer();
447     
448     synchronized (_timeBuffer) {
449       System.arraycopy(data, 0, buffer, offset, sublen);
450     }
451
452         offset += sublen;
453     break;
454
455       case 'T':
456     {
457       long startTime = request.getStartTime();
458       long endTime = Alarm.getExactTime();
459
460       offset = print(buffer, offset, (int) ((endTime - startTime + 500) / 1000));
461       break;
462     }
463
464       case 'D':
465     {
466       long startTime = request.getStartTime();
467       long endTime = Alarm.getExactTime();
468
469       offset = print(buffer, offset, (int) ((endTime - startTime) * 1000));
470       break;
471     }
472
473       case 'u':
474     value = request.getRemoteUser(false);
475         if (value == null)
476           buffer[offset++] = (byte) '-';
477         else {
478           buffer[offset++] = (byte) '"';
479           offset = print(buffer, offset, value);
480           buffer[offset++] = (byte) '"';
481         }
482     break;
483
484       case 'U':
485         offset = print(buffer, offset, request.getRequestURI());
486     break;
487
488       default:
489     throw new IOException JavaDoc();
490       }
491     }
492
493     return offset;
494   }
495
496   /**
497    * Prints a CharSegment to the log.
498    *
499    * @param buffer receiving byte buffer.
500    * @param offset offset into the receiving buffer.
501    * @param cb the new char segment to be logged.
502    * @return the new offset into the byte buffer.
503    */

504   private int print(byte []buffer, int offset, CharSegment cb)
505   {
506     char []charBuffer = cb.getBuffer();
507     int cbOffset = cb.getOffset();
508     int length = cb.getLength();
509
510     // truncate for hacker attacks
511
if (buffer.length - offset - 256 < length)
512       length = buffer.length - offset - 256;
513
514     for (int i = length - 1; i >= 0; i--)
515       buffer[offset + i] = (byte) charBuffer[cbOffset + i];
516
517     return offset + length;
518   }
519
520   /**
521    * Prints a String to the log.
522    *
523    * @param buffer receiving byte buffer.
524    * @param offset offset into the receiving buffer.
525    * @param s the new string to be logged.
526    * @return the new offset into the byte buffer.
527    */

528   private int print(byte []buffer, int offset, String JavaDoc s)
529   {
530     int length = s.length();
531
532     _cb.ensureCapacity(length);
533     char []cBuf = _cb.getBuffer();
534
535     s.getChars(0, length, cBuf, 0);
536
537     for (int i = length - 1; i >= 0; i--)
538       buffer[offset + i] = (byte) cBuf[i];
539
540     return offset + length;
541   }
542
543   /**
544    * Prints a String to the log.
545    *
546    * @param buffer receiving byte buffer.
547    * @param offset offset into the receiving buffer.
548    * @param s the new string to be logged.
549    * @return the new offset into the byte buffer.
550    */

551   private int print(byte []buffer, int offset,
552                     char []cb, int cbOff, int length)
553   {
554     for (int i = length - 1; i >= 0; i--)
555       buffer[offset + i] = (byte) cb[cbOff + i];
556
557     return offset + length;
558   }
559
560   /**
561    * Prints an integer to the log.
562    *
563    * @param buffer receiving byte buffer.
564    * @param offset offset into the receiving buffer.
565    * @param v the new integer to be logged.
566    * @return the new offset into the byte buffer.
567    */

568   private int print(byte []buffer, int offset, long v)
569   {
570     if (v == 0) {
571       buffer[offset] = (byte) '0';
572       return offset + 1;
573     }
574
575     if (v < 0) {
576       buffer[offset++] = (byte) '-';
577       v = -v;
578     }
579
580     int length = 0;
581     int exp = 10;
582     
583     for (; exp <= v && exp > 0; length++)
584       exp = 10 * exp;
585
586     offset += length;
587     for (int i = 0; i <= length; i++) {
588       buffer[offset - i] = (byte) (v % 10 + '0');
589       v = v / 10;
590     }
591
592     return offset + 1;
593   }
594
595   /**
596    * Flushes the log.
597    */

598   public void flush()
599   {
600     _logWriter.flush();
601   }
602
603   /**
604    * The alarm listener.
605    */

606   public void handleAlarm(Alarm alarm)
607   {
608     try {
609       flush();
610     } finally {
611       alarm = _alarm;
612       if (alarm != null)
613     alarm.queue(60000);
614     }
615   }
616
617   /**
618    * Closes the log, flushing the results.
619    */

620   public void destroy()
621     throws IOException JavaDoc
622   {
623     _isActive = false;
624     
625     Alarm alarm = _alarm;;
626     _alarm = null;
627
628     if (alarm != null)
629       alarm.dequeue();
630
631     _logWriter.close();
632   }
633
634   /**
635    * Fills the time buffer with the formatted time.
636    *
637    * @param date current time in milliseconds
638    */

639   private void fillTime(long date)
640     throws IOException JavaDoc
641   {
642     if (date / 1000 == _lastTime / 1000)
643       return;
644
645     synchronized (_timeBuffer) {
646       if (_timeFormatSecondOffset >= 0 && date / 60000 == _lastTime / 60000) {
647     byte []bBuf = _timeBuffer.getBuffer();
648     
649     int sec = (int) (date / 1000 % 60);
650
651     bBuf[_timeFormatSecondOffset + 0] = (byte) ('0' + sec / 10);
652     bBuf[_timeFormatSecondOffset + 1] = (byte) ('0' + sec % 10);
653
654     return;
655       }
656       
657       _timeCharBuffer.clear();
658       QDate.formatLocal(_timeCharBuffer, date, _timeFormat);
659
660       if (_timeFormatSecondOffset >= 0)
661     _timeFormatSecondOffset = _timeCharBuffer.lastIndexOf(':') + 1;
662       
663       char []cBuf = _timeCharBuffer.getBuffer();
664       int length = _timeCharBuffer.getLength();
665
666       _timeBuffer.setLength(length);
667       byte []bBuf = _timeBuffer.getBuffer();
668
669       for (int i = length - 1; i >= 0; i--)
670     bBuf[i] = (byte) cBuf[i];
671     }
672       
673     _lastTime = date;
674   }
675
676   /**
677    * Represents one portion of the access log.
678    */

679   static class Segment {
680     final static int TEXT = 0;
681     final static int CHAR = 1;
682     final static int SET_COOKIE = 2;
683     
684     int _code;
685     byte []_data;
686     byte _ch;
687     String JavaDoc _string;
688     AccessLog _log;
689
690     /**
691      * Creates a new log segment.
692      *
693      * @param log the owning log
694      * @param code the segment code, telling what kind of segment it is
695      * @param string the parameter for the segment code.
696      */

697     Segment(AccessLog log, int code, String JavaDoc string)
698     {
699       _log = log;
700       _code = code;
701       
702       _string = string;
703       if (string != null) {
704         if (code == 'o' && string.equalsIgnoreCase("Set-Cookie"))
705           _code = SET_COOKIE;
706           
707         _data = _string.getBytes();
708         if (code == TEXT && _string.length() == 1) {
709           _ch = (byte) _string.charAt(0);
710           _code = CHAR;
711         }
712       }
713     }
714   }
715 }
716
Popular Tags