blob: 1f8e49a3215a249d09a9965b862a231f64aa6b0f [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.qpid.server.logging.logback;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Pattern;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.rolling.RollingPolicy;
import ch.qos.logback.core.rolling.RollingPolicyBase;
import ch.qos.logback.core.rolling.RolloverFailure;
import ch.qos.logback.core.rolling.helper.CompressionMode;
import ch.qos.logback.core.rolling.helper.FileNamePattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RollingPolicyDecorator implements RollingPolicy
{
public static final int DEFAULT_RESCAN_DELAY = 5000;
public static final String ROLLOVER_RESCAN_DELAY_MS_PROPERTY_NAME = "qpid.logger_rollover_rescan_delay_ms";
public static final int DEFAULT_RESCAN_LIMIT = 60;
public static final String ROLLOVER_RESCAN_LIMIT_PROPERTY_NAME = "qpid.logger_rollover_rescan_limit";
private static final Logger LOGGER = LoggerFactory.getLogger(RollingPolicyDecorator.class);
private final RollingPolicyBase _decorated;
private final RolloverListener _listener;
private final Path _rolledFilesBaseFolder;
private final Pattern _rolledFileRegExp;
private final ScheduledExecutorService _executorService;
private final long _rescanDelayMillis = Long.getLong(ROLLOVER_RESCAN_DELAY_MS_PROPERTY_NAME, DEFAULT_RESCAN_DELAY);
private final long _rescanLimit = Long.getLong(ROLLOVER_RESCAN_LIMIT_PROPERTY_NAME, DEFAULT_RESCAN_LIMIT);
private final Lock _publishResultsLock = new ReentrantLock();
private ScanTask _currentScanTask;
private String[] _previousScanResults;
public RollingPolicyDecorator(RollingPolicyBase decorated, RolloverListener listener, ScheduledExecutorService executorService)
{
_decorated = decorated;
_listener = listener;
_executorService = executorService;
String filePathPattern = _decorated.getFileNamePattern();
String filePathRegExp = new FileNamePattern(filePathPattern, _decorated.getContext()).toRegex();
_rolledFilesBaseFolder = getRolledFilesBaseFolderFromRegExp(filePathRegExp);
_rolledFileRegExp = Pattern.compile(filePathRegExp);
_currentScanTask = null;
}
@Override
public void rollover() throws RolloverFailure
{
ScanTask task = createScanTaskAndCancelInProgress();
_decorated.rollover();
_executorService.execute(task);
}
@Override
public String getActiveFileName()
{
return _decorated.getActiveFileName();
}
@Override
public CompressionMode getCompressionMode()
{
return _decorated.getCompressionMode();
}
@Override
public void setParent(FileAppender appender)
{
_decorated.setParent(appender);
}
@Override
public void start()
{
ScanTask task = createScanTaskAndCancelInProgress();
_decorated.start();
_executorService.execute(task);
}
@Override
public void stop()
{
_decorated.stop();
synchronized (_publishResultsLock)
{
if (_currentScanTask != null)
{
_currentScanTask.cancel();
}
_previousScanResults = null;
}
}
@Override
public boolean isStarted()
{
return _decorated.isStarted();
}
public interface RolloverListener
{
void onRollover(Path baseFolder, String[] relativeFileNames);
void onNoRolloverDetected(Path baseFolder, String[] relativeFileNames);
}
public RollingPolicyBase getDecorated()
{
return _decorated;
}
private ScanTask createScanTaskAndCancelInProgress()
{
ScanTask task = new ScanTask();
synchronized (_publishResultsLock)
{
if (_currentScanTask != null)
{
_currentScanTask.cancel();
}
_currentScanTask = task;
}
return task;
}
private Path getRolledFilesBaseFolderFromRegExp(String fileNamePattern)
{
int firstDigitPatternPosition= fileNamePattern.indexOf("\\d");
if (firstDigitPatternPosition == -1)
{
throw new RuntimeException("Rolling policy file pattern does not seem to contain date or integer token");
}
int slashBeforeDigitPatternPosition = fileNamePattern.lastIndexOf("/", firstDigitPatternPosition);
if (slashBeforeDigitPatternPosition != -1)
{
return new File(fileNamePattern.substring(0, slashBeforeDigitPatternPosition)).toPath().toAbsolutePath();
}
else
{
return new File(System.getProperty("user.dir")).toPath().toAbsolutePath();
}
}
private class ScanTask implements Runnable
{
private int _rescanCounter;
private volatile boolean _canceled;
@Override
public void run()
{
if (!isCanceled() )
{
String[] rolloverFiles = scan();
if (!publishScanResults(rolloverFiles) && !isCanceled())
{
if (_rescanCounter < _rescanLimit)
{
++_rescanCounter;
_executorService.schedule(this, _rescanDelayMillis, TimeUnit.MILLISECONDS);
}
else
{
_listener.onNoRolloverDetected(_rolledFilesBaseFolder, rolloverFiles);
}
}
}
}
private boolean publishScanResults(String[] rolloverFiles)
{
boolean published = false;
if (rolloverFiles != null && !isCanceled() )
{
synchronized (_publishResultsLock)
{
if (!isCanceled() && (_previousScanResults == null || !Arrays.equals(rolloverFiles, _previousScanResults)))
{
_previousScanResults = rolloverFiles;
published = true;
}
}
}
if (published)
{
_listener.onRollover(_rolledFilesBaseFolder, rolloverFiles);
}
return published;
}
public String[] scan()
{
final List<Path> rolledFiles = new ArrayList<>();
try
{
Files.walkFileTree(_rolledFilesBaseFolder, new FileVisitor<Path>()
{
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
{
return (isCanceled() ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
{
String absolutePath = file.toAbsolutePath().toString();
if (File.separatorChar == '\\')
{
absolutePath = absolutePath.replace('\\', '/');
}
if (_rolledFileRegExp.matcher(absolutePath).matches())
{
rolledFiles.add(file);
}
return (isCanceled() ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE);
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc)
{
return (isCanceled() ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE);
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
{
return (isCanceled() ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE);
}
});
}
catch(IOException e)
{
LOGGER.warn("Unexpected IOException while scanning for rollover files.", e);
}
return (isCanceled() ? null : relativizeAndSort(_rolledFilesBaseFolder, rolledFiles));
}
public void cancel()
{
_canceled = true;
}
private String[] relativizeAndSort(Path parent, List<Path> rolledFiles)
{
String[] results = new String[rolledFiles.size()];
int i = 0;
for (Path f : rolledFiles)
{
results[i++] = parent.relativize(f).toString();
}
Arrays.sort(results, new Comparator<String>()
{
@Override
public int compare(String o1, String o2)
{
return o1.compareTo(o2);
}
});
return results;
}
public boolean isCanceled()
{
return _canceled || Thread.currentThread().isInterrupted();
}
}
}