001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.appender.rolling; 018 019import java.io.File; 020import java.io.IOException; 021import java.nio.file.DirectoryStream; 022import java.nio.file.Files; 023import java.nio.file.Path; 024import java.util.ArrayList; 025import java.util.List; 026import java.util.SortedMap; 027import java.util.TreeMap; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030 031import org.apache.logging.log4j.Logger; 032import org.apache.logging.log4j.LoggingException; 033import org.apache.logging.log4j.core.appender.rolling.action.Action; 034import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction; 035import org.apache.logging.log4j.core.lookup.StrSubstitutor; 036import org.apache.logging.log4j.core.pattern.NotANumber; 037import org.apache.logging.log4j.status.StatusLogger; 038 039/** 040 * 041 */ 042public abstract class AbstractRolloverStrategy implements RolloverStrategy { 043 044 /** 045 * Allow subclasses access to the status logger without creating another instance. 046 */ 047 protected static final Logger LOGGER = StatusLogger.getLogger(); 048 049 public static final Pattern PATTERN_COUNTER= Pattern.compile(".*%((?<ZEROPAD>0)?(?<PADDING>\\d+))?i.*"); 050 051 protected final StrSubstitutor strSubstitutor; 052 053 protected AbstractRolloverStrategy(final StrSubstitutor strSubstitutor) { 054 this.strSubstitutor = strSubstitutor; 055 } 056 057 058 public StrSubstitutor getStrSubstitutor() { 059 return strSubstitutor; 060 } 061 062 protected Action merge(final Action compressAction, final List<Action> custom, final boolean stopOnError) { 063 if (custom.isEmpty()) { 064 return compressAction; 065 } 066 if (compressAction == null) { 067 return new CompositeAction(custom, stopOnError); 068 } 069 final List<Action> all = new ArrayList<>(); 070 all.add(compressAction); 071 all.addAll(custom); 072 return new CompositeAction(all, stopOnError); 073 } 074 075 protected int suffixLength(final String lowFilename) { 076 for (final FileExtension extension : FileExtension.values()) { 077 if (extension.isExtensionFor(lowFilename)) { 078 return extension.length(); 079 } 080 } 081 return 0; 082 } 083 084 085 protected SortedMap<Integer, Path> getEligibleFiles(final RollingFileManager manager) { 086 return getEligibleFiles(manager, true); 087 } 088 089 protected SortedMap<Integer, Path> getEligibleFiles(final RollingFileManager manager, 090 final boolean isAscending) { 091 final StringBuilder buf = new StringBuilder(); 092 final String pattern = manager.getPatternProcessor().getPattern(); 093 manager.getPatternProcessor().formatFileName(strSubstitutor, buf, NotANumber.NAN); 094 final String fileName = manager.isDirectWrite() ? "" : manager.getFileName(); 095 return getEligibleFiles(fileName, buf.toString(), pattern, isAscending); 096 } 097 098 protected SortedMap<Integer, Path> getEligibleFiles(final String path, final String pattern) { 099 return getEligibleFiles("", path, pattern, true); 100 } 101 102 @Deprecated 103 protected SortedMap<Integer, Path> getEligibleFiles(final String path, final String logfilePattern, 104 final boolean isAscending) { 105 return getEligibleFiles("", path, logfilePattern, isAscending); 106 } 107 108 protected SortedMap<Integer, Path> getEligibleFiles(final String currentFile, final String path, 109 final String logfilePattern, final boolean isAscending) { 110 final TreeMap<Integer, Path> eligibleFiles = new TreeMap<>(); 111 final File file = new File(path); 112 File parent = file.getParentFile(); 113 if (parent == null) { 114 parent = new File("."); 115 } else { 116 parent.mkdirs(); 117 } 118 if (!PATTERN_COUNTER.matcher(logfilePattern).matches()) { 119 return eligibleFiles; 120 } 121 final Path dir = parent.toPath(); 122 String fileName = file.getName(); 123 final int suffixLength = suffixLength(fileName); 124 // use Pattern.quote to treat all initial parts of the fileName as literal 125 // this fixes issues with filenames containing 'magic' regex characters 126 if (suffixLength > 0) { 127 fileName = Pattern.quote(fileName.substring(0, fileName.length() - suffixLength)) + ".*"; 128 } else { 129 fileName = Pattern.quote(fileName); 130 } 131 // since we insert a pattern inside a regex escaped string, 132 // surround it with quote characters so that (\d) is treated as a pattern and not a literal 133 final String filePattern = fileName.replaceFirst("0?\\u0000", "\\\\E(0?\\\\d+)\\\\Q"); 134 final Pattern pattern = Pattern.compile(filePattern); 135 final Path current = currentFile.length() > 0 ? new File(currentFile).toPath() : null; 136 LOGGER.debug("Current file: {}", currentFile); 137 138 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { 139 for (final Path entry: stream) { 140 final Matcher matcher = pattern.matcher(entry.toFile().getName()); 141 if (matcher.matches() && !entry.equals(current)) { 142 try { 143 final Integer index = Integer.parseInt(matcher.group(1)); 144 eligibleFiles.put(index, entry); 145 } catch (NumberFormatException ex) { 146 LOGGER.debug("Ignoring file {} which matches pattern but the index is invalid.", 147 entry.toFile().getName()); 148 } 149 } 150 } 151 } catch (final IOException ioe) { 152 throw new LoggingException("Error reading folder " + dir + " " + ioe.getMessage(), ioe); 153 } 154 return isAscending? eligibleFiles : eligibleFiles.descendingMap(); 155 } 156}