View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements. See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache license, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License. You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the license for the specific language governing permissions and
    15   * limitations under the license.
    16   */
    17  package org.apache.logging.log4j.core.appender.rolling;
    18
    19  import java.io.File;
    20  import java.io.IOException;
    21  import java.io.OutputStream;
    22  import java.io.RandomAccessFile;
    23  import java.io.Serializable;
    24  import java.nio.ByteBuffer;
    25
    26  import org.apache.logging.log4j.core.Layout;
    27  import org.apache.logging.log4j.core.appender.AppenderLoggingException;
    28  import org.apache.logging.log4j.core.appender.ManagerFactory;
    29  import org.apache.logging.log4j.core.util.NullOutputStream;
    30
    31  /**
    32   * Extends RollingFileManager but instead of using a buffered output stream,
    33   * this class uses a {@code ByteBuffer} and a {@code RandomAccessFile} to do the
    34   * I/O.
    35   */
    36  public class RollingRandomAccessFileManager extends RollingFileManager {
    37      /**
    38       * The default buffer size
    39       */
    40      public static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
    41
    42      private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory();
    43
    44      private final boolean isImmediateFlush;
    45      private RandomAccessFile randomAccessFile;
    46      private final ByteBuffer buffer;
    47      private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<Boolean>();
    48
    49      public RollingRandomAccessFileManager(final RandomAccessFile raf, final String fileName,
    50              final String pattern, final OutputStream os, final boolean append,
    51              final boolean immediateFlush, final int bufferSize, final long size, final long time,
    52              final TriggeringPolicy policy, final RolloverStrategy strategy,
    53              final String advertiseURI, final Layout<? extends Serializable> layout) {
    54          super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout, bufferSize);
    55          this.isImmediateFlush = immediateFlush;
    56          this.randomAccessFile = raf;
    57          isEndOfBatch.set(Boolean.FALSE);
    58          this.buffer = ByteBuffer.allocate(bufferSize);
    59          writeHeader();
    60      }
    61
    62      /**
    63       * Writes the layout's header to the file if it exists.
    64       */
    65      private void writeHeader() {
    66          if (layout == null) {
    67              return;
    68          }
    69          final byte[] header = layout.getHeader();
    70          if (header == null) {
    71              return;
    72          }
    73          try {
    74              // write to the file, not to the buffer: the buffer may not be empty
    75              randomAccessFile.write(header, 0, header.length);
    76          } catch (final IOException ioe) {
    77              LOGGER.error("Unable to write header", ioe);
    78          }
    79      }
    80
    81      public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName,
    82              final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize,
    83              final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
    84              final Layout<? extends Serializable> layout) {
    85          return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend,
    86                  immediateFlush, bufferSize, policy, strategy, advertiseURI, layout), FACTORY);
    87      }
    88
    89      public Boolean isEndOfBatch() {
    90          return isEndOfBatch.get();
    91      }
    92
    93      public void setEndOfBatch(final boolean isEndOfBatch) {
    94          this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch));
    95      }
    96
    97      @Override
    98      protected synchronized void write(final byte[] bytes, int offset, int length) {
    99          super.write(bytes, offset, length); // writes to dummy output stream, needed to track file size
    100
    101         int chunk = 0;
    102         do {
    103             if (length > buffer.remaining()) {
    104                 flush();
    105             }
    106             chunk = Math.min(length, buffer.remaining());
    107             buffer.put(bytes, offset, chunk);
    108             offset += chunk;
    109             length -= chunk;
    110         } while (length > 0);
    111
    112         if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) {
    113             flush();
    114         }
    115     }
    116
    117     @Override
    118     protected void createFileAfterRollover() throws IOException {
    119         this.randomAccessFile = new RandomAccessFile(getFileName(), "rw");
    120         if (isAppend()) {
    121             randomAccessFile.seek(randomAccessFile.length());
    122         }
    123         writeHeader();
    124     }
    125
    126     @Override
    127     public synchronized void flush() {
    128         buffer.flip();
    129         try {
    130             randomAccessFile.write(buffer.array(), 0, buffer.limit());
    131         } catch (final IOException ex) {
    132             final String msg = "Error writing to RandomAccessFile " + getName();
    133             throw new AppenderLoggingException(msg, ex);
    134         }
    135         buffer.clear();
    136     }
    137
    138     @Override
    139     public synchronized void close() {
    140         flush();
    141         try {
    142             randomAccessFile.close();
    143         } catch (final IOException ex) {
    144             LOGGER.error("Unable to close RandomAccessFile " + getName() + ". "
    145                     + ex);
    146         }
    147     }
    148
    149     /**
    150      * Returns the buffer capacity.
    151      * @return the buffer size
    152      */
    153     @Override
    154     public int getBufferSize() {
    155         return buffer.capacity();
    156     }
    157
    158     /**
    159      * Factory to create a RollingRandomAccessFileManager.
    160      */
    161     private static class RollingRandomAccessFileManagerFactory implements ManagerFactory<RollingRandomAccessFileManager, FactoryData> {
    162
    163         /**
    164          * Create the RollingRandomAccessFileManager.
    165          *
    166          * @param name The name of the entity to manage.
    167          * @param data The data required to create the entity.
    168          * @return a RollingFileManager.
    169          */
    170         @Override
    171         public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) {
    172             final File file = new File(name);
    173             final File parent = file.getParentFile();
    174             if (null != parent && !parent.exists()) {
    175                 parent.mkdirs();
    176             }
    177
    178             if (!data.append) {
    179                 file.delete();
    180             }
    181             final long size = data.append ? file.length() : 0;
    182             final long time = file.exists() ? file.lastModified() : System.currentTimeMillis();
    183
    184             RandomAccessFile raf = null;
    185             try {
    186                 raf = new RandomAccessFile(name, "rw");
    187                 if (data.append) {
    188                     final long length = raf.length();
    189                     LOGGER.trace("RandomAccessFile {} seek to {}", name, length);
    190                     raf.seek(length);
    191                 } else {
    192                     LOGGER.trace("RandomAccessFile {} set length to 0", name);
    193                     raf.setLength(0);
    194                 }
    195                 return new RollingRandomAccessFileManager(raf, name, data.pattern, NullOutputStream.NULL_OUTPUT_STREAM,
    196                         data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, data.strategy,
    197                         data.advertiseURI, data.layout);
    198             } catch (final IOException ex) {
    199                 LOGGER.error("Cannot access RandomAccessFile {}) " + ex);
    200                 if (raf != null) {
    201                     try {
    202                         raf.close();
    203                     } catch (final IOException e) {
    204                         LOGGER.error("Cannot close RandomAccessFile {}", name, e);
    205                     }
    206                 }
    207             }
    208             return null;
    209         }
    210     }
    211
    212     /**
    213      * Factory data.
    214      */
    215     private static class FactoryData {
    216         private final String pattern;
    217         private final boolean append;
    218         private final boolean immediateFlush;
    219         private final int bufferSize;
    220         private final TriggeringPolicy policy;
    221         private final RolloverStrategy strategy;
    222         private final String advertiseURI;
    223         private final Layout<? extends Serializable> layout;
    224
    225         /**
    226          * Create the data for the factory.
    227          *
    228          * @param pattern The pattern.
    229          * @param append The append flag.
    230          * @param immediateFlush
    231          * @param bufferSize
    232          * @param policy
    233          * @param strategy
    234          * @param advertiseURI
    235          * @param layout
    236          */
    237         public FactoryData(final String pattern, final boolean append, final boolean immediateFlush,
    238                 final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy,
    239                 final String advertiseURI, final Layout<? extends Serializable> layout) {
    240             this.pattern = pattern;
    241             this.append = append;
    242             this.immediateFlush = immediateFlush;
    243             this.bufferSize = bufferSize;
    244             this.policy = policy;
    245             this.strategy = strategy;
    246             this.advertiseURI = advertiseURI;
    247             this.layout = layout;
    248         }
    249     }
    250
    251 }