SpringBoot实现日志系统的完整代码

 更新时间:2026年01月23日 16:09:03   作者:悟空码字  
这篇文章主要为大家详细介绍了SpringBoot实现日志系统的相关方法,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下

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

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

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

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日志系统的资料请关注脚本之家其它相关文章!

相关文章

  • Springboot详解整合SpringSecurity实现全过程

    Springboot详解整合SpringSecurity实现全过程

    Spring Security基于Spring开发,项目中如果使用Springboot作为基础,配合Spring Security做权限更加方便,而Shiro需要和Spring进行整合开发。因此作为spring全家桶中的Spring Security在java领域很常用
    2022-07-07
  • Java线程池配置原则与实战解析

    Java线程池配置原则与实战解析

    本文详细介绍了Java线程池的配置原则和实战建议,主要内容包括核心参数解析、任务类型配置、等待队列选择策略、行业实践参考、配置步骤和检查清单、SpringBoot配置示例以及黄金法则是,感兴趣的朋友跟随小编一起看看吧
    2025-12-12
  • SpringCloud Stream 整合RabbitMQ的基本步骤

    SpringCloud Stream 整合RabbitMQ的基本步骤

    这篇文章主要介绍了SpringCloud Stream 整合RabbitMQ的基本步骤,从项目介绍到生产者结合示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • JAVA实现DOC转PDF的示例代码

    JAVA实现DOC转PDF的示例代码

    Word作为目前主流的文本编辑软件之一,功能十分强大,但是在传输的时候不稳定,那么如何从DOC转PDF,本文就来介绍一下,感兴趣的可以了解一下
    2021-08-08
  • JAVA多线程之中断机制及处理中断的方法

    JAVA多线程之中断机制及处理中断的方法

    这篇文章主要记录使用 interrupt() 方法中断线程,以及如何对InterruptedException进行处理,感觉对InterruptedException异常进行处理是一件谨慎且有技巧的活儿,需要的朋友可以参考下
    2023-02-02
  • SpringBoot实现单文件与多文件上传

    SpringBoot实现单文件与多文件上传

    本次例子不基于第三方存储(如七牛云对象存储、阿里云对象存储、腾讯云对象存储等),仅基于本地存储。本文主要内容如下:公共文件存储代码;单文件上传代码;多文件上传代码
    2021-05-05
  • springsecurity 企业微信登入的实现示例

    springsecurity 企业微信登入的实现示例

    本文主要介绍了springsecurity 企业微信登入的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • Java软件设计模式之适配器模式详解

    Java软件设计模式之适配器模式详解

    这篇文章主要介绍了Java软件设计模式之适配器模式详解,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系,需要的朋友可以参考下
    2023-07-07
  • Java管理对象方法总结

    Java管理对象方法总结

    在本篇文章中小编给大家分享了关于Java管理对象方法以及相关代码知识点,需要的朋友们跟着学习下。
    2019-05-05
  • Java压缩解压缩工具类

    Java压缩解压缩工具类

    这篇文章主要为大家详细介绍了Java压缩解压缩工具类,如何压缩单个文件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-12-12

最新评论