设计通用日志审计方案

编程

1.需求

1.用最少的代码,且不用改变业务逻辑代码,记录详细的日志信息

2.支持日志以文件形式输出或数据库存储,通过配置文件灵活切换。

2.实现

要实现肯定是用注解和 aop实现拦截记录日志,这样对业务代码改动最小。

要使用时只需一行代码:

@RequestMapping(value="/test", method = RequestMethod.GET)

@AuditLog(moduleName="系统模块",operation = ""测试" + #username")

public Result list(String username) { return Result.ok(username); }

Aop拦截类:

// 判断日志审计功能是否开启,spring.audit.log.enabled定义在application.properties中
@ConditionalOnProperty(name = "spring.audit.log.enabled", havingValue = "true")
// 表示当只工程中程中引用了HttpServletRequest 或RequestContextHolder 才会构建这个bean
@ConditionalOnClass({ HttpServletRequest.class, RequestContextHolder.class })

@Component

@Slf4j

@Aspect

// 判断日志审计功能是否开启

@ConditionalOnProperty(name = "spring.audit.log.enabled", havingValue = "true")

// 表示当只工程中程中引用了HttpServletRequest 或RequestContextHolder 才会构建这个bean

@ConditionalOnClass({ HttpServletRequest.class, RequestContextHolder.class })

public class AuditLogAspect {

public static final String USERID_HEADER = "userid-header";

private static final String AUDIT_OBJ = "audit";

private static ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<>();

private static final String START_TIME = "startTime";

@Autowired

private AuditService auditService;

/**

* 用于SpEL表达式解析.

*/

private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

/**

* 用于获取方法参数定义名字.

*/

private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

@Pointcut("@annotation(com.xxx.log.annoation.AuditLog)")

public void ServiceAspect() {

}

public AuditLogAspect() {

super();

}

@Before("ServiceAspect()")

public void beforeMethod(JoinPoint joinPoint) {

long startTime = System.currentTimeMillis();

MethodSignature signature = (MethodSignature) joinPoint.getSignature();

Method method = signature.getMethod();

AuditLog auditLog = method.getAnnotation(AuditLog.class);

Audit audit = recordInfo(joinPoint, auditLog);

Map<String, Object> threadInfo = new HashMap<>();

threadInfo.put(START_TIME, startTime);

threadInfo.put(AUDIT_OBJ, audit);

threadLocal.set(threadInfo);

}

@AfterReturning(value = "ServiceAspect()", returning = "returnValue")

public void afterReturning(JoinPoint point, Object returnValue) {

Map<String, Object> threadInfo = threadLocal.get();

long takeTime = System.currentTimeMillis()

- (long) threadInfo.getOrDefault(START_TIME, System.currentTimeMillis());

Audit audit = (Audit) threadInfo.get(AUDIT_OBJ);

audit.setElapsedTime(takeTime);

audit.setError((byte) 0);

threadLocal.remove();

saveLogInfo(audit);

}

@AfterThrowing(value = "ServiceAspect()", throwing = "throwable")

public void doAfterThrowing(JoinPoint point, Throwable throwable) {

Map<String, Object> threadInfo = threadLocal.get();

long takeTime = System.currentTimeMillis()

- (long) threadInfo.getOrDefault(START_TIME, System.currentTimeMillis());

Audit audit = (Audit) threadInfo.get(AUDIT_OBJ);

audit.setElapsedTime(takeTime);

audit.setError((byte) 1);

/**

* 把堆栈信息转成string

*/

StringWriter sw = new StringWriter();

PrintWriter pw = new PrintWriter(sw);

throwable.printStackTrace(pw);

/**

* 存入堆栈信息

*/

audit.setStatck(sw.toString());

threadLocal.remove();

saveLogInfo(audit);

}

private void saveLogInfo(Audit audit) {

auditService.save(audit);

}

/**

* 解析spEL表达式

*/

private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {

// 获取方法形参名数组

String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());

if (paramNames != null && paramNames.length > 0) {

Expression expression = spelExpressionParser.parseExpression(spEL);

// spring的表达式上下文对象

EvaluationContext context = new StandardEvaluationContext();

// 给上下文赋值

for (int i = 0; i < args.length; i++) {

context.setVariable(paramNames[i], args[i]);

}

return expression.getValue(context).toString();

}

return "";

}

