分享一个基于java的简单文本日志记录器,可以达到每秒10万条的写入速度

这是本人在CSDN上的第一篇博客,以后我将在这里分享一些我个人觉得有用的小组件、框架、原创项目源码等东西。简单介绍一下自己,我工作10来年,早几年从事.net相关的开发工作,近几年除了.net以久开始自学习了java相关平台的开发(公司项目所需)。本人学历、技术水平都一般,当然去不了什么互联网大厂,但是我还是比较喜欢学习新的东西以及尽可能去多研究一下以提高自己的整体实力。对我来说去技术大厂早已不是我的目标,我最大的梦想就是希望有生之年能与志同道合的人一起做事业,无论大小就社会价值就好。好了,言归正传,下面我就直接说说我要分享的东西。

1. 组件结构

组件很简单,就是一个java类命名为TxtLogger2,可以在windows 和linux 下使用,对外提供3个静态接口:

//设置日志根路径,不设置将用默认的路径
public static void SetRootDir(String rootString)

public static void log(Throwable ex, LogFileCreateType logFileCreateType, String customDir)

public static void log(String log, LogTye logTye, LogFileCreateType logFileCreateType, String customDir)

2. 组件处理逻辑

简单地说,就是内部提供一个线程安全的队列,基本生产、消费者模式的方式进行工作。内部会有一个单独的线程进行消费工作,然后通过文件缓冲流的方式按批次写入

3. 调用方式

 for (int i = 0; i < 100000; i++) {
            String log = "这是对一个简单的文本日志记录器的测试,针对小并发应用是可以应付的! 变化值:" + i;
            //按天
            TxtLogger2.log("info" + log, TxtLogger2.LogTye.INFO, TxtLogger2.LogFileCreateType.OneFileEveryDay, "");
            TxtLogger2.log("debug" + log, TxtLogger2.LogTye.Debug, TxtLogger2.LogFileCreateType.OneFileEveryDay, "");
            TxtLogger2.log("error" + log, TxtLogger2.LogTye.ERROR, TxtLogger2.LogFileCreateType.OneFileEveryDay, "");
            //按小时
            TxtLogger2.log(log, TxtLogger2.LogTye.INFO, TxtLogger2.LogFileCreateType.OneFileAnHour, "");
            TxtLogger2.log(log, TxtLogger2.LogTye.Debug, TxtLogger2.LogFileCreateType.OneFileAnHour, "");
            TxtLogger2.log(log, TxtLogger2.LogTye.ERROR, TxtLogger2.LogFileCreateType.OneFileAnHour, "");
            //按每10分钟
            TxtLogger2.log(log, TxtLogger2.LogTye.INFO, TxtLogger2.LogFileCreateType.OneFilePerTenMinutes, "");
            TxtLogger2.log(log, TxtLogger2.LogTye.Debug, TxtLogger2.LogFileCreateType.OneFilePerTenMinutes, "");
            TxtLogger2.log(log, TxtLogger2.LogTye.ERROR, TxtLogger2.LogFileCreateType.OneFilePerTenMinutes, "");

            //按每分钟
            TxtLogger2.log(log, TxtLogger2.LogTye.INFO, TxtLogger2.LogFileCreateType.OneFilePerMinute, "");
            TxtLogger2.log(log, TxtLogger2.LogTye.Debug, TxtLogger2.LogFileCreateType.OneFilePerMinute, "");
            TxtLogger2.log(log, TxtLogger2.LogTye.ERROR, TxtLogger2.LogFileCreateType.OneFilePerMinute, ""); 
        }

4. 测试结果

由于日志组件可以根据用户调用参数设定的日志类型,以及自定义的目录会动态创建对应的目录和文件(如按年月日/每10分钟/每分钟的目录结构),因此在实际中项目多线程环境下可能同时有不确定数量的不同路径下的文件。我通过一个单线程连续发送10万次,每次10条不同文件的日志记录进行测试,相当于总共记录了100万条数据,我在idea下测试下,主线程调用日志接口大概5秒左右,而实际日志内部全部写入磁盘大概是10秒左右。大家如果有兴趣可以自己去测试下并可以继续改进!

以上我将贴出全部源代码,有兴趣的朋友自行下载

package logger2;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;

/***
 * this java class is created by huchengdong 2018/8/22
 * this is a simple txt log writer
 */
public class TxtLogger2 {
    private  static  String dirName="AppLog1";
    private static String rootDirString = "C:/"+dirName;
    private static String enterString = "\r\n";
    private static int saveCountPer = 4000;
    //定义一个线程安全的并发队列
    static ConcurrentLinkedQueue concurrentLinkedQueue = new ConcurrentLinkedQueue<>();

