I wonder if Duration belongs in core.util; we could reuse it other places, like in the Configuration monitorInterval setting.
Gary ---------- Forwarded message ---------- From: <rpo...@apache.org> Date: Sat, Nov 14, 2015 at 11:25 AM Subject: [1/4] logging-log4j2 git commit: LOG4J2-435 actions and filters for delete-on-rollover support To: comm...@logging.apache.org Repository: logging-log4j2 Updated Branches: refs/heads/LOG4J2-435-delete-on-rollover [created] 07175148e LOG4J2-435 actions and filters for delete-on-rollover support - AbstractPathAction defines an Action that can process multiple files starting at some base path and excluding files rejected by nested filters - DeleteAction extends AbstractPathAction to run Files.walkFileTree with a DeletingVisitor - DeletingVisitor deletes files that are accepted by all PathFilters - PathFilter accepts/rejects a basePath + relativePath + fileAttributes combination - And, Or, Not are composite PathFilters - FileLastModifiedFilter accepts paths whose lastModifiedTime is at least the specified duration in the past - Duration implements ISO8601 - FileNameFilter accepts paths based on either a regex or a name check (with simple wildcarts) Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/7b7bdfaf Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/7b7bdfaf Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/7b7bdfaf Branch: refs/heads/LOG4J2-435-delete-on-rollover Commit: 7b7bdfafb348caf59025c8f6efc530a78d0ff45f Parents: d9fa7eb Author: rpopma <rpo...@apache.org> Authored: Sun Nov 15 04:22:27 2015 +0900 Committer: rpopma <rpo...@apache.org> Committed: Sun Nov 15 04:22:27 2015 +0900 ---------------------------------------------------------------------- .../rolling/action/AbstractPathAction.java | 154 +++++++++++ .../log4j/core/appender/rolling/action/And.java | 76 ++++++ .../appender/rolling/action/DeleteAction.java | 83 ++++++ .../rolling/action/DeletingVisitor.java | 74 ++++++ .../core/appender/rolling/action/Duration.java | 256 +++++++++++++++++++ .../rolling/action/FileLastModifiedFilter.java | 77 ++++++ .../appender/rolling/action/FileNameFilter.java | 144 +++++++++++ .../log4j/core/appender/rolling/action/Not.java | 70 +++++ .../log4j/core/appender/rolling/action/Or.java | 74 ++++++ .../appender/rolling/action/PathFilter.java | 37 +++ 10 files changed, 1045 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractPathAction.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractPathAction.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractPathAction.java new file mode 100644 index 0000000..d976549 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractPathAction.java @@ -0,0 +1,154 @@ +/* + * 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.logging.log4j.core.appender.rolling.action; + +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.core.lookup.StrSubstitutor; + +/** + * Abstract action for processing files that are accepted by the specified PathFilters. + */ +public abstract class AbstractPathAction extends AbstractAction { + + private final String basePathString; + private final Set<FileVisitOption> options; + private final int maxDepth; + private final List<PathFilter> pathFilters; + private final StrSubstitutor subst; + + /** + * Creates a new AbstractPathAction that starts scanning for files to process from the specified base path. + * + * @param basePath base path from where to start scanning for files to process. + * @param followSymbolicLinks whether to follow symbolic links. Default is false. + * @param maxDepth The maxDepth parameter is the maximum number of levels of directories to visit. A value of 0 + * means that only the starting file is visited, unless denied by the security manager. A value of + * MAX_VALUE may be used to indicate that all levels should be visited. + * @param pathFilters an array of path filters (if more than one, they all need to accept a path before it is + * processed). + */ + protected AbstractPathAction(final String basePath, final boolean followSymbolicLinks, final int maxDepth, + final PathFilter[] pathFilters, final StrSubstitutor subst) { + this.basePathString = basePath; + this.options = followSymbolicLinks ? EnumSet.of(FileVisitOption.FOLLOW_LINKS) // + : Collections.<FileVisitOption> emptySet(); + this.maxDepth = maxDepth; + this.pathFilters = Arrays.asList(Arrays.copyOf(pathFilters, pathFilters.length)); + this.subst = subst; + } + + @Override + public boolean execute() throws IOException { + return execute(createFileVisitor(getBasePath(), pathFilters)); + } + + public boolean execute(final FileVisitor<Path> visitor) throws IOException { + final long start = System.nanoTime(); + LOGGER.debug("Starting {}", this); + + Files.walkFileTree(getBasePath(), options, maxDepth, visitor); + + final double duration = System.nanoTime() - start; + LOGGER.debug("{} complete in {} seconds", getClass().getSimpleName(), duration / TimeUnit.SECONDS.toNanos(1)); + + // TODO return (visitor.success || ignoreProcessingFailure) + return true; // do not abort rollover even if processing failed + } + + /** + * Creates a new {@code FileVisitor<Path>} to pass to the {@link Files#walkFileTree(Path, Set, int, FileVisitor)} + * method when the {@link #execute()} method is invoked. + * <p> + * The visitor is responsible for processing the files it encounters that are accepted by all filters. + * + * @param visitorBaseDir base dir from where to start scanning for files to process + * @param visitorFilters filters that determine if a file should be processed + * @return a new {@code FileVisitor<Path>} + */ + protected abstract FileVisitor<Path> createFileVisitor(final Path visitorBaseDir, + final List<PathFilter> visitorFilters); + + /** + * Returns the base path from where to start scanning for files to delete. Lookups are resolved, so if the + * configuration was <code><Delete basePath="${sys:user.home}/abc" /></code> then this method returns a path + * to the "abc" file or directory in the user's home directory. + * + * @return the base path (all lookups resolved) + */ + public Path getBasePath() { + return FileSystems.getDefault().getPath(subst.replace(getBasePathString())); + } + + /** + * Returns the base path as it was specified in the configuration. Lookups are not resolved. + * + * @return the base path as it was specified in the configuration + */ + public String getBasePathString() { + return basePathString; + } + + public StrSubstitutor getStrSubstitutor() { + return subst; + } + + /** + * Returns whether to follow symbolic links or not. + * + * @return the options + */ + public Set<FileVisitOption> getOptions() { + return Collections.unmodifiableSet(options); + } + + /** + * Returns the the maximum number of directory levels to visit. + * + * @return the maxDepth + */ + public int getMaxDepth() { + return maxDepth; + } + + /** + * Returns the list of PathFilter objects. + * + * @return the pathFilters + */ + public List<PathFilter> getPathFilters() { + return Collections.unmodifiableList(pathFilters); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[basePath=" + getBasePath() + ", options=" + options + ", maxDepth=" + + maxDepth + ", filters=" + pathFilters + "]"; + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/And.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/And.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/And.java new file mode 100644 index 0000000..c701c8e --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/And.java @@ -0,0 +1,76 @@ +/* + * 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.logging.log4j.core.appender.rolling.action; + +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; +import java.util.Objects; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +/** + * Composite {@code DeleteFilter} that only accepts objects that are accepted by <em>all</em> component filters. + */ +@Plugin(name = "And", category = "Core", printObject = true) +public final class And implements PathFilter { + + private final PathFilter[] components; + + private And(final PathFilter... filters) { + this.components = Objects.requireNonNull(filters, "filters"); + } + + public PathFilter[] getDeleteFilters() { + return components; + } + + /* + * (non-Javadoc) + * + * @see org.apache.logging.log4j.core.appender.rolling.action.DeleteFilter#accept(java.nio.file.Path, + * java.nio.file.Path) + */ + @Override + public boolean accept(final Path baseDir, final Path relativePath, final BasicFileAttributes attrs) { + for (final PathFilter component : components) { + if (!component.accept(baseDir, relativePath, attrs)) { + return false; + } + } + return true; + } + + /** + * Create a Composite DeleteFilter. + * + * @param components The component filters. + * @return A CompositeDeleteFilter. + */ + @PluginFactory + public static And createAndFilter( // + @PluginElement("Filters") final PathFilter... components) { + return new And(components); + } + + @Override + public String toString() { + return "And(filters=" + Arrays.toString(components) + ")"; + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java new file mode 100644 index 0000000..47fddc5 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java @@ -0,0 +1,83 @@ +/* + * 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.logging.log4j.core.appender.rolling.action; + +import java.nio.file.FileVisitor; +import java.nio.file.Path; +import java.util.List; + +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; + +/** + * Rollover or scheduled action for deleting old log files that are accepted by the specified PathFilters. + */ +@Plugin(name = "Delete", category = "Core", printObject = true) +public class DeleteAction extends AbstractPathAction { + + /** + * Creates a new DeleteAction that starts scanning for files to delete from the specified base path. + * + * @param basePath base path from where to start scanning for files to delete. + * @param followSymbolicLinks whether to follow symbolic links. Default is false. + * @param maxDepth The maxDepth parameter is the maximum number of levels of directories to visit. A value of 0 + * means that only the starting file is visited, unless denied by the security manager. A value of + * MAX_VALUE may be used to indicate that all levels should be visited. + * @param pathFilters an array of path filters (if more than one, they all need to accept a path before it is + * deleted). + */ + DeleteAction(final String basePath, final boolean followSymbolicLinks, final int maxDepth, + final PathFilter[] pathFilters, final StrSubstitutor subst) { + super(basePath, followSymbolicLinks, maxDepth, pathFilters, subst); + } + + @Override + protected FileVisitor<Path> createFileVisitor(final Path visitorBaseDir, final List<PathFilter> visitorFilters) { + return new DeletingVisitor(visitorBaseDir, visitorFilters); + } + + /** + * Create a DeleteAction. + * + * @param basePath base path from where to start scanning for files to delete. + * @param followLinks whether to follow symbolic links. Default is false. + * @param maxDepth The maxDepth parameter is the maximum number of levels of directories to visit. A value of 0 + * means that only the starting file is visited, unless denied by the security manager. A value of + * MAX_VALUE may be used to indicate that all levels should be visited. + * @param pathFilters an array of path filters (if more than one, they all need to accept a path before it is + * deleted). + * @param config The Configuration. + * @return A DeleteAction. + */ + @PluginFactory + public static DeleteAction createDeleteAction( + // @formatter:off + @PluginAttribute("basePath") final String basePath, // + @PluginAttribute(value = "followLinks", defaultBoolean = false) final boolean followLinks, + @PluginAttribute(value = "maxDepth", defaultInt = 1) final int maxDepth, + @PluginElement("PathFilters") final PathFilter[] pathFilters, + @PluginConfiguration final Configuration config) { + // @formatter:on + return new DeleteAction(basePath, followLinks, maxDepth, pathFilters, config.getStrSubstitutor()); + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java new file mode 100644 index 0000000..ba290d9 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java @@ -0,0 +1,74 @@ +/* + * 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.logging.log4j.core.appender.rolling.action; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.List; +import java.util.Objects; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * FileVisitor that deletes files that are accepted by all PathFilters. Directories are ignored. + */ +public class DeletingVisitor extends SimpleFileVisitor<Path> { + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final Path basePath; + private final List<? extends PathFilter> pathFilters; + + /** + * Constructs a new DeletingVisitor. + * + * @param basePath used to relativize paths + * @param pathFilters objects that need to confirm whether a file can be deleted + */ + public DeletingVisitor(final Path basePath, final List<? extends PathFilter> pathFilters) { + this.basePath = Objects.requireNonNull(basePath, "basePath"); + this.pathFilters = Objects.requireNonNull(pathFilters, "filters"); + } + + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { + for (final PathFilter pathFilter : pathFilters) { + if (!pathFilter.accept(basePath, basePath.relativize(file), attrs)) { + LOGGER.trace("Not deleting {}", file); + return FileVisitResult.CONTINUE; + } + } + delete(file); + return FileVisitResult.CONTINUE; + } + + /** + * Deletes the specified file. + * + * @param file the file to delete + * @throws IOException if a problem occurred deleting the file + */ + protected void delete(final Path file) throws IOException { + LOGGER.trace("Deleting {}", file); + Files.delete(file); + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Duration.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Duration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Duration.java new file mode 100644 index 0000000..d2b1e18 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Duration.java @@ -0,0 +1,256 @@ +/* + * 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.logging.log4j.core.appender.rolling.action; + +import java.io.Serializable; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Simplified implementation of the <a href=" https://en.wikipedia.org/wiki/ISO_8601#Durations">ISO-8601 Durations</a> + * standard. The supported format is {@code PnDTnHnMnS}. Days are considered to be exactly 24 hours. + * <p> + * Similarly to the {@code java.time.Duration} class, this class does not support year or month sections in the format. + * This implementation does not support fractions or negative values. + * + * @see #parse(CharSequence) + */ +public class Duration implements Serializable, Comparable<Duration> { + private static final long serialVersionUID = -3756810052716342061L; + + /** + * Constant for a duration of zero. + */ + public static final Duration ZERO = new Duration(0); + + /** + * Hours per day. + */ + private static final int HOURS_PER_DAY = 24; + /** + * Minutes per hour. + */ + private static final int MINUTES_PER_HOUR = 60; + /** + * Seconds per minute. + */ + private static final int SECONDS_PER_MINUTE = 60; + /** + * Seconds per hour. + */ + private static final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR; + /** + * Seconds per day. + */ + private static final int SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY; + + /** + * The pattern for parsing. + */ + private static final Pattern PATTERN = Pattern.compile("P(?:([0-9]+)D)?" + + "(T(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+)?S)?)?", Pattern.CASE_INSENSITIVE); + + /** + * The number of seconds in the duration. + */ + private final long seconds; + + /** + * Constructs an instance of {@code Duration} using seconds. + * + * @param seconds the length of the duration in seconds, positive or negative + */ + private Duration(long seconds) { + super(); + this.seconds = seconds; + } + + /** + * Obtains a {@code Duration} from a text string such as {@code PnDTnHnMnS}. + * <p> + * This will parse a textual representation of a duration, including the string produced by {@code toString()}. The + * formats accepted are based on the ISO-8601 duration format {@code PnDTnHnMnS} with days considered to be exactly + * 24 hours. + * <p> + * This implementation does not support negative numbers or fractions (so the smallest non-zero value a Duration can + * have is one second). + * <p> + * The string starts with the ASCII letter "P" in upper or lower case. There are then four sections, each consisting + * of a number and a suffix. The sections have suffixes in ASCII of "D", "H", "M" and "S" for days, hours, minutes + * and seconds, accepted in upper or lower case. The suffixes must occur in order. The ASCII letter "T" must occur + * before the first occurrence, if any, of an hour, minute or second section. At least one of the four sections must + * be present, and if "T" is present there must be at least one section after the "T". The number part of each + * section must consist of one or more ASCII digits. The number may not be prefixed by the ASCII negative or + * positive symbol. The number of days, hours, minutes and seconds must parse to a {@code long}. + * <p> + * Examples: + * + * <pre> + * "PT20S" -- parses as "20 seconds" + * "PT15M" -- parses as "15 minutes" (where a minute is 60 seconds) + * "PT10H" -- parses as "10 hours" (where an hour is 3600 seconds) + * "P2D" -- parses as "2 days" (where a day is 24 hours or 86400 seconds) + * "P2DT3H4M" -- parses as "2 days, 3 hours and 4 minutes" + * </pre> + * + * @param text the text to parse, not null + * @return the parsed duration, not null + * @throws IllegalArgumentException if the text cannot be parsed to a duration + */ + public static Duration parse(CharSequence text) { + Objects.requireNonNull(text, "text"); + Matcher matcher = PATTERN.matcher(text); + if (matcher.matches()) { + // check for letter T but no time sections + if ("T".equals(matcher.group(2)) == false) { + String dayMatch = matcher.group(1); + String hourMatch = matcher.group(3); + String minuteMatch = matcher.group(4); + String secondMatch = matcher.group(5); + if (dayMatch != null || hourMatch != null || minuteMatch != null || secondMatch != null) { + long daysAsSecs = parseNumber(text, dayMatch, SECONDS_PER_DAY, "days"); + long hoursAsSecs = parseNumber(text, hourMatch, SECONDS_PER_HOUR, "hours"); + long minsAsSecs = parseNumber(text, minuteMatch, SECONDS_PER_MINUTE, "minutes"); + long seconds = parseNumber(text, secondMatch, 1, "seconds"); + try { + return create(daysAsSecs, hoursAsSecs, minsAsSecs, seconds); + } catch (ArithmeticException ex) { + throw new IllegalArgumentException("Text cannot be parsed to a Duration (overflow) " + text, ex); + } + } + } + } + throw new IllegalArgumentException("Text cannot be parsed to a Duration: " + text); + } + + private static long parseNumber(final CharSequence text, final String parsed, final int multiplier, + final String errorText) { + // regex limits to [0-9]+ + if (parsed == null) { + return 0; + } + try { + final long val = Long.parseLong(parsed); + return val * multiplier; + } catch (final Exception ex) { + throw new IllegalArgumentException("Text cannot be parsed to a Duration: " + errorText + " (in " + text + + ")", ex); + } + } + + private static Duration create(final long daysAsSecs, final long hoursAsSecs, final long minsAsSecs, final long secs) { + return create(daysAsSecs + hoursAsSecs + minsAsSecs + secs); + } + + /** + * Obtains an instance of {@code Duration} using seconds. + * + * @param seconds the length of the duration in seconds, positive only + */ + private static Duration create(final long seconds) { + if ((seconds) == 0) { + return ZERO; + } + return new Duration(seconds); + } + + /** + * Converts this duration to the total length in milliseconds. + * + * @return the total length of the duration in milliseconds + */ + public long toMillis() { + return seconds * 1000L; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Duration)) { + return false; + } + Duration other = (Duration) obj; + return other.seconds == this.seconds; + } + + @Override + public int hashCode() { + return (int) (seconds ^ (seconds >>> 32)); + } + + /** + * A string representation of this duration using ISO-8601 seconds based representation, such as {@code PT8H6M12S}. + * <p> + * The format of the returned string will be {@code PnDTnHnMnS}, where n is the relevant days, hours, minutes or + * seconds part of the duration. If a section has a zero value, it is omitted. The hours, minutes and seconds are + * all positive. + * <p> + * Examples: + * + * <pre> + * "20 seconds" -- "PT20S + * "15 minutes" (15 * 60 seconds) -- "PT15M" + * "10 hours" (10 * 3600 seconds) -- "PT10H" + * "2 days" (2 * 86400 seconds) -- "P2D" + * </pre> + * + * @return an ISO-8601 representation of this duration, not null + */ + @Override + public String toString() { + if (this == ZERO) { + return "PT0S"; + } + final long days = seconds / SECONDS_PER_DAY; + final long hours = (seconds % SECONDS_PER_DAY) / SECONDS_PER_HOUR; + final int minutes = (int) ((seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE); + final int secs = (int) (seconds % SECONDS_PER_MINUTE); + final StringBuilder buf = new StringBuilder(24); + buf.append("P"); + if (days != 0) { + buf.append(days).append('D'); + } + if ((hours | minutes | secs) != 0) { + buf.append('T'); + } + if (hours != 0) { + buf.append(hours).append('H'); + } + if (minutes != 0) { + buf.append(minutes).append('M'); + } + if (secs == 0 && buf.length() > 0) { + return buf.toString(); + } + buf.append(secs).append('S'); + return buf.toString(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + @Override + public int compareTo(Duration other) { + return Long.signum(toMillis() - other.toMillis()); + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileLastModifiedFilter.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileLastModifiedFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileLastModifiedFilter.java new file mode 100644 index 0000000..6904ade --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileLastModifiedFilter.java @@ -0,0 +1,77 @@ +/* + * 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.logging.log4j.core.appender.rolling.action; + +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.Objects; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.util.Clock; +import org.apache.logging.log4j.core.util.ClockFactory; + +/** + * PathFilter that accepts paths that are older than the specified duration. + */ +@Plugin(name = "LastModified", category = "Core", printObject = true) +public final class FileLastModifiedFilter implements PathFilter { + private static final Clock CLOCK = ClockFactory.getClock(); + + private final Duration duration; + + private FileLastModifiedFilter(final Duration duration) { + this.duration = Objects.requireNonNull(duration, "duration"); + } + + public Duration getDuration() { + return duration; + } + + /* + * (non-Javadoc) + * + * @see org.apache.logging.log4j.core.appender.rolling.action.PathFilter#accept(java.nio.file.Path, + * java.nio.file.Path) + */ + @Override + public boolean accept(final Path baseDir, final Path relativePath, final BasicFileAttributes attrs) { + final FileTime fileTime = attrs.lastModifiedTime(); + final long millis = fileTime.toMillis(); + final long ageMillis = CLOCK.currentTimeMillis() - millis; + return ageMillis >= duration.toMillis(); + } + + /** + * Create a FileLastModifiedFilter filter. + * + * @param duration The path age that is accepted by this filter. Must be a valid Duration. + * @return A FileLastModifiedFilter filter. + */ + @PluginFactory + public static FileLastModifiedFilter createAgeFilter( // + @PluginAttribute("duration") final Duration duration) { + return new FileLastModifiedFilter(duration); + } + + @Override + public String toString() { + return "FileLastModifiedFilter(age=" + duration + ")"; + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileNameFilter.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileNameFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileNameFilter.java new file mode 100644 index 0000000..cbde2a9 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileNameFilter.java @@ -0,0 +1,144 @@ +/* + * 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.logging.log4j.core.appender.rolling.action; + +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +/** + * PathFilter that accepts files for deletion if their relative path matches either a path pattern or a regular + * expression. + * <p> + * If both a regular expression and a path pattern are specified the path pattern is used and the regular expression is + * ignored. + * <p> + * The path pattern may contain '?' and '*' wildcarts. + */ +@Plugin(name = "File", category = "Core", printObject = true) +public final class FileNameFilter implements PathFilter { + + private final Pattern regex; + private final String pathPattern; + + /** + * Constructs a FileNameFilter filter. If both a regular expression and a path pattern are specified the path + * pattern is used and the regular expression is ignored. + * + * @param path the baseDir-relative path pattern of the files to delete (may contain '*' and '?' wildcarts) + * @param regex the regular expression that matches the baseDir-relative path of the file(s) to delete + */ + private FileNameFilter(final String path, final String regex) { + if (regex == null && path == null) { + throw new IllegalArgumentException("Specify either a path or a regular expression. Both cannot be null."); + } + this.regex = regex != null ? Pattern.compile(regex) : null; + this.pathPattern = path; + } + + /** + * Returns the compiled regular expression that matches the baseDir-relative path of the file(s) to delete, or + * {@code null} if no regular expression was specified. + * + * @return the compiled regular expression, or {@code null} + */ + public Pattern getRegex() { + return regex; + } + + /** + * Returns the baseDir-relative path pattern of the files to delete, or {@code null} if not specified. This path + * pattern may contain '*' and '?' wildcarts. + * + * @return relative path of the file(s) to delete (may contain '*' and '?' wildcarts) + */ + public String getPathPattern() { + return pathPattern; + } + + /* + * (non-Javadoc) + * + * @see org.apache.logging.log4j.core.appender.rolling.action.PathFilter#accept(java.nio.file.Path, + * java.nio.file.Path) + */ + @Override + public boolean accept(final Path baseDir, final Path relativePath, final BasicFileAttributes attrs) { + if (pathPattern != null) { + return isMatch(relativePath.toString(), pathPattern); + } else { + Matcher matcher = regex.matcher(relativePath.toString()); + return (matcher.matches()); + } + } + + // package protected for unit tests + static boolean isMatch(final String text, final String pattern) { + int i = 0; + int j = 0; + int starIndex = -1; + int iIndex = -1; + + while (i < text.length()) { + if (j < pattern.length() && (pattern.charAt(j) == '?' || pattern.charAt(j) == text.charAt(i))) { + ++i; + ++j; + } else if (j < pattern.length() && pattern.charAt(j) == '*') { + starIndex = j; + iIndex = i; + j++; + } else if (starIndex != -1) { + j = starIndex + 1; + i = iIndex + 1; + iIndex++; + } else { + return false; + } + } + + while (j < pattern.length() && pattern.charAt(j) == '*') { + ++j; + } + return j == pattern.length(); + } + + /** + * Creates a FileNameFilter filter. If both a regular expression and a path pattern are specified the path pattern + * is used and the regular expression is ignored. + * + * @param path the baseDir-relative path pattern of the files to delete (may contain '*' and '?' wildcarts) + * @param regex the regular expression that matches the baseDir-relative path of the file(s) to delete + * @return A FileNameFilter filter. + */ + @PluginFactory + public static FileNameFilter createNameFilter( // + @PluginAttribute("path") final String path, // + @PluginAttribute("regex") final String regex) { + return new FileNameFilter(path, regex); + } + + @Override + public String toString() { + final String pattern = regex == null ? "null" : regex.pattern(); + return "FileNameFilter(regex=" + pattern + ", name=" + pathPattern + ")"; + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Not.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Not.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Not.java new file mode 100644 index 0000000..7265dce --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Not.java @@ -0,0 +1,70 @@ +/* + * 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.logging.log4j.core.appender.rolling.action; + +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Objects; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +/** + * Wrapper {@code DeleteFilter} that accepts objects that are rejected by the wrapped component filter. + */ +@Plugin(name = "Not", category = "Core", printObject = true) +public final class Not implements PathFilter { + + private final PathFilter negate; + + private Not(final PathFilter negate) { + this.negate = Objects.requireNonNull(negate, "filter"); + } + + public PathFilter getWrappedFilter() { + return negate; + } + + /* + * (non-Javadoc) + * + * @see org.apache.logging.log4j.core.appender.rolling.action.DeleteFilter#accept(java.nio.file.Path, + * java.nio.file.Path) + */ + @Override + public boolean accept(final Path baseDir, final Path relativePath, final BasicFileAttributes attrs) { + return !negate.accept(baseDir, relativePath, attrs); + } + + /** + * Create a Not filter. + * + * @param filter The filter to negate. + * @return A Not filter. + */ + @PluginFactory + public static Not createNotFilter( // + @PluginElement("Filters") final PathFilter filter) { + return new Not(filter); + } + + @Override + public String toString() { + return "Not(filters=" + negate + ")"; + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Or.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Or.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Or.java new file mode 100644 index 0000000..68de219 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Or.java @@ -0,0 +1,74 @@ +/* + * 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.logging.log4j.core.appender.rolling.action; + +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; +import java.util.Objects; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +/** + * Composite {@code DeleteFilter} that accepts objects that are accepted by <em>any</em> component filters. + */ +@Plugin(name = "Or", category = "Core", printObject = true) +public final class Or implements PathFilter { + + private final PathFilter[] components; + + private Or(final PathFilter... filters) { + this.components = Objects.requireNonNull(filters, "filters"); + } + + public PathFilter[] getDeleteFilters() { + return components; + } + + /* (non-Javadoc) + * @see org.apache.logging.log4j.core.appender.rolling.action.DeleteFilter#accept(java.nio.file.Path, + * java.nio.file.Path) + */ + @Override + public boolean accept(final Path baseDir, final Path relativePath, final BasicFileAttributes attrs) { + for (final PathFilter component : components) { + if (component.accept(baseDir, relativePath, attrs)) { + return true; + } + } + return false; + } + + /** + * Create a Composite DeleteFilter. + * + * @param components The component filters. + * @return A CompositeDeleteFilter. + */ + @PluginFactory + public static Or createOrFilter( // + @PluginElement("Filters") final PathFilter... components) { + return new Or(components); + } + + @Override + public String toString() { + return "Or(filters=" + Arrays.toString(components) + ")"; + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathFilter.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathFilter.java new file mode 100644 index 0000000..f6b782a --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathFilter.java @@ -0,0 +1,37 @@ +/* + * 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.logging.log4j.core.appender.rolling.action; + +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; + +/** + * Filter that accepts or rejects a candidate {@code Path} for deletion. + */ +public interface PathFilter { + + /** + * Returns {@code true} if the specified candidate path should be deleted, {@code false} otherwise. + * + * @param baseDir the directory from where to start scanning for deletion candidate files + * @param relativePath the candidate for deletion. This path is relative to the baseDir. + * @param attrs attributes of the candidate path + * @return whether the candidate path should be deleted + */ + boolean accept(final Path baseDir, final Path relativePath, final BasicFileAttributes attrs); +} -- E-Mail: garydgreg...@gmail.com | ggreg...@apache.org Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/> Spring Batch in Action <http://www.manning.com/templier/> Blog: http://garygregory.wordpress.com Home: http://garygregory.com/ Tweet! http://twitter.com/GaryGregory