SpringBoot實(shí)現(xiàn)日志系統(tǒng)的完整指南
日志系統(tǒng)是什么
想象一下,你的程序是個(gè)有點(diǎn)健忘的程序員同事(沒錯(cuò),就是那個(gè)總說(shuō)“我本地是好的”的家伙)。日志系統(tǒng)就是給他配的貼身小秘書,每天拿著小本本記錄:
- 幾點(diǎn)幾分干了啥(時(shí)間戳)
- 心里想什么(調(diào)試信息)
- 今天工作順利嗎(INFO信息)
- 出問(wèn)題了(ERROR信息)
- 救火啊要炸了(FATAL信息)
沒有日志系統(tǒng)?那就好比程序生病了,你問(wèn)他“哪不舒服?”,他只會(huì)回答“我掛了”。有了日志,他就能詳細(xì)告訴你:“昨天下午3點(diǎn),我在處理用戶訂單時(shí),因?yàn)閿?shù)據(jù)庫(kù)連接斷了,導(dǎo)致...”
好了,廢話不多說(shuō),讓我們給SpringBoot程序配個(gè)“貼心小秘書”!
第1步:SpringBoot的“天生麗質(zhì)”
SpringBoot這家伙很貼心,已經(jīng)內(nèi)置了日志系統(tǒng)!就像你買手機(jī),相機(jī)APP已經(jīng)預(yù)裝好了。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogDemoController {
// 創(chuàng)建日志記錄器,就像給這個(gè)類配了個(gè)專屬記者
private static final Logger logger = LoggerFactory.getLogger(LogDemoController.class);
@GetMapping("/hello")
public String sayHello() {
// 不同的日志級(jí)別,就像不同的說(shuō)話語(yǔ)氣
logger.trace("這是最詳細(xì)的跟蹤信息 - 連我呼吸都要記錄");
logger.debug("調(diào)試信息 - 我在想:用戶到底點(diǎn)了啥按鈕?");
logger.info("普通信息 - 用戶訪問(wèn)了hello接口,一切正常");
logger.warn("警告信息 - 內(nèi)存有點(diǎn)高,像吃了太多內(nèi)存的胖子");
logger.error("錯(cuò)誤信息 - 數(shù)據(jù)庫(kù)連接失??!快來(lái)人?。?);
// 還可以帶參數(shù),像填空一樣
String userName = "碼農(nóng)小張";
int userId = 123;
logger.info("用戶 {} (ID: {}) 登錄成功", userName, userId);
return "Hello World! 快去控制臺(tái)看日志吧!";
}
@GetMapping("/oops")
public String makeMistake() {
try {
// 故意制造一個(gè)錯(cuò)誤
int result = 10 / 0;
return "這行代碼永遠(yuǎn)執(zhí)行不到";
} catch (Exception e) {
// 記錄異常信息,參數(shù)e會(huì)自動(dòng)打印堆棧
logger.error("數(shù)學(xué)老師沒教好,除零錯(cuò)誤了!", e);
return "哎呀,出錯(cuò)了!詳情請(qǐng)看日志";
}
}
}
第2步:配置文件 - 給秘書定規(guī)矩
在application.yml(或application.properties)中配置。這是告訴小秘書:“哪些話要記,哪些話不用記,記在哪里...”
# application.yml - 日志系統(tǒng)的“規(guī)章制度”
# 第一部分:全局日志級(jí)別設(shè)置
logging:
level:
root: INFO # 根日志級(jí)別:INFO及以上才記錄
com.example.demo: DEBUG # 我們自己的包可以詳細(xì)點(diǎn)
org.springframework.web: WARN # Spring的web包,只記錄警告及以上
# 第二部分:輸出到哪里(控制臺(tái)和文件都記)
file:
name: logs/myapp.log # 日志文件路徑
max-size: 10MB # 單個(gè)文件最大10MB,超過(guò)就切分
max-history: 30 # 保留最近30天的日志
# 第三部分:日志格式 - 給小秘書的"記錄模板"
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
# 第四部分:按包或類單獨(dú)設(shè)置(精細(xì)化管理)
group:
web: org.springframework.web, org.springframework.security
app: com.example.demo.controller, com.example.demo.service
level:
web: INFO
app: DEBUG
# 如果你想用logback的詳細(xì)配置(高級(jí)玩法)
# 在resources目錄下創(chuàng)建logback-spring.xml
第3步:高級(jí)玩法 - 自定義日志配置
在resources目錄下創(chuàng)建logback-spring.xml,這是給小秘書的詳細(xì)工作手冊(cè):
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<!-- 控制臺(tái)輸出 - 給開發(fā)人員看的 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 彩色日志,讓控制臺(tái)不再單調(diào)! -->
<pattern>%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan(%logger{36}) - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 按天滾動(dòng)的文件輸出 - 給運(yùn)維人員看的 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<!-- 每天一個(gè)文件,最多保存30天 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 錯(cuò)誤日志單獨(dú)文件 - 重要的事情說(shuō)三遍,重要的錯(cuò)誤單獨(dú)記 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/error.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>90</maxHistory> <!-- 錯(cuò)誤日志保留更久 -->
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 不同環(huán)境的配置 -->
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
<logger name="com.example" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
</springProfile>
<springProfile name="prod">
<root level="WARN">
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<logger name="com.example" level="INFO" additivity="false">
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</logger>
</springProfile>
</configuration>
第4步:AOP實(shí)現(xiàn)方法日志 - 自動(dòng)記錄每個(gè)方法
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MethodLogAspect {
private static final Logger logger = LoggerFactory.getLogger(MethodLogAspect.class);
/**
* 自動(dòng)記錄Controller層每個(gè)方法的執(zhí)行情況
* 就像給每個(gè)方法配了個(gè)貼身觀察員
*/
@Around("execution(* com.example.demo.controller..*.*(..))")
public Object logControllerMethods(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
Object[] args = joinPoint.getArgs();
long startTime = System.currentTimeMillis();
logger.info("方法開始執(zhí)行: {},參數(shù): {}", methodName, args);
try {
Object result = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - startTime;
logger.info("方法執(zhí)行成功: {},耗時(shí): {}ms,返回值: {}",
methodName, executionTime, result);
return result;
} catch (Exception e) {
long executionTime = System.currentTimeMillis() - startTime;
logger.error("方法執(zhí)行失敗: {},耗時(shí): {}ms,異常: {}",
methodName, executionTime, e.getMessage(), e);
throw e;
}
}
}
第5步:日志工具類 - 讓日志更智能
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StopWatch;
/**
* 日志工具類 - 給日志加點(diǎn)"智能"
*/
public class LogUtil {
/**
* 性能監(jiān)控 - 記錄代碼塊執(zhí)行時(shí)間
*/
public static <T> T monitorPerformance(Logger logger, String taskName,
Supplier<T> task) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
logger.info("開始執(zhí)行: {}", taskName);
try {
T result = task.get();
stopWatch.stop();
logger.info("執(zhí)行完成: {},耗時(shí): {}ms",
taskName, stopWatch.getTotalTimeMillis());
return result;
} catch (Exception e) {
stopWatch.stop();
logger.error("執(zhí)行失敗: {},耗時(shí): {}ms,錯(cuò)誤: {}",
taskName, stopWatch.getTotalTimeMillis(), e.getMessage(), e);
throw e;
}
}
/**
* 業(yè)務(wù)日志 - 記錄關(guān)鍵業(yè)務(wù)操作
*/
public static void businessLog(Logger logger, String operation,
String userId, Object... details) {
// 這里可以擴(kuò)展,比如記錄到數(shù)據(jù)庫(kù)或發(fā)送到消息隊(duì)列
logger.info("業(yè)務(wù)操作 - 用戶: {}, 操作: {}, 詳情: {}",
userId, operation, details);
}
// 使用示例
public void exampleUsage() {
Logger logger = LoggerFactory.getLogger(this.getClass());
// 監(jiān)控性能
String result = monitorPerformance(logger, "計(jì)算用戶報(bào)表", () -> {
// 模擬耗時(shí)操作
Thread.sleep(1000);
return "報(bào)表數(shù)據(jù)";
});
// 記錄業(yè)務(wù)日志
businessLog(logger, "用戶登錄", "user123", "IP: 192.168.1.1", "設(shè)備: Chrome");
}
}
第6步:與ELK等日志系統(tǒng)集成(高級(jí)玩法)
# application-prod.yml - 生產(chǎn)環(huán)境配置
logging:
# 輸出JSON格式,方便ELK采集
pattern:
console: '{"timestamp":"%d{yyyy-MM-dd HH:mm:ss.SSS}", "level":"%level", "thread":"%thread", "logger":"%logger", "message":"%msg", "exception":"%ex"}'
# Logstash收集配置(如果需要)
logstash:
enabled: true
host: localhost
port: 5000
總結(jié):日志系統(tǒng)的"生存指南"
經(jīng)過(guò)這一番折騰,我們的SpringBoot程序終于有了一個(gè)稱職的貼身秘書。讓我們總結(jié)一下日志系統(tǒng)的幾個(gè)關(guān)鍵點(diǎn):
為什么要用日志系統(tǒng)
- 故障排查:程序說(shuō)“我掛了” → 日志說(shuō)“3點(diǎn)15分?jǐn)?shù)據(jù)庫(kù)連接超時(shí)”
- 性能分析:用戶說(shuō)“好卡” → 日志說(shuō)“這個(gè)接口平均響應(yīng)2.3秒”
- 行為追蹤:老板說(shuō)“誰(shuí)干的” → 日志說(shuō)“用戶admin在10點(diǎn)修改了配置”
- 數(shù)據(jù)統(tǒng)計(jì):產(chǎn)品說(shuō)“有多少人用” → 日志說(shuō)“今天有15234次訪問(wèn)”
最佳實(shí)踐
- 級(jí)別要分明:DEBUG用于開發(fā),INFO用于日常,ERROR用于異常
- 信息要詳細(xì):時(shí)間、線程、級(jí)別、類名、消息、異常,一個(gè)都不能少
- 性能要注意:日志IO是性能殺手,異步日志是個(gè)好選擇
- 安全要牢記:密碼、token等敏感信息別往日志里寫
常見坑點(diǎn)
- 日志太多:把DEBUG級(jí)別放到生產(chǎn)環(huán)境,日志文件瞬間爆炸
- 日志太少:出問(wèn)題時(shí),日志里只有“出錯(cuò)了”,沒有“為啥錯(cuò)”
- 格式混亂:今天用JSON,明天用文本,后天ELK不認(rèn)了
- 忘記歸檔:日志文件把磁盤寫滿了,程序真掛了
最后
給你的日志系統(tǒng)起個(gè)名字吧!比如叫“小日志”、“程序記錄儀”、“代碼攝像頭”。畢竟,它要陪你度過(guò)無(wú)數(shù)個(gè)排查BUG的不眠之夜,是你在茫茫代碼海洋中的燈塔,是你在程序崩潰時(shí)的救命稻草。
現(xiàn)在,去給你的SpringBoot程序配個(gè)“貼心小秘書”吧!當(dāng)程序再次崩潰時(shí),至少有人(日志)能告訴你:“親,這次是因?yàn)?..”
到此這篇關(guān)于SpringBoot實(shí)現(xiàn)日志系統(tǒng)的完整指南的文章就介紹到這了,更多相關(guān)SpringBoot日志系統(tǒng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文搞懂java中類及static關(guān)鍵字執(zhí)行順序
這篇文章主要介紹了一文搞懂java中類及static關(guān)鍵字執(zhí)行順序,文章通過(guò)類的生命周期展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
Java實(shí)現(xiàn)將容器 Map中的內(nèi)容保存到數(shù)組
這篇文章主要介紹了Java實(shí)現(xiàn)將容器 Map中的內(nèi)容保存到數(shù)組,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
基于Java解決華為機(jī)試實(shí)現(xiàn)密碼截取?
這篇文章主要介紹了基于Java解決華為機(jī)試實(shí)現(xiàn)密碼截取,文章圍繞主題相關(guān)資料展開詳細(xì)內(nèi)容,具有一的參考價(jià)值,需要的小伙伴可以參考一下,希望對(duì)你有所幫助2022-02-02
java Volatile與Synchronized的區(qū)別
這篇文章主要介紹了java Volatile與Synchronized的區(qū)別,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-12-12
JAXB簡(jiǎn)介_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了JAXB簡(jiǎn)介的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
Java測(cè)試題 實(shí)現(xiàn)一個(gè)注冊(cè)功能過(guò)程解析
這篇文章主要介紹了Java測(cè)試題 實(shí)現(xiàn)一個(gè)注冊(cè)功能過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
Java中的clone方法詳解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
clone顧名思義就是復(fù)制, 在Java語(yǔ)言中, clone方法被對(duì)象調(diào)用,所以會(huì)復(fù)制對(duì)象。下面通過(guò)本文給大家介紹java中的clone方法,感興趣的朋友一起看看吧2017-06-06

