Spring统一日志输出实现指南
ClearSky Drizzle Lv4

概述

本文档详细记录了如何在Spring Boot项目中实现统一的日志输出系统,包括标准化日志格式、分环境配置、日志分类存储、接口访问日志和业务日志记录。

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
src/main/java/com/yourpackage/
├── util/
│ └── LogUtil.java # 统一日志工具类
├── interceptor/
│ └── LogInterceptor.java # 请求日志拦截器
├── aspect/
│ └── LogAspect.java # 日志切面
├── config/
│ └── LogConfig.java # 日志配置
├── annotation/
│ └── LogBusiness.java # 业务日志注解
└── controller/
└── LogDemoController.java # 使用示例

src/main/resources/
├── logback-spring.xml # 日志配置文件
└── application.yml # 环境配置

实现步骤

1. 添加依赖

pom.xml 中添加必要依赖:

1
2
3
4
5
6
7
8
9
10
11
<!-- AOP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>

2. 创建日志配置文件

文件路径: src/main/resources/logback-spring.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 定义日志文件的存储地址 -->
<property name="LOG_HOME" value="logs"/>
<property name="APP_NAME" value="YourAppName"/>

<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>

<!-- 主日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxHistory>30</MaxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>

<!-- 错误日志文件 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}_error.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/${APP_NAME}_error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxHistory>30</MaxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>

<!-- 业务日志文件 -->
<appender name="BUSINESS_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}_business.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/${APP_NAME}_business.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxHistory>30</MaxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>

<!-- SQL日志文件 -->
<appender name="SQL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}_sql.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/${APP_NAME}_sql.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxHistory>30</MaxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>

<!-- 开发环境 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>

<logger name="org.mybatis" level="DEBUG" additivity="false">
<appender-ref ref="SQL_FILE"/>
<appender-ref ref="STDOUT"/>
</logger>

<logger name="java.sql" level="DEBUG" additivity="false">
<appender-ref ref="SQL_FILE"/>
<appender-ref ref="STDOUT"/>
</logger>

<logger name="com.yourpackage" level="DEBUG" additivity="false">
<appender-ref ref="BUSINESS_FILE"/>
<appender-ref ref="STDOUT"/>
</logger>
</springProfile>

<!-- 测试环境 -->
<springProfile name="test">
<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>

<logger name="com.yourpackage" level="INFO" additivity="false">
<appender-ref ref="BUSINESS_FILE"/>
</logger>
</springProfile>

<!-- 生产环境 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>

<logger name="com.yourpackage" level="INFO" additivity="false">
<appender-ref ref="BUSINESS_FILE"/>
</logger>
</springProfile>
</configuration>

3. 更新application.yml配置

文件路径: src/main/resources/application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
profiles:
active: dev # 默认使用开发环境

# 日志配置
logging:
level:
com.yourpackage: DEBUG
org.mybatis: DEBUG
java.sql: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n"

4. 创建统一日志工具类

