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 time, 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, time, policy, strategy, advertiseURI, layout, 071 filePermissions, fileOwner, fileGroup, 072 writeHeader, ByteBuffer.wrap(new byte[bufferSize])); 073 this.randomAccessFile = raf; 074 isEndOfBatch.set(Boolean.FALSE); 075 writeHeader(); 076 } 077 078 /** 079 * Writes the layout's header to the file if it exists. 080 */ 081 private void writeHeader() { 082 if (layout == null) { 083 return; 084 } 085 final byte[] header = layout.getHeader(); 086 if (header == null) { 087 return; 088 } 089 try { 090 if (randomAccessFile.length() == 0) { 091 // write to the file, not to the buffer: the buffer may not be empty 092 randomAccessFile.write(header, 0, header.length); 093 } 094 } catch (final IOException e) { 095 logError("Unable to write header", e); 096 } 097 } 098 099 public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName, 100 final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize, 101 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, 102 final Layout<? extends Serializable> layout, final String filePermissions, final String fileOwner, final String fileGroup, 103 final Configuration configuration) { 104 if (strategy instanceof DirectWriteRolloverStrategy && fileName != null) { 105 LOGGER.error("The fileName attribute must not be specified with the DirectWriteRolloverStrategy"); 106 return null; 107 } 108 final String name = fileName == null ? filePattern : fileName; 109 return narrow(RollingRandomAccessFileManager.class, getManager(name, new FactoryData(fileName, filePattern, isAppend, 110 immediateFlush, bufferSize, policy, strategy, advertiseURI, layout, 111 filePermissions, fileOwner, fileGroup, configuration), FACTORY)); 112 } 113 114 public Boolean isEndOfBatch() { 115 return isEndOfBatch.get(); 116 } 117 118 public void setEndOfBatch(final boolean endOfBatch) { 119 this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); 120 } 121 122 // override to make visible for unit tests 123 @Override 124 protected synchronized void write(final byte[] bytes, final int offset, final int length, 125 final boolean immediateFlush) { 126 super.write(bytes, offset, length, immediateFlush); 127 } 128 129 @Override 130 protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) { 131 try { 132 if (randomAccessFile == null) { 133 String fileName = getFileName(); 134 File file = new File(fileName); 135 FileUtils.makeParentDirs(file); 136 createFileAfterRollover(fileName); 137 } 138 randomAccessFile.write(bytes, offset, length); 139 size += length; 140 } catch (final IOException ex) { 141 final String msg = "Error writing to RandomAccessFile " + getName(); 142 throw new AppenderLoggingException(msg, ex); 143 } 144 } 145 146 @Override 147 protected void createFileAfterRollover() throws IOException { 148 createFileAfterRollover(getFileName()); 149 } 150 151 private void createFileAfterRollover(String fileName) throws IOException { 152 this.randomAccessFile = new RandomAccessFile(fileName, "rw"); 153 if (isAppend()) { 154 randomAccessFile.seek(randomAccessFile.length()); 155 } 156 writeHeader(); 157 } 158 159 @Override 160 public synchronized void flush() { 161 flushBuffer(byteBuffer); 162 } 163 164 @Override 165 public synchronized boolean closeOutputStream() { 166 flush(); 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 176 /** 177 * Returns the buffer capacity. 178 * 179 * @return the buffer size 180 */ 181 @Override 182 public int getBufferSize() { 183 return byteBuffer.capacity(); 184 } 185 186 /** 187 * Factory to create a RollingRandomAccessFileManager. 188 */ 189 private static class RollingRandomAccessFileManagerFactory implements 190 ManagerFactory<RollingRandomAccessFileManager, FactoryData> { 191 192 /** 193 * Create the RollingRandomAccessFileManager. 194 * 195 * @param name The name of the entity to manage. 196 * @param data The data required to create the entity. 197 * @return a RollingFileManager. 198 */ 199 @Override 200 public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) { 201 File file = null; 202 long size = 0; 203 long time = System.currentTimeMillis(); 204 RandomAccessFile raf = null; 205 if (data.fileName != null) { 206 file = new File(name); 207 208 if (!data.append) { 209 file.delete(); 210 } 211 size = data.append ? file.length() : 0; 212 if (file.exists()) { 213 time = file.lastModified(); 214 } 215 try { 216 FileUtils.makeParentDirs(file); 217 raf = new RandomAccessFile(name, "rw"); 218 if (data.append) { 219 final long length = raf.length(); 220 LOGGER.trace("RandomAccessFile {} seek to {}", name, length); 221 raf.seek(length); 222 } else { 223 LOGGER.trace("RandomAccessFile {} set length to 0", name); 224 raf.setLength(0); 225 } 226 } catch (final IOException ex) { 227 LOGGER.error("Cannot access RandomAccessFile " + ex, ex); 228 if (raf != null) { 229 try { 230 raf.close(); 231 } catch (final IOException e) { 232 LOGGER.error("Cannot close RandomAccessFile {}", name, e); 233 } 234 } 235 return null; 236 } 237 } 238 final boolean writeHeader = !data.append || file == null || !file.exists(); 239 240 final RollingRandomAccessFileManager rrm = new RollingRandomAccessFileManager(data.getLoggerContext(), raf, name, data.pattern, 241 NullOutputStream.getInstance(), data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, 242 data.strategy, data.advertiseURI, data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader); 243 if (rrm.isAttributeViewEnabled()) { 244 rrm.defineAttributeView(file.toPath()); 245 } 246 return rrm; 247 } 248 } 249 250 /** 251 * Factory data. 252 */ 253 private static class FactoryData extends ConfigurationFactoryData { 254 private final String fileName; 255 private final String pattern; 256 private final boolean append; 257 private final boolean immediateFlush; 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 private final String filePermissions; 264 private final String fileOwner; 265 private final String fileGroup; 266 267 /** 268 * Create the data for the factory. 269 * 270 * @param fileName The file name. 271 * @param pattern The pattern. 272 * @param append The append flag. 273 * @param immediateFlush 274 * @param bufferSize 275 * @param policy 276 * @param strategy 277 * @param advertiseURI 278 * @param layout 279 * @param filePermissions File permissions 280 * @param fileOwner File owner 281 * @param fileGroup File group 282 * @param configuration 283 */ 284 public FactoryData(final String fileName, final String pattern, final boolean append, final boolean immediateFlush, 285 final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy, 286 final String advertiseURI, final Layout<? extends Serializable> layout, 287 final String filePermissions, final String fileOwner, final String fileGroup, 288 final Configuration configuration) { 289 super(configuration); 290 this.fileName = fileName; 291 this.pattern = pattern; 292 this.append = append; 293 this.immediateFlush = immediateFlush; 294 this.bufferSize = bufferSize; 295 this.policy = policy; 296 this.strategy = strategy; 297 this.advertiseURI = advertiseURI; 298 this.layout = layout; 299 this.filePermissions = filePermissions; 300 this.fileOwner = fileOwner; 301 this.fileGroup = fileGroup; 302 } 303 304 public TriggeringPolicy getTriggeringPolicy() 305 { 306 return this.policy; 307 } 308 309 public RolloverStrategy getRolloverStrategy() 310 { 311 return this.strategy; 312 } 313 } 314 315 @Override 316 public void updateData(final Object data) { 317 final FactoryData factoryData = (FactoryData) data; 318 setRolloverStrategy(factoryData.getRolloverStrategy()); 319 setTriggeringPolicy(factoryData.getTriggeringPolicy()); 320 } 321}