news 2026/4/16 14:39:18

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/4/13 21:42:15

NewBie-image-Exp0.1 vs Waifu Diffusion:GPU算力适配性全面对比

NewBie-image-Exp0.1 vs Waifu Diffusion&#xff1a;GPU算力适配性全面对比 在当前AI图像生成领域&#xff0c;动漫风格模型因其高度风格化和社区活跃度&#xff0c;成为许多创作者与研究者的首选。NewBie-image-Exp0.1 和 Waifu Diffusion 是两个备受关注的开源项目&#xf…

作者头像 李华
网站建设 2026/4/12 8:47:23

Open-AutoGLM网络切换自动化:WiFi/数据切换执行部署

Open-AutoGLM网络切换自动化&#xff1a;WiFi/数据切换执行部署 1. 什么是Open-AutoGLM&#xff1f;手机端AI Agent的轻量落地新范式 Open-AutoGLM 是智谱开源的一套面向移动端的 AI Agent 框架&#xff0c;它不是简单地把大模型“搬”到手机上跑&#xff0c;而是围绕“真实设…

作者头像 李华
网站建设 2026/4/16 12:39:43

Llama3-8B能源行业应用:设备维护知识库实战案例

Llama3-8B能源行业应用&#xff1a;设备维护知识库实战案例 1. 为什么选Llama3-8B做能源设备知识库&#xff1f; 在能源行业&#xff0c;变电站巡检记录、风电机组故障日志、燃气管道维保报告这些文档往往分散在不同系统里&#xff0c;一线工程师查个“SF6断路器漏气处理步骤…

作者头像 李华
网站建设 2026/4/16 7:48:37

JavaScript窗口管理入门:从零实现安全关闭

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个简单的教学示例&#xff0c;演示基本的窗口管理&#xff1a;1. 添加按钮打开3个不同URL的窗口 2. 将这些窗口引用存储在数组中 3. 添加关闭按钮只关闭这3个窗口 4. 添加错…

作者头像 李华
网站建设 2026/4/16 12:44:25

医院管理系统ER图实战案例解析

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 设计一个医院管理系统的ER图&#xff0c;包含患者挂号、医生排班、药品库存、检查项目等核心功能。患者信息包括基本信息、病历记录&#xff1b;医生信息包含职称、专长&#xff1…

作者头像 李华
网站建设 2026/4/13 16:35:23

电池管理系统(有完整资料)

资料查找方式&#xff1a; 特纳斯电子&#xff08;电子校园网&#xff09;&#xff1a;搜索下面编号即可 编号&#xff1a; CJ-51-2021-038 设计简介&#xff1a; 本设计是基于单片机的电池管理系统&#xff0c;主要实现以下功能&#xff1a; 可通过LCD1602显示温度、电压电…

作者头像 李华