怎么说,手写一个日志框架

手写一个日志框架

什么是日志框架

日志框架是一种用于记录和管理应用程序运行时信息的软件组件。它通常提供了一套API(Application Programming Interface),让开发人员能够在代码中插入日志语句,以便在应用程序运行时生成有关其状态和执行流的信息。

一个优秀的日志框架应该具备的功能:

  1. 级别控制: 日志框架通常支持多个日志级别,如 DEBUG、INFO、WARN、ERROR 等。开发人员可以根据需要选择记录的信息级别,以便在不同场景中控制日志输出。
  2. 输出目标: 日志框架支持将日志信息输出到不同的目标,如控制台、文件、数据库、远程服务器等。这使得开发人员能够根据实际需求选择合适的输出方式。
  3. 性能优化: 一些日志框架支持异步日志记录,以减少对应用程序性能的影响。它们可能使用缓冲机制、线程池等技术,以提高日志记录的效率。
  4. 灵活性: 日志框架提供了灵活的配置选项,使得开发人员能够根据实际需求进行定制和调整。

常见的Java日志框架包括 Log4j、Logback、java.util.logging 等。

手写一个简单的日志框架应该怎么做?

首先,我们需要明确我们要做的功能。

从日渐成熟的日志框架中,我们总计日志框架的核心功能有;

  • 级别控制(DEBUG、INFO、WARN、ERROR)
  • 输出(控制台输出、文件输出)
  • 性能优化(做到不浪费性能)

我写了一个简单的源码

java 复制代码
import java.io.*;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * LeLog类用于记录不同日志级别的消息。
 */
public class LeLog {
    /**
     * 日志级别的枚举: INFO, WARN, ERROR
     */
    public enum LogLevel {
        INFO, WARN, ERROR
    }