文件路径: src/main/java/com/yourpackage/util/LogUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package com.yourpackage.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
* 统一日志工具类
*/
public class LogUtil {

private static final ObjectMapper objectMapper = new ObjectMapper();

/**
* 获取Logger实例
*/
public static Logger getLogger(Class<?> clazz) {
return LoggerFactory.getLogger(clazz);
}

/**
* 设置请求追踪ID
*/
public static void setTraceId() {
String traceId = UUID.randomUUID().toString().replace("-", "");
MDC.put("traceId", traceId);
}

/**
* 获取请求追踪ID
*/
public static String getTraceId() {
return MDC.get("traceId");
}

/**
* 清除请求追踪ID
*/
public static void clearTraceId() {
MDC.clear();
}

/**
* 记录业务日志
*/
public static void logBusiness(Logger logger, String operation, Object request, Object response) {
try {
String logMessage = String.format(
"[业务日志] 操作: %s, 请求: %s, 响应: %s",
operation,
objectMapper.writeValueAsString(request),
objectMapper.writeValueAsString(response)
);
logger.info(logMessage);
} catch (Exception e) {
logger.error("记录业务日志失败", e);
}
}

/**
* 记录接口访问日志
*/
public static void logAccess(Logger logger, String method, String uri, String ip,
Object params, Object result, long duration) {
try {
String logMessage = String.format(
"[接口访问] %s %s, IP: %s, 参数: %s, 耗时: %dms, 结果: %s",
method, uri, ip,
objectMapper.writeValueAsString(params),
duration,
objectMapper.writeValueAsString(result)
);
logger.info(logMessage);
} catch (Exception e) {
logger.error("记录接口访问日志失败", e);
}
}

/**
* 记录异常日志
*/
public static void logException(Logger logger, String operation, Throwable throwable, Object... params) {
try {
String logMessage = String.format(
"[异常日志] 操作: %s, 参数: %s, 异常: %s",
operation,
params.length > 0 ? objectMapper.writeValueAsString(params) : "无",
throwable.getMessage()
);
logger.error(logMessage, throwable);
} catch (Exception e) {
logger.error("记录异常日志失败", e);
}
}

/**
* 记录性能日志
*/
public static void logPerformance(Logger logger, String operation, long duration, Object... params) {
try {
String logMessage = String.format(
"[性能日志] 操作: %s, 耗时: %dms, 参数: %s",
operation, duration,
params.length > 0 ? objectMapper.writeValueAsString(params) : "无"
);
logger.info(logMessage);
} catch (Exception e) {
logger.error("记录性能日志失败", e);
}
}

/**
* 记录调试日志
*/
public static void logDebug(Logger logger, String operation, Object... params) {
if (logger.isDebugEnabled()) {
try {
String logMessage = String.format(
"[调试日志] 操作: %s, 参数: %s",
operation,
params.length > 0 ? objectMapper.writeValueAsString(params) : "无"
);
logger.debug(logMessage);
} catch (Exception e) {
logger.error("记录调试日志失败", e);
}
}
}

/**
* 记录SQL日志
*/
public static void logSql(Logger logger, String sql, Object... params) {
try {
String logMessage = String.format(
"[SQL日志] SQL: %s, 参数: %s",
sql,
params.length > 0 ? objectMapper.writeValueAsString(params) : "无"
);
logger.debug(logMessage);
} catch (Exception e) {
logger.error("记录SQL日志失败", e);
}
}

/**
* 记录用户操作日志
*/
public static void logUserAction(Logger logger, String userId, String action, Object details) {
try {
String logMessage = String.format(
"[用户操作] 用户ID: %s, 操作: %s, 详情: %s",
userId, action,
objectMapper.writeValueAsString(details)
);
logger.info(logMessage);
} catch (Exception e) {
logger.error("记录用户操作日志失败", e);
}
}

/**
* 记录安全日志
*/
public static void logSecurity(Logger logger, String userId, String action, String ip, Object details) {
try {
String logMessage = String.format(
"[安全日志] 用户ID: %s, 操作: %s, IP: %s, 详情: %s",
userId, action, ip,
objectMapper.writeValueAsString(details)
);
logger.warn(logMessage);
} catch (Exception e) {
logger.error("记录安全日志失败", e);
}
}

/**
* 构建日志上下文
*/
public static Map<String, Object> buildContext(String key, Object value) {
Map<String, Object> context = new HashMap<>();
context.put(key, value);
return context;
}
}

5. 创建日志拦截器

文件路径: src/main/java/com/yourpackage/interceptor/LogInterceptor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.yourpackage.interceptor;

import com.yourpackage.util.LogUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class LogInterceptor implements HandlerInterceptor {

private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
private static final String START_TIME_ATTR = "requestStartTime";

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
LogUtil.setTraceId();
request.setAttribute(START_TIME_ATTR, System.currentTimeMillis());

Map<String, Object> context = LogUtil.buildContext("uri", request.getRequestURI());
context.put("method", request.getMethod());
context.put("ip", getClientIpAddress(request));
context.put("params", request.getParameterMap());

LogUtil.logDebug(logger, "请求开始", context);
return true;
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
Long startTime = (Long) request.getAttribute(START_TIME_ATTR);
long duration = System.currentTimeMillis() - (startTime != null ? startTime : System.currentTimeMillis());

LogUtil.logAccess(logger,
request.getMethod(),
request.getRequestURI(),
getClientIpAddress(request),
request.getParameterMap(),
Map.of("status", response.getStatus()),
duration);

if (ex != null) {
LogUtil.logException(logger, request.getRequestURI() + " 请求异常", ex);
}

LogUtil.clearTraceId();
}

