一、日志系统:程序员的“侦探助手”
如果你的程序突然“挂掉”了,你却不知道它死前经历了什么——这比看悬疑电影看到一半停电还难受!日志系统就是你的“侦探助手”,它悄咪咪地记录着程序的一举一动,就像:
- 摄像头:谁在什么时候访问了哪个接口
- 记事本:程序想了什么、做了什么、遇到了什么挫折
- 告密者:偷偷告诉你“老板,数据库又连不上了!”
- 时间机器:能让你穿越回错误发生的瞬间
springboot的日志系统就像一个“智能管家”,你不配置它也能工作,但配置好了它就能变成“超级管家”!
二、详细步骤:打造你的“程序监控室”
第1步:创建springboot项目
# 用spring initializr创建一个新项目 # 或者用ide的spring initializr功能 # 记得勾选: # - spring web (因为我们要写接口) # - lombok (减少代码量,程序员要懒一点)
第2步:基础配置 - 给日志系统“定规矩”
在application.yml(或application.properties)中添加:
# application.yml
spring:
application:
name: log-system-demo
logging:
# 日志级别:trace < debug < info < warn < error
level:
root: info # 根日志级别
com.example.demo: debug # 我们的包用debug级别
org.springframework.web: info
org.hibernate: warn
# 文件输出配置(让日志有个“家”)
file:
name: logs/my-app.log # 日志文件路径
max-size: 10mb # 单个文件最大10mb
max-history: 30 # 保留30天的日志
# 控制台输出美化(让日志“颜值”更高)
pattern:
console: "%d{yyyy-mm-dd hh:mm:ss} - %magenta([%thread]) - %highlight(%-5level) - %cyan(%logger{36}) - %msg%n"
file: "%d{yyyy-mm-dd hh:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
# 日志分组(给日志“分班”)
group:
web: org.springframework.core.codec, org.springframework.http
sql: org.hibernate.sql, org.springframework.jdbc
第3步:创建日志工具类
package com.example.demo.utils;
import lombok.extern.slf4j.slf4j;
import org.springframework.stereotype.component;
@component
@slf4j // lombok的魔法注解,自动生成log对象
public class logutil {
/**
* 记录方法进入(就像进门前喊“我进来啦!”)
*/
public void methodenter(string methodname, object... params) {
log.debug("方法 {} 被调用,参数: {}", methodname, params);
}
/**
* 记录方法退出(出门说“我走啦!”)
*/
public void methodexit(string methodname, object result) {
log.debug("方法 {} 执行完成,返回值: {}", methodname, result);
}
/**
* 记录业务关键点(重要的事说三遍?不,记一遍就行)
*/
public void businesslog(string template, object... args) {
log.info("业务日志: " + template, args);
}
/**
* 记录异常(错误发生时大喊“着火啦!”)
*/
public void error(string message, throwable e) {
log.error("发生异常: {} - 异常详情: ", message, e);
}
/**
* 慢查询警告(程序说“我...有点卡...”)
*/
public void slowquery(long costtime, string query) {
if (costtime > 1000) { // 超过1秒
log.warn("慢查询警告! 耗时: {}ms, sql: {}", costtime, query);
}
}
}
第4步:创建aop切面 - 给所有方法“装上摄像头”
package com.example.demo.aop;
import com.example.demo.utils.logutil;
import lombok.requiredargsconstructor;
import lombok.extern.slf4j.slf4j;
import org.aspectj.lang.proceedingjoinpoint;
import org.aspectj.lang.annotation.around;
import org.aspectj.lang.annotation.aspect;
import org.aspectj.lang.annotation.pointcut;
import org.springframework.stereotype.component;
import org.springframework.web.context.request.requestcontextholder;
import org.springframework.web.context.request.servletrequestattributes;
import javax.servlet.http.httpservletrequest;
import java.util.arrays;
@aspect
@component
@slf4j
@requiredargsconstructor
public class logaspect {
private final logutil logutil;
/**
* 切点:所有controller层的方法
*/
@pointcut("execution(* com.example.demo.controller..*.*(..))")
public void controllerpointcut() {}
/**
* 切点:所有service层的方法
*/
@pointcut("execution(* com.example.demo.service..*.*(..))")
public void servicepointcut() {}
/**
* 环绕通知:controller层日志
*/
@around("controllerpointcut()")
public object logcontroller(proceedingjoinpoint joinpoint) throws throwable {
// 获取请求信息
servletrequestattributes attributes =
(servletrequestattributes) requestcontextholder.getrequestattributes();
string requesturl = "unknown";
string httpmethod = "unknown";
string ip = "unknown";
if (attributes != null) {
httpservletrequest request = attributes.getrequest();
requesturl = request.getrequesturl().tostring();
httpmethod = request.getmethod();
ip = request.getremoteaddr();
}
string methodname = joinpoint.getsignature().getname();
string classname = joinpoint.gettarget().getclass().getsimplename();
object[] args = joinpoint.getargs();
// 记录请求开始
log.info("\n========== 请求进入 ==========");
log.info("url: {} {}", httpmethod, requesturl);
log.info("ip: {}", ip);
log.info("类: {}.{}", classname, methodname);
log.info("参数: {}", arrays.tostring(args));
long starttime = system.currenttimemillis();
object result;
try {
// 执行原方法
result = joinpoint.proceed();
long costtime = system.currenttimemillis() - starttime;
// 记录请求完成
log.info("请求成功,耗时: {}ms", costtime);
log.info("返回结果: {}", result);
log.info("========== 请求结束 ==========\n");
return result;
} catch (exception e) {
long costtime = system.currenttimemillis() - starttime;
// 记录异常
log.error("请求失败,耗时: {}ms", costtime);
log.error("异常信息: {}", e.getmessage());
log.info("========== 请求异常结束 ==========\n");
throw e;
}
}
/**
* 环绕通知:service层日志
*/
@around("servicepointcut()")
public object logservice(proceedingjoinpoint joinpoint) throws throwable {
string methodname = joinpoint.getsignature().getname();
object[] args = joinpoint.getargs();
logutil.methodenter(methodname, args);
long starttime = system.currenttimemillis();
try {
object result = joinpoint.proceed();
long costtime = system.currenttimemillis() - starttime;
logutil.methodexit(methodname, result);
logutil.slowquery(costtime, methodname + " 方法执行");
return result;
} catch (exception e) {
logutil.error("service方法执行失败: " + methodname, e);
throw e;
}
}
}
第5步:创建controller和service - 让日志系统“有活干”
// usercontroller.java
package com.example.demo.controller;
import com.example.demo.service.userservice;
import lombok.requiredargsconstructor;
import lombok.extern.slf4j.slf4j;
import org.springframework.web.bind.annotation.*;
@restcontroller
@requestmapping("/users")
@slf4j
@requiredargsconstructor
public class usercontroller {
private final userservice userservice;
@getmapping("/{id}")
public string getuser(@pathvariable long id) {
log.info("查询用户,id: {}", id);
return userservice.getuserbyid(id);
}
@postmapping
public string createuser(@requestbody string userdata) {
log.info("创建用户,数据: {}", userdata);
// 模拟业务异常
if ("bad".equals(userdata)) {
throw new runtimeexception("用户数据不合法!");
}
return "用户创建成功: " + userdata;
}
}
// userservice.java
package com.example.demo.service;
import com.example.demo.utils.logutil;
import lombok.requiredargsconstructor;
import org.springframework.stereotype.service;
@service
@requiredargsconstructor
public class userservice {
private final logutil logutil;
public string getuserbyid(long id) {
logutil.businesslog("根据id查询用户,id: {}", id);
// 模拟数据库查询
try {
thread.sleep(50); // 模拟耗时
if (id == 999) {
throw new runtimeexception("用户不存在!");
}
return "用户" + id;
} catch (interruptedexception e) {
logutil.error("查询用户时发生异常", e);
return "查询失败";
}
}
}
第6步:创建全局异常处理 - 给错误“擦屁股”
package com.example.demo.handler;
import com.example.demo.utils.logutil;
import lombok.requiredargsconstructor;
import lombok.extern.slf4j.slf4j;
import org.springframework.web.bind.annotation.exceptionhandler;
import org.springframework.web.bind.annotation.restcontrolleradvice;
import javax.servlet.http.httpservletrequest;
import java.util.hashmap;
import java.util.map;
@restcontrolleradvice
@slf4j
@requiredargsconstructor
public class globalexceptionhandler {
private final logutil logutil;
@exceptionhandler(exception.class)
public map<string, object> handleexception(httpservletrequest request, exception e) {
// 记录异常日志
logutil.error("全局异常捕获", e);
// 返回友好错误信息
map<string, object> result = new hashmap<>();
result.put("success", false);
result.put("message", "服务器开小差了,请稍后再试!");
result.put("path", request.getrequesturi());
result.put("timestamp", system.currenttimemillis());
// 开发环境显示详细错误
if (isdevelopment()) {
result.put("error", e.getmessage());
result.put("stacktrace", e.getstacktrace());
}
return result;
}
private boolean isdevelopment() {
// 这里可以根据配置判断环境
return true; // 假设是开发环境
}
}
第7步:创建日志查看接口(可选) - 给日志开个“后门”
package com.example.demo.controller;
import org.springframework.web.bind.annotation.*;
import java.io.bufferedreader;
import java.io.filereader;
import java.io.ioexception;
import java.util.arraylist;
import java.util.list;
@restcontroller
@requestmapping("/log")
public class logcontroller {
@getmapping("/tail")
public list<string> getlogtail(@requestparam(defaultvalue = "100") int lines) {
list<string> result = new arraylist<>();
string logfile = "logs/my-app.log";
try (bufferedreader reader = new bufferedreader(new filereader(logfile))) {
list<string> alllines = new arraylist<>();
string line;
while ((line = reader.readline()) != null) {
alllines.add(line);
}
// 获取最后n行
int start = math.max(0, alllines.size() - lines);
for (int i = start; i < alllines.size(); i++) {
result.add(alllines.get(i));
}
} catch (ioexception e) {
result.add("读取日志文件失败: " + e.getmessage());
}
return result;
}
}
第8步:配置文件分离(高级技巧) - 给不同环境“穿不同衣服”
# application-dev.yml (开发环境)
logging:
level:
root: debug # 开发环境详细日志
file:
name: logs/dev-app.log
# application-prod.yml (生产环境)
logging:
level:
root: info # 生产环境精简日志
com.example.demo: warn # 自己的包只记录警告
file:
name: /var/log/my-app/app.log # linux系统标准日志目录
三、启动和测试
1. 启动应用
# 设置激活的环境 java -jar demo.jar --spring.profiles.active=dev
2. 测试接口
# 正常请求 curl http://localhost:8080/users/1 # 触发异常 curl -x post http://localhost:8080/users -d "bad" # 查看日志 curl http://localhost:8080/log/tail?lines=50
3. 观察控制台输出
你会看到彩色高亮的日志:
2026-01-21 10:30:25 - [http-nio-8080-exec-1] - info - c.e.demo.controller.usercontroller - 查询用户,id: 1
2026-01-21 10:30:25 - [http-nio-8080-exec-1] - debug - c.e.demo.aop.logaspect - 方法 getuserbyid 被调用,参数: [1]
四、高级功能扩展
1. 添加日志脱敏(保护敏感信息)
@component
public class logsensitivefilter {
public string filtersensitive(string logcontent) {
// 脱敏手机号
logcontent = logcontent.replaceall("(1[3-9]\\d{9})", "$1****");
// 脱敏身份证
logcontent = logcontent.replaceall("(\\d{4})\\d{10}(\\w{4})", "$1**********$2");
// 脱敏邮箱
logcontent = logcontent.replaceall("(\\w{3})(\\w+)(@\\w+\\.\\w+)", "$1****$3");
return logcontent;
}
}
2. 集成elk(日志分析全家桶)
# 添加logstash依赖 dependencies: implementation 'net.logstash.logback:logstash-logback-encoder:7.0'
配置logback-spring.xml:
<appender name="logstash" class="net.logstash.logback.appender.logstashtcpsocketappender">
<destination>localhost:5000</destination>
<encoder class="net.logstash.logback.encoder.loggingeventcompositejsonencoder">
<providers>
<timestamp/>
<loglevel/>
<loggername/>
<message/>
<mdc/>
<stacktrace/>
</providers>
</encoder>
</appender>
3. 自定义appender(发送到企业微信/钉钉)
public class dingtalkappender extends appenderbase<iloggingevent> {
@override
protected void append(iloggingevent event) {
if (event.getlevel().isgreaterorequal(level.error)) {
string message = string.format("【系统告警】\n时间: %s\n级别: %s\n消息: %s",
new date(event.gettimestamp()),
event.getlevel(),
event.getformattedmessage());
// 调用钉钉机器人api
sendtodingtalk(message);
}
}
}
五、总结:日志系统的“生存法则”
1.日志不是越多越好
就像吃饭不是越多越好一样,日志也要“适量”:
- debug级别:开发环境用,生产环境关掉
- info级别:记录关键业务路径
- warn级别:需要关注但不紧急的问题
- error级别:必须立即处理的问题
2.日志要“有意义”
糟糕的日志:用户操作完成 好的日志:用户[张三]于[2026-01-21 10:30:25]完成了订单[202601210001]的支付,金额[299.00]元
3.结构化日志是趋势
{
"timestamp": "2026-01-21t10:30:25.123z",
"level": "info",
"service": "user-service",
"traceid": "abc-123-def-456",
"userid": "user_001",
"action": "place_order",
"details": {
"orderid": "202601210001",
"amount": 299.00
}
}
4.性能很重要
- 使用异步日志:
asyncappender - 避免在日志中拼接大字符串
- 生产环境关掉不必要的日志级别
5.安全不能忘
- 敏感信息必须脱敏
- 日志文件要设置权限
- 生产环境日志不能包含调试信息
6.监控告警要跟上
- 错误日志实时告警
- 慢查询统计
- 接口调用量监控
六、最后
- 写日志就像写日记:不仅要记录“做了什么”,还要记录“为什么这么做”
- 日志是给“未来的你”看的:想象一下凌晨3点被报警电话叫醒,清晰的日志能让你少掉几根头发
- 日志不是万能的:关键业务逻辑该有监控还要有监控,该有告警还要有告警
- 定期review日志:就像定期体检,能发现潜在问题
一个好的日志系统就像一位可靠的“副驾驶”,在你开车的路上,它不会打扰你,但会在你需要的时候,准确地告诉你:
- “前面有坑!”(error)
- “油不多了”(warn)
- “风景不错”(info)
- “我在记录一切”(debug)
去给你的springboot应用装上这个“智能行车记录仪”吧!你的程序会感谢你,你的同事会感谢你,凌晨三点被叫醒处理问题的那个你,更会感谢你!
以上就是springboot实现日志系统的完整代码的详细内容,更多关于springboot日志系统的资料请关注代码网其它相关文章!
发表评论