用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
英文地址,請注明出處:http://en.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;我將通過一個完整的…