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.BufferedOutputStream; 020import java.io.File; 021import java.io.FileNotFoundException; 022import java.io.FileOutputStream; 023import java.io.IOException; 024import java.io.OutputStream; 025import java.io.Serializable; 026import java.util.concurrent.Semaphore; 027 028import org.apache.logging.log4j.core.Layout; 029import org.apache.logging.log4j.core.LogEvent; 030import org.apache.logging.log4j.core.appender.FileManager; 031import org.apache.logging.log4j.core.appender.ManagerFactory; 032import org.apache.logging.log4j.core.appender.rolling.action.AbstractAction; 033import org.apache.logging.log4j.core.appender.rolling.action.Action; 034 035/** 036 * The Rolling File Manager. 037 */ 038public class RollingFileManager extends FileManager { 039 040 private static RollingFileManagerFactory factory = new RollingFileManagerFactory(); 041 042 private long size; 043 private long initialTime; 044 private final PatternProcessor patternProcessor; 045 private final Semaphore semaphore = new Semaphore(1); 046 private final TriggeringPolicy triggeringPolicy; 047 private final RolloverStrategy rolloverStrategy; 048 049 protected RollingFileManager(final String fileName, final String pattern, final OutputStream os, 050 final boolean append, final long size, final long time, final TriggeringPolicy triggeringPolicy, 051 final RolloverStrategy rolloverStrategy, final String advertiseURI, 052 final Layout<? extends Serializable> layout, final int bufferSize, final boolean writeHeader) { 053 super(fileName, os, append, false, advertiseURI, layout, bufferSize, writeHeader); 054 this.size = size; 055 this.initialTime = time; 056 this.triggeringPolicy = triggeringPolicy; 057 this.rolloverStrategy = rolloverStrategy; 058 this.patternProcessor = new PatternProcessor(pattern); 059 triggeringPolicy.initialize(this); 060 } 061 062 /** 063 * Returns a RollingFileManager. 064 * @param fileName The file name. 065 * @param pattern The pattern for rolling file. 066 * @param append true if the file should be appended to. 067 * @param bufferedIO true if data should be buffered. 068 * @param policy The TriggeringPolicy. 069 * @param strategy The RolloverStrategy. 070 * @param advertiseURI the URI to use when advertising the file 071 * @param layout The Layout. 072 * @param bufferSize buffer size to use if bufferedIO is true 073 * @return A RollingFileManager. 074 */ 075 public static RollingFileManager getFileManager(final String fileName, final String pattern, final boolean append, 076 final boolean bufferedIO, final TriggeringPolicy policy, final RolloverStrategy strategy, 077 final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize) { 078 079 return (RollingFileManager) getManager(fileName, new FactoryData(pattern, append, 080 bufferedIO, policy, strategy, advertiseURI, layout, bufferSize), factory); 081 } 082 083 @Override 084 protected synchronized void write(final byte[] bytes, final int offset, final int length) { 085 size += length; 086 super.write(bytes, offset, length); 087 } 088 089 /** 090 * Returns the current size of the file. 091 * @return The size of the file in bytes. 092 */ 093 public long getFileSize() { 094 return size; 095 } 096 097 /** 098 * Returns the time the file was created. 099 * @return The time the file was created. 100 */ 101 public long getFileTime() { 102 return initialTime; 103 } 104 105 /** 106 * Determine if a rollover should occur. 107 * @param event The LogEvent. 108 */ 109 public synchronized void checkRollover(final LogEvent event) { 110 if (triggeringPolicy.isTriggeringEvent(event) && rollover(rolloverStrategy)) { 111 try { 112 size = 0; 113 initialTime = System.currentTimeMillis(); 114 createFileAfterRollover(); 115 } catch (final IOException ex) { 116 LOGGER.error("FileManager (" + getFileName() + ") " + ex); 117 } 118 } 119 } 120 121 protected void createFileAfterRollover() throws IOException { 122 final OutputStream os = new FileOutputStream(getFileName(), isAppend()); 123 if (getBufferSize() > 0) { // negative buffer size means no buffering 124 setOutputStream(new BufferedOutputStream(os, getBufferSize())); 125 } else { 126 setOutputStream(os); 127 } 128 } 129 130 /** 131 * Returns the pattern processor. 132 * @return The PatternProcessor. 133 */ 134 public PatternProcessor getPatternProcessor() { 135 return patternProcessor; 136 } 137 138 /** 139 * Returns the triggering policy 140 * @return The TriggeringPolicy 141 */ 142 public <T extends TriggeringPolicy> T getTriggeringPolicy() { 143 // TODO We could parameterize this class with a TriggeringPolicy instead of type casting here. 144 return (T) this.triggeringPolicy; 145 } 146 147 /** 148 * Returns the rollover strategy 149 * @return The RolloverStrategy 150 */ 151 public RolloverStrategy getRolloverStrategy() { 152 return this.rolloverStrategy; 153 } 154 155 private boolean rollover(final RolloverStrategy strategy) { 156 157 try { 158 // Block until the asynchronous operation is completed. 159 semaphore.acquire(); 160 } catch (final InterruptedException ie) { 161 LOGGER.error("Thread interrupted while attempting to check rollover", ie); 162 return false; 163 } 164 165 boolean success = false; 166 Thread thread = null; 167 168 try { 169 final RolloverDescription descriptor = strategy.rollover(this); 170 if (descriptor != null) { 171 writeFooter(); 172 close(); 173 if (descriptor.getSynchronous() != null) { 174 LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous()); 175 try { 176 success = descriptor.getSynchronous().execute(); 177 } catch (final Exception ex) { 178 LOGGER.error("Error in synchronous task", ex); 179 } 180 } 181 182 if (success && descriptor.getAsynchronous() != null) { 183 LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous()); 184 thread = new Thread(new AsyncAction(descriptor.getAsynchronous(), this)); 185 thread.start(); 186 } 187 return true; 188 } 189 return false; 190 } finally { 191 if (thread == null || !thread.isAlive()) { 192 semaphore.release(); 193 } 194 } 195 196 } 197 198 /** 199 * Performs actions asynchronously. 200 */ 201 private static class AsyncAction extends AbstractAction { 202 203 private final Action action; 204 private final RollingFileManager manager; 205 206 /** 207 * Constructor. 208 * @param act The action to perform. 209 * @param manager The manager. 210 */ 211 public AsyncAction(final Action act, final RollingFileManager manager) { 212 this.action = act; 213 this.manager = manager; 214 } 215 216 /** 217 * Perform an action. 218 * 219 * @return true if action was successful. A return value of false will cause 220 * the rollover to be aborted if possible. 221 * @throws java.io.IOException if IO error, a thrown exception will cause the rollover 222 * to be aborted if possible. 223 */ 224 @Override 225 public boolean execute() throws IOException { 226 try { 227 return action.execute(); 228 } finally { 229 manager.semaphore.release(); 230 } 231 } 232 233 /** 234 * Cancels the action if not already initialized or waits till completion. 235 */ 236 @Override 237 public void close() { 238 action.close(); 239 } 240 241 /** 242 * Determines if action has been completed. 243 * 244 * @return true if action is complete. 245 */ 246 @Override 247 public boolean isComplete() { 248 return action.isComplete(); 249 } 250 } 251 252 /** 253 * Factory data. 254 */ 255 private static class FactoryData { 256 private final String pattern; 257 private final boolean append; 258 private final boolean bufferedIO; 259 private final int bufferSize; 260 private final TriggeringPolicy policy; 261 private final RolloverStrategy strategy; 262 private final String advertiseURI; 263 private final Layout<? extends Serializable> layout; 264 265 /** 266 * Create the data for the factory. 267 * @param pattern The pattern. 268 * @param append The append flag. 269 * @param bufferedIO The bufferedIO flag. 270 * @param advertiseURI 271 * @param layout The Layout. 272 * @param bufferSize the buffer size 273 */ 274 public FactoryData(final String pattern, final boolean append, final boolean bufferedIO, 275 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, 276 final Layout<? extends Serializable> layout, final int bufferSize) { 277 this.pattern = pattern; 278 this.append = append; 279 this.bufferedIO = bufferedIO; 280 this.bufferSize = bufferSize; 281 this.policy = policy; 282 this.strategy = strategy; 283 this.advertiseURI = advertiseURI; 284 this.layout = layout; 285 } 286 } 287 288 /** 289 * Factory to create a RollingFileManager. 290 */ 291 private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> { 292 293 /** 294 * Create the RollingFileManager. 295 * @param name The name of the entity to manage. 296 * @param data The data required to create the entity. 297 * @return a RollingFileManager. 298 */ 299 @Override 300 public RollingFileManager createManager(final String name, final FactoryData data) { 301 final File file = new File(name); 302 final File parent = file.getParentFile(); 303 if (null != parent && !parent.exists()) { 304 parent.mkdirs(); 305 } 306 try { 307 file.createNewFile(); 308 } catch (final IOException ioe) { 309 LOGGER.error("Unable to create file " + name, ioe); 310 return null; 311 } 312 final long size = data.append ? file.length() : 0; 313 314 final boolean writeHeader = !data.append || !file.exists(); 315 OutputStream os; 316 try { 317 os = new FileOutputStream(name, data.append); 318 int bufferSize = data.bufferSize; 319 if (data.bufferedIO) { 320 os = new BufferedOutputStream(os, bufferSize); 321 } else { 322 bufferSize = -1; // negative buffer size signals bufferedIO was configured false 323 } 324 final long time = file.lastModified(); // LOG4J2-531 create file first so time has valid value 325 return new RollingFileManager(name, data.pattern, os, data.append, size, time, data.policy, 326 data.strategy, data.advertiseURI, data.layout, bufferSize, writeHeader); 327 } catch (final FileNotFoundException ex) { 328 LOGGER.error("FileManager (" + name + ") " + ex); 329 } 330 return null; 331 } 332 } 333 334}