private String getClientIpAddress(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}

String xRealIp = request.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty()) {
return xRealIp;
}

return request.getRemoteAddr();
}
}

6. 创建日志配置类

文件路径: src/main/java/com/yourpackage/config/LogConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.yourpackage.config;

import com.yourpackage.interceptor.LogInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class LogConfig implements WebMvcConfigurer {

@Autowired
private LogInterceptor logInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/swagger-ui/**",
"/swagger-resources/**",
"/v3/api-docs/**",
"/webjars/**",
"/actuator/**",
"/error"
);
}
}

7. 创建日志切面

文件路径: src/main/java/com/yourpackage/aspect/LogAspect.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.yourpackage.aspect;

import com.yourpackage.annotation.LogBusiness;
import com.yourpackage.util.LogUtil;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogAspect {

private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

@Pointcut("within(@org.springframework.stereotype.Controller *) || " +
"within(@org.springframework.web.bind.annotation.RestController *) || " +
"within(@org.springframework.stereotype.Service *) || " +
"within(@org.springframework.stereotype.Repository *)")
public void applicationPackagePointcut() {}

@Around("applicationPackagePointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
String operation = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();

long startTime = System.currentTimeMillis();
Object result = null;
Exception thrownException = null;

try {
LogUtil.logDebug(logger, "方法调用开始", operation, joinPoint.getArgs());
result = joinPoint.proceed();
return result;
} catch (Exception e) {
thrownException = e;
throw e;
} finally {
long duration = System.currentTimeMillis() - startTime;

if (thrownException != null) {
LogUtil.logException(logger, operation, thrownException, joinPoint.getArgs());
} else {
LogUtil.logPerformance(logger, operation, duration, joinPoint.getArgs(), result);
}
}
}

@Pointcut("@annotation(com.yourpackage.annotation.LogBusiness)")
public void logBusinessPointcut() {}

@Around("logBusinessPointcut()")
public Object logBusinessAround(ProceedingJoinPoint joinPoint) throws Throwable {
String operation = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();

Object result = null;
Exception thrownException = null;

try {
result = joinPoint.proceed();
return result;
} catch (Exception e) {
thrownException = e;
throw e;
} finally {
if (thrownException == null) {
LogUtil.logBusiness(logger, operation, joinPoint.getArgs(), result);
}
}
}
}

8. 创建业务日志注解

文件路径: src/main/java/com/yourpackage/annotation/LogBusiness.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.yourpackage.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogBusiness {
String value() default "";
boolean logParams() default true;
boolean logResult() default true;
}

9. 使用示例

文件路径: src/main/java/com/yourpackage/controller/LogDemoController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.yourpackage.controller;

import com.yourpackage.annotation.LogBusiness;
import com.yourpackage.util.LogUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/log-demo")
public class LogDemoController {

private static final Logger logger = LoggerFactory.getLogger(LogDemoController.class);

@GetMapping("/test")
public Map<String, Object> testLog(@RequestParam(defaultValue = "test") String name) {
Map<String, Object> params = new HashMap<>();
params.put("name", name);
LogUtil.logDebug(logger, "测试日志记录", params);

Map<String, Object> result = new HashMap<>();
result.put("message", "Hello " + name);
result.put("timestamp", System.currentTimeMillis());

LogUtil.logBusiness(logger, "测试业务操作", params, result);
return result;
}

@GetMapping("/business")
@LogBusiness("用户查询操作")
public Map<String, Object> testBusinessLog(@RequestParam String userId) {
Map<String, Object> result = new HashMap<>();
result.put("userId", userId);
result.put("username", "张三");
return result;
}

@GetMapping("/user-action")
public Map<String, Object> testUserAction(@RequestParam String userId, @RequestParam String action) {
Map<String, Object> details = new HashMap<>();
details.put("action", action);
details.put("time", System.currentTimeMillis());

LogUtil.logUserAction(logger, userId, action, details);
LogUtil.logSecurity(logger, userId, action, "127.0.0.1", details);

Map<String, Object> result = new HashMap<>();
result.put("status", "logged");
return result;
}
}

日志文件说明

日志文件分类

  • 主日志: logs/YourAppName.log - 应用主要日志
  • 错误日志: logs/YourAppName_error.log - 仅包含ERROR级别日志
  • 业务日志: logs/YourAppName_business.log - 业务操作日志
  • SQL日志: logs/YourAppName_sql.log - 数据库操作日志
  • 访问日志: logs/YourAppName_access.log - 接口访问日志(自动生成)

日志格式

1
2024-01-15 10:30:45.123 [http-nio-8080-exec-1] INFO  [c.y.c.LogDemoController] - [接口访问] GET /api/test, IP: 127.0.0.1, 参数: {"id":1}, 耗时: 150ms, 结果: {"success":true}

使用规范

1. 日志级别规范

  • DEBUG: 调试信息,仅在开发环境使用
  • INFO: 业务操作、接口访问、性能统计
  • WARN: 业务警告、安全相关操作
  • ERROR: 系统异常、业务异常

2. 日志记录场景

场景类型 记录方式 日志级别
接口访问 自动记录 INFO
业务操作 @LogBusiness注解 INFO
异常处理 LogUtil.logException ERROR
性能统计 LogUtil.logPerformance INFO
用户操作 LogUtil.logUserAction INFO
安全操作 LogUtil.logSecurity WARN

3. 使用方法

基础日志记录

1
2
3
4
5
6
7
8
9
10
11
// 获取logger
Logger logger = LogUtil.getLogger(YourClass.class);

// 记录业务日志
LogUtil.logBusiness(logger, "用户登录", loginRequest, loginResponse);

// 记录异常日志
LogUtil.logException(logger, "订单处理", exception, orderId, userId);

// 记录性能日志
LogUtil.logPerformance(logger, "数据库查询", duration, queryParams);

注解方式记录

1
2
3
4
5
6
7
8
9
@Service
public class UserService {

@LogBusiness("用户注册")
public User registerUser(UserRegisterRequest request) {
// 方法执行后会自动记录业务日志
return user;
}
}

请求追踪

1
2
3
4
// 在拦截器中自动设置
LogUtil.setTraceId(); // 设置追踪ID
String traceId = LogUtil.getTraceId(); // 获取追踪ID
LogUtil.clearTraceId(); // 清理追踪ID

环境配置

开发环境 (dev)

  • 日志级别: DEBUG
  • 输出: 控制台 + 所有文件
  • SQL日志: 开启

测试环境 (test)

  • 日志级别: INFO
  • 输出: 文件输出
  • SQL日志: 关闭

生产环境 (prod)

  • 日志级别: INFO
  • 输出: 文件输出
  • SQL日志: 关闭

测试验证

启动项目后,可以通过以下接口测试:

  • GET /log-demo/test?name=test - 测试基础日志
  • GET /log-demo/business?userId=123 - 测试业务日志注解
  • GET /log-demo/user-action?userId=123&action=login - 测试用户操作日志

注意事项

  1. 日志文件大小: 单个日志文件最大100MB,保留30天
  2. 敏感信息: 避免在日志中记录密码、token等敏感信息
  3. 性能影响: 生产环境避免记录过多DEBUG日志
  4. JSON序列化: 复杂对象确保有默认构造函数
  5. 追踪ID: 所有日志自动包含追踪ID,便于链路追踪

扩展建议

  1. ELK集成: 可接入ELK进行日志分析和可视化
  2. 异步日志: 高并发场景可使用异步日志提升性能
  3. 日志脱敏: 对敏感信息进行脱敏处理
  4. 链路追踪: 集成Zipkin或SkyWalking进行分布式追踪
 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
This site is deployed on
Unique Visitor Page View