io.gravitee.reporter.file.FileReporter.java Source code

Java tutorial

Introduction

Here is the source code for io.gravitee.reporter.file.FileReporter.java

Source

/**
 * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
 *
 * 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 io.gravitee.reporter.file;

import io.gravitee.common.service.AbstractService;
import io.gravitee.reporter.api.Reportable;
import io.gravitee.reporter.api.Reporter;
import io.gravitee.reporter.api.http.Metrics;
import io.gravitee.reporter.api.http.SecurityType;
import io.gravitee.reporter.file.config.Config;
import org.apache.commons.lang3.time.FastDateFormat;
import org.eclipse.jetty.util.RolloverFileOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.TimeZone;

/**
 * Write an access log to a file by using the following line format:
 *
 * <pre>
 *     [TIMESTAMP] (LOCAL_IP) REMOTE_IP API KEY METHOD PATH STATUS LENGTH TOTAL_RESPONSE_TIME
 * </pre>
 * 
 * This class is not thread safe, the record method should only be called by a single thread
 *
 * @author David BRASSELY (brasseld at gmail.com)
 */
@SuppressWarnings("rawtypes")
public class FileReporter extends AbstractService implements Reporter {

    @Autowired
    private Config config;

    private static final Logger LOGGER = LoggerFactory.getLogger(FileReporter.class);

    private static final String RFC_3339_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";

    private static final FastDateFormat dateFormatter = FastDateFormat.getInstance(RFC_3339_DATE_FORMAT);

    private static final String NO_STRING_DATA_VALUE = "-";

    private static final String NO_INTEGER_DATA_VALUE = "-1";

    // buffer reused between calls to the report method
    private final StringBuilder accessLogBuffer = new StringBuilder(256);
    private final StringBuffer dateFormatBuffer = new StringBuffer();
    private final char[] stringBuilderConverterBuffer = new char[2048];

    private transient OutputStream _out;

    private transient Writer _writer;

    private void write(StringBuilder accessLog) throws IOException {
        synchronized (this) {
            if (_writer == null) {
                return;
            }

            /*
             * OMG What's going on ?
             * Why aren't you doing a _writer.write(accessLog.toString()) or _writer.write(accessLog) ?
             * Because it's doing too many memory allocations !
             * accessLog.toString() will create a copy of the the StringBuilder content into a String
             * then _writer.write will make another copy of the content of the String
             * Here we're doing only one copy AND we reuse the buffer.
             */
            int length = accessLog.length();
            int chunkLength = stringBuilderConverterBuffer.length;
            for (int srcBegin = 0; srcBegin < length; srcBegin += chunkLength) {
                final int srcEnd = Math.min(srcBegin + chunkLength, length);
                accessLog.getChars(srcBegin, srcEnd, stringBuilderConverterBuffer, 0);
                _writer.write(stringBuilderConverterBuffer, 0, srcEnd - srcBegin);
            }
            _writer.flush();
        }
    }

    private StringBuilder format(Metrics metrics) {
        StringBuilder buf = accessLogBuffer;
        StringBuffer dateBuffer = dateFormatBuffer;
        buf.setLength(0);
        dateBuffer.setLength(0);

        // Append request timestamp
        buf.append('[');
        dateFormatter.format(metrics.timestamp().toEpochMilli(), dateBuffer);
        buf.append(dateBuffer);
        buf.append("] ");

        // Append request id
        buf.append(metrics.getRequestId());
        buf.append(" ");

        // Append transaction id
        buf.append(metrics.getTransactionId());
        buf.append(" ");

        // Append local IP
        buf.append('(');
        buf.append(metrics.getLocalAddress());
        buf.append(") ");

        // Append remote IP
        buf.append(metrics.getRemoteAddress());
        buf.append(' ');

        // Append Api name
        String apiName = metrics.getApi();
        if (apiName == null) {
            apiName = NO_STRING_DATA_VALUE;
        }

        buf.append(apiName);
        buf.append(' ');

        // Append security type
        SecurityType securityType = metrics.getSecurityType();
        if (securityType == null) {
            buf.append(NO_STRING_DATA_VALUE);
        } else {
            buf.append(securityType.name());
        }
        buf.append(' ');

        // Append security token
        String securityToken = metrics.getSecurityToken();
        if (securityToken == null) {
            securityToken = NO_STRING_DATA_VALUE;
        }
        buf.append(securityToken);
        buf.append(' ');

        // Append request method and URI
        buf.append(metrics.getHttpMethod());
        buf.append(' ');
        buf.append(metrics.getUri());
        buf.append(' ');

        // Append response status
        int status = metrics.getStatus();
        if (status <= 0) {
            status = 404;
        }
        buf.append((char) ('0' + ((status / 100) % 10)));
        buf.append((char) ('0' + ((status / 10) % 10)));
        buf.append((char) ('0' + (status % 10)));
        buf.append(' ');

        // Append response length
        long responseLength = metrics.getResponseContentLength();
        if (responseLength >= 0) {
            if (responseLength > 99999) {
                buf.append(responseLength);
            } else {
                if (responseLength > 9999)
                    buf.append((char) ('0' + ((responseLength / 10000) % 10)));
                if (responseLength > 999)
                    buf.append((char) ('0' + ((responseLength / 1000) % 10)));
                if (responseLength > 99)
                    buf.append((char) ('0' + ((responseLength / 100) % 10)));
                if (responseLength > 9)
                    buf.append((char) ('0' + ((responseLength / 10) % 10)));
                buf.append((char) ('0' + (responseLength) % 10));
            }
        } else {
            buf.append(NO_INTEGER_DATA_VALUE);
        }
        buf.append(' ');

        // Append total response time
        buf.append(metrics.getProxyResponseTimeMs());
        buf.append(' ');

        // Append proxy latency
        buf.append(metrics.getProxyLatencyMs());

        buf.append(System.lineSeparator());

        return buf;
    }

    @Override
    public synchronized void doStart() throws Exception {
        String filename = config.getFilename();
        if (filename != null) {
            _out = new RolloverFileOutputStream(filename, config.isAppend(), config.getRetainDays(),
                    TimeZone.getDefault(), config.getDateFormat(), config.getBackupFormat());
            LOGGER.info("Opened rollover access log file " + filename);
        }

        synchronized (this) {
            _writer = new OutputStreamWriter(_out);
        }
    }

    @Override
    public synchronized void doStop() throws Exception {
        synchronized (this) {
            try {
                if (_writer != null)
                    _writer.flush();
            } catch (IOException ioe) {
                LOGGER.error("", ioe);
            }
            if (_out != null)
                try {
                    _out.close();
                } catch (IOException ioe) {
                    LOGGER.error("", ioe);
                }

            _out = null;
            _writer = null;
        }
    }

    @Override
    public void report(Reportable reportable) {
        try {
            write(format((Metrics) reportable));
        } catch (IOException ioe) {
            LOGGER.error("", ioe);
        }
    }

    @Override
    public boolean canHandle(Reportable reportable) {
        return (reportable instanceof Metrics);
    }
}