Some time ago, the architecture asked me to make a log burying point, because other work has been postponed, and there is no clear demand for it up to now, here is a simple set of handwritten log burying point:
Step 1: Introduce dependencies
<! --aop--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <! --log4j--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> Slf4j </groupId> <artifactId>slf4j-log4j12</artifactId> </artifactId> The < version > 1.7.25 < / version > < / dependency >Copy the code
Step 2: Since the company has its own log configuration, HERE I simply configure one to make do with the application. Properties configuration:
For simplicity I use the default logging.path=F:/Log4jCopy the code
Step 3: Customize annotations:
package com.javaliao.portal.annotations; import org.springframework.web.bind.annotation.ResponseBody; import java.lang.annotation.*; /** * @target ({ElementType. @retention (retentionPolicy.runtime)// RUNTIME annotation @documented // Indicates that the annotation should be Documented by the Javadoc tool Public @interface AppController {/** * Service description * @return */ String description() default ""; /** * Boolean isLog() default true; }Copy the code
Step 4: Intercept user requests
package com.javaliao.portal.aspect;
import com.alibaba.fastjson.JSON;
import com.javaliao.portal.annotations.AppController;
import com.javaliao.portal.common.CommonResult;
import com.javaliao.portal.model.TbLogVisit;
import com.javaliao.portal.service.ActionService;
import com.javaliao.portal.util.CollectionHelp;
import com.javaliao.portal.util.TimeUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
@Component
@Aspect
@SuppressWarnings("all") //@SuppressWarnings("all")抑制所有警告.@SuppressWarnings注解主要用在取消一些编译器产生的警告对代码左侧行列的遮挡,有时候这会挡住我们断点调试时打的断点
public class AppControllerAspect {
//注入Service用于把日志保存数据库,实际项目入库采用队列做异步
@Resource
private ActionService actionService;
//日志工厂获取日志对象
static Logger logger = LoggerFactory.getLogger(AppControllerAspect.class);
/**
* ThreadLocal多线程环境下,创建多个副本,各自执行,互不干扰
*/
//startTime存放开始时间
ThreadLocal<Map<String, Long >> startTime = new ThreadLocal<>();
//count存放方法被调用的次数O 使用volatile利用它的三大特性:保证可见性(遵守JMM的可见性),不保证原子性,禁止指令重排
volatile ThreadLocal<Map<String, Long>> count = new ThreadLocal<>();
//timeConsuming存放方法总耗时
ThreadLocal<Map<String, Long >> timeConsuming = new ThreadLocal<>();
//fromType存放渠道
ThreadLocal<Map<String, String >> fromType = new ThreadLocal<>();
//tbLogVisit日志访问对象
ThreadLocal<TbLogVisit> tbLogVisit = new ThreadLocal<>();
//Controller层切点
@Pointcut("@annotation(com.javaliao.portal.annotations.AppController)")
public void controllerAspectse() {
}
//前置通知 用于拦截Controller层记录用户的操作
@Before("controllerAspectse()")
public void before(JoinPoint pjp) {
//初始化
TbLogVisit tbLogVisit = this.tbLogVisit.get();
tbLogVisit = new TbLogVisit();
Map<String, Long> countMap = this.count.get();
countMap = new HashMap<>();
this.count.set(countMap);
Map<String, Long> timeConsumingMap = this.timeConsuming.get();
timeConsumingMap = new HashMap<>();
this.timeConsuming.set(timeConsumingMap);
Map<String, String> fromTypeMap = this.fromType.get();
fromTypeMap = new HashMap<>();
this.fromType.set(fromTypeMap);
Map<String, Long> map = new HashMap<>();
map.put("startTime",System.currentTimeMillis());
this.startTime.set(map);
logger.info("==============前置通知开始:记录用户的操作==============");
String currentTime = TimeUtils.getCurrentTime("YYYY-MM-dd HH:mm:ss");
logger.info("请求开始时间:" + currentTime);
tbLogVisit.setVisitStartTime(new Date());
String resultString = "";
// 是否打日志 默认打
boolean isLog = true;
try {
MethodSignature signature = (MethodSignature) pjp.getSignature();
AppController appController = signature.getMethod().getAnnotation(AppController.class);
//是否开启日志打印
isLog = appController.isLog();
if(isLog){
//开始打印日志
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
String api = pjp.getTarget().getClass().getName() + "." + pjp.getSignature().getName();
logger.info("请求API:" + api);
tbLogVisit.setVisitApi(api);
String methodDescription = getControllerMethodDescription(pjp);
logger.info("方法描述:" + methodDescription);
tbLogVisit.setVisitDescription(methodDescription);
String ipAddress = InetAddress.getLocalHost().toString().substring(InetAddress.getLocalHost().toString().lastIndexOf("/") + 1);
logger.info("请求ip:"+ ipAddress);
tbLogVisit.setVisitIpAddress(ipAddress);
String hostName = InetAddress.getLocalHost().getHostName();
logger.info("机器名:" + hostName);
tbLogVisit.setVisitHostName(hostName);
Enumeration<?> enu = request.getParameterNames();
String params = "{";
while (enu.hasMoreElements()) {
String paraName = (String) enu.nextElement();
params += "\"" + paraName + "\":\"" + request.getParameter(paraName) + "\",";
}
String methodParams = params + "}";
String substring = methodParams.substring(0, methodParams.length() - 2);
substring = substring + "}";
logger.info("方法参数:" + substring);
tbLogVisit.setVisitParams(substring);
StringBuffer url = request.getRequestURL();
logger.info("URL:" + url);
tbLogVisit.setVisitUrl(String.valueOf(url));
}
} catch (Exception e) {
StackTraceElement stackTraceElement2 = e.getStackTrace()[2];
String reason = "异常:【"+
"类名:"+stackTraceElement2.getClassName()+";"+
"文件:"+stackTraceElement2.getFileName()+";"+"行:"+
stackTraceElement2.getLineNumber()+";"+"方法:"
+stackTraceElement2.getMethodName() + "】";
//记录本地异常日志
logger.error("==============前置通知异常:记录访问异常信息==============");
String message = e.getMessage() + "|" + reason;
logger.error("异常信息:",message);
tbLogVisit.setVisitThrowingErro(message);
tbLogVisit.setVisitResult("请求发生异常,异常信息:" + message);
}finally {
this.tbLogVisit.set(tbLogVisit);
}
}
@Around("controllerAspectse()")
public Object around(ProceedingJoinPoint pjp)throws Throwable {
String result = JSON.toJSONString(pjp.proceed());
logger.info("请求结果:" + result);
TbLogVisit tbLogVisit = this.tbLogVisit.get();
tbLogVisit.setVisitResult(result);
this.tbLogVisit.set(tbLogVisit);
return CommonResult.success("");
}
/**
* 对Controller下面的方法执行后进行切入,统计方法执行的次数和耗时情况
* 注意,这里的执行方法统计的数据不止包含Controller下面的方法,也包括环绕切入的所有方法的统计信息
* @param jp
*/
@AfterReturning("controllerAspectse()")
public void afterMehhod(JoinPoint jp) {
logger.info("==============方法执行完成==============");
TbLogVisit tbLogVisit = this.tbLogVisit.get();
try {
//获取方法名
String methodName = jp.getSignature().getName();
//开始统计数量与耗时
if(count.get().get(methodName) == null){
//第一次赋值为0
count.get().put(methodName,0L);
}
//使用原子整型进行增值
AtomicLong atomicInteger = new AtomicLong(count.get().get(methodName));
//加一 这里暂时不解决ABA问题,仅保证原子性 解决了volatile不保证原子性的问题 getAndIncrement()先返回再加1,incrementAndGet()先加1再返回
long increment = atomicInteger.incrementAndGet();
//然后增加新值
count.get().replace(methodName,increment);
Long end = System.currentTimeMillis();
Long total = end - startTime.get().get("startTime");
logger.info("执行总耗时为:" +total);
if(timeConsuming.get().containsKey(methodName)){
timeConsuming.get().replace(methodName, total);
}else {
timeConsuming.get().put(methodName, (total));
}
tbLogVisit = this.tbLogVisit.get();
tbLogVisit.setVisitTimeConsuming(String.valueOf(total));
String endTime = TimeUtils.getCurrentTime("YYYY-MM-dd HH:mm:ss");
logger.info("请求结束时间:" + endTime);
tbLogVisit.setVisitEndTime(new Date());
/**
* 从原来的map中将最后的连接点方法给移除了,替换成最终的,避免连接点方法多次进行叠加计算,
* 由于原来的map受ThreadLocal的保护,这里不支持remove,因此,需要单开一个map进行数据交接
*/
//重新new一个map
Map<String, Long> map = new HashMap<>();
for(Map.Entry<String, Long> entry:timeConsuming.get().entrySet()){
if(entry.getKey().equals(methodName)){
map.put(methodName, total);
}else{
map.put(entry.getKey(), entry.getValue());
}
}
for (Map.Entry<String, Long> entry :count.get().entrySet()) {
for(Map.Entry<String, Long> entry2 :map.entrySet()){
if(entry.getKey().equals(entry2.getKey())){
Long num = entry.getValue();
logger.info("调用次数:" + num);
tbLogVisit.setVisitNum(num);
}
}
}
//这里的渠道暂时写死
Map<String, String> stringMap = fromType.get();
Map<String, String> fromMap ;
if(CollectionHelp.isMapNotEmpty(stringMap)){
fromMap = stringMap;
}else {
fromMap = new HashMap<>();
fromMap.put(methodName,"个人开发电商平台");
}
String channel = fromMap.get(methodName);
logger.info("渠道:" + channel);
tbLogVisit.setVisitChannel(channel);
} catch (Exception e) {
StackTraceElement stackTraceElement2 = e.getStackTrace()[2];
String reason = "异常:【"+
"类名:"+stackTraceElement2.getClassName()+";"+
"文件:"+stackTraceElement2.getFileName()+";"+"行:"+
stackTraceElement2.getLineNumber()+";"+"方法:"
+stackTraceElement2.getMethodName() + "】";
//记录本地异常日志
logger.error("==============通知异常:记录访问异常信息==============");
String message = e.getMessage() + "|" + reason;
logger.error("异常信息:",message);
tbLogVisit.setVisitThrowingErro(message);
tbLogVisit.setVisitResult("请求发生异常!!!");
} finally {
this.tbLogVisit.set(tbLogVisit);
//添加日志信息入库
actionService.insertLogVisit(this.tbLogVisit.get());
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*/
public static String getControllerMethodDescription(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();//目标方法名
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String description = "";
for (Method method:methods) {
if (method.getName().equals(methodName)){
Class[] clazzs = method.getParameterTypes();
if (clazzs.length==arguments.length){
description = method.getAnnotation(AppController.class).description();
break;
}
}
}
return description;
}
}
Copy the code
Step 5: The business layer sends messages:
package com.javaliao.portal.service.impl; import com.javaliao.portal.mapper.TbLogVisitMapper; import com.javaliao.portal.model.TbLogVisit; import com.javaliao.portal.service.ActionService; import com.javaliao.portal.util.ActiveMQUtil; import net.sf.json.JSONObject; import org.apache.activemq.command.ActiveMQMapMessage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.jms.*; @Service public class ActionServiceImpl implements ActionService { @Autowired TbLogVisitMapper tbLogVisitMapper; @Autowired ActiveMQUtil activeMQUtil; @override public void insertLogVisit(TbLogVisit TbLogVisit) {try {// Connection Connection = activeMQUtil.getConnection(); connection.start(); Session Session = Connection.createsession (true, session.session_transacted); Queue testQueue = session.createQueue("LOG_VISIT_QUEUE"); MessageProducer producer = session.createProducer(testqueue); MapMessage mapMessage=new ActiveMQMapMessage(); String toString = JSONObject.fromObject(tbLogVisit).toString(); mapMessage.setString("tbLogVisit",toString); producer.setDeliveryMode(DeliveryMode.PERSISTENT); producer.send(mapMessage); session.commit(); Connection.close (); // A transactional message that must be committed to take effect. } catch (JMSException e) { e.printStackTrace(); }}}Copy the code
Step 6: Receive the message to add services:
package com.javaliao.portal.listener; import com.javaliao.portal.log4j.BaseLogger; import com.javaliao.portal.model.TbLogVisit; import com.javaliao.portal.service.ActionService; import net.sf.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.annotation.JmsListener; import org.springframework.stereotype.Component; import javax.jms.JMSException; import javax.jms.MapMessage; @Component public class LogVisitListener { @Autowired ActionService actionService; @JmsListener(containerFactory = "jmsQueueListener" ,destination = "LOG_VISIT_QUEUE") public void consumeLogResult(MapMessage mapMessage){ try { String object = mapMessage.getString("tbLogVisit"); JSONObject jsonObject = new JSONObject().fromObject(object); TbLogVisit logVisit = (TbLogVisit) JSONObject.toBean(jsonObject, TbLogVisit.class); int count = actionService.insertLog(logVisit); If (count < 1){baselogger.info (" log update failed "); } } catch (JMSException e) { e.printStackTrace(); }}}Copy the code
Performing business:
package com.javaliao.portal.service.impl; import com.javaliao.portal.mapper.TbLogVisitMapper; import com.javaliao.portal.model.TbLogVisit; import com.javaliao.portal.model.TbLogVisitExample; import com.javaliao.portal.service.ActionService; import com.javaliao.portal.util.ActiveMQUtil; import com.javaliao.portal.util.CollectionHelp; import com.javaliao.portal.util.NumberUtils; import net.sf.json.JSONObject; import org.apache.activemq.command.ActiveMQMapMessage; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.jms.*; import java.util.Date; import java.util.List; @Service public class ActionServiceImpl implements ActionService { @Autowired TbLogVisitMapper tbLogVisitMapper; @param tbLogVisit * @return */ @override public int insertLog(tbLogVisit tbLogVisit) { tbLogVisit.setUpdateTime(new Date()); int count = 0; / / if there is abnormal added directly if (StringUtils. IsNoneEmpty (tbLogVisit. GetVisitThrowingErro ())) {tbLogVisit. SetCreateTime (new Date ()); count = tbLogVisitMapper.insert(tbLogVisit); }else { String visitIpAddress = tbLogVisit.getVisitIpAddress(); String visitApi = tbLogVisit.getVisitApi(); TbLogVisitExample tbLogVisitExample = new TbLogVisitExample(); TbLogVisitExample.Criteria criteria = tbLogVisitExample.createCriteria(); criteria.andVisitIpAddressEqualTo(visitIpAddress); criteria.andVisitApiEqualTo(visitApi); List<TbLogVisit> tbLogVisits = tbLogVisitMapper.selectByExample(tbLogVisitExample); if(CollectionHelp.isNotEmpty(tbLogVisits)){ Long nums = 0L; Double sums = 0D; For (TbLogVisit logVisit: tbLogVisits) {// Count the number of calls Long visitNum = logvisit.getVisitnum (); nums = tbLogVisit.getVisitNum() + visitNum; / / statistics takes Double visitTimeConsumingData = NumberUtils. Double (logVisit. GetVisitTimeConsuming ()); Double visitTimeConsumingParam = NumberUtils.Double(tbLogVisit.getVisitTimeConsuming()); Double sum = visitTimeConsumingData + visitTimeConsumingParam; sums = sums + sum; } Double numDouble = NumberUtils.Double(String.valueOf(nums)); // Sums/Double avg = sums/numDouble; tbLogVisit.setVisitTimeConsuming(avg.toString()); tbLogVisit.setVisitNum(nums); count = tbLogVisitMapper.updateByExample(tbLogVisit,tbLogVisitExample); }else { tbLogVisit.setCreateTime(new Date()); count = tbLogVisitMapper.insert(tbLogVisit); } } return count; }}Copy the code
Here is the address of the project I wrote: github.com/javaliao/pe…
Behind the design is good at the beginning, forcing changes, causes the visitors start time and end time is last time, but I changed the time consuming of each request to the average time is barely effect (but some abnormal request takes time is influence takes statistics, well not to say, the first no good design, also calculate force), the efficiency is also not too bad, It doesn’t affect performance too much,