用java实现一个自定义基于logback的日志工具类

✅ 动态创建: 无需配置文件,通过代码动态创建logback日志对象
✅ Class对象支持: 使用LogUtil.getLogger(MyClass.class)的方式获取日志
✅ 日期格式文件: 自动生成info.%d{yyyy-MM-dd}.log格式的日志文件
✅ 文件数量管理: 只保留最近3个文件,自动删除历史文件
✅ 单例保证: 相同类名和目录的日志对象保证是同一个实例
✅ 启动时清理: 每次启动程序时自动清理超过保留数量的历史文件
✅ 控制台输出控制: 通过全局变量控制是否启用控制台输出

import java.io.File;
import java.util.concurrent.ConcurrentHashMap;import org.slf4j.LoggerFactory;import com.staryea.stream.runner.MainRunner;import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;/*** 动态日志工具类* ✅ 动态创建: 无需配置文件,通过代码动态创建logback日志对象* ✅ Class对象支持: 使用LogUtil.getLogger(MyClass.class)的方式获取日志* ✅ 日期格式文件: 自动生成info.%d{yyyy-MM-dd}.log格式的日志文件* ✅ 文件数量管理: 只保留最近3个文件,自动删除历史文件* ✅ 单例保证: 相同类名和目录的日志对象保证是同一个实例* ✅ 启动时清理: 每次启动程序时自动清理超过保留数量的历史文件* ✅ 控制台输出控制: 通过全局变量控制是否启用控制台输出*/
public class LogUtil {// 缓存日志对象,key为类名private static final ConcurrentHashMap<String, Logger> loggerCache = new ConcurrentHashMap<>();// 日志格式private static final String LOG_PATTERN = "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{200}:%L - %msg%n";// 默认日志目录private static final String DEFAULT_LOG_DIR = "logs";/*** 获取日志对象* @param clazz 类对象* @return Logger对象*/public static Logger getLogger(Class<?> clazz) {return getLogger(clazz, DEFAULT_LOG_DIR);}/*** 获取日志对象* @param clazz 类对象* @param logDir 日志文件目录* @return Logger对象*/public static Logger getLogger(Class<?> clazz, String logDir) {String className = clazz.getName();String key = className + "_" + logDir;return loggerCache.computeIfAbsent(key, k -> createLogger(className, logDir));}/*** 创建日志对象* @param className 类名* @param logDir 日志文件目录* @return Logger对象*/private static Logger createLogger(String className, String logDir) {System.out.println("开始创建日志对象,类名: " + className + ", 目录: " + logDir);// 确保目录存在File dir = new File(logDir);if (!dir.exists()) {boolean created = dir.mkdirs();System.out.println("创建目录结果: " + created);if (!created) {throw new RuntimeException("无法创建日志目录: " + logDir);}}System.out.println("目录是否存在: " + dir.exists());System.out.println("目录是否可写: " + dir.canWrite());// 获取LoggerContextLoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();// 创建Logger,使用类名作为Logger名称。这个有BUG:Logback的LoggerContext对于相同类名总是返回同一个Logger实例,导致后续配置覆盖了之前的配置(相同类名时路径被覆盖)。解决方案是使用包含目录信息的唯一Logger名称。
//        Logger logger = loggerContext.getLogger(className);// 使用类名 + 目录作为Logger名称,确保唯一性String loggerName = className + "_" + logDir.hashCode();Logger logger = loggerContext.getLogger(loggerName);logger.setAdditive(false); // 不继承父Logger的Appender// 清除已有的Appenderlogger.detachAndStopAllAppenders();// 根据全局开关决定是否添加控制台输出if (MainRunner.isTest) {ConsoleAppender<ILoggingEvent> consoleAppender = createConsoleAppender(loggerContext);logger.addAppender(consoleAppender);System.out.println("控制台Appender添加成功: " + consoleAppender.isStarted());} else {System.out.println("控制台输出已禁用,跳过控制台Appender添加");}// 添加文件输出(始终存在)RollingFileAppender<ILoggingEvent> fileAppender = createFileAppender(loggerContext, logDir);logger.addAppender(fileAppender);System.out.println("文件Appender添加成功: " + fileAppender.isStarted());// 设置日志级别logger.setLevel(Level.INFO);System.out.println("Logger创建完成,名称: " + className);System.out.println("Logger级别: " + logger.getLevel());System.out.println("Appender数量: " + logger.iteratorForAppenders().hasNext());return logger;}/*** 创建控制台Appender*/private static ConsoleAppender<ILoggingEvent> createConsoleAppender(LoggerContext loggerContext) {ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();consoleAppender.setContext(loggerContext);consoleAppender.setName("console");PatternLayoutEncoder encoder = new PatternLayoutEncoder();encoder.setContext(loggerContext);encoder.setPattern(LOG_PATTERN);encoder.start();consoleAppender.setEncoder(encoder);consoleAppender.start();return consoleAppender;}/*** 创建文件Appender*/private static RollingFileAppender<ILoggingEvent> createFileAppender(LoggerContext loggerContext, String logDir) {RollingFileAppender<ILoggingEvent> fileAppender = new RollingFileAppender<>();fileAppender.setContext(loggerContext);fileAppender.setName("file");// 创建编码器PatternLayoutEncoder encoder = new PatternLayoutEncoder();encoder.setContext(loggerContext);encoder.setPattern(LOG_PATTERN);encoder.start();fileAppender.setEncoder(encoder);// 创建滚动策略 - 只按时间轮转TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new TimeBasedRollingPolicy<>();rollingPolicy.setContext(loggerContext);rollingPolicy.setParent(fileAppender);// 设置滚动文件路径模式 - 固定为info.%d{yyyy-MM-dd}.logString rollingFile = logDir + File.separator + "info.%d{yyyy-MM-dd}.log";rollingPolicy.setFileNamePattern(rollingFile);// 设置保留文件数量(只保留3个文件)rollingPolicy.setMaxHistory(3);// 启动时清理历史文件rollingPolicy.setCleanHistoryOnStart(true);rollingPolicy.start();fileAppender.setRollingPolicy(rollingPolicy);// 设置立即刷新fileAppender.setImmediateFlush(true);fileAppender.start();System.out.println("文件Appender启动状态: " + fileAppender.isStarted());System.out.println("文件Appender名称: " + fileAppender.getName());System.out.println("滚动文件模式: " + rollingFile);System.out.println("保留文件数量: " + rollingPolicy.getMaxHistory());System.out.println("启动时清理: " + rollingPolicy.isCleanHistoryOnStart());return fileAppender;}/*** 重新创建所有已缓存的Logger(应用新的控制台输出设置)*/public static void refreshAllLoggers() {System.out.println("开始刷新所有Logger");clearAllLoggers();System.out.println("所有Logger已刷新完成");}/*** 清理指定类名的日志对象缓存* @param clazz 类对象*/public static void clearLogger(Class<?> clazz) {clearLogger(clazz, DEFAULT_LOG_DIR);}/*** 清理指定类名和目录的日志对象缓存* @param clazz 类对象* @param logDir 日志文件目录*/public static void clearLogger(Class<?> clazz, String logDir) {String className = clazz.getName();String key = className + "_" + logDir;Logger logger = loggerCache.remove(key);if (logger != null) {logger.detachAndStopAllAppenders();}}/*** 清理所有日志对象缓存*/public static void clearAllLoggers() {loggerCache.values().forEach(logger -> logger.detachAndStopAllAppenders());loggerCache.clear();}/*** 获取缓存中的日志对象数量* @return 日志对象数量*/public static int getLoggerCount() {return loggerCache.size();}public static void main(String[] args) {String logDir1 = "E:\\logs\\1";String logDir2 = "E:\\logs\\2";System.out.println("=== 开始测试日志功能 ===");// 测试不同类名的日志对象 - 使用Class对象Logger log1 = LogUtil.getLogger(TestClass1.class, logDir1);Logger log2 = LogUtil.getLogger(TestClass2.class, logDir2);// 测试日志输出System.out.println("=== 开始输出日志 ===");log1.info("TestClass1的日志信息: {}", "这是第一条日志");log1.error("TestClass1的错误日志: {}", "这是错误信息");log2.info("TestClass2的日志信息: {}", "这是第二条日志");log2.warn("TestClass2的警告日志: {}", "这是警告信息");}// 测试用的内部类public static class TestClass1 {}public static class TestClass2 {}
}

