Java手寫(xiě)一個(gè)日志框架的示例代碼
什么是日志框架
日志框架是一種用于記錄和管理應(yīng)用程序運(yùn)行時(shí)信息的軟件組件。它通常提供了一套API(Application Programming Interface),讓開(kāi)發(fā)人員能夠在代碼中插入日志語(yǔ)句,以便在應(yīng)用程序運(yùn)行時(shí)生成有關(guān)其狀態(tài)和執(zhí)行流的信息。
一個(gè)優(yōu)秀的日志框架應(yīng)該具備的功能:
- 級(jí)別控制: 日志框架通常支持多個(gè)日志級(jí)別,如 DEBUG、INFO、WARN、ERROR 等。開(kāi)發(fā)人員可以根據(jù)需要選擇記錄的信息級(jí)別,以便在不同場(chǎng)景中控制日志輸出。
- 輸出目標(biāo): 日志框架支持將日志信息輸出到不同的目標(biāo),如控制臺(tái)、文件、數(shù)據(jù)庫(kù)、遠(yuǎn)程服務(wù)器等。這使得開(kāi)發(fā)人員能夠根據(jù)實(shí)際需求選擇合適的輸出方式。
- 性能優(yōu)化: 一些日志框架支持異步日志記錄,以減少對(duì)應(yīng)用程序性能的影響。它們可能使用緩沖機(jī)制、線程池等技術(shù),以提高日志記錄的效率。
- 靈活性: 日志框架提供了靈活的配置選項(xiàng),使得開(kāi)發(fā)人員能夠根據(jù)實(shí)際需求進(jìn)行定制和調(diào)整。
常見(jiàn)的Java日志框架包括 Log4j、Logback、java.util.logging 等。
手寫(xiě)一個(gè)簡(jiǎn)單的日志框架應(yīng)該怎么做
首先,我們需要明確我們要做的功能。
從日漸成熟的日志框架中,我們總計(jì)日志框架的核心功能有;
- 級(jí)別控制(DEBUG、INFO、WARN、ERROR)
- 輸出(控制臺(tái)輸出、文件輸出)
- 性能優(yōu)化(做到不浪費(fèi)性能)
我寫(xiě)了一個(gè)簡(jiǎn)單的源碼
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類用于記錄不同日志級(jí)別的消息。
*/
public class LeLog {
/**
* 日志級(jí)別的枚舉: INFO, WARN, ERROR
*/
public enum LogLevel {
INFO, WARN, ERROR
}
private static Class<?> defaultClass; // 用于日志記錄的默認(rèn)類
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); // 日志記錄的日期格式
private static Map<Class<?>, LeLog> instances = new HashMap<>(); // 存儲(chǔ)LeLog實(shí)例的映射
private Class<?> clazz; // 與日志關(guān)聯(lián)的類
/**
* 構(gòu)造函數(shù),設(shè)置默認(rèn)日志記錄的類。
* @param clazz 與日志關(guān)聯(lián)的類
*/
public LeLog(Class<?> clazz) {
this.clazz = clazz;
defaultClass = clazz; // 設(shè)置默認(rèn)日志記錄的類
}
/**
* 多例模式創(chuàng)建實(shí)例
* @param clazz 類
* @return LeLog實(shí)例
*/
public static synchronized LeLog getLeLog(Class<?> clazz) {
if (!instances.containsKey(clazz)) {
LeLog instance = new LeLog(clazz);
instances.put(clazz, instance);
}
return instances.get(clazz);
}
/**
* 將日志消息寫(xiě)入控制臺(tái)和文件。
* @param level 日志級(jí)別
* @param logName 日志名稱
* @param message 日志消息
*/
private static void write(LogLevel level, String logName, String message) {
// 創(chuàng)建并格式化日志消息
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;
// 輸出日志消息到控制臺(tái)
System.out.println(logMessage);
// 將日志消息寫(xiě)入文件
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;
}
/**
* 將日志消息寫(xiě)入文件。
* @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級(jí)別的日志消息。
* @param message 日志消息
*/
public static void info(String message){
write(LeLog.LogLevel.INFO,defaultClass.getName(),message);
}
/**
* 記錄WARN級(jí)別的日志消息。
* @param message 日志消息
*/
public static void warn(String message){
write(LogLevel.WARN,defaultClass.getName(),message);
}
/**
* 記錄ERROR級(jí)別的日志消息。
* @param message 日志消息
*/
public static void error(String message){
write(LogLevel.ERROR,defaultClass.getName(),message);
}
}
代碼結(jié)構(gòu)和核心思路
LeLog 類是一個(gè)用于記錄不同日志級(jí)別消息的工具類。以下是它的核心思路和代碼結(jié)構(gòu):
日志級(jí)別枚舉: LeLog 定義了一個(gè)LogLevel枚舉,包括 INFO、WARN 和 ERROR 三個(gè)級(jí)別,分別表示信息、警告和錯(cuò)誤。
/**
* 日志級(jí)別的枚舉: INFO, WARN, ERROR
*/
public enum LogLevel {
INFO, WARN, ERROR
}
類與實(shí)例關(guān)聯(lián): LeLog 通過(guò)多例模式創(chuàng)建實(shí)例,每個(gè)實(shí)例與一個(gè)特定的類關(guān)聯(lián)。這種設(shè)計(jì)使得每個(gè)類都有自己的日志記錄器,方便了解來(lái)自不同類的日志信息。
private static Map<Class<?>, LeLog> instances = new HashMap<>(); // 存儲(chǔ)LeLog實(shí)例的映射
private Class<?> clazz; // 與日志關(guān)聯(lián)的類
/**
* 構(gòu)造函數(shù),設(shè)置默認(rèn)日志記錄的類。
* @param clazz 與日志關(guān)聯(lián)的類
*/
public LeLog(Class<?> clazz) {
this.clazz = clazz;
defaultClass = clazz; // 設(shè)置默認(rèn)日志記錄的類
}
/**
* 多例模式創(chuàng)建實(shí)例
* @param clazz 類
* @return LeLog實(shí)例
*/
public static synchronized LeLog getLeLog(Class<?> clazz) {
if (!instances.containsKey(clazz)) {
LeLog instance = new LeLog(clazz);
instances.put(clazz, instance);
}
return instances.get(clazz);
}
日志消息格式化: 在 write 方法中,使用 SimpleDateFormat 對(duì)日期進(jìn)行格式化,同時(shí)獲取線程信息和日志級(jí)別,將這些信息拼接成格式化的日志消息。
/**
* 將日志消息寫(xiě)入控制臺(tái)和文件。
* @param level 日志級(jí)別
* @param logName 日志名稱
* @param message 日志消息
*/
private static void write(LogLevel level, String logName, String message) {
// 創(chuàng)建并格式化日志消息
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;
// 輸出日志消息到控制臺(tái)
System.out.println(logMessage);
// 將日志消息寫(xiě)入文件
writeToFile(logMessage);
}
輸出到控制臺(tái)和文件: LeLog 將日志消息輸出到控制臺(tái),并通過(guò) writeToFile 方法將消息寫(xiě)入到文件。日志文件路徑和文件名從配置文件中讀取,提高了靈活性。日志文件名中包含當(dāng)前日期,每天生成一個(gè)新的日志文件,方便按日期查看日志。
# 日志輸出路徑 logFilePath=logs/xxx/xx # 日志輸出名稱 logFileName=xia_le
/**
* 獲取日志文件路徑。
* @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;
}
/**
* 將日志消息寫(xiě)入文件。
* @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 記錄日志非常簡(jiǎn)單。首先,通過(guò) LeLog.getLeLog(Class clazz) 方法獲取 LeLog 實(shí)例,然后通過(guò)該實(shí)例的 info、warn、error 方法記錄不同級(jí)別的日志消息。以下是一個(gè)簡(jiǎn)單的示例:
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();
}
}
控制臺(tái)輸出如下

文件輸出如下:


后期優(yōu)化
這個(gè)類可以說(shuō)是漏洞百出,我寫(xiě)出來(lái)不過(guò)是給大伙提供一個(gè)思路和一個(gè)敢于手寫(xiě)框架造輪子的程序員桀驁不馴的心。
那這個(gè)類來(lái)說(shuō),值得噴的點(diǎn)有哪些:
- 異常處理你是一點(diǎn)沒(méi)做啊!
- 文件輸出類型太單一,我只做了windows的日志路徑,Linux上的你是一點(diǎn)沒(méi)做??!
- 文件的格式問(wèn)題,現(xiàn)在日志都流行是json格式的,以便于是以后在NoSql數(shù)據(jù)庫(kù)展示和分析。
- 過(guò)濾我也是一個(gè)都么看見(jiàn)。
- 異常追蹤呢?
- 最重要的是一個(gè)框架的靈活性,你這個(gè)你不覺(jué)得笨重嗎?
- 字符編碼和亂碼問(wèn)題呢,
System.out.println很耗費(fèi)性能的好吧。 - ..........太多了,懶得吐槽。
以上就是Java手寫(xiě)一個(gè)日志框架的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Java日志框架的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
tomcat目錄結(jié)構(gòu)簡(jiǎn)介_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了tomcat目錄結(jié)構(gòu)簡(jiǎn)介_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理的相關(guān)資料,需要的朋友可以參考下2017-07-07
mybatis插入一條數(shù)據(jù)返回相應(yīng)數(shù)據(jù)ID問(wèn)題及解決
MyBatis插入數(shù)據(jù)并獲取自增ID的方法,通過(guò)設(shè)置useGeneratedKeys=true和keyProperty屬性,可以自動(dòng)將生成的ID賦值給實(shí)體對(duì)象的相應(yīng)屬性2026-05-05
java中javamail發(fā)送帶附件的郵件實(shí)現(xiàn)方法
這篇文章主要介紹了java中javamail發(fā)送帶附件的郵件實(shí)現(xiàn)方法,較為詳細(xì)的分析了JavaMail發(fā)送郵件的用法,是非常實(shí)用的技巧,需要的朋友可以參考下2015-01-01
Java8 用Lambda表達(dá)式給List集合排序的實(shí)現(xiàn)
這篇文章主要介紹了Java8 用Lambda表達(dá)式給List集合排序的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
spring-@Autowired注入與構(gòu)造函數(shù)注入使用方式
這篇文章主要介紹了spring-@Autowired注入與構(gòu)造函數(shù)注入使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
java實(shí)現(xiàn)百度云文字識(shí)別接口代碼
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)百度云文字識(shí)別的接口代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11
Spring Boot實(shí)現(xiàn)數(shù)據(jù)訪問(wèn)計(jì)數(shù)器方案詳解
在Spring Boot項(xiàng)目中,有時(shí)需要數(shù)據(jù)訪問(wèn)計(jì)數(shù)器,怎么實(shí)現(xiàn)數(shù)據(jù)訪問(wèn)計(jì)數(shù)器呢?下面小編給大家?guī)?lái)了Spring Boot數(shù)據(jù)訪問(wèn)計(jì)數(shù)器的實(shí)現(xiàn)方案,需要的朋友參考下吧2021-08-08

