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