    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     */
    017    package org.apache.logging.log4j.core.appender.rolling;
    018
    019    import java.io.File;
    020    import java.io.IOException;
    021    import java.io.OutputStream;
    022    import java.io.RandomAccessFile;
    023    import java.io.Serializable;
    024    import java.nio.ByteBuffer;
    025
    026    import org.apache.logging.log4j.core.Layout;
    027    import org.apache.logging.log4j.core.appender.AppenderLoggingException;
    028    import org.apache.logging.log4j.core.appender.ManagerFactory;
    029    import org.apache.logging.log4j.core.util.NullOutputStream;
    030
    031    /**
    032     * Extends RollingFileManager but instead of using a buffered output stream,
    033     * this class uses a {@code ByteBuffer} and a {@code RandomAccessFile} to do the
    034     * I/O.
    035     */
    036    public class RollingRandomAccessFileManager extends RollingFileManager {
    037        /**
    038         * The default buffer size
    039         */
    040        public static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
    041
    042        private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory();
    043
    044        private final boolean isImmediateFlush;
    045        private RandomAccessFile randomAccessFile;
    046        private final ByteBuffer buffer;
    047        private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<Boolean>();
    048
    049        public RollingRandomAccessFileManager(final RandomAccessFile raf, final String fileName,
    050                final String pattern, final OutputStream os, final boolean append,
    051                final boolean immediateFlush, final int bufferSize, final long size, final long time,
    052                final TriggeringPolicy policy, final RolloverStrategy strategy,
    053                final String advertiseURI, final Layout<? extends Serializable> layout) {
    054            super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout, bufferSize);
    055            this.isImmediateFlush = immediateFlush;
    056            this.randomAccessFile = raf;
    057            isEndOfBatch.set(Boolean.FALSE);
    058            this.buffer = ByteBuffer.allocate(bufferSize);
    059            writeHeader();
    060        }
    061
    062        /**
    063         * Writes the layout's header to the file if it exists.
    064         */
    065        private void writeHeader() {
    066            if (layout == null) {
    067                return;
    068            }
    069            final byte[] header = layout.getHeader();
    070            if (header == null) {
    071                return;
    072            }
    073            try {
    074                // write to the file, not to the buffer: the buffer may not be empty
    075                randomAccessFile.write(header, 0, header.length);
    076            } catch (final IOException ioe) {
    077                LOGGER.error("Unable to write header", ioe);
    078            }
    079        }
    080
    081        public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName,
    082                final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize,
    083                final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
    084                final Layout<? extends Serializable> layout) {
    085            return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend,
    086                    immediateFlush, bufferSize, policy, strategy, advertiseURI, layout), FACTORY);
    087        }
    088
    089        public Boolean isEndOfBatch() {
    090            return isEndOfBatch.get();
    091        }
    092
    093        public void setEndOfBatch(final boolean isEndOfBatch) {
    094            this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch));
    095        }
    096
    097        @Override
    098        protected synchronized void write(final byte[] bytes, int offset, int length) {
    099            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    }