执行结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/88510.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/88510.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试现场:奇哥扮猪吃老虎,RocketMQ高级原理吊打面试官

“你了解RocketMQ的高级原理和源码吗&#xff1f;” 面试官推了推眼镜&#xff0c;嘴角带笑&#xff0c;眼神里透着一丝轻蔑。 奇哥笑而不语&#xff0c;开始表演。面试场景描写 公司位于高楼林立的CBD&#xff0c;电梯直达28楼。面试室宽敞明亮&#xff0c;空气中混着咖啡香与…

Django Nginx+uWSGI 安装配置指南

Django Nginx+uWSGI 安装配置指南 引言 Django 是一个高级的 Python Web 框架,用于快速开发和部署 Web 应用程序。Nginx 是一个高性能的 HTTP 和反向代理服务器,而 uWSGI 是一个 WSGI 服务器,用于处理 Python Web 应用。本文将详细介绍如何在您的服务器上安装和配置 Djang…

外设数据到昇腾310推理卡 之二dma_alloc_attrs

目录 内核源码及路径 CONFIG_DMA_DECLARE_COHERENT DTS示例配置 dma_direct_alloc 特殊属性快速路径 (DMA_ATTR_NO_KERNEL_MAPPING) 主体流程 1. 内存分配核心 2. 地址转换 3. 缓存一致性处理 映射 attrs不同属性的cache处理 cache的标示&#xff08;ARM64&#xff0…

