diff --git a/docs/LOGBACK_CONFIG.md b/docs/LOGBACK_CONFIG.md new file mode 100644 index 0000000..4bf629f --- /dev/null +++ b/docs/LOGBACK_CONFIG.md @@ -0,0 +1,245 @@ +# Logback 日志配置说明 + +## 配置位置 + +- **配置文件**: `reading-platform-java/src/main/resources/logback-spring.xml` +- **日志目录**: `logs/`(应用启动时自动创建) + +--- + +## 日志滚动策略 + +### 保留策略 +| 配置项 | 值 | 说明 | +|--------|-----|------| +| 保留天数 | 30 天 | 自动删除 30 天前的日志 | +| 单文件大小 | 100MB | 单个日志文件最大 100MB | +| 总大小上限 | 2GB | 所有日志文件总计不超过 2GB | + +### 滚动规则 +1. **按天滚动**: 每天生成一个新的日志文件 +2. **按大小滚动**: 当单个文件超过 100MB 时自动切分 +3. **自动清理**: 超过 30 天的日志自动删除 +4. **总量控制**: 当日志总量达到 2GB 时,删除最旧的日志 + +--- + +## 日志文件分类 + +| 日志类型 | 文件名 | 说明 | 大小上限 | +|---------|--------|------|---------| +| 主日志 | `reading-platform.log` | 业务日志、系统日志 | 2GB | +| 错误日志 | `reading-platform-error.log` | 仅记录 ERROR 级别日志 | 500MB | +| 请求日志 | `reading-platform-request.log` | 所有 API 请求日志 | 500MB | +| SQL 日志 | `reading-platform-sql.log` | 仅记录慢 SQL 和错误 SQL | 300MB | + +### 日志文件命名示例 + +``` +logs/ +├── reading-platform.log # 当前主日志 +├── reading-platform.2026-03-26.1.log # 按天滚动 +├── reading-platform.2026-03-26.2.log # 按大小滚动 +├── reading-platform-error.log # 当前错误日志 +├── reading-platform-error.2026-03-26.1.log +├── reading-platform-request.log # 当前请求日志 +├── reading-platform-request.2026-03-26.1.log +└── reading-platform-sql.log # 当前 SQL 日志 +``` + +--- + +## 日志级别配置 + +### 生产环境 (prod) + +| 日志类别 | 日志级别 | 输出目标 | 说明 | +|---------|---------|---------|------| +| 根日志 | INFO | FILE, ERROR_FILE | 基础日志级别 | +| 业务日志 | INFO | FILE | `com.reading.platform` 包 | +| **请求日志** | **INFO** | **REQUEST_FILE** | **所有 API 请求单独输出** | +| **SQL 日志** | **WARN** | **SQL_FILE** | **仅记录慢 SQL 和错误 SQL** | +| Spring 框架 | WARN | - | 框架日志 | + +### 开发环境 (dev) + +| 日志类别 | 日志级别 | 说明 | +|---------|---------|------| +| 业务日志 | DEBUG | 详细业务日志 | +| 请求日志 | INFO | 所有 API 请求单独输出 | +| SQL 日志 | DEBUG | 全量 SQL 日志 | + +--- + +## 请求日志格式 + +每个 API 请求会记录以下信息: + +``` +===== 请求开始 ===== +接口地址:POST /api/v1/teacher/courses +请求方法:com.reading.platform.controller.teacher.CourseController.createCourse +请求参数:{"name":"新课程","gradeId":1} +响应时间:156ms +响应结果:{"code":"SUCCESS","data":123} +===== 请求结束 ===== +``` + +异常请求: + +``` +===== 请求异常 ===== +接口地址:POST /api/v1/teacher/courses +执行时间:45ms +异常信息:参数校验失败 +异常类型:com.reading.platform.common.exception.BusinessException +堆栈信息:... +===== 请求异常结束 ===== +``` + +--- + +## SQL 日志策略 + +### 生产环境(推荐) + +**只记录慢 SQL 和错误 SQL**,通过设置 `WARN` 级别实现: +- `DEBUG` 级别:所有 SQL(开发环境) +- `INFO` 级别:SQL 摘要(测试环境) +- `WARN` 级别:**仅慢 SQL 和错误 SQL**(生产环境) +- `ERROR` 级别:仅错误 SQL + +### 慢 SQL 配置(可选) + +如需记录执行时间超过指定阈值的 SQL,可在 MyBatis-Plus 配置中设置: + +```yaml +# application-prod.yml +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl +``` + +--- + +## 常用命令 + +### 查看实时请求日志 +```bash +tail -f logs/reading-platform-request.log +``` + +### 查看实时错误日志 +```bash +tail -f logs/reading-platform-error.log +``` + +### 查看实时 SQL 日志 +```bash +tail -f logs/reading-platform-sql.log +``` + +### 查看指定日期的日志 +```bash +cat logs/reading-platform-request.2026-03-26.1.log +``` + +### 搜索特定接口日志 +```bash +grep "/api/v1/teacher/courses" logs/reading-platform-request.*.log +``` + +### 搜索错误日志 +```bash +grep "ERROR" logs/reading-platform-error.log +``` + +### 查看日志目录大小 +```bash +du -sh logs/ +``` + +### 手动清理日志(可选) +```bash +# 删除 7 天前的日志 +find logs/ -name "*.log" -mtime +7 -delete +``` + +--- + +## 配置修改 + +### 调整日志保留天数 + +修改 `logback-spring.xml`: +```xml + +``` + +### 调整单个文件大小限制 + +```xml + +``` + +### 调整总日志大小上限 + +```xml + +``` + +### 修改后重启服务 + +```bash +cd /www/wwwroot/reading-platform +./stop.sh +./start.sh +``` + +--- + +## 环境切换 + +### 开发环境 +```bash +export SPRING_PROFILES_ACTIVE=dev +java -jar reading-platform.jar +``` + +### 生产环境 +```bash +export SPRING_PROFILES_ACTIVE=prod +java -jar reading-platform.jar +``` + +--- + +## 注意事项 + +1. **请求日志独立输出**: 所有 API 请求日志输出到 `request.log`,不影响主日志 +2. **SQL 日志精简**: 生产环境仅记录 WARN 和 ERROR 级别 SQL,避免日志爆炸 +3. **错误日志单独记录**: 所有 ERROR 级别日志输出到 `error.log`,便于问题排查 +4. **日志目录权限**: 确保 `logs` 目录有写权限(chmod 755) +5. **磁盘空间监控**: 建议设置磁盘告警,当日志目录接近总上限时收到通知 + +--- + +## 故障排查 + +### 请求日志不输出 +- 检查 `RequestLogAspect` 切面是否生效 +- 确认日志级别配置为 `INFO` +- 检查 `additivity="false"` 是否配置 + +### SQL 日志不输出 +- 生产环境只记录 WARN 和 ERROR 级别(慢 SQL 和错误 SQL) +- 如需查看全量 SQL,临时改为 `DEBUG` 级别 + +### 日志文件丢失 +- 检查是否达到 `maxHistory`(30 天) +- 检查是否达到 `totalSizeCap`(总大小上限) + +--- + +**配置日期**: 2026-03-26 +**最后更新**: 2026-03-26 diff --git a/reading-platform-frontend/.DS_Store b/reading-platform-frontend/.DS_Store deleted file mode 100644 index 7722f3b..0000000 Binary files a/reading-platform-frontend/.DS_Store and /dev/null differ diff --git a/reading-platform-frontend/src/.DS_Store b/reading-platform-frontend/src/.DS_Store deleted file mode 100644 index 7d9f7df..0000000 Binary files a/reading-platform-frontend/src/.DS_Store and /dev/null differ diff --git a/reading-platform-java/src/main/java/com/reading/platform/common/aspect/RequestLogAspect.java b/reading-platform-java/src/main/java/com/reading/platform/common/aspect/RequestLogAspect.java new file mode 100644 index 0000000..3536615 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/common/aspect/RequestLogAspect.java @@ -0,0 +1,103 @@ +package com.reading.platform.common.aspect; + +import com.alibaba.fastjson2.JSON; +import jakarta.servlet.http.HttpServletRequest; +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.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.lang.reflect.Method; +import java.util.Objects; + +/** + * 请求日志切面 + * 记录所有 Controller 层的请求信息 + */ +@Slf4j +@Aspect +@Component +public class RequestLogAspect { + + /** + * 定义切点:Controller 层所有方法 + */ + @Pointcut("execution(* com.reading.platform.controller..*.*(..))") + public void controllerPointcut() { + } + + /** + * 环绕通知:记录请求日志 + */ + @Around("controllerPointcut()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + // 获取请求信息 + ServletRequestAttributes attributes = + (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); + + // 获取方法签名 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + + // 记录请求开始 + long startTime = System.currentTimeMillis(); + String requestURI = request.getRequestURI(); + String methodType = request.getMethod(); + String className = joinPoint.getTarget().getClass().getName(); + String methodName = method.getName(); + String params = JSON.toJSONString(getRequestParams(joinPoint)); + + log.info("===== 请求开始 ====="); + log.info("接口地址:{} {}", methodType, requestURI); + log.info("请求方法:{}.{}", className, methodName); + log.info("请求参数:{}", params); + + try { + // 执行目标方法 + Object result = joinPoint.proceed(); + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + + // 记录响应结果 + log.info("响应时间:{}ms", duration); + log.info("响应结果:{}", JSON.toJSONString(result)); + log.info("===== 请求结束 ====="); + + return result; + } catch (Throwable e) { + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + + // 记录异常信息 + log.error("===== 请求异常 ====="); + log.error("接口地址:{} {}", methodType, requestURI); + log.error("执行时间:{}ms", duration); + log.error("异常信息:{}", e.getMessage()); + log.error("异常类型:{}", e.getClass().getName()); + log.error("堆栈信息:", e); + log.error("===== 请求异常结束 ====="); + + throw e; + } + } + + /** + * 获取请求参数 + */ + private Object getRequestParams(ProceedingJoinPoint joinPoint) { + Object[] args = joinPoint.getArgs(); + if (args.length == 0) { + return null; + } else if (args.length == 1) { + return args[0]; + } else { + return args; + } + } +} diff --git a/reading-platform-java/src/main/resources/logback-spring.xml b/reading-platform-java/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..74b0992 --- /dev/null +++ b/reading-platform-java/src/main/resources/logback-spring.xml @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + UTF-8 + + + + + + ${LOG_HOME}/${APP_NAME}.log + + + + ${LOG_HOME}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log + + + ${LOG_MAX_HISTORY} + + + + ${LOG_MAX_FILE_SIZE} + + + + ${LOG_TOTAL_CAP} + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + UTF-8 + + + + + + ${LOG_HOME}/${APP_NAME}-error.log + + + + ERROR + ACCEPT + DENY + + + + ${LOG_HOME}/${APP_NAME}-error.%d{yyyy-MM-dd}.%i.log + ${LOG_MAX_HISTORY} + + ${LOG_MAX_FILE_SIZE} + + 500MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + UTF-8 + + + + + + ${LOG_HOME}/${APP_NAME}-request.log + + + ${LOG_HOME}/${APP_NAME}-request.%d{yyyy-MM-dd}.%i.log + ${LOG_MAX_HISTORY} + + ${LOG_MAX_FILE_SIZE} + + 500MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + UTF-8 + + + + + + ${LOG_HOME}/${APP_NAME}-sql.log + + + ${LOG_HOME}/${APP_NAME}-sql.%d{yyyy-MM-dd}.%i.log + ${LOG_MAX_HISTORY} + + ${LOG_MAX_FILE_SIZE} + + 300MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + UTF-8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +