实现思路
- 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);
}
@Override
protected Logger getLogger() {
return this.logger;
}
@Override
protected void cleanMDC() {
super.cleanMDC();
MDC.remove(TENANT_ID);
MDC.remove(BIZ_ID);
MDC.remove(ACTION);
MDC.remove(OPERATOR);
}
}
**OaLoggerFactory, 日志工厂类**
```java
package 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: 40001
servlet:
context-path: /api
logging:
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>> {
@Autowired
protected M baseMapper;
/**
* 获取业务日志记录器
*
* @return 日志记录器
*/
protected BizLogger getBizLogger() {
return null;
}
@Override
public int create(T entity) {
int count = baseMapper.insert(entity);
getBizLogger().log(entity.getPrimaryKey(), ActionConstants.ADD);
return count;
}
@Override
public int modify(T entity) {
int count = baseMapper.updateById(entity);
getBizLogger().log(entity.getPrimaryKey(), ActionConstants.MODIFY);
return count;
}
@Override
public int remove(Long id) {
int count = baseMapper.deleteById(id);
getBizLogger().log(id, ActionConstants.DELETE);
return count;
}
@Override
public T get(Long id) {
return baseMapper.selectById(id);
}
@Override
public T get(LambdaQueryWrapper<T> wrapper) {
return baseMapper.selectOne(wrapper);
}
@Override
public List<T> list() {
return baseMapper.selectList(new LambdaQueryWrapper<>());
}
@Override
public List<T> list(LambdaQueryWrapper<T> wrapper) {
return baseMapper.selectList(wrapper);
}
@Override
public IPage<T> listPage(int page, int pageSize) {
return baseMapper.selectPage(new Page<>(page,pageSize),new LambdaQueryWrapper<>());
}
@Override
public 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
*/
@Service
public class ClientServiceImpl extends AbstractService<ClientMapper, Client> implements ClientService {
}
日志写入ES
待补充