Java 大视界:基于 Java 的大数据可视化在智慧城市能源消耗动态监测与优化决策中的应用(2025 实战全景)

​​摘要​​在“双碳”战略深化落地的 2025 年&#xff0c;城市能源管理面临 ​​实时性​​、​​复杂性​​、​​可决策性​​ 三重挑战。本文提出基于 Java 技术栈的智慧能源管理平台&#xff0c;融合 ​​Flink 流处理引擎​​、​​Elasticsearch 实时检索​​、​​ECh…

微信小程序控制空调之微信小程序篇

目录 前言 下载微信开发者工具 一、项目简述 核心功能 技术亮点 二、MQTT协议实现详解 1. MQTT连接流程 2. 协议包结构实现 CONNECT包构建 PUBLISH包构建 三、核心功能实现 1. 智能重连机制 2. 温度控制逻辑 3. 模式控制实现 四、调试系统实现 1. 调试信息收集…

spring boot 详解以及原理

Spring Boot 是 Spring 框架的扩展&#xff0c;旨在简化 Spring 应用的开发和部署。它通过自动配置和约定优于配置的原则&#xff0c;让开发者能够快速搭建独立运行的、生产级别的 Spring 应用。以下是 Spring Boot 的详细解析和工作原理&#xff1a; 一、Spring Boot 的核心特…

3.4 ASPICE的系统架构与设计过程

ASPICE&#xff08;Automotive SPICE&#xff09;在系统架构与设计过程中&#xff0c;强调了在汽车软件开发中确保系统稳定性、可靠性和安全性的重要性。以下是ASPICE在系统架构与设计过程中的主要内容和步骤&#xff1a;系统架构设计准备阶段&#xff1a;需求分析&#xff1a;…

自助KTV选址指南与优化策略

选址四大铁律&#xff08;硬性条件&#xff09;产权合规&#xff1a;纯商业产权消防双通道&#xff1a;必须通过消防验收远离敏感区&#xff1a;距居民区、学校、医院等200米以上面积达标&#xff1a;满足包厢规划需求选址核心逻辑&#xff08;优先级排序&#xff09;要素关键策…

深度学习11(调参设参+批标准化)

调参技巧对于调参&#xff0c;通常采用跟机器学习中介绍的网格搜索一致&#xff0c;让所有参数的可能组合在一起&#xff0c;得到N组结果。然后去测试每一组的效果去选择。 假设我们现在有两个参数 α&#xff1a;0.1, 0.01, 0.001β&#xff1a;0.8, 0.88. 0.9这样会有9种…

Python 中 enumerate(s) 和 range() 的对比

