1//
2//  ========================================================================
3//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4//  ------------------------------------------------------------------------
5//  All rights reserved. This program and the accompanying materials
6//  are made available under the terms of the Eclipse Public License v1.0
7//  and Apache License v2.0 which accompanies this distribution.
8//
9//      The Eclipse Public License is available at
10//      http://www.eclipse.org/legal/epl-v10.html
11//
12//      The Apache License v2.0 is available at
13//      http://www.opensource.org/licenses/apache2.0.php
14//
15//  You may elect to redistribute this code under either of these licenses.
16//  ========================================================================
17//
18
19package org.eclipse.jetty.server;
20
21import java.io.IOException;
22import java.io.OutputStream;
23import java.io.OutputStreamWriter;
24import java.io.Writer;
25import java.util.Locale;
26import java.util.TimeZone;
27
28import javax.servlet.http.Cookie;
29
30import org.eclipse.jetty.http.HttpHeaders;
31import org.eclipse.jetty.http.PathMap;
32import org.eclipse.jetty.util.DateCache;
33import org.eclipse.jetty.util.RolloverFileOutputStream;
34import org.eclipse.jetty.util.StringUtil;
35import org.eclipse.jetty.util.component.AbstractLifeCycle;
36import org.eclipse.jetty.util.log.Log;
37import org.eclipse.jetty.util.log.Logger;
38
39/**
40 * This {@link RequestLog} implementation outputs logs in the pseudo-standard
41 * NCSA common log format. Configuration options allow a choice between the
42 * standard Common Log Format (as used in the 3 log format) and the Combined Log
43 * Format (single log format). This log format can be output by most web
44 * servers, and almost all web log analysis software can understand these
45 * formats.
46 *
47 * @org.apache.xbean.XBean element="ncsaLog"
48 */
49
50/* ------------------------------------------------------------ */
51/**
52 */
53public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
54{
55    private static final Logger LOG = Log.getLogger(NCSARequestLog.class);
56    private static ThreadLocal<StringBuilder> _buffers = new ThreadLocal<StringBuilder>()
57            {
58                @Override
59                protected StringBuilder initialValue()
60                {
61                    return new StringBuilder(256);
62                }
63            };
64
65    private String _filename;
66    private boolean _extended;
67    private boolean _append;
68    private int _retainDays;
69    private boolean _closeOut;
70    private boolean _preferProxiedForAddress;
71    private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z";
72    private String _filenameDateFormat = null;
73    private Locale _logLocale = Locale.getDefault();
74    private String _logTimeZone = "GMT";
75    private String[] _ignorePaths;
76    private boolean _logLatency = false;
77    private boolean _logCookies = false;
78    private boolean _logServer = false;
79    private boolean _logDispatch = false;
80
81    private transient OutputStream _out;
82    private transient OutputStream _fileOut;
83    private transient DateCache _logDateCache;
84    private transient PathMap _ignorePathMap;
85    private transient Writer _writer;
86
87    /* ------------------------------------------------------------ */
88    /**
89     * Create request log object with default settings.
90     */
91    public NCSARequestLog()
92    {
93        _extended = true;
94        _append = true;
95        _retainDays = 31;
96    }
97
98    /* ------------------------------------------------------------ */
99    /**
100     * Create request log object with specified output file name.
101     *
102     * @param filename the file name for the request log.
103     *                 This may be in the format expected
104     *                 by {@link RolloverFileOutputStream}
105     */
106    public NCSARequestLog(String filename)
107    {
108        _extended = true;
109        _append = true;
110        _retainDays = 31;
111        setFilename(filename);
112    }
113
114    /* ------------------------------------------------------------ */
115    /**
116     * Set the output file name of the request log.
117     * The file name may be in the format expected by
118     * {@link RolloverFileOutputStream}.
119     *
120     * @param filename file name of the request log
121     *
122     */
123    public void setFilename(String filename)
124    {
125        if (filename != null)
126        {
127            filename = filename.trim();
128            if (filename.length() == 0)
129                filename = null;
130        }
131        _filename = filename;
132    }
133
134    /* ------------------------------------------------------------ */
135    /**
136     * Retrieve the output file name of the request log.
137     *
138     * @return file name of the request log
139     */
140    public String getFilename()
141    {
142        return _filename;
143    }
144
145    /* ------------------------------------------------------------ */
146    /**
147     * Retrieve the file name of the request log with the expanded
148     * date wildcard if the output is written to the disk using
149     * {@link RolloverFileOutputStream}.
150     *
151     * @return file name of the request log, or null if not applicable
152     */
153    public String getDatedFilename()
154    {
155        if (_fileOut instanceof RolloverFileOutputStream)
156            return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
157        return null;
158    }
159
160    /* ------------------------------------------------------------ */
161    /**
162     * Set the timestamp format for request log entries in the file.
163     * If this is not set, the pre-formated request timestamp is used.
164     *
165     * @param format timestamp format string
166     */
167    public void setLogDateFormat(String format)
168    {
169        _logDateFormat = format;
170    }
171
172    /* ------------------------------------------------------------ */
173    /**
174     * Retrieve the timestamp format string for request log entries.
175     *
176     * @return timestamp format string.
177     */
178    public String getLogDateFormat()
179    {
180        return _logDateFormat;
181    }
182
183    /* ------------------------------------------------------------ */
184    /**
185     * Set the locale of the request log.
186     *
187     * @param logLocale locale object
188     */
189    public void setLogLocale(Locale logLocale)
190    {
191        _logLocale = logLocale;
192    }
193
194    /* ------------------------------------------------------------ */
195    /**
196     * Retrieve the locale of the request log.
197     *
198     * @return locale object
199     */
200    public Locale getLogLocale()
201    {
202        return _logLocale;
203    }
204
205    /* ------------------------------------------------------------ */
206    /**
207     * Set the timezone of the request log.
208     *
209     * @param tz timezone string
210     */
211    public void setLogTimeZone(String tz)
212    {
213        _logTimeZone = tz;
214    }
215
216    /* ------------------------------------------------------------ */
217    /**
218     * Retrieve the timezone of the request log.
219     *
220     * @return timezone string
221     */
222    public String getLogTimeZone()
223    {
224        return _logTimeZone;
225    }
226
227    /* ------------------------------------------------------------ */
228    /**
229     * Set the number of days before rotated log files are deleted.
230     *
231     * @param retainDays number of days to keep a log file
232     */
233    public void setRetainDays(int retainDays)
234    {
235        _retainDays = retainDays;
236    }
237
238    /* ------------------------------------------------------------ */
239    /**
240     * Retrieve the number of days before rotated log files are deleted.
241     *
242     * @return number of days to keep a log file
243     */
244    public int getRetainDays()
245    {
246        return _retainDays;
247    }
248
249    /* ------------------------------------------------------------ */
250    /**
251     * Set the extended request log format flag.
252     *
253     * @param extended true - log the extended request information,
254     *                 false - do not log the extended request information
255     */
256    public void setExtended(boolean extended)
257    {
258        _extended = extended;
259    }
260
261    /* ------------------------------------------------------------ */
262    /**
263     * Retrieve the extended request log format flag.
264     *
265     * @return value of the flag
266     */
267    public boolean isExtended()
268    {
269        return _extended;
270    }
271
272    /* ------------------------------------------------------------ */
273    /**
274     * Set append to log flag.
275     *
276     * @param append true - request log file will be appended after restart,
277     *               false - request log file will be overwritten after restart
278     */
279    public void setAppend(boolean append)
280    {
281        _append = append;
282    }
283
284    /* ------------------------------------------------------------ */
285    /**
286     * Retrieve append to log flag.
287     *
288     * @return value of the flag
289     */
290    public boolean isAppend()
291    {
292        return _append;
293    }
294
295    /* ------------------------------------------------------------ */
296    /**
297     * Set request paths that will not be logged.
298     *
299     * @param ignorePaths array of request paths
300     */
301    public void setIgnorePaths(String[] ignorePaths)
302    {
303        _ignorePaths = ignorePaths;
304    }
305
306    /* ------------------------------------------------------------ */
307    /**
308     * Retrieve the request paths that will not be logged.
309     *
310     * @return array of request paths
311     */
312    public String[] getIgnorePaths()
313    {
314        return _ignorePaths;
315    }
316
317    /* ------------------------------------------------------------ */
318    /**
319     * Controls logging of the request cookies.
320     *
321     * @param logCookies true - values of request cookies will be logged,
322     *                   false - values of request cookies will not be logged
323     */
324    public void setLogCookies(boolean logCookies)
325    {
326        _logCookies = logCookies;
327    }
328
329    /* ------------------------------------------------------------ */
330    /**
331     * Retrieve log cookies flag
332     *
333     * @return value of the flag
334     */
335    public boolean getLogCookies()
336    {
337        return _logCookies;
338    }
339
340    /* ------------------------------------------------------------ */
341    /**
342     * Controls logging of the request hostname.
343     *
344     * @param logServer true - request hostname will be logged,
345     *                  false - request hostname will not be logged
346     */
347    public void setLogServer(boolean logServer)
348    {
349        _logServer = logServer;
350    }
351
352    /* ------------------------------------------------------------ */
353    /**
354     * Retrieve log hostname flag.
355     *
356     * @return value of the flag
357     */
358    public boolean getLogServer()
359    {
360        return _logServer;
361    }
362
363    /* ------------------------------------------------------------ */
364    /**
365     * Controls logging of request processing time.
366     *
367     * @param logLatency true - request processing time will be logged
368     *                   false - request processing time will not be logged
369     */
370    public void setLogLatency(boolean logLatency)
371    {
372        _logLatency = logLatency;
373    }
374
375    /* ------------------------------------------------------------ */
376    /**
377     * Retrieve log request processing time flag.
378     *
379     * @return value of the flag
380     */
381    public boolean getLogLatency()
382    {
383        return _logLatency;
384    }
385
386    /* ------------------------------------------------------------ */
387    /**
388     * Controls whether the actual IP address of the connection or
389     * the IP address from the X-Forwarded-For header will be logged.
390     *
391     * @param preferProxiedForAddress true - IP address from header will be logged,
392     *                                false - IP address from the connection will be logged
393     */
394    public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
395    {
396        _preferProxiedForAddress = preferProxiedForAddress;
397    }
398
399    /* ------------------------------------------------------------ */
400    /**
401     * Retrieved log X-Forwarded-For IP address flag.
402     *
403     * @return value of the flag
404     */
405    public boolean getPreferProxiedForAddress()
406    {
407        return _preferProxiedForAddress;
408    }
409
410    /* ------------------------------------------------------------ */
411    /**
412     * Set the log file name date format.
413     * @see RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)
414     *
415     * @param logFileDateFormat format string that is passed to {@link RolloverFileOutputStream}
416     */
417    public void setFilenameDateFormat(String logFileDateFormat)
418    {
419        _filenameDateFormat = logFileDateFormat;
420    }
421
422    /* ------------------------------------------------------------ */
423    /**
424     * Retrieve the file name date format string.
425     *
426     * @return the log File Date Format
427     */
428    public String getFilenameDateFormat()
429    {
430        return _filenameDateFormat;
431    }
432
433    /* ------------------------------------------------------------ */
434    /**
435     * Controls logging of the request dispatch time
436     *
437     * @param value true - request dispatch time will be logged
438     *              false - request dispatch time will not be logged
439     */
440    public void setLogDispatch(boolean value)
441    {
442        _logDispatch = value;
443    }
444
445    /* ------------------------------------------------------------ */
446    /**
447     * Retrieve request dispatch time logging flag
448     *
449     * @return value of the flag
450     */
451    public boolean isLogDispatch()
452    {
453        return _logDispatch;
454    }
455
456    /* ------------------------------------------------------------ */
457    /**
458     * Writes the request and response information to the output stream.
459     *
460     * @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response)
461     */
462    public void log(Request request, Response response)
463    {
464        try
465        {
466            if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
467                return;
468
469            if (_fileOut == null)
470                return;
471
472            StringBuilder buf= _buffers.get();
473            buf.setLength(0);
474
475            if (_logServer)
476            {
477                buf.append(request.getServerName());
478                buf.append(' ');
479            }
480
481            String addr = null;
482            if (_preferProxiedForAddress)
483            {
484                addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR);
485            }
486
487            if (addr == null)
488                addr = request.getRemoteAddr();
489
490            buf.append(addr);
491            buf.append(" - ");
492            Authentication authentication=request.getAuthentication();
493            if (authentication instanceof Authentication.User)
494                buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName());
495            else
496                buf.append(" - ");
497
498            buf.append(" [");
499            if (_logDateCache != null)
500                buf.append(_logDateCache.format(request.getTimeStamp()));
501            else
502                buf.append(request.getTimeStampBuffer().toString());
503
504            buf.append("] \"");
505            buf.append(request.getMethod());
506            buf.append(' ');
507            buf.append(request.getUri().toString());
508            buf.append(' ');
509            buf.append(request.getProtocol());
510            buf.append("\" ");
511            if (request.getAsyncContinuation().isInitial())
512            {
513                int status = response.getStatus();
514                if (status <= 0)
515                    status = 404;
516                buf.append((char)('0' + ((status / 100) % 10)));
517                buf.append((char)('0' + ((status / 10) % 10)));
518                buf.append((char)('0' + (status % 10)));
519            }
520            else
521                buf.append("Async");
522
523            long responseLength = response.getContentCount();
524            if (responseLength >= 0)
525            {
526                buf.append(' ');
527                if (responseLength > 99999)
528                    buf.append(responseLength);
529                else
530                {
531                    if (responseLength > 9999)
532                        buf.append((char)('0' + ((responseLength / 10000) % 10)));
533                    if (responseLength > 999)
534                        buf.append((char)('0' + ((responseLength / 1000) % 10)));
535                    if (responseLength > 99)
536                        buf.append((char)('0' + ((responseLength / 100) % 10)));
537                    if (responseLength > 9)
538                        buf.append((char)('0' + ((responseLength / 10) % 10)));
539                    buf.append((char)('0' + (responseLength) % 10));
540                }
541                buf.append(' ');
542            }
543            else
544                buf.append(" - ");
545
546
547            if (_extended)
548                logExtended(request, response, buf);
549
550            if (_logCookies)
551            {
552                Cookie[] cookies = request.getCookies();
553                if (cookies == null || cookies.length == 0)
554                    buf.append(" -");
555                else
556                {
557                    buf.append(" \"");
558                    for (int i = 0; i < cookies.length; i++)
559                    {
560                        if (i != 0)
561                            buf.append(';');
562                        buf.append(cookies[i].getName());
563                        buf.append('=');
564                        buf.append(cookies[i].getValue());
565                    }
566                    buf.append('\"');
567                }
568            }
569
570            if (_logDispatch || _logLatency)
571            {
572                long now = System.currentTimeMillis();
573
574                if (_logDispatch)
575                {
576                    long d = request.getDispatchTime();
577                    buf.append(' ');
578                    buf.append(now - (d==0 ? request.getTimeStamp():d));
579                }
580
581                if (_logLatency)
582                {
583                    buf.append(' ');
584                    buf.append(now - request.getTimeStamp());
585                }
586            }
587
588            buf.append(StringUtil.__LINE_SEPARATOR);
589
590            String log = buf.toString();
591            write(log);
592        }
593        catch (IOException e)
594        {
595            LOG.warn(e);
596        }
597    }
598
599    /* ------------------------------------------------------------ */
600    protected void write(String log) throws IOException
601    {
602        synchronized(this)
603        {
604            if (_writer==null)
605                return;
606            _writer.write(log);
607            _writer.flush();
608        }
609    }
610
611
612    /* ------------------------------------------------------------ */
613    /**
614     * Writes extended request and response information to the output stream.
615     *
616     * @param request request object
617     * @param response response object
618     * @param b StringBuilder to write to
619     * @throws IOException
620     */
621    protected void logExtended(Request request,
622                               Response response,
623                               StringBuilder b) throws IOException
624    {
625        String referer = request.getHeader(HttpHeaders.REFERER);
626        if (referer == null)
627            b.append("\"-\" ");
628        else
629        {
630            b.append('"');
631            b.append(referer);
632            b.append("\" ");
633        }
634
635        String agent = request.getHeader(HttpHeaders.USER_AGENT);
636        if (agent == null)
637            b.append("\"-\" ");
638        else
639        {
640            b.append('"');
641            b.append(agent);
642            b.append('"');
643        }
644    }
645
646    /* ------------------------------------------------------------ */
647    /**
648     * Set up request logging and open log file.
649     *
650     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
651     */
652    @Override
653    protected synchronized void doStart() throws Exception
654    {
655        if (_logDateFormat != null)
656        {
657            _logDateCache = new DateCache(_logDateFormat,_logLocale);
658            _logDateCache.setTimeZoneID(_logTimeZone);
659        }
660
661        if (_filename != null)
662        {
663            _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null);
664            _closeOut = true;
665            LOG.info("Opened " + getDatedFilename());
666        }
667        else
668            _fileOut = System.err;
669
670        _out = _fileOut;
671
672        if (_ignorePaths != null && _ignorePaths.length > 0)
673        {
674            _ignorePathMap = new PathMap();
675            for (int i = 0; i < _ignorePaths.length; i++)
676                _ignorePathMap.put(_ignorePaths[i],_ignorePaths[i]);
677        }
678        else
679            _ignorePathMap = null;
680
681        synchronized(this)
682        {
683            _writer = new OutputStreamWriter(_out);
684        }
685        super.doStart();
686    }
687
688    /* ------------------------------------------------------------ */
689    /**
690     * Close the log file and perform cleanup.
691     *
692     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
693     */
694    @Override
695    protected void doStop() throws Exception
696    {
697        synchronized (this)
698        {
699            super.doStop();
700            try
701            {
702                if (_writer != null)
703                    _writer.flush();
704            }
705            catch (IOException e)
706            {
707                LOG.ignore(e);
708            }
709            if (_out != null && _closeOut)
710                try
711                {
712                    _out.close();
713                }
714                catch (IOException e)
715                {
716                    LOG.ignore(e);
717                }
718
719            _out = null;
720            _fileOut = null;
721            _closeOut = false;
722            _logDateCache = null;
723            _writer = null;
724        }
725    }
726}
727