/**

* 构建审计对象

*/

private Audit recordInfo(JoinPoint joinPoint, AuditLog auditLog) {

Audit audit = new Audit();

audit.setTimestamp(LocalDateTime.now());

audit.setModuleName(auditLog.moduleName());

MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

audit.setClassName(methodSignature.getDeclaringTypeName());

audit.setMethodName(methodSignature.getName());

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())

.getRequest();

String queryStr = request.getQueryString();

// userId存放在request header 中

String userId = request.getHeader(USERID_HEADER);

audit.setUserId(userId);

if (StringUtils.isNotEmpty(queryStr)) {

audit.setRequestUrl(request.getRequestURI() + "?" + queryStr);

} else {

audit.setRequestUrl(request.getRequestURI());

}

String operation = auditLog.operation();

if (operation.contains("#")) {

// 获取方法参数值

Object[] args = joinPoint.getArgs();

if (!ObjectUtils.isEmpty(args)) {

operation = getValBySpEL(operation, methodSignature, args);

}

}

audit.setOperation(operation);

return audit;

}

}

数据库日志记录实现,只有当spring.audit.log.log-type=db时才执行此spring bean

@Service

@Slf4j

//表示只有当spring.audit.log.log-type=db时才执行此spring bean

@ConditionalOnProperty(name = "spring.audit.log.log-type", havingValue = "db")

@ConditionalOnClass(JdbcTemplate.class)

public class DbAuditServiceImpl implements AuditService {

private static final String INSERT_SQL = " insert into sys_logger " +

" (module_name, class_name, method_name, elapsed_time, request_url, error, statck, operation, timestamp,user_id) " +

" values (?,?,?,?,?,?,?,?,?,?)";

@Autowired

private JdbcTemplate jdbcTemplate;

@PostConstruct

public void init() {

String sql = "CREATE TABLE IF NOT EXISTS `sys_logger` (

" +

" `id` int(11) NOT NULL AUTO_INCREMENT,

" +

" `module_name` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT "模块名",

" +

" `class_name` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT "类名",

" +

" `method_name` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT "方法名",

" +

" `elapsed_time` int(11) NULL COMMENT "耗时",

" +

" `request_url` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT "请求Url",

" +

" `error` int(11) NULL COMMENT "错误标志",

" +

" `statck` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT "错误信息",

" +

" `operation` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT "操作信息",

" +

" `timestamp` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT "创建时间",

" +

" `user_id` int(11) NULL COMMENT "用户ID",

" +

" PRIMARY KEY (`id`) USING BTREE

" +

") ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;";

this.jdbcTemplate.execute(sql);

}

/**

* @Async表示 异常插入

*/

@Async

@Override

public void save(Audit audit) {

this.jdbcTemplate.update(INSERT_SQL

, audit.getModuleName(), audit.getClassName(), audit.getMethodName(),audit.getElapsedTime()

, audit.getRequestUrl(), audit.getError(), audit.getStatck()

, audit.getOperation(), audit.getTimestamp(),audit.getUserId());

}

}

日志文件记录方式 

@Slf4j

@Service

@ConditionalOnProperty(name = "spring.audit.log.log-type", havingValue = "logger", matchIfMissing = true)

public class LogAuditServiceImpl implements AuditService {

private static final DateTimeFormatter OF_PATTERN = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

private static final String MSG_PATTERN = "{}|{}|{}|{}|{}|{}|{}";

@Override

public void save(Audit audit) {

if (1 == audit.getError()) {

log.error(MSG_PATTERN, audit.getTimestamp().format(OF_PATTERN),

audit.getModuleName(),audit.getUserId(), audit.getRequestUrl(), audit.getOperation(), audit.getElapsedTime(),

audit.getStatck());

} else {

log.debug(MSG_PATTERN, audit.getTimestamp().format(OF_PATTERN),

audit.getModuleName(), audit.getUserId(),audit.getRequestUrl(), audit.getOperation(), audit.getElapsedTime(),

audit.getError());

}

}

}

 

以上是 设计通用日志审计方案 的全部内容, 来源链接: utcz.com/z/516312.html

回到顶部