All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alibaba.metrics.reporter.file.RollingFileAppender Maven / Gradle / Ivy

There is a newer version: 2.0.6
Show newest version
package com.alibaba.metrics.reporter.file;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.nio.charset.Charset;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 默认把日志写到 ~/logs 下面。非线程安全,只允许一个线程操作。日志会自动滚动。
 *
 * TODO 在移动日志时,增加类似logback的处理,当文件处于不同Volume时,采用copy的方法
 *
 *
 */
public class RollingFileAppender implements FileAppender {
    private static final Logger logger = LoggerFactory.getLogger(RollingFileAppender.class);
    public static final Charset UTF_8 = Charset.forName("UTF-8");

    public static final int DEFAULT_MAX_FILE_SIZE = 50 * 1024 * 1024;
    public static final int DEFAULT_MAX_INDEX = 3;

    // 大部分情况下写入4K数据,可以保证数据一次性写入
    public static final int DEFAULT_BUFFER_SIZE = 4 * 1024;

    public static final String DELETEFILESUFFIX = ".deleted";

    int bufferSize = DEFAULT_BUFFER_SIZE;

    int fileSize = DEFAULT_MAX_FILE_SIZE;

    int maxIndex = DEFAULT_MAX_INDEX;

    /**
     * 从上次setFile之后,已经写入磁盘的数据的大小。 用于保证多进程写入数据文件时,避免因为文件被rename之后,没有及时roll。
     */
    volatile long writtenSizeAfterLastSetFile = 0;
    /**
     * 记录上次setFile时,文件的大小,防止多线程下,文件被rename为index文件之后,长时间继续写。
     * 或者原来的日志文件被删除之后,还在向被删除的文件写入
     */
    volatile long fileLengthWhenLastSetFile = 0;

    static ScheduledExecutorService executor = Executors
            .newSingleThreadScheduledExecutor(new NamedThreadFactory("RollingFileAppender"));

    /**
     * 由一个独立线程去检测文件大小,看是否需要roll了
     */
    volatile boolean bNeedRolling = false;

    String path;

    BufferedOutputStream bufferedOutputStream;

    /**
     * A simple named thread factory.
     */
    private static class NamedThreadFactory implements ThreadFactory {
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        private NamedThreadFactory(String name) {
            final SecurityManager s = System.getSecurityManager();
            this.group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
            this.namePrefix = "metrics-" + name + "-thread-";
        }

        @Override
        public Thread newThread(Runnable r) {
            final Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
            t.setDaemon(true);
            if (t.getPriority() != Thread.NORM_PRIORITY) {
                t.setPriority(Thread.NORM_PRIORITY);
            }
            return t;
        }
    }

    public static class Builder {
        String baseDir;
        String name;
        int maxIndex = DEFAULT_MAX_INDEX;
        int fileSize = DEFAULT_MAX_FILE_SIZE;

        /**
         * 默认baseDir 是 ~/logs
         *
         * @param dir
         * @return
         */
        public Builder baseDir(String baseDir) {
            this.baseDir = baseDir;
            return this;
        }

        /**
         * 默认情况下文件会放在 ~/logs 下,需要放在其它路径下,请设置baseDir。最终路径是
         * {@code new File(baseDir, name).getAbsolutePath() }
         *
         * @param name
         * @return
         */
        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder maxIndex(int maxIndex) {
            this.maxIndex = maxIndex;
            return this;
        }

        public Builder fileSize(int fileSize) {
            this.fileSize = fileSize;
            return this;
        }

        public RollingFileAppender build() {
            if (baseDir == null) {
                baseDir = System.getProperty("user.home", "/tmp");
                if (baseDir != null) {
                    baseDir = new File(baseDir, "logs").getAbsolutePath();
                }
            }
            return new RollingFileAppender(new File(baseDir, name).getAbsolutePath(), maxIndex, fileSize);
        }
    }

    public static Builder builder() {
        return new Builder();
    }

