切面日志
演示
根据下边的项目结构,我是在_00common里面测试的
controller
数据库记录
会记录请求正常和请求异常,根据log_type区分,INFO正常,ERROR异常
介绍
在controller上边加@Log注解,里面输入接口的信息,调用完接口之后会直接保存到数据库里面,如果要使用的话直接引入这个jar包就可以,然后直接使用
代码
测试调用接口就不写了,直接看切面实现代码
项目结构
添加依赖
还需要引入一些启动依赖,比如数据库依赖啊,mybatisplus依赖,springboot依赖啊,这些自行引入
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
日志注解接口
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 Log {
String value() default "";
String from() default "";
}
日志注解实现
import com.alibaba.fastjson.JSON;
import com.baicaizhi.utils.RequestHolder;
import com.baicaizhi.utils.StringUtils;
import com.baicaizhi.utils.ThrowableUtils;
import com.baicaizhi.entity.SysLog;
import com.baicaizhi.service.ISysLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Component
@Aspect
@Slf4j
@RequiredArgsConstructor
public class LogAspect {
private final ISysLogService iSysLogService;
private static ThreadLocal<Long> currentTime = new ThreadLocal<>();
/**
* 配置切入点
*/
@Pointcut("@annotation(com.baicaizhi.annotation.Log)")
public void logPointcut() {
// 该方法无方法体,主要为了让同类中其他方法使用此切入点
}
/**
* 配置环绕通知,使用在方法logPointcut()上注册的切入点
*
* @param joinPoint join point for advice
*/
@Around("logPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object result;
// 设置当前时间戳
currentTime.set(System.currentTimeMillis());
// 参数值
List<Object> argValues = new ArrayList<>(Arrays.asList(joinPoint.getArgs()));
String paramStr;
try {
// 将参数值转换为JSON字符串
paramStr = JSON.toJSONString(argValues);
} catch (Exception e1) {
// 如果转换失败,则使用参数值的toString方法生成字符串
paramStr = argValues.toString();
}
result = joinPoint.proceed();
// 创建SysLog对象,记录执行时间
//SysLog sysLog = new SysLog("INFO",System.currentTimeMillis() - currentTime.get());
SysLog sysLog = new SysLog("INFO", System.currentTimeMillis());
currentTime.remove();
HttpServletRequest request = RequestHolder.getHttpServletRequest();
String thisUser = getUsername();
// 储存日志信息
iSysLogService.saveLog(request,
paramStr,
argValues,
thisUser,
StringUtils.getBrowser(request),
StringUtils.getIp(request),
joinPoint,
sysLog
);
// 控制台和日志文件只在debug模式下进行记录,具体信息存储在
// 输出日志信息到控制台和日志文件
log.debug("收到 用户 :" + thisUser + " 请求URL :" + request.getRequestURI() + " 参数为 :" + paramStr);
log.debug("返回前端的结果为 : " + JSON.toJSONString(result));
return result;
}
/**
* 配置异常通知
*
* @param joinPoint join point for advice
* @param e exception
*/
@AfterThrowing(pointcut = "logPointcut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
// 设置当前时间为系统时间
currentTime.set(System.currentTimeMillis());
// 创建SysLog对象,设置状态为"ERROR",并计算耗时
SysLog sysLog = new SysLog("ERROR", System.currentTimeMillis() - currentTime.get());
// 移除当前时间
currentTime.remove();
// 设置异常详情
sysLog.setExceptionDetail(ThrowableUtils.getStackTrace(e).getBytes());
// 获取当前请求对象
HttpServletRequest request = RequestHolder.getHttpServletRequest();
//参数值
List<Object> argValues = new ArrayList<>(Arrays.asList(joinPoint.getArgs()));
String thisUser = getUsername();
String paramStr;
try {
// 将参数值转换为JSON字符串
paramStr = JSON.toJSONString(argValues);
} catch (Exception e1) {
// 转换失败,则直接将参数值转换为字符串
paramStr = argValues.toString();
}
// 保存日志
iSysLogService.saveLog(request,
paramStr,
argValues,
thisUser,
StringUtils.getBrowser(request),
StringUtils.getIp(request),
(ProceedingJoinPoint) joinPoint,
sysLog
);
//打印结果
// 打印异常日志,包含用户、请求URL和参数
log.error(">>>>>>异常异常异常<<<<<<< 收到 用户 :{} 请求URL :{} 参数 : {}", thisUser, request.getRequestURI(), joinPoint.getArgs());
}
/**
* 获取当前用户名称
* @return
*/
private String getUsername() {
// final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// return Objects.isNull(authentication) ? "" : authentication.getName();
return "baicaizhi";
}
}
SysLog实体类
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
public class SysLog implements Serializable {
private Long id;
/** 操作用户 */
private String username;
/** 描述 */
private String description;
/** 方法名 */
private String method;
/** 参数 */
private String params;
/** 日志类型 */
private String logType;
/** 请求ip */
private String requestIp;
/** 地址 */
private String address;
/** 浏览器 */
private String browser;
/** 请求耗时 */
private Long time;
/** 异常详细 */
private byte[] exceptionDetail;
/** 创建日期 */
private LocalDateTime createTime;
public SysLog(String logType, Long time) {
this.logType = logType;
this.time = time;
}
}
Dao层
这里需要注意一下啊,我这边引用了mybatisplus,所以这里复制后会报错,需要引入下依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybaits-plus.version}</version>
</dependency>
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baicaizhi.entity.SysLog;
/**
* Created by sunjx on 2020/06/12
*/
public interface SysLogMapper extends BaseMapper<SysLog>{
}
Service类
import com.baomidou.mybatisplus.extension.service.IService;
import com.baicaizhi.entity.SysLog;
import org.aspectj.lang.ProceedingJoinPoint;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* Created by sunjx on 2020/06/12
*/
public interface ISysLogService extends IService<SysLog> {
void saveLog(HttpServletRequest request,
String paramStr,
List<Object> argValues,
String username,
String browser,
String ip,
ProceedingJoinPoint joinPoint,
SysLog sysLog
);
}
Service实现类
import cn.hutool.json.JSONObject;
import com.baicaizhi.utils.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baicaizhi.annotation.Log;
import com.baicaizhi.entity.SysLog;
import com.baicaizhi.mapper.SysLogMapper;
import com.baicaizhi.service.ISysLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.List;
/**
* baicaizhi
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class SysLogServiceImpl extends ServiceImpl<SysLogMapper, SysLog> implements ISysLogService {
private final SysLogMapper sysLogMapper;
/**
* 保存日志信息
*
* @param request HttpServletRequest对象
* @param paramStr 参数字符串
* @param argValues 方法参数值列表
* @param username 用户名
* @param browser 浏览器类型
* @param ip 用户IP地址
* @param joinPoint 切面连接点
* @param sysLog 系统日志对象
*/
@Override
public void saveLog(HttpServletRequest request,
String paramStr,
List<Object> argValues,
String username,
String browser,
String ip,
ProceedingJoinPoint joinPoint,
SysLog sysLog
) {
// 获取当前执行的方法的签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取方法上的Log注解
Log aopLog = method.getAnnotation(Log.class);
// 方法路径
String methodName = joinPoint.getTarget().getClass().getName()+"."+signature.getName()+"()";
// 描述
// 判断Log注解是否存在
if (aopLog != null) {
sysLog.setDescription(aopLog.value());
}
assert aopLog != null;
sysLog.setRequestIp(ip);
String loginPath = "login";
// 判断当前执行的方法名是否为login
if(loginPath.equals(signature.getName())){
try {
// 从参数列表中获取username参数
username = new JSONObject(argValues.get(0)).get("username").toString();
}catch (Exception e){
e.printStackTrace();
}
} else if (StringUtils.isBlank(username)){
// 如果username为空,则从Log注解的from属性中获取
username = aopLog.from();
}
sysLog.setAddress("system");
sysLog.setMethod(methodName);
sysLog.setUsername(username);
sysLog.setParams(paramStr);
sysLog.setBrowser(browser);
sysLog.setCreateTime(LocalDateTime.now());
sysLogMapper.insert(sysLog);
}
}
utils工具类
RequestHolder
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
public class RequestHolder {
/**
* 获取当前线程的 HttpServletRequest 对象。
*
* @return 当前线程的 HttpServletRequest 对象
* @throws IllegalStateException 如果当前线程没有关联的 HttpServletRequest 对象,则抛出此异常
*/
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
}
}
StringUtils
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.UserAgent;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.Date;
/**
* 字符串工具类, 继承org.apache.commons.lang3.StringUtils类
*/
public class StringUtils extends org.apache.commons.lang3.StringUtils {
private static final char SEPARATOR = '_';
private static final String UNKNOWN = "unknown";
/**
* 驼峰命名法工具
*
* @return toCamelCase(" hello_world ") == "helloWorld"
* toCapitalizeCamelCase("hello_world") == "HelloWorld"
* toUnderScoreCase("helloWorld") = "hello_world"
*/
public static String toCamelCase(String s) {
if (s == null) {
return null;
}
s = s.toLowerCase();
StringBuilder sb = new StringBuilder(s.length());
boolean upperCase = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == SEPARATOR) {
upperCase = true;
} else if (upperCase) {
sb.append(Character.toUpperCase(c));
upperCase = false;
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* 驼峰命名法工具
*
* @return toCamelCase(" hello_world ") == "helloWorld"
* toCapitalizeCamelCase("hello_world") == "HelloWorld"
* toUnderScoreCase("helloWorld") = "hello_world"
*/
public static String toCapitalizeCamelCase(String s) {
if (s == null) {
return null;
}
s = toCamelCase(s);
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
/**
* 驼峰命名法工具
*
* @return toCamelCase(" hello_world ") == "helloWorld"
* toCapitalizeCamelCase("hello_world") == "HelloWorld"
* toUnderScoreCase("helloWorld") = "hello_world"
*/
static String toUnderScoreCase(String s) {
if (s == null) {
return null;
}
StringBuilder sb = new StringBuilder();
boolean upperCase = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
boolean nextUpperCase = true;
if (i < (s.length() - 1)) {
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
}
if ((i > 0) && Character.isUpperCase(c)) {
if (!upperCase || !nextUpperCase) {
sb.append(SEPARATOR);
}
upperCase = true;
} else {
upperCase = false;
}
sb.append(Character.toLowerCase(c));
}
return sb.toString();
}
/**
* 获取ip地址
*/
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
String comma = ",";
String localhost = "127.0.0.1";
if (ip.contains(comma)) {
ip = ip.split(",")[0];
}
if (localhost.equals(ip)) {
// 获取本机真正的ip地址
try {
ip = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
return ip;
}
/**
* 根据请求头中的User-Agent信息获取浏览器名称
*
* @param request HttpServletRequest对象
* @return 返回浏览器名称字符串
*/
public static String getBrowser(HttpServletRequest request){
UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
Browser browser = userAgent.getBrowser();
return browser.getName();
}
/**
* 获得当天是周几
*/
public static String getWeekDay(){
String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
if (w < 0){
w = 0;
}
return weekDays[w];
}
/**
* 获取当前机器的IP
* @return /
*/
public static String getLocalIp(){
InetAddress addr;
try {
addr = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
return "unknown";
}
byte[] ipAddr = addr.getAddress();
StringBuilder ipAddrStr = new StringBuilder();
for (int i = 0; i < ipAddr.length; i++) {
if (i > 0) {
ipAddrStr.append(".");
}
ipAddrStr.append(ipAddr[i] & 0xFF);
}
return ipAddrStr.toString();
}
}
ThrowableUtils
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* 异常工具 2019-01-06
*/
public class ThrowableUtils {
/**
* 获取堆栈信息
*/
public static String getStackTrace(Throwable throwable){
StringWriter sw = new StringWriter();
try (PrintWriter pw = new PrintWriter(sw)) {
throwable.printStackTrace(pw);
return sw.toString();
}
}
}
日志SQL
CREATE TABLE `sys_log` (
`username` varchar(255) DEFAULT NULL COMMENT '操作用户',
`description` varchar(255) DEFAULT NULL COMMENT '描述',
`params` longtext COMMENT '参数',
`create_time` datetime DEFAULT NULL COMMENT '创建日期',
`log_type` varchar(20) DEFAULT NULL COMMENT '日志类型',
`exception_detail` text COMMENT '异常详细',
`method` varchar(255) DEFAULT NULL COMMENT '方法名',
`request_ip` varchar(255) DEFAULT NULL COMMENT '请求ip',
`time` bigint(20) DEFAULT NULL COMMENT '请求耗时',
`address` varchar(255) DEFAULT NULL COMMENT '地址',
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`browser` varchar(255) DEFAULT NULL COMMENT '浏览器',
PRIMARY KEY (`id`) USING BTREE,
KEY `log_create_time_index` (`create_time`) USING BTREE,
KEY `inx_log_type` (`log_type`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1145606 DEFAULT CHARSET=utf8mb4;