一、enumerate(s) 是什么&#xff1f;for i, c in enumerate(s):...enumerate(s) 是一个内置函数&#xff0c;用于在遍历可迭代对象时&#xff0c;同时获得元素的索引和值。它返回的是一个**(index, element)** 元组。常用于遍历字符串、列表、元组等时&#xff0c;如果你既想拿…

【一起来学AI大模型】RAG系统流程:查询→向量化→检索→生成

RAG&#xff08;Retrieval-Augmented Generation&#xff09;系统核心流程非常精准&#xff1a; 查询 → 向量化 → 检索 → 生成 这是 RAG 实现“知识增强”的关键路径。下面我们结合具体组件&#xff08;如 ChromaDB、LangChain 检索器&#xff09;详细拆解每个步骤&#xff…

图像硬解码和软解码

一、什么是图像解码&#xff1f; 图像解码是指将压缩编码&#xff08;如 JPEG、PNG、WebP、H.264/AVC、H.265/HEVC 等格式&#xff09;的图像或视频数据还原为原始像素数据&#xff08;如 RGB、YUV&#xff09;的过程。 解码可以在CPU&#xff08;软件解码&#xff09;或专用硬…

Camera2API笔记

1. 常用对象CameraManager 相机服务。用于获取相机对象和相机信息。CameraDevices 相机设备。负责连接相机、创建会话、生成拍摄请求&#xff0c;管理相机生命周期。CameraCaptureSession 相机拍摄会话。用于预览和拍摄。一个相机只能有一个活跃会话。打开新会话时&#xff0c;…

触控屏gt1947

比较器判断是否翻转&#xff0c;周期控制器负责控制周期&#xff08;period&#xff09;。sample采器有多个影子&#xff0c;每次采样查看是否到了翻转的时候。

DNS和ICMP

域名介绍在网络通信中&#xff0c;需要用到ip加port&#xff0c;但是ip并不方便记忆&#xff0c;于是我们常用域名来对应一个ip例如&#xff1a;www.baidu.com 对应 156.36.56.98&#xff08;随便写的&#xff09;com: 一级域名. 表示这是一个企业域名. 同级的还有 "…

2022 年 12 月青少年软编等考 C 语言六级真题解析

目录 T1. 电话号码T2. 区间合并T3. 扑克牌排序T4. 现代艺术思路分析T1. 电话号码 题目链接:SOJ D1137 此题为 2021 年 12 月六级第一题原题,见 2021 年 12 月青少年软编等考 C 语言六级真题解析中的 T1。 T2. 区间合并 题目链接:SOJ D1112 此题为 2021 年 9 月六级第三…

无锁队列:从零构建生产者-消费者数据结构

高性能无锁队列&#xff1a;从零构建生产者-消费者数据结构 问题的本质 生产者-消费者问题的核心挑战不在于数据传输&#xff0c;而在于协调。传统的锁机制虽然简单&#xff0c;但带来了三个致命问题&#xff1a; 性能瓶颈&#xff1a;线程阻塞和上下文切换优先级反转&#xff…

JAVA面试宝典 -《Spring IOC核心:Bean生命周期全解析》

文章目录&#x1f331; 《Spring IOC核心&#xff1a;Bean生命周期全解析》1️⃣ 引言&#xff1a;Bean 生命周期为什么重要&#xff1f;2️⃣ Bean 生命周期概览&#xff08;图示 简要说明&#xff09;3️⃣ 每一步详细解析&#xff08;源码理解 示例&#xff09;3.1 &#…

Python 类型注解实战:`Optional` 与安全数据处理的艺术

Python 类型注解实战&#xff1a;Optional 与安全数据处理的艺术 在 Python 开发中&#xff0c;类型注解&#xff08;Type Hints&#xff09;已经成为现代 Python 项目的标配。本文将通过一个真实的认证令牌获取函数 get_auth_token()&#xff0c;深入解析 Optional 类型的应用…

深入MyBatis:CRUD操作与高级查询实战

引言 在上一篇文章中&#xff0c;我们介绍了Mybatis的基础使用。 如有需要请移步查看&#xff1a; MyBatis入门&#xff1a;快速掌握用户查询实战https://blog.csdn.net/qq_52331401/article/details/149270402?spm1001.2014.3001.5502 今天&#xff0c;我将通过一个完整的…