实现思路
- springboot默认使用slf4j作为日志门面, logback作为其实现
(logback的配置文件加载顺序为logback.xml—->application.properties—->logback-spring.xml, 一般在logback.xml中定义变量, 在logback-spring.xml中实现)
- 日志要全局保持统一规范的格式, 以便后续拓展, 比如写入es后供前端展示查阅等
- 在service层对mybatisPlus已有的Mapper接口做自定义封装, 添加上日志记录, 这里舍弃了mybatisPlus默认的IService和ServiceImpl, 因为这两者接口都是default, 不便做日志的拓展
配置解析
公共模块core包
base.xml, 基础日志配置
- 这里定义了三个appender, 一个控制台输出, 一个文件记录, 一个写入es
- 这里使用的滚动策略是SizeAndTimeBasedRollingPolicy, 默认配置为每天生成日志文件, 每个文件最大100M(一天内日志记录大于100M, 则生成多个文件), 保存10个历史记录日志文件, 所有日志文件一起不能超过10G ```java <?xml version=”1.0” encoding=”UTF-8”?>
这里因为保存位置为src/java/xxx/logger下, 因此注意打包时需要一起打入resources```java<resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource></resources>
AbstractLogger
package top.xinzhang0618.oa.logger;import org.slf4j.Logger;import org.slf4j.MDC;import top.xinzhang0618.oa.Assert;import top.xinzhang0618.oa.util.StringUtils;/*** AbstractLogger**/public abstract class AbstractLogger {protected abstract Logger getLogger();private String preProcessMessage(String message) {return StringUtils.isBlank(message) ? "" : message;}protected void putMDCItem(String key, Object value) {if (!Assert.isNull(key) && !Assert.isNull(value)) {MDC.put(key, String.valueOf(value));}}protected void doInfo(String message, Object... args) {getLogger().info(preProcessMessage(message), args);cleanMDC();}protected void doWarn(String message, Object... args) {getLogger().warn(preProcessMessage(message), args);cleanMDC();}protected void doDebug(String message, Object... args) {getLogger().debug(preProcessMessage(message), args);cleanMDC();}protected void doError(String message, Object... args) {getLogger().error(preProcessMessage(message), args);cleanMDC();}protected void doError(String message, Throwable throwable) {getLogger().error(message, throwable);cleanMDC();}protected void doTrace(String message, Object... args) {getLogger().trace(preProcessMessage(message), args);cleanMDC();}public boolean isTraceEnabled() {return getLogger().isTraceEnabled();}public boolean isDebugEnabled() {return getLogger().isDebugEnabled();}public boolean isInfoEnabled() {return getLogger().isInfoEnabled();}public boolean isWarnEnabled() {return getLogger().isWarnEnabled();}public boolean isErrorEnabled() {return getLogger().isErrorEnabled();}protected void cleanMDC() {}}
BizLogger, 业务日志类
- 其他业务自行定义日志结构, 比如MallLogger等
- 此处采用通过路径获取Logger, 效果如下, 如果是通过Clazz获取Logger, 路径为全路径类名
```java package top.xinzhang0618.oa.logger;10:06:52.396 INFO [http-nio-40001-exec-1] oa.biz.user - 1334318150742269954, 新增
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import top.xinzhang0618.oa.BizContext;
/**
- 业务日志. *
- @author buer */ public class BizLogger extends AbstractLogger {
private static final String BIZ_ID = "bizId";private static final String ACTION = "action";private static final String OPERATOR = "operator";private static final String TENANT_ID = "tenantId";private final Logger logger;public BizLogger(final OaModule module) {this.logger = getLogger(module.getLogPath());}private Logger getLogger(String type) {return LoggerFactory.getLogger("oa.biz." + type.toLowerCase());}public void log(Long bizId, String action) {log(bizId, action, bizId + ", " + action);}public void log(Long bizId, String action, String message, Object... args) {log(BizContext.getTenantId(), bizId, BizContext.getUserName(), action, message, args);}public void log(Long tenantId, Long bizId, String nickname, String action, String message, Object... args) {putMDC(tenantId, bizId, nickname, action);this.doInfo(message, args);}protected void putMDC(Long tenantId, Long bizId, String nickname, String action) {putMDCItem(TENANT_ID, tenantId);putMDCItem(BIZ_ID, bizId);putMDCItem(OPERATOR, nickname);putMDCItem(ACTION, action);}@Overrideprotected Logger getLogger() {return this.logger;}@Overrideprotected void cleanMDC() {super.cleanMDC();MDC.remove(TENANT_ID);MDC.remove(BIZ_ID);MDC.remove(ACTION);MDC.remove(OPERATOR);}
}
**OaLoggerFactory, 日志工厂类**```javapackage top.xinzhang0618.oa.logger;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/*** OaLoggerFactory*/public class OaLoggerFactory {/*** 获取日志记录器** @param clazz 类型*/public static Logger getLogger(Class<?> clazz) {return LoggerFactory.getLogger(clazz);}/*** 获取日志记录器** @param logName 日志名称*/public static Logger getLogger(String logName) {return LoggerFactory.getLogger(logName);}/*** 获取业务日志记录类** @param module 模块* @return 日志实例*/public static BizLogger getBizLogger(final OaModule module) {return new BizLogger(module);}}
其他
ActionConstants
package top.xinzhang0618.oa.logger;/*** 操作常量**/public class ActionConstants {public static final String ADD = "新增";public static final String AUDIT = "审核";public static final String REVIEW = "复核";public static final String MODIFY = "修改";public static final String DELETE = "删除";public static final String CANCEL = "取消";public static final String ENABLE = "启用";public static final String DISABLE = "禁用";public static final String INVALID = "作废";public static final String BEGIN = "开始";public static final String END = "结束";}
OaModule
package top.xinzhang0618.oa.logger;/*** OAModule*/public enum OaModule {/*** 角色管理*/ROLE("角色管理", "role"),/*** 用户角色*/USER_ROLE("角色用户", "user.role"),/*** 用户管理*/USER("用户管理", "user"),/*** 授权*/PRIVILEGE("授权","privilege");private String caption;private String logPath;OaModule(String caption, String logPath) {this.caption = caption;this.logPath = logPath;}public String getCaption() {return caption;}public String getLogPath() {return logPath;}}
服务配置
例如web服务, 需配置resources/config/logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?><configuration><property name="log_file" value="log/web.log"/><property name="log_file_pattern" value="log/service.%d{yyyy-MM-dd}.%i.log"/><include resource="top/xinzhang0618/oa/logger/base.xml"/><springProfile name="dev,test"><logger name="top.xinzhang0618.oa" level="debug" additivity="false"><appender-ref ref="file_out"/><appender-ref ref="stdout"/></logger></springProfile><springProfile name="prod"><logger name="top.xinzhang0618.oa" level="info" additivity="false"><appender-ref ref="file_out"/></logger></springProfile></configuration>
此外在yml中也需指定配置文件位置
server:port: 40001servlet:context-path: /apilogging:config: classpath:config/logback-spring.xml
BIZ接口处理
BizService, 这里提出了一些通用接口, 可以根据业务情况进一步优化
package top.xinzhang0618.oa.service;import com.baomidou.mybatisplus.core.conditions.Wrapper;import com.baomidou.mybatisplus.core.metadata.IPage;import top.xinzhang0618.oa.BaseDO;import java.util.List;/*** @author xinzhang* @date 2020/12/2 16:35*/public interface BizService<T extends BaseDO, W extends Wrapper<T>> {/*** 新写入数据库记录.** @return 影响的记录数*/int create(T entity);/*** 根据主键来更新符合条件的数据库记录.** @return 影响的记录数*/int modify(T entity);/*** 根据主键删除记录.** @return 影响的记录数*/int remove(Long id);/*** 根据主键查询数据.** @return 实体对象*/T get(Long id);/*** 根据示例获取单条数据.** @return 实体对象*/T get(W w);/*** 查询列表.** @return 实体对象列表*/List<T> list();/*** 根据条件查询实体对象.** @return 实体对象列表*/List<T> list(W w);/*** 查询分页.** @return 分页的实体对象列表*/IPage<T> listPage( int page, int pageSize);/*** 根据条件查询实体对象返回分页结果.** @return 分页的实体对象列表*/IPage<T> listPage(W w, int page, int pageSize);}
AbstractService, 提供了接口的默认实现, 其中添加了日志记录
package top.xinzhang0618.oa.service;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.baomidou.mybatisplus.core.metadata.IPage;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import org.springframework.beans.factory.annotation.Autowired;import top.xinzhang0618.oa.BaseDO;import top.xinzhang0618.oa.logger.ActionConstants;import top.xinzhang0618.oa.logger.BizLogger;import java.util.List;/*** @author xinzhang* @date 2020/12/2 15:13*/public abstract class AbstractService<M extends BaseMapper<T>, T extends BaseDO> implements BizService<T,LambdaQueryWrapper<T>> {@Autowiredprotected M baseMapper;/*** 获取业务日志记录器** @return 日志记录器*/protected BizLogger getBizLogger() {return null;}@Overridepublic int create(T entity) {int count = baseMapper.insert(entity);getBizLogger().log(entity.getPrimaryKey(), ActionConstants.ADD);return count;}@Overridepublic int modify(T entity) {int count = baseMapper.updateById(entity);getBizLogger().log(entity.getPrimaryKey(), ActionConstants.MODIFY);return count;}@Overridepublic int remove(Long id) {int count = baseMapper.deleteById(id);getBizLogger().log(id, ActionConstants.DELETE);return count;}@Overridepublic T get(Long id) {return baseMapper.selectById(id);}@Overridepublic T get(LambdaQueryWrapper<T> wrapper) {return baseMapper.selectOne(wrapper);}@Overridepublic List<T> list() {return baseMapper.selectList(new LambdaQueryWrapper<>());}@Overridepublic List<T> list(LambdaQueryWrapper<T> wrapper) {return baseMapper.selectList(wrapper);}@Overridepublic IPage<T> listPage(int page, int pageSize) {return baseMapper.selectPage(new Page<>(page,pageSize),new LambdaQueryWrapper<>());}@Overridepublic IPage<T> listPage(LambdaQueryWrapper<T> wrapper, int page, int pageSize) {return baseMapper.selectPage(new Page<>(page,pageSize),wrapper);}}
实现示例
/*** ClientService** @author autoGenerate* @version 2020-12-02*/public interface ClientService extends BizService<Client, LambdaQueryWrapper<Client>> {}
/*** ClientServiceImpl** @author autoGenerate* @version 2020-12-02*/@Servicepublic class ClientServiceImpl extends AbstractService<ClientMapper, Client> implements ClientService {}
日志写入ES
待补充
