news 2026/5/1 10:43:41

SpringBoot实现日志系统,代码世界的“摄像头”与“记事本”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot实现日志系统,代码世界的“摄像头”与“记事本”

大家好,我是小悟。

一、日志系统:程序员的“侦探助手”

如果你的程序突然“挂掉”了,你却不知道它死前经历了什么——这比看悬疑电影看到一半停电还难受!日志系统就是你的“侦探助手”,它悄咪咪地记录着程序的一举一动,就像:

  1. 摄像头:谁在什么时候访问了哪个接口
  2. 记事本:程序想了什么、做了什么、遇到了什么挫折
  3. 告密者:偷偷告诉你“老板,数据库又连不上了!”
  4. 时间机器:能让你穿越回错误发生的瞬间

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.监控告警要跟上

  • 错误日志实时告警
  • 慢查询统计
  • 接口调用量监控

六、最后

  1. 写日志就像写日记:不仅要记录“做了什么”,还要记录“为什么这么做”
  2. 日志是给“未来的你”看的:想象一下凌晨3点被报警电话叫醒,清晰的日志能让你少掉几根头发
  3. 日志不是万能的:关键业务逻辑该有监控还要有监控,该有告警还要有告警
  4. 定期review日志:就像定期体检,能发现潜在问题

一个好的日志系统就像一位可靠的“副驾驶”,在你开车的路上,它不会打扰你,但会在你需要的时候,准确地告诉你:

  • “前面有坑!”(ERROR)
  • “油不多了”(WARN)
  • “风景不错”(INFO)
  • “我在记录一切”(DEBUG)

去给你的SpringBoot应用装上这个“智能行车记录仪”吧!你的程序会感谢你,你的同事会感谢你,凌晨三点被叫醒处理问题的那个你,更会感谢你!

谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。

您的一键三连,是我更新的最大动力,谢谢

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/1 5:19:56

Qwen图像生成器商业变现路径:儿童IP衍生品开发实战案例

Qwen图像生成器商业变现路径&#xff1a;儿童IP衍生品开发实战案例 1. 从一张小熊图开始的生意机会 你有没有想过&#xff0c;一个看起来简单的“毛茸茸小熊穿背带裤”提示词&#xff0c;背后可能是一整条儿童IP衍生品的起跑线&#xff1f; 这不是概念演示&#xff0c;而是真…

作者头像 李华
网站建设 2026/5/1 5:19:19

Qwen-Image-Layered部署踩坑总结,少走弯路

Qwen-Image-Layered部署踩坑总结&#xff0c;少走弯路 你是不是也和我一样&#xff0c;看到 Qwen-Image-Layered 能一键把图片拆成多个可编辑的图层&#xff0c;瞬间就想试试&#xff1f;尤其是它支持对每个RGBA图层独立操作——换颜色、删元素、调大小、移动位置&#xff0c;…

作者头像 李华
网站建设 2026/5/1 5:20:05

智能垃圾桶(语音版)(有完整资料)

资料查找方式&#xff1a; 特纳斯电子&#xff08;电子校园网&#xff09;&#xff1a;搜索下面编号即可 编号&#xff1a; CJ-51-2021-037 设计简介&#xff1a; 本设计是基于单片机的垃圾桶系统&#xff0c;主要实现以下功能&#xff1a; 可实现通过步进电机完成垃圾桶盖…

作者头像 李华
网站建设 2026/5/1 6:27:17

轻松掌握核心技能:大模型微调入门实战课程推荐

轻松掌握核心技能&#xff1a;大模型微调入门实战课程推荐 1. 为什么你该学大模型微调&#xff1f; 你是不是也经常看到“微调一个大模型”这样的说法&#xff0c;觉得门槛很高、需要一堆GPU、还得懂深度学习&#xff1f;其实&#xff0c;随着工具链的成熟&#xff0c;现在用…

作者头像 李华
网站建设 2026/5/1 9:56:10

24小时开发:SOLIDWORKS清理工具原型验证

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 在24小时内开发一个SOLIDWORKS清理工具最小可行产品(MVP)&#xff0c;包含&#xff1a;1. 基本文件扫描功能&#xff1b;2. 关键注册表项检测&#xff1b;3. 安全清理模块&#xf…

作者头像 李华