org.callimachusproject.server.AccessLog.java Source code

Java tutorial

Introduction

Here is the source code for org.callimachusproject.server.AccessLog.java

Source

/*
 * Copyright (c) 2013 3 Round Stones Inc., Some Rights Reserved
 *
 * 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 org.callimachusproject.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.Header;
import org.apache.http.HttpConnection;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpInetConnection;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.callimachusproject.client.StreamingHttpEntity;
import org.callimachusproject.io.ChannelUtil;
import org.callimachusproject.server.helpers.ResponseCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AccessLog implements AsyncExecChain {
    private static final String NIL = "-";
    private static final String FORENSIC_ATTR = AccessLog.class.getName() + "#forensicId";
    private static final Pattern TOKENS_REGEX = Pattern.compile(
            "\\s*([\\w\\!\\#\\$\\%\\&\\'\\*\\+\\-\\.\\^\\_\\`\\~]+)(?:\\s*=\\s*(?:\"([^\"]*)\"|([^,\"]*)))?\\s*,?");
    private final Logger logger = LoggerFactory.getLogger(AccessLog.class);
    private final String uid = "t" + Long.toHexString(System.currentTimeMillis()) + "x";
    private final AtomicLong seq = new AtomicLong(0);
    private final AsyncExecChain delegate;

    public AccessLog(AsyncExecChain delegate) {
        this.delegate = delegate;
    }

    @Override
    public Future<HttpResponse> execute(HttpHost target, final HttpRequest request, final HttpContext context,
            FutureCallback<HttpResponse> callback) {
        final String addr = getClientAddress(context);
        traceRequest(request, context, addr == null);
        return delegate.execute(target, request, context, new ResponseCallback(callback) {
            public void completed(HttpResponse result) {
                try {
                    logResponse(request, context, result);
                    super.completed(result);
                } catch (RuntimeException ex) {
                    super.failed(ex);
                }
            }

            public void failed(Exception ex) {
                logCancel(addr, request);
                super.failed(ex);
            }

            public void cancelled() {
                logCancel(addr, request);
                super.cancelled();
            }
        });
    }

    void logResponse(HttpRequest req, HttpContext context, final HttpResponse resp) {
        final String addr = getClientAddress(context);
        traceResponse(req, context, resp, addr == null);
        if (addr == null)
            return;
        final int code = resp.getStatusLine().getStatusCode();
        if (logger.isInfoEnabled() || logger.isWarnEnabled() && code >= 400
                || logger.isErrorEnabled() && code >= 500) {
            final String username = getUsername(req).replaceAll("\\s+", "_");
            final String line = req.getRequestLine().toString();
            final Header referer = req.getFirstHeader("Referer");
            final Header agent = req.getFirstHeader("User-Agent");
            HttpEntity entity = resp.getEntity();
            if (entity == null) {
                log(addr, username, line, code, 0, referer, agent);
            } else {
                final long length = entity.getContentLength();
                resp.setEntity(new StreamingHttpEntity(entity) {
                    @Override
                    protected InputStream getDelegateContent() throws IOException {
                        InputStream in = super.getDelegateContent();
                        return logOnClose(addr, username, line, code, length, referer, agent, in);
                    }
                });
            }
        }
    }

    InputStream logOnClose(final String addr, final String username, final String line, final int code,
            final long length, final Header referer, final Header agent, InputStream in) {
        final ReadableByteChannel delegate = ChannelUtil.newChannel(in);
        return ChannelUtil.newInputStream(new ReadableByteChannel() {
            private long size = 0;
            private boolean complete;
            private boolean error;

            public boolean isOpen() {
                return delegate.isOpen();
            }

            public synchronized void close() throws IOException {
                delegate.close();
                if (!complete) {
                    complete = true;
                    if (error) {
                        log(addr, username, line, 599, size, referer, agent);
                    } else if (size < length) {
                        log(addr, username, line, 499, size, referer, agent);
                    } else {
                        log(addr, username, line, code, size, referer, agent);
                    }
                }
            }

            public synchronized int read(ByteBuffer dst) throws IOException {
                error = true;
                int read = delegate.read(dst);
                if (read < 0) {
                    complete = true;
                    log(addr, username, line, code, size, referer, agent);
                } else {
                    size += read;
                }
                error = false;
                return read;
            }
        });
    }

    void logCancel(final String addr, final HttpRequest request) {
        String username = getUsername(request).replaceAll("\\s+", "_");
        String line = request.getRequestLine().toString();
        Header referer = request.getFirstHeader("Referer");
        Header agent = request.getFirstHeader("User-Agent");
        log(addr, username, line, 599, 0, referer, agent);
    }

    void log(String addr, String username, String line, int code, long length, Header referer, Header agent) {
        StringBuilder sb = new StringBuilder();
        sb.append(addr).append('\t').append(username);
        sb.append('\t').append('"').append(line).append('"');
        sb.append('\t').append(code).append('\t').append(length);
        if (referer == null) {
            sb.append('\t').append('-');
        } else {
            sb.append('\t').append('"').append(referer.getValue()).append('"');
        }
        if (agent == null) {
            sb.append('\t').append('-');
        } else {
            sb.append('\t').append('"').append(agent.getValue()).append('"');
        }
        if (code < 400 || code == 401) {
            logger.info(sb.toString());
        } else if (code < 500) {
            logger.warn(sb.toString());
        } else {
            logger.error(sb.toString());
        }
    }

    private String getUsername(HttpRequest req) {
        for (Header hd : req.getHeaders("Authorization")) {
            Matcher m = TOKENS_REGEX.matcher(hd.getValue());
            while (m.find()) {
                String key = m.group(1);
                if ("username".equals(key))
                    return m.group(2);
            }
        }
        for (Header hd : req.getHeaders("Cookie")) {
            for (String cookie : hd.getValue().split("\\s*,\\s*")) {
                if (!cookie.contains("username"))
                    continue;
                String[] pair = cookie.split("\\s*;\\s*");
                for (String p : pair) {
                    if (p.startsWith("username") && p.indexOf('=') > 0) {
                        return decode(p.substring(p.indexOf('=') + 1));
                    }
                }
            }
        }
        return NIL;
    }

    private void traceRequest(HttpRequest req, HttpContext context, boolean trace) {
        if (logger.isDebugEnabled() && !trace || logger.isTraceEnabled()) {
            StringBuilder sb = new StringBuilder();
            sb.append("+").append(getForensicId(context));
            sb.append("|").append(req.getRequestLine().toString().replace('|', '_'));
            for (Header hd : req.getAllHeaders()) {
                sb.append("|").append(hd.getName().replace('|', '_'));
                sb.append(":").append(hd.getValue().replace('|', '_'));
            }
            if (trace) {
                logger.trace(sb.toString());
            } else {
                logger.debug(sb.toString());
            }
        }
    }

    private String getForensicId(HttpContext context) {
        String id = (String) context.getAttribute(FORENSIC_ATTR);
        if (id == null) {
            id = uid + seq.getAndIncrement();
            context.setAttribute(FORENSIC_ATTR, id);
        }
        return id;
    }

    private void traceResponse(HttpRequest req, HttpContext context, HttpResponse resp, boolean trace) {
        if (logger.isDebugEnabled() && !trace || logger.isTraceEnabled()) {
            StringBuilder sb = new StringBuilder();
            sb.append('-').append(getForensicId(context));
            sb.append('|').append(resp.getStatusLine().toString().replace('|', '_'));
            for (Header hd : resp.getAllHeaders()) {
                sb.append('|').append(hd.getName().replace('|', '_'));
                sb.append(':').append(hd.getValue().replace('|', '_'));
            }
            if (trace) {
                logger.trace(sb.toString());
            } else {
                logger.debug(sb.toString());
            }
        }
    }

    private String decode(String string) {
        try {
            return URLDecoder.decode(string, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new AssertionError(e);
        }
    }

    private String getClientAddress(HttpContext context) {
        if (context == null)
            return null;
        HttpConnection con = HttpCoreContext.adapt(context).getConnection();
        if (con instanceof HttpInetConnection) {
            InetAddress remoteAddress = ((HttpInetConnection) con).getRemoteAddress();
            if (remoteAddress != null)
                return remoteAddress.getHostAddress();
        }
        return null;
    }

}