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) { 053 super(fileName, os, append, false, advertiseURI, layout, bufferSize); 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 TriggeringPolicy getTriggeringPolicy() { 143 return this.triggeringPolicy; 144 } 145 146 /** 147 * Returns the rollover strategy 148 * @return The RolloverStrategy 149 */ 150 public RolloverStrategy getRolloverStrategy() { 151 return this.rolloverStrategy; 152 } 153 154 private boolean rollover(final RolloverStrategy strategy) { 155 156 try { 157 // Block until the asynchronous operation is completed. 158 semaphore.acquire(); 159 } catch (final InterruptedException ie) { 160 LOGGER.error("Thread interrupted while attempting to check rollover", ie); 161 return false; 162 } 163 164 boolean success = false; 165 Thread thread = null; 166 167 try { 168 final RolloverDescription descriptor = strategy.rollover(this); 169 if (descriptor != null) { 170 writeFooter(); 171 close(); 172 if (descriptor.getSynchronous() != null) { 173 LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous()); 174 try { 175 success = descriptor.getSynchronous().execute(); 176 } catch (final Exception ex) { 177 LOGGER.error("Error in synchronous task", ex); 178 } 179 } 180 181 if (success && descriptor.getAsynchronous() != null) { 182 LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous()); 183 thread = new Thread(new AsyncAction(descriptor.getAsynchronous(), this)); 184 thread.start(); 185 } 186 return true; 187 } 188 return false; 189 } finally { 190 if (thread == null || !thread.isAlive()) { 191 semaphore.release(); 192 } 193 } 194 195 } 196 197 /** 198 * Performs actions asynchronously. 199 */ 200 private static class AsyncAction extends AbstractAction { 201 202 private final Action action; 203 private final RollingFileManager manager; 204 205 /** 206 * Constructor. 207 * @param act The action to perform. 208 * @param manager The manager. 209 */ 210 public AsyncAction(final Action act, final RollingFileManager manager) { 211 this.action = act; 212 this.manager = manager; 213 } 214 215 /** 216 * Perform an action. 217 * 218 * @return true if action was successful. A return value of false will cause 219 * the rollover to be aborted if possible. 220 * @throws java.io.IOException if IO error, a thrown exception will cause the rollover 221 * to be aborted if possible. 222 */ 223 @Override 224 public boolean execute() throws IOException { 225 try { 226 return action.execute(); 227 } finally { 228 manager.semaphore.release(); 229 } 230 } 231 232 /** 233 * Cancels the action if not already initialized or waits till completion. 234 */ 235 @Override 236 public void close() { 237 action.close(); 238 } 239 240 /** 241 * Determines if action has been completed. 242 * 243 * @return true if action is complete. 244 */ 245 @Override 246 public boolean isComplete() { 247 return action.isComplete(); 248 } 249 } 250 251 /** 252 * Factory data. 253 */ 254 private static class FactoryData { 255 private final String pattern; 256 private final boolean append; 257 private final boolean bufferedIO; 258 private final int bufferSize; 259 private final TriggeringPolicy policy; 260 private final RolloverStrategy strategy; 261 private final String advertiseURI; 262 private final Layout<? extends Serializable> layout; 263 264 /** 265 * Create the data for the factory. 266 * @param pattern The pattern. 267 * @param append The append flag. 268 * @param bufferedIO The bufferedIO flag. 269 * @param advertiseURI 270 * @param layout The Layout. 271 * @param bufferSize the buffer size 272 */ 273 public FactoryData(final String pattern, final boolean append, final boolean bufferedIO, 274 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, 275 final Layout<? extends Serializable> layout, final int bufferSize) { 276 this.pattern = pattern; 277 this.append = append; 278 this.bufferedIO = bufferedIO; 279 this.bufferSize = bufferSize; 280 this.policy = policy; 281 this.strategy = strategy; 282 this.advertiseURI = advertiseURI; 283 this.layout = layout; 284 } 285 } 286 287 /** 288 * Factory to create a RollingFileManager. 289 */ 290 private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> { 291 292 /** 293 * Create the RollingFileManager. 294 * @param name The name of the entity to manage. 295 * @param data The data required to create the entity. 296 * @return a RollingFileManager. 297 */ 298 @Override 299 public RollingFileManager createManager(final String name, final FactoryData data) { 300 final File file = new File(name); 301 final File parent = file.getParentFile(); 302 if (null != parent && !parent.exists()) { 303 parent.mkdirs(); 304 } 305 try { 306 file.createNewFile(); 307 } catch (final IOException ioe) { 308 LOGGER.error("Unable to create file " + name, ioe); 309 return null; 310 } 311 final long size = data.append ? file.length() : 0; 312 313 OutputStream os; 314 try { 315 os = new FileOutputStream(name, data.append); 316 int bufferSize = data.bufferSize; 317 if (data.bufferedIO) { 318 os = new BufferedOutputStream(os, bufferSize); 319 } else { 320 bufferSize = -1; // negative buffer size signals bufferedIO was configured false 321 } 322 final long time = file.lastModified(); // LOG4J2-531 create file first so time has valid value 323 return new RollingFileManager(name, data.pattern, os, data.append, size, time, data.policy, 324 data.strategy, data.advertiseURI, data.layout, bufferSize); 325 } catch (final FileNotFoundException ex) { 326 LOGGER.error("FileManager (" + name + ") " + ex); 327 } 328 return null; 329 } 330 } 331 332}