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