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.io.OutputStream; 022import java.io.RandomAccessFile; 023import java.io.Serializable; 024import java.nio.ByteBuffer; 025 026import org.apache.logging.log4j.core.Layout; 027import org.apache.logging.log4j.core.LoggerContext; 028import org.apache.logging.log4j.core.appender.AppenderLoggingException; 029import org.apache.logging.log4j.core.appender.ConfigurationFactoryData; 030import org.apache.logging.log4j.core.appender.ManagerFactory; 031import org.apache.logging.log4j.core.config.Configuration; 032import org.apache.logging.log4j.core.util.FileUtils; 033import org.apache.logging.log4j.core.util.NullOutputStream; 034 035/** 036 * Extends RollingFileManager but instead of using a buffered output stream, this class uses a {@code ByteBuffer} and a 037 * {@code RandomAccessFile} to do the I/O. 038 */ 039public class RollingRandomAccessFileManager extends RollingFileManager { 040 /** 041 * The default buffer size. 042 */ 043 public static final int DEFAULT_BUFFER_SIZE = 256 * 1024; 044 045 private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory(); 046 047 private RandomAccessFile randomAccessFile; 048 private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>(); 049 050 @Deprecated 051 public RollingRandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile raf, 052 final String fileName, final String pattern, final OutputStream os, final boolean append, 053 final boolean immediateFlush, final int bufferSize, final long size, final long time, 054 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, 055 final Layout<? extends Serializable> layout, final boolean writeHeader) { 056 this(loggerContext, raf, fileName, pattern, os, append, immediateFlush, bufferSize, size, time, policy, strategy, advertiseURI, 057 layout, null, null, null, writeHeader); 058 } 059 060 /** 061 * @since 2.8.3 062 */ 063 public RollingRandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile raf, 064 final String fileName, final String pattern, final OutputStream os, final boolean append, 065 final boolean immediateFlush, final int bufferSize, final long size, final long initialTime, 066 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, 067 final Layout<? extends Serializable> layout, 068 final String filePermissions, final String fileOwner, final String fileGroup, 069 final boolean writeHeader) { 070 super(loggerContext, fileName, pattern, os, append, false, size, initialTime, policy, strategy, advertiseURI, 071 layout, filePermissions, fileOwner, fileGroup, writeHeader, ByteBuffer.wrap(new byte[bufferSize])); 072 this.randomAccessFile = raf; 073 isEndOfBatch.set(Boolean.FALSE); 074 writeHeader(); 075 } 076 077 /** 078 * Writes the layout's header to the file if it exists. 079 */ 080 private void writeHeader() { 081 if (layout == null) { 082 return; 083 } 084 final byte[] header = layout.getHeader(); 085 if (header == null) { 086 return; 087 } 088 try { 089 if (randomAccessFile != null && randomAccessFile.length() == 0) { 090 // write to the file, not to the buffer: the buffer may not be empty 091 randomAccessFile.write(header, 0, header.length); 092 } 093 } catch (final IOException e) { 094 logError("Unable to write header", e); 095 } 096 } 097 098 public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName, 099 final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize, 100 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, 101 final Layout<? extends Serializable> layout, final String filePermissions, final String fileOwner, final String fileGroup, 102 final Configuration configuration) { 103 if (strategy instanceof DirectWriteRolloverStrategy && fileName != null) { 104 LOGGER.error("The fileName attribute must not be specified with the DirectWriteRolloverStrategy"); 105 return null; 106 } 107 final String name = fileName == null ? filePattern : fileName; 108 return narrow(RollingRandomAccessFileManager.class, getManager(name, new FactoryData(fileName, filePattern, isAppend, 109 immediateFlush, bufferSize, policy, strategy, advertiseURI, layout, 110 filePermissions, fileOwner, fileGroup, configuration), FACTORY)); 111 } 112 113 public Boolean isEndOfBatch() { 114 return isEndOfBatch.get(); 115 } 116 117 public void setEndOfBatch(final boolean endOfBatch) { 118 this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); 119 } 120 121 // override to make visible for unit tests 122 @Override 123 protected synchronized void write(final byte[] bytes, final int offset, final int length, 124 final boolean immediateFlush) { 125 super.write(bytes, offset, length, immediateFlush); 126 } 127 128 @Override 129 protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) { 130 try { 131 if (randomAccessFile == null) { 132 final String fileName = getFileName(); 133 final File file = new File(fileName); 134 FileUtils.makeParentDirs(file); 135 createFileAfterRollover(fileName); 136 } 137 randomAccessFile.write(bytes, offset, length); 138 size += length; 139 } catch (final IOException ex) { 140 final String msg = "Error writing to RandomAccessFile " + getName(); 141 throw new AppenderLoggingException(msg, ex); 142 } 143 } 144 145 @Override 146 protected void createFileAfterRollover() throws IOException { 147 createFileAfterRollover(getFileName()); 148 } 149 150 private void createFileAfterRollover(final String fileName) throws IOException { 151 this.randomAccessFile = new RandomAccessFile(fileName, "rw"); 152 if (isAppend()) { 153 randomAccessFile.seek(randomAccessFile.length()); 154 } 155 writeHeader(); 156 } 157 158 @Override 159 public synchronized void flush() { 160 flushBuffer(byteBuffer); 161 } 162 163 @Override 164 public synchronized boolean closeOutputStream() { 165 flush(); 166 if (randomAccessFile != null) { 167 try { 168 randomAccessFile.close(); 169 return true; 170 } catch (final IOException e) { 171 logError("Unable to close RandomAccessFile", e); 172 return false; 173 } 174 } 175 return true; 176 } 177 178 /** 179 * Returns the buffer capacity. 180 * 181 * @return the buffer size 182 */ 183 @Override 184 public int getBufferSize() { 185 return byteBuffer.capacity(); 186 } 187 188 /** 189 * Factory to create a RollingRandomAccessFileManager. 190 */ 191 private static class RollingRandomAccessFileManagerFactory implements 192 ManagerFactory<RollingRandomAccessFileManager, FactoryData> { 193 194 /** 195 * Create the RollingRandomAccessFileManager. 196 * 197 * @param name The name of the entity to manage. 198 * @param data The data required to create the entity. 199 * @return a RollingFileManager. 200 */ 201 @Override 202 public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) { 203 File file = null; 204 long size = 0; 205 long time = System.currentTimeMillis(); 206 RandomAccessFile raf = null; 207 if (data.fileName != null) { 208 file = new File(name); 209 210 if (!data.append) { 211 file.delete(); 212 } 213 size = data.append ? file.length() : 0; 214 if (file.exists()) { 215 time = file.lastModified(); 216 } 217 try { 218 FileUtils.makeParentDirs(file); 219 raf = new RandomAccessFile(name, "rw"); 220 if (data.append) { 221 final long length = raf.length(); 222 LOGGER.trace("RandomAccessFile {} seek to {}", name, length); 223 raf.seek(length); 224 } else { 225 LOGGER.trace("RandomAccessFile {} set length to 0", name); 226 raf.setLength(0); 227 } 228 } catch (final IOException ex) { 229 LOGGER.error("Cannot access RandomAccessFile " + ex, ex); 230 if (raf != null) { 231 try { 232 raf.close(); 233 } catch (final IOException e) { 234 LOGGER.error("Cannot close RandomAccessFile {}", name, e); 235 } 236 } 237 return null; 238 } 239 } 240 final boolean writeHeader = !data.append || file == null || !file.exists(); 241 242 final RollingRandomAccessFileManager rrm = new RollingRandomAccessFileManager(data.getLoggerContext(), raf, name, data.pattern, 243 NullOutputStream.getInstance(), data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, 244 data.strategy, data.advertiseURI, data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader); 245 if (rrm.isAttributeViewEnabled()) { 246 rrm.defineAttributeView(file.toPath()); 247 } 248 return rrm; 249 } 250 } 251 252 /** 253 * Factory data. 254 */ 255 private static class FactoryData extends ConfigurationFactoryData { 256 private final String fileName; 257 private final String pattern; 258 private final boolean append; 259 private final boolean immediateFlush; 260 private final int bufferSize; 261 private final TriggeringPolicy policy; 262 private final RolloverStrategy strategy; 263 private final String advertiseURI; 264 private final Layout<? extends Serializable> layout; 265 private final String filePermissions; 266 private final String fileOwner; 267 private final String fileGroup; 268 269 /** 270 * Create the data for the factory. 271 * 272 * @param fileName The file name. 273 * @param pattern The pattern. 274 * @param append The append flag. 275 * @param immediateFlush 276 * @param bufferSize 277 * @param policy 278 * @param strategy 279 * @param advertiseURI 280 * @param layout 281 * @param filePermissions File permissions 282 * @param fileOwner File owner 283 * @param fileGroup File group 284 * @param configuration 285 */ 286 public FactoryData(final String fileName, final String pattern, final boolean append, final boolean immediateFlush, 287 final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy, 288 final String advertiseURI, final Layout<? extends Serializable> layout, 289 final String filePermissions, final String fileOwner, final String fileGroup, 290 final Configuration configuration) { 291 super(configuration); 292 this.fileName = fileName; 293 this.pattern = pattern; 294 this.append = append; 295 this.immediateFlush = immediateFlush; 296 this.bufferSize = bufferSize; 297 this.policy = policy; 298 this.strategy = strategy; 299 this.advertiseURI = advertiseURI; 300 this.layout = layout; 301 this.filePermissions = filePermissions; 302 this.fileOwner = fileOwner; 303 this.fileGroup = fileGroup; 304 } 305 306 public String getPattern() { 307 return pattern; 308 } 309 310 public TriggeringPolicy getTriggeringPolicy() { 311 return this.policy; 312 } 313 314 public RolloverStrategy getRolloverStrategy() { 315 return this.strategy; 316 } 317 318 } 319 320 @Override 321 public void updateData(final Object data) { 322 final FactoryData factoryData = (FactoryData) data; 323 setRolloverStrategy(factoryData.getRolloverStrategy()); 324 setTriggeringPolicy(factoryData.getTriggeringPolicy()); 325 setPatternProcessor(new PatternProcessor(factoryData.getPattern(), getPatternProcessor())); 326 } 327}