io.airlift.http.client.jetty.DefaultHttpClientLogger Maven / Gradle / Ivy
The newest version!
/*
* 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.client.jetty;
import ch.qos.logback.core.AsyncAppenderBase;
import ch.qos.logback.core.ContextBase;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import ch.qos.logback.core.status.ErrorStatus;
import ch.qos.logback.core.util.FileSize;
import com.google.common.math.LongMath;
import io.airlift.log.Logger;
import io.airlift.units.DataSize;
import io.airlift.units.Duration;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;
import static io.airlift.http.client.jetty.HttpRequestEvent.createHttpRequestEvent;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
class DefaultHttpClientLogger
implements HttpClientLogger
{
private static final Logger LOG = Logger.get(DefaultHttpClientLogger.class);
private static final String TEMP_FILE_EXTENSION = ".tmp";
private static final String LOG_FILE_EXTENSION = ".log";
private final AsyncAppenderBase asyncAppender;
DefaultHttpClientLogger(
String filename,
int maxHistory,
int queueSize,
DataSize bufferSize,
Duration flushInterval,
long maxFileSizeInBytes,
boolean compressionEnabled)
{
ContextBase context = new ContextBase();
HttpClientLogLayout httpLogLayout = new HttpClientLogLayout();
recoverTempFiles(filename);
FlushingFileAppender fileAppender = new FlushingFileAppender<>(flushInterval);
SizeAndTimeBasedFNATP triggeringPolicy = new SizeAndTimeBasedFNATP<>();
TimeBasedRollingPolicy rollingPolicy = new TimeBasedRollingPolicy<>();
rollingPolicy.setContext(context);
rollingPolicy.setMaxHistory(maxHistory); // limits number of logging periods (i.e. days) kept
rollingPolicy.setTimeBasedFileNamingAndTriggeringPolicy(triggeringPolicy);
rollingPolicy.setParent(fileAppender);
rollingPolicy.setFileNamePattern(filename + "-%d{yyyy-MM-dd}.%i.log");
if (compressionEnabled) {
rollingPolicy.setFileNamePattern(rollingPolicy.getFileNamePattern() + ".gz");
}
// Limit total log files occupancy on disk. Ideally we would keep exactly
// `maxHistory` files (not logging periods). This is closest currently possible.
rollingPolicy.setTotalSizeCap(new FileSize(LongMath.saturatedMultiply(maxFileSizeInBytes, maxHistory)));
triggeringPolicy.setContext(context);
triggeringPolicy.setTimeBasedRollingPolicy(rollingPolicy);
triggeringPolicy.setMaxFileSize(new FileSize(maxFileSizeInBytes));
fileAppender.setContext(context);
fileAppender.setFile(filename);
fileAppender.setAppend(true);
fileAppender.setBufferSize(new FileSize(bufferSize.toBytes()));
fileAppender.setLayout(httpLogLayout);
fileAppender.setRollingPolicy(rollingPolicy);
fileAppender.setImmediateFlush(false);
asyncAppender = new AsyncAppenderBase<>();
asyncAppender.setContext(context);
asyncAppender.setQueueSize(queueSize);
asyncAppender.addAppender(fileAppender);
rollingPolicy.start();
triggeringPolicy.start();
fileAppender.start();
asyncAppender.start();
}
@Override
public void log(RequestInfo requestInfo, ResponseInfo responseInfo)
{
asyncAppender.doAppend(createHttpRequestEvent(requestInfo, responseInfo));
}
@Override
public void close()
{
asyncAppender.stop();
}
@Override
public int getQueueSize()
{
return asyncAppender.getNumberOfElementsInQueue();
}
private static void recoverTempFiles(String logPath)
{
// logback has a tendency to leave around temp files if it is interrupted
// these .tmp files are log files that are about to be compressed.
// This method recovers them so that they aren't orphaned
File logPathFile = new File(logPath).getParentFile();
File[] tempFiles = logPathFile.listFiles((directory, name) -> name.endsWith(TEMP_FILE_EXTENSION));
if (tempFiles != null) {
for (File tempFile : tempFiles) {
String newName = tempFile.getName().substring(0, tempFile.getName().length() - TEMP_FILE_EXTENSION.length());
File newFile = new File(tempFile.getParent(), newName + LOG_FILE_EXTENSION);
if (tempFile.renameTo(newFile)) {
LOG.info("Recovered temp file: %s", tempFile);
}
else {
LOG.warn("Could not rename temp file [%s] to [%s]", tempFile, newFile);
}
}
}
}
private static class FlushingFileAppender
extends RollingFileAppender
{
private final AtomicLong lastFlushed = new AtomicLong(System.nanoTime());
private final long flushIntervalNanos;
private FlushingFileAppender(Duration flushInterval)
{
this.flushIntervalNanos = flushInterval.roundTo(NANOSECONDS);
}
@Override
protected void subAppend(T event)
{
super.subAppend(event);
long now = System.nanoTime();
long last = lastFlushed.get();
if (((now - last) > flushIntervalNanos) && lastFlushed.compareAndSet(last, now)) {
flush();
}
}
@SuppressWarnings("Duplicates")
private void flush()
{
try {
streamWriteLock.lock();
try {
getOutputStream().flush();
}
finally {
streamWriteLock.unlock();
}
}
catch (IOException e) {
started = false;
addStatus(new ErrorStatus("IO failure in appender", this, e));
}
}
}
}