    static {
        try {
            if (new File("D:/").exists()) {
                rootDirString = "D:/"+dirName;
            } else if (new File("E:/").exists()) {
                rootDirString = "E:/"+dirName;
            }

            Properties props = System.getProperties();
            String os_name = props.getProperty("os.name").toLowerCase();
            if (os_name != null && !os_name.contains("windows")) {
                rootDirString = "/usr/"+dirName;
                enterString = "\n";
            }

            //启动处理队列线程
            Thread thread = new Thread() {
                public void run() {
                    handQueue();
                }
            };
            thread.setDaemon(true);
            thread.start();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public static void SetRootDir(String rootString) {
        rootDirString = rootString;
    }

    private static void handQueue() {
        List list = new ArrayList<>();
        FileItem fileItem = null;
        while (true) {
            try {
                fileItem = concurrentLinkedQueue.poll();
                if (fileItem != null) {
                    list.add(fileItem);
                    //考虑到如果并发特别大,可以一下就会累积过多的日志
                    if (list.size() >= saveCountPer) {
                        writeToDisk2(list);
                        list.clear();
                    }
                } else {
                    //没有了,这个时候就要去开始落盘
                    writeToDisk2(list);
                    list.clear();
                }

                //为空的情况下可以适当让点时间给CPU
                if (concurrentLinkedQueue.isEmpty()) {
                    Thread.sleep(1);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }


    /**
     * 单线程 通过多个文件流去处理
     * @param list
     * @throws IOException
     */
    private static void writeToDisk2(List list) throws IOException {
        if (list == null || list.size() == 0) return;
        BufferedWriter out = null;
        Map map = new HashMap<>(100);
        for (FileItem item : list) {
            if (map.containsKey(item.getFilePath())) {
                map.get(item.getFilePath()).write(item.getLog() + enterString);
            } else {
                String dirPath = item.getFilePath().substring(0, item.getFilePath().lastIndexOf("/"));
                File file = new File(dirPath);
                if (!file.exists()) {
                    file.mkdirs();
                }

                file = new File(item.getFilePath());
                if (!file.exists()) {
                    file.createNewFile();
                }
                out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true), "utf-8"));
                out.write(item.getLog() + enterString);
                map.put(item.getFilePath(), out);
            }
        }

        for (BufferedWriter bufferedWriter : map.values()) {
            try {
                bufferedWriter.flush();
                bufferedWriter.close();
            } catch (Exception ex) {
            }
        }

    }

    /**
     * 添加错误日志
     *
     * @param ex
     * @param logFileCreateType
     * @param customDir
     */
    public static void log(Throwable ex, LogFileCreateType logFileCreateType, String customDir) {
        StringBuffer sb = new StringBuffer();
        for (StackTraceElement element : ex.getStackTrace()) {
            sb.append(element.toString() + enterString);
        }

        log(ex.getMessage() + " " + sb.toString(), LogTye.ERROR, logFileCreateType, customDir);
    }

    /**
     * 添加文本日志
     *
     * @param log
     * @param logTye
     * @param logFileCreateType
     * @param customDir
     */
    public static void log(String log, LogTye logTye, LogFileCreateType logFileCreateType, String customDir) {
        try {
            String path = rootDirString;
            String filePath = "";
            if (customDir == null || customDir.length() <= 0) {
            } else {
                path += "/" + customDir;
            }

            path += "/" + logTye.name();
            Calendar c = Calendar.getInstance();//可以对每个时间域单独修改
            int year = c.get(Calendar.YEAR);
            int month = c.get(Calendar.MONTH) + 1;
            int date = c.get(Calendar.DATE);
            int hour = c.get(Calendar.HOUR_OF_DAY);
            int minute = c.get(Calendar.MINUTE);
            int second = c.get(Calendar.SECOND);
            if (logFileCreateType == LogFileCreateType.OneFileEveryDay) {
                path += "/" + year + "/" + month;
                filePath = path + "/" + date + ".log";
            } else if (logFileCreateType == LogFileCreateType.OneFileAnHour) {
                path += "/" + year + "/" + month + "/" + date;
                filePath = path + "/" + hour + ".log";
            } else if (logFileCreateType == LogFileCreateType.OneFilePerTenMinutes) {
                path += "/" + year + "/" + month + "/" + date + "/" + hour;
                filePath = path + "/" + WhichTenMinutes(minute) + ".log";
            }
            else if (logFileCreateType == LogFileCreateType.OneFilePerMinute) {
                path += "/" + year + "/" + month + "/" + date + "/" + hour;
                filePath = path + "/" + minute + ".log";
            }


            Date now = new Date();
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
            //加入到队列
            concurrentLinkedQueue.add(new FileItem(filePath, dateFormat.format(now) + ":" + log));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static String WhichTenMinutes(int minute) {
        return String.valueOf(minute / 10 + 1)+"_10";
    }

    public enum LogTye {
        Debug, INFO, ERROR;
    }

    public enum LogFileCreateType {
        OneFileEveryDay,
        OneFileAnHour,
        OneFilePerTenMinutes,
        OneFilePerMinute
    }

    static class FileItem {
        public String getFilePath() {
            return filePath;
        }

        public String getLog() {
            return log;
        }

        private String filePath;
        private String log;

        public FileItem(String filePath, String log) {
            this.filePath = filePath;
            this.log = log;
        }

    }

}

 

你可能感兴趣的:(文本日志记录器)