All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.airlift.http.server.DelimitedRequestLogHandler Maven / Gradle / Ivy

/*
 * 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.airlift.http.server;

import jakarta.annotation.Nullable;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.handler.EventsHandler;
import org.eclipse.jetty.util.NanoTime;

import java.util.DoubleSummaryStatistics;
import java.util.List;

import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.NANOSECONDS;

public class DelimitedRequestLogHandler
        extends EventsHandler
        implements RequestLog
{
    public static final String REQUEST_BEGIN_TO_HANDLE_ATTRIBUTE = DelimitedRequestLogHandler.class.getName() + ".begin_to_handle";
    private static final String RESPONSE_CONTENT_TIMESTAMPS_ATTRIBUTE = DelimitedRequestLogHandler.class.getName() + ".response_content_timestamps";
    private static final Object MARKER = new Object();

    private final DelimitedRequestLog logger;

    public DelimitedRequestLogHandler(DelimitedRequestLog logger)
    {
        this.logger = requireNonNull(logger, "logger is null");
    }

    @Override
    protected void onResponseWriteComplete(Request request, Throwable failure)
    {
        // Instead of using mutable field, let's set individual attributes instead
        request.setAttribute(RESPONSE_CONTENT_TIMESTAMPS_ATTRIBUTE + "." + NanoTime.now(), MARKER);
    }

    @Override
    protected void onBeforeHandling(Request request)
    {
        request.setAttribute(REQUEST_BEGIN_TO_HANDLE_ATTRIBUTE, NanoTime.since(request.getBeginNanoTime()));
    }

    @Override
    public void log(Request request, Response response)
    {
        List contentTimestamps = getContentTimestamps(request);
        long firstToLastContentTimeInMillis = -1;
        if (!contentTimestamps.isEmpty()) {
            firstToLastContentTimeInMillis = NANOSECONDS.toMillis(contentTimestamps.get(contentTimestamps.size() - 1) - contentTimestamps.get(0));
        }

        long beginToHandleMillis = NANOSECONDS.toMillis((Long) request.getAttribute(REQUEST_BEGIN_TO_HANDLE_ATTRIBUTE));
        long beginToEndMillis = NANOSECONDS.toMillis(NanoTime.since(request.getBeginNanoTime()));

        logger.log(request,
                response,
                beginToHandleMillis,
                beginToEndMillis,
                firstToLastContentTimeInMillis,
                processContentTimestamps(contentTimestamps));
    }

    private List getContentTimestamps(Request request)
    {
        return request.getAttributeNameSet().stream()
                .filter(name -> name.startsWith(RESPONSE_CONTENT_TIMESTAMPS_ATTRIBUTE))
                .map(name -> name.substring(RESPONSE_CONTENT_TIMESTAMPS_ATTRIBUTE.length() + 1))
                .map(Long::valueOf)
                .collect(toImmutableList());
    }

    /**
     * Calculate the summary statistics for the interarrival time of the onResponseContent callbacks.
     */
    @Nullable
    private static DoubleSummaryStats processContentTimestamps(List contentTimestamps)
    {
        requireNonNull(contentTimestamps, "contentTimestamps is null");

        // no content (HTTP 204) or there was a single response chunk (so no interarrival time)
        if (contentTimestamps.size() == 0 || contentTimestamps.size() == 1) {
            return null;
        }

        DoubleSummaryStatistics statistics = new DoubleSummaryStatistics();
        long previousTimestamp = contentTimestamps.get(0);
        for (int i = 1; i < contentTimestamps.size(); i++) {
            long timestamp = contentTimestamps.get(i);
            statistics.accept(NANOSECONDS.toMillis(timestamp - previousTimestamp));
            previousTimestamp = timestamp;
        }
        return new DoubleSummaryStats(statistics);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy