| /* |
| * |
| * 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(); |
| } |
| |
| } |
| } |