    private static Class<?> defaultClass; // 用于日志记录的默认类
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); // 日志记录的日期格式
    private static Map<Class<?>, LeLog> instances = new HashMap<>(); // 存储LeLog实例的映射
    private Class<?> clazz; // 与日志关联的类

    /**
     * 构造函数,设置默认日志记录的类。
     * @param clazz 与日志关联的类
     */
    public LeLog(Class<?> clazz) {
        this.clazz = clazz;
        defaultClass = clazz; // 设置默认日志记录的类
    }

    /**
     * 多例模式创建实例
     * @param clazz 类
     * @return LeLog实例
     */
    public static synchronized LeLog getLeLog(Class<?> clazz) {
        if (!instances.containsKey(clazz)) {
            LeLog instance = new LeLog(clazz);
            instances.put(clazz, instance);
        }
        return instances.get(clazz);
    }

    /**
     * 将日志消息写入控制台和文件。
     * @param level 日志级别
     * @param logName 日志名称
     * @param message 日志消息
     */
    private static void write(LogLevel level, String logName, String message) {
        // 创建并格式化日志消息
        String timeStamp = dateFormat.format(new Date());
        String threadName = Thread.currentThread().getName();
        long threadId = Thread.currentThread().getId();
        String logMessage = timeStamp + " [" + level + "] "
                + "Thread: " + threadName + " (ID: " + threadId + ") "
                + logName + " - " + message;
        // 输出日志消息到控制台
        System.out.println(logMessage);
        // 将日志消息写入文件
        writeToFile(logMessage);
    }

    /**
     * 获取日志文件路径。
     * @return 日志文件路径
     */
    private static String getLogFile() {
        Properties prop = new Properties();
        try (FileInputStream input = new FileInputStream("le.log.properties")) {
            prop.load(input);
            String logFilePath = prop.getProperty("logFilePath");

            File logFolder = new File(logFilePath);
            if (!logFolder.exists()) {
                logFolder.mkdirs();
            }

            String logFileName = prop.getProperty("logFileName");
            return logFilePath + "/" + logFileName + "_" + LocalDate.now() + ".log";
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将日志消息写入文件。
     * @param message 日志消息
     */
    private static void writeToFile(String message) {
        String logFile = getLogFile();
        if (logFile != null) {
            try (PrintWriter out = new PrintWriter(new FileWriter(logFile, true))) {
                out.println(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 记录INFO级别的日志消息。
     * @param message 日志消息
     */
    public static void info(String message){
        write(LeLog.LogLevel.INFO,defaultClass.getName(),message);
    }

    /**
     * 记录WARN级别的日志消息。
     * @param message 日志消息
     */
    public static void warn(String message){
        write(LogLevel.WARN,defaultClass.getName(),message);
    }

    /**
     * 记录ERROR级别的日志消息。
     * @param message 日志消息
     */
    public static void error(String message){
        write(LogLevel.ERROR,defaultClass.getName(),message);
    }

}

代码结构和核心思路

LeLog 类是一个用于记录不同日志级别消息的工具类。以下是它的核心思路和代码结构:

  1. 日志级别枚举: LeLog 定义了一个LogLevel枚举,包括 INFO、WARN 和 ERROR 三个级别,分别表示信息、警告和错误。

    java 复制代码
        /**
         * 日志级别的枚举: INFO, WARN, ERROR
         */
        public enum LogLevel {
            INFO, WARN, ERROR
        }
  2. 类与实例关联: LeLog 通过多例模式创建实例,每个实例与一个特定的类关联。这种设计使得每个类都有自己的日志记录器,方便了解来自不同类的日志信息。

    java 复制代码
        private static Map<Class<?>, LeLog> instances = new HashMap<>(); // 存储LeLog实例的映射
        private Class<?> clazz; // 与日志关联的类
    
        /**
         * 构造函数,设置默认日志记录的类。
         * @param clazz 与日志关联的类
         */
        public LeLog(Class<?> clazz) {
            this.clazz = clazz;
            defaultClass = clazz; // 设置默认日志记录的类
        }
    
        /**
         * 多例模式创建实例
         * @param clazz 类
         * @return LeLog实例
         */
        public static synchronized LeLog getLeLog(Class<?> clazz) {
            if (!instances.containsKey(clazz)) {
                LeLog instance = new LeLog(clazz);
                instances.put(clazz, instance);
            }
            return instances.get(clazz);
        }
  3. 日志消息格式化: 在 write 方法中,使用 SimpleDateFormat 对日期进行格式化,同时获取线程信息和日志级别,将这些信息拼接成格式化的日志消息。

    java 复制代码
        /**
         * 将日志消息写入控制台和文件。
         * @param level 日志级别
         * @param logName 日志名称
         * @param message 日志消息
         */
        private static void write(LogLevel level, String logName, String message) {
            // 创建并格式化日志消息
            String timeStamp = dateFormat.format(new Date());
            String threadName = Thread.currentThread().getName();
            long threadId = Thread.currentThread().getId();
            String logMessage = timeStamp + " [" + level + "] "
                    + "Thread: " + threadName + " (ID: " + threadId + ") "
                    + logName + " - " + message;
            // 输出日志消息到控制台
            System.out.println(logMessage);
            // 将日志消息写入文件
            writeToFile(logMessage);
        }
  4. 输出到控制台和文件: LeLog 将日志消息输出到控制台,并通过 writeToFile 方法将消息写入到文件。日志文件路径和文件名从配置文件中读取,提高了灵活性。日志文件名中包含当前日期,每天生成一个新的日志文件,方便按日期查看日志。

    properties 复制代码
    # 日志输出路径
    logFilePath=logs/xxx/xx
    # 日志输出名称
    logFileName=xia_le
    java 复制代码
        /**
         * 获取日志文件路径。
         * @return 日志文件路径
         */
        private static String getLogFile() {
            Properties prop = new Properties();
            try (FileInputStream input = new FileInputStream("le.log.properties")) {
                prop.load(input);
                String logFilePath = prop.getProperty("logFilePath");
    
                File logFolder = new File(logFilePath);
                if (!logFolder.exists()) {
                    logFolder.mkdirs();
                }
    
                String logFileName = prop.getProperty("logFileName");
                return logFilePath + "/" + logFileName + "_" + LocalDate.now() + ".log";
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 将日志消息写入文件。
         * @param message 日志消息
         */
        private static void writeToFile(String message) {
            String logFile = getLogFile();
            if (logFile != null) {
                try (PrintWriter out = new PrintWriter(new FileWriter(logFile, true))) {
                    out.println(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    使用方法

    使用 LeLog 记录日志非常简单。首先,通过 LeLog.getLeLog(Class clazz) 方法获取 LeLog 实例,然后通过该实例的 infowarnerror 方法记录不同级别的日志消息。以下是一个简单的示例:

    java 复制代码
    public class TestOne {
        private static final LeLog leLog = LeLog.getLeLog(TestOne.class);
        public void test(){
            leLog.info("This is an information message.");
            leLog.warn("This is a warning message.");
            leLog.error("This is an error message.");
        }
    }
    
    public class TestTwo {
        private static final LeLog leLog = LeLog.getLeLog(TestTwo.class);
        public void test(){
            leLog.info("This is an information message.");
            leLog.warn("This is a warning message.");
            leLog.error("This is an error message.");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            new TestOne().test();
            new TestTwo().test();
        }
    }

    控制台输出如下

文件输出如下:

后期优化

这个类可以说是漏洞百出,我写出来不过是给大伙提供一个思路和一个敢于手写框架造轮子的程序员桀骜不驯的心。

那这个类来说,值得喷的点有哪些:

  • 异常处理你是一点没做啊!
  • 文件输出类型太单一,我只做了windows的日志路径,Linux上的你是一点没做啊!
  • 文件的格式问题,现在日志都流行是json格式的,以便于是以后在NoSql数据库展示和分析。
  • 过滤我也是一个都么看见。
  • 异常追踪呢?
  • 最重要的是一个框架的灵活性,你这个你不觉得笨重吗?
  • 字符编码和乱码问题呢,System.out.println很耗费性能的好吧。
  • ..........太多了,懒得吐槽。

总结

各位程序员,相信自己,你也可以手写任何东西,记住,程序改变世界。

一定要多思考,如果人永远待在舒适圈的话,人永远不会成长。共勉。

觉得作者写的不错的,值得你们借鉴的话,就请点一个免费的赞吧!这个对我来说真的很重要。૮(˶ᵔ ᵕ ᵔ˶)ა

相关推荐
阿伟*rui10 分钟前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
码农小旋风4 小时前
详解K8S--声明式API
后端
Peter_chq4 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~4 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616884 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7895 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot