SpringBoot实现日志系统,Bug现形记

大家好,我是小悟。

日志系统是什么?

想象一下,你的程序是个有点健忘的程序员同事(没错,就是那个总说"我本地是好的"的家伙)。日志系统就是给他配的贴身小秘书,每天拿着小本本记录:

  • 🕒 几点几分干了啥(时间戳)
  • 🤔 心里想什么(调试信息)
  • 😊 今天工作顺利吗(INFO信息)
  • 😨 卧槽出问题了(ERROR信息)
  • 🔥 救火啊要炸了(FATAL信息)

没有日志系统?那就好比程序生病了,你问他"哪不舒服?",他只会回答"我挂了"。有了日志,他就能详细告诉你:"昨天下午3点,我在处理用户订单时,因为数据库连接断了,导致..."

好了,废话不多说,让我们给SpringBoot程序配个"贴心小秘书"!


第1步:SpringBoot的"天生丽质"

SpringBoot这家伙很贴心,已经内置了日志系统!就像你买手机,相机APP已经预装好了。

kotlin 复制代码
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 {
    
    // 创建日志记录器,就像给这个类配了个专属记者
    private static final Logger logger = LoggerFactory.getLogger(LogDemoController.class);
    
    @GetMapping("/hello")
    public String sayHello() {
        // 不同的日志级别,就像不同的说话语气
        logger.trace("这是最详细的跟踪信息 - 连我呼吸都要记录");
        logger.debug("调试信息 - 我在想:用户到底点了啥按钮?");
        logger.info("普通信息 - 用户访问了hello接口,一切正常");
        logger.warn("警告信息 - 内存有点高,像吃了太多内存的胖子");
        logger.error("错误信息 - 数据库连接失败!快来人啊!");
        
        // 还可以带参数,像填空一样
        String userName = "码农小张";
        int userId = 123;
        logger.info("用户 {} (ID: {}) 登录成功", userName, userId);
        
        return "Hello World! 快去控制台看日志吧!";
    }
    
    @GetMapping("/oops")
    public String makeMistake() {
        try {
            // 故意制造一个错误
            int result = 10 / 0;
            return "这行代码永远执行不到";
        } catch (Exception e) {
            // 记录异常信息,参数e会自动打印堆栈
            logger.error("数学老师没教好,除零错误了!", e);
            return "哎呀,出错了!详情请看日志";
        }
    }
}

第2步:配置文件 - 给秘书定规矩

application.yml(或application.properties)中配置。这是告诉小秘书:"哪些话要记,哪些话不用记,记在哪里..."

yaml 复制代码
# application.yml - 日志系统的"规章制度"

# 第一部分:全局日志级别设置
logging:
  level:
    root: INFO  # 根日志级别:INFO及以上才记录
    com.example.demo: DEBUG  # 我们自己的包可以详细点
    org.springframework.web: WARN  # Spring的web包,只记录警告及以上
    
  # 第二部分:输出到哪里(控制台和文件都记)
  file:
    name: logs/myapp.log  # 日志文件路径
    max-size: 10MB  # 单个文件最大10MB,超过就切分
    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"
    
  # 第四部分:按包或类单独设置(精细化管理)
  group:
    web: org.springframework.web, org.springframework.security
    app: com.example.demo.controller, com.example.demo.service
    
  level:
    web: INFO
    app: DEBUG

# 如果你想用logback的详细配置(高级玩法)
# 在resources目录下创建logback-spring.xml

第3步:高级玩法 - 自定义日志配置

resources目录下创建logback-spring.xml,这是给小秘书的详细工作手册

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    
    <!-- 控制台输出 - 给开发人员看的 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 彩色日志,让控制台不再单调! -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan(%logger{36}) - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    
    <!-- 按天滚动的文件输出 - 给运维人员看的 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/application.log</file>
        <!-- 每天一个文件,最多保存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>
    
    <!-- 错误日志单独文件 - 重要的事情说三遍,重要的错误单独记 -->
    <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>  <!-- 错误日志保留更久 -->
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- 不同环境的配置 -->
    <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实现方法日志 - 自动记录每个方法

ini 复制代码
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);
    
    /**
     * 自动记录Controller层每个方法的执行情况
     * 就像给每个方法配了个贴身观察员
     */
    @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("方法开始执行: {},参数: {}", methodName, args);
        
        try {
            Object result = joinPoint.proceed();
            long executionTime = System.currentTimeMillis() - startTime;
            
            logger.info("方法执行成功: {},耗时: {}ms,返回值: {}", 
                       methodName, executionTime, result);
            return result;
            
        } catch (Exception e) {
            long executionTime = System.currentTimeMillis() - startTime;
            logger.error("方法执行失败: {},耗时: {}ms,异常: {}", 
                        methodName, executionTime, e.getMessage(), e);
            throw e;
        }
    }
}

第5步:日志工具类 - 让日志更智能

typescript 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StopWatch;

/**
 * 日志工具类 - 给日志加点"智能"
 */
public class LogUtil {
    
    /**
     * 性能监控 - 记录代码块执行时间
     */
    public static <T> T monitorPerformance(Logger logger, String taskName, 
                                          Supplier<T> task) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        
        logger.info("开始执行: {}", taskName);
        
        try {
            T result = task.get();
            stopWatch.stop();
            
            logger.info("执行完成: {},耗时: {}ms", 
                       taskName, stopWatch.getTotalTimeMillis());
            return result;
            
        } catch (Exception e) {
            stopWatch.stop();
            logger.error("执行失败: {},耗时: {}ms,错误: {}", 
                        taskName, stopWatch.getTotalTimeMillis(), e.getMessage(), e);
            throw e;
        }
    }
    
    /**
     * 业务日志 - 记录关键业务操作
     */
    public static void businessLog(Logger logger, String operation, 
                                  String userId, Object... details) {
        // 这里可以扩展,比如记录到数据库或发送到消息队列
        logger.info("业务操作 - 用户: {}, 操作: {}, 详情: {}", 
                   userId, operation, details);
    }
    
    // 使用示例
    public void exampleUsage() {
        Logger logger = LoggerFactory.getLogger(this.getClass());
        
        // 监控性能
        String result = monitorPerformance(logger, "计算用户报表", () -> {
            // 模拟耗时操作
            Thread.sleep(1000);
            return "报表数据";
        });
        
        // 记录业务日志
        businessLog(logger, "用户登录", "user123", "IP: 192.168.1.1", "设备: Chrome");
    }
}

第6步:与ELK等日志系统集成(高级玩法)

makefile 复制代码
# application-prod.yml - 生产环境配置
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

总结:日志系统的"生存指南"

经过这一番折腾,我们的SpringBoot程序终于有了一个称职的贴身秘书。让我们总结一下日志系统的几个关键点:

为什么要用日志系统?

  1. 故障排查:程序说"我挂了" → 日志说"3点15分数据库连接超时"
  2. 性能分析:用户说"好卡" → 日志说"这个接口平均响应2.3秒"
  3. 行为追踪:老板说"谁干的" → 日志说"用户admin在10点修改了配置"
  4. 数据统计:产品说"有多少人用" → 日志说"今天有15234次访问"

最佳实践:

  1. 级别要分明:DEBUG用于开发,INFO用于日常,ERROR用于异常
  2. 信息要详细:时间、线程、级别、类名、消息、异常,一个都不能少
  3. 性能要注意:日志IO是性能杀手,异步日志是个好选择
  4. 安全要牢记:密码、token等敏感信息别往日志里写

常见坑点:

  • 日志太多:把DEBUG级别放到生产环境,日志文件瞬间爆炸
  • 日志太少:出问题时,日志里只有"出错了",没有"为啥错"
  • 格式混乱:今天用JSON,明天用文本,后天ELK不认了
  • 忘记归档:日志文件把磁盘写满了,程序真挂了

最后:

给你的日志系统起个名字吧!比如叫"小日志"、"程序记录仪"、"代码摄像头"。毕竟,它要陪你度过无数个排查BUG的不眠之夜 ,是你在茫茫代码海洋中的灯塔 ,是你在程序崩溃时的救命稻草

现在,去给你的SpringBoot程序配个"贴心小秘书"吧!当程序再次崩溃时,至少有人(日志)能告诉你:"亲,这次是因为..."

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

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

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

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

相关推荐
iナナ1 小时前
Java自定义协议的发布订阅式消息队列(二)
java·开发语言·jvm·学习·spring·消息队列
狂奔小菜鸡1 小时前
Day24 | Java泛型通配符与边界解析
java·后端·java ee
用户68545375977691 小时前
为什么你的Python代码那么乱?因为你不会用装饰器
后端
xjz18421 小时前
ThreadPoolExecutor线程回收流程详解
后端
天天摸鱼的java工程师1 小时前
🐇RabbitMQ 从入门到业务实战:一个 Java 程序员的实战手记
java·后端
ZHang......1 小时前
JDBC 笔记
java·笔记
uup1 小时前
多线程下线程安全的单例模式实现缺陷
java
橙序员小站1 小时前
Java 接入Pinecone搭建知识库踩坑实记
java·开发语言·人工智能