    private RollingFileAppender(String path, int maxIndex, int fileSize) {
        this.path = path;
        this.maxIndex = maxIndex;
        this.fileSize = fileSize;

        setFile();

        executor.scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {
                try {
                    // 检查日志是否要滚动
                    checkAndMarkRolling();
                    // 检查是否有要删除掉的日志
                    deleteNeedDeleteFile();
                } catch (Throwable e) {
                    logger.error("RollingFileAppender schedule task error.", e);
                }
            }

        }, 1, 1, TimeUnit.SECONDS);
    }

    @Override
    public void append(String message) {
        byte[] bytes = message.getBytes(UTF_8);
        append(bytes);
    }

    @Override
    public void append(byte[] data) {
        try {
            bufferedOutputStream.write(data);
            writtenSizeAfterLastSetFile += data.length;
            rollOver();
        } catch (Throwable e) {
            logger.error("append error, path:" + path, e);
            setFile();
        }
    }

    @Override
    public void flush() throws IOException {
        if (bufferedOutputStream != null) {
            bufferedOutputStream.flush();
            File file = new File(path);
            if (file.exists()) {
                if ((file.length() - this.fileLengthWhenLastSetFile) != this.writtenSizeAfterLastSetFile) {
                    // 检测上次setFile之后,写入的数据大小和文件长度的比较,如果是多进程写入,则文件长度的变化不等于
                    // writtenSizeAfterLastSetFile。因为这里flush里,所以判断条件是严格相等
                    bNeedRolling = true;
                }
            }

        }
    }

    /**
     * 检查,并标记日志是否需要roll了,从效率考虑,由定时任务线程来调用
     */
    void checkAndMarkRolling() {
        // 检测上次setFile之后,写入的数据是否已超过文件大小的限制
        if (writtenSizeAfterLastSetFile >= this.fileSize) {
            bNeedRolling = true;
            return;
        }

        File file = new File(path);
        if (file.exists()) {
            long fileLength = file.length();
            if (fileLength >= fileSize) {
                bNeedRolling = true;
            } else if ((fileLength - this.fileLengthWhenLastSetFile) > this.writtenSizeAfterLastSetFile) {
                // 检测上次setFile之后,写入的数据大小和文件长度的比较,如果是多进程写入,则文件长度的变化大于
                // writtenSizeAfterLastSetFile
                bNeedRolling = true;
            } else {
                bNeedRolling = false;
            }
        } else {
            // 文件可能被外部程序删除掉了,这时也需要重新打开
            bNeedRolling = true;
        }
    }

    /**
     * 删除所有以 .delete 结尾的文件,从效率考虑,由定时任务线程来调用,因为大文件删除可能比较慢
     */
    void deleteNeedDeleteFile() {
        File logFile = new File(path);
        File parent = logFile.getParentFile();
        if (parent != null) {
            if (parent.isDirectory()) {
                File[] listFiles = parent.listFiles();
                if (listFiles != null) {
                    for (File file : listFiles) {
                        if (file.isFile() && file.getName().startsWith(logFile.getName())
                                && file.getName().endsWith(DELETEFILESUFFIX)) {
                            file.delete();
                        }
                    }
                }
            }
        }
    }

    private void setFile() {
        writtenSizeAfterLastSetFile = 0;

        File file = new File(path);
        if (!file.exists()) {
            File parentFile = file.getParentFile();
            if (parentFile != null) {
                parentFile.mkdirs();
            }
        }

        if (bufferedOutputStream != null) {
            IOUtils.closeQuietly(bufferedOutputStream);
        }

        try {
            fileLengthWhenLastSetFile = file.length();
            bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file, true), bufferSize);
        } catch (FileNotFoundException e) {
            logger.error("can not open file:" + path, e);
        }
    }

    private void rollOver() {
        if (bNeedRolling) {
            // 获取文件锁
            final String lockFilePath = path + ".lock";
            final File lockFile = new File(lockFilePath);

            RandomAccessFile raf = null;
            FileLock fileLock = null;
            try {
                try {
                    raf = new RandomAccessFile(lockFile, "rw");
                } catch (FileNotFoundException e) {
                    // 在windows下面多个进程同时以rw模式打开文件,会抛出FileNotFoundException异常
                    // ignore
                    return;
                }

                fileLock = raf.getChannel().tryLock();
                if (fileLock == null) {
                    return;
                }

                // 获取到锁之后,检查文件大小,是否需要滚动
                File logFile = new File(path);
                long logFileLength = logFile.length();

                // 当文件不存在时,长度为0,这时需要重新打开文件
                if (logFileLength == 0) {
                    setFile();
                }

                if (logFileLength < fileSize) {
                    return;
                }

                rollIndexFile();

                // 尝试把日志文件rename为 log.1 文件
                File index1File = indexFile(1);
                // 要先close,否则在windows下rename失败
                closeFile();
                logFile.renameTo(index1File);

            } catch (IOException e) {
                logger.error("try to roll log file error. file:" + lockFile.getAbsolutePath(), e);
            } finally {
                IOUtils.closeQuietly(fileLock);
                IOUtils.closeQuietly(raf);

                // 重新打开日志文件。因为原文件可能被删除掉了,所以当需要rolling时,都重新打开文件。 #28457
                setFile();
            }
        }
    }

    /**
     * 把已存在的index文件编号依次滚动增大
     */
    private void rollIndexFile() {
        int currentMaxIndex = 0;
        // 先从最小的index查找,看哪个index文件是不存在的
        for (int i = 1; i <= maxIndex; ++i) {
            File indexFile = indexFile(i);
            if (!indexFile.exists() || (i == maxIndex)) {
                currentMaxIndex = i;
                break;
            }
        }

        // 如果当前的最大index文件已经是 maxIndex,则需要删除掉
        if (currentMaxIndex == maxIndex) {
            // 先尝试把最大index的文件重命名为 .deleted 文件,在windows下有可能失败
            // .delete 后缀文件会被异步定时清理
            // 如果失败,则尝试直接删除
            File maxIndexFile = indexFile(maxIndex);
            if (maxIndexFile.exists()) {
                if (!maxIndexFile.renameTo(new File(maxIndexFile.getAbsolutePath() + DELETEFILESUFFIX))) {
                    if (!maxIndexFile.delete()) {
                        logger.error("can not delete file:" + maxIndexFile.getAbsolutePath());
                    }
                }
            }
        }

        // 尝试把index文件的index编号依次加大
        for (int index = currentMaxIndex - 1; index > 0; --index) {
            File indexFile = indexFile(index);
            if (indexFile.exists()) {
                File destFile = indexFile(index + 1);
                if (!indexFile.renameTo(destFile)) {
                    logger.error("can not rename file. src:" + indexFile.getAbsolutePath() + ", dest:"
                            + destFile.getAbsolutePath());
                    // 当rename 失败时,尝试删除掉index文件,保证日志可以滚动写
                    if (!indexFile.delete()) {
                        logger.error("can not delete file:" + indexFile.getAbsolutePath());
                    }
                }
            }
        }
    }

    private void closeFile() {
        if (bufferedOutputStream != null) {
            try {
                bufferedOutputStream.close();
            } catch (Exception e) {
                logger.error("can not close file:" + path, e);
            }
            bufferedOutputStream = null;
        }
    }

    private File indexFile(int index) {
        return new File(path + "." + index);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy