日志处理 三:Filter+自定义注解实现 系统日志跟踪功能

本文主要是介绍日志处理 三:Filter+自定义注解实现 系统日志跟踪功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章有点长,可以先看目录。
代码有点多,如果感兴趣或者需要的话,欢迎和我交流。


一般情况下,对来自浏览器的请求的拦截,是利用Filter实现的,这种方式可以实现Bean预处理、后处理。 

这里是利用springmvc的拦截器开发了log功能,用于跟踪、记录系统用户的操作轨迹,以便日后的认责。

该功能使用很方便,是可配置的、细粒度的日志记录功能。之所以细粒度,因为level分为三层,默认包层(rootLogLevel默认值TRACE),自定义包层(customLogLevel),具体方法层(@Log默认值TRACE)

 

简单介绍SpringMVCHandlerInterceptorAdapter

Spring MVC的拦截器不仅可实现Filter的所有功能,还可以更精确的控制拦截精度。 

Spring为我们提供了org.springframework.web.servlet.handler.HandlerInterceptorAdapter这个适配器,继承此类,可以非常方便的实现自己的拦截器。他有三个方法:

public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception{ return true; } public voidpostHandle( HttpServletRequest request, HttpServletResponse response, Objecthandler, ModelAndView modelAndView) throwsException { } public voidafterCompletion( HttpServletRequest request, HttpServletResponse response, Objecthandler, Exception ex) throwsException { } 


分别实现预处理、后处理(调用了Service并返回ModelAndView,但未进行页面渲染)、返回处理(已经渲染了页面) 

在preHandle中,可以进行编码、安全控制等处理; 

在postHandle中,有机会修改ModelAndView; 

在afterCompletion中,可以根据ex是否为null判断是否发生了异常,进行日志记录。 

 

配置:spring-mvc.xml

<!--系统日志跟踪功能 -->
<beanid="log4JDBCImpl"class="com.ketayao.ketacustom.log.impl.Log4JDBCImpl" >
<propertyname="logEntityService" ref="logEntityServiceImpl"/>
<propertyname="rootLogLevel" value="ERROR"/>
<!--自定义日志级别 -->
<propertyname="customLogLevel"><map><entrykey="com.ketayao.ketacustom" value="TRACE" /><entrykey="com.sample" value="INFO" /></map></property>
</bean><mvc:interceptors>
<mvc:interceptor>
<mvc:mappingpath="/management/**" />
<mvc:mappingpath="/login/timeout/success"/>
<beanclass="com.ketayao.ketacustom.log.spring.LogInterceptor" >
<propertyname="logAPI" ref="log4JDBCImpl"/>
</bean>
</mvc:interceptor>                
</mvc:interceptors>


 

mvc:interceptors

这个标签用于注册一个自定义拦截器或者是WebRequestInterceptors.

可以通过定义URL来进行路径请求拦截,可以做到较为细粒度的拦截控制。

 

 

日志的JDBC实现

LogAPI:自定义LogAPI接口

/***        自定义LogAPI接口* 定义日志记录和日志级别规范*/publicinterface LogAPI {
voidlog(String message, LogLevel logLevel);voidlog(String message, Object[] objects, LogLevel logLevel);/**** 得到全局日志等级* @return*/
LogLevelgetRootLogLevel();/**** 得到自定义包的日志等级* @return*/
Map<String,LogLevel> getCustomLogLevel();
}


 

LogLevel:日志级别枚举类型

/*** 值越大,等级越高。        */publicenum LogLevel {
TRACE("TRACE"),DEBUG("DEBUG"),INFO("INFO"),WARN("WARN"),ERROR("ERROR");privateString value;LogLevel(Stringvalue) {
this.value= value;
}publicString value() {
returnthis.value;
}
}


由代码发现,枚举值后面有属性值,这是Enums的构造函数的用法

 

Log4JDBCImpl:实现LogAPI接口

/*** 全局日志等级<包日志等级<类和方法日志等级* @author        <ahref="mailto:ketayao@gmail.com">ketayao</a>* Version 2.1.0* @since  2013-5-3 下午4:41:55*/
publicclass Log4JDBCImpl implements LogAPI {privateLogLevel rootLogLevel = LogLevel.ERROR;privateLogEntityService logEntityService;privateMap<String, LogLevel> customLogLevel = Maps.newHashMap();/**** @param message* @param objects* @param logLevel * @seecom.ketayao.ketacustom.log.impl.LogAdapter#log(java.lang.String,java.lang.Object[], com.ketayao.ketacustom.log.LogLevel)*/
@Override
publicvoid log(String message, Object[] objects, LogLevel logLevel){        MessageFormatmFormat = new MessageFormat(message);
Stringresult = mFormat.format(objects);if(!StringUtils.isNotBlank(result)) {
return;
}Subjectsubject = SecurityUtils.getSubject();
ShiroDbRealm.ShiroUsershiroUser = (ShiroDbRealm.ShiroUser)subject.getPrincipal();//result= shiroUser.toString() + ":" + result;LogEntitylogEntity = new LogEntity();
logEntity.setCreateTime(newDate());logEntity.setUsername(shiroUser.getLoginName());
logEntity.setMessage(result);
logEntity.setIpAddress(shiroUser.getIpAddress());
logEntity.setLogLevel(logLevel);logEntityService.save(logEntity);
}publicvoid setRootLogLevel(LogLevel rootLogLevel) {
this.rootLogLevel= rootLogLevel;
}/**  ** @return * @seecom.ketayao.ketacustom.log.LogTemplate#getRootLogLevel() */
@Override
publicLogLevel getRootLogLevel() {
returnrootLogLevel;
}publicvoid setCustomLogLevel(Map<String, LogLevel> customLogLevel) {
this.customLogLevel= customLogLevel;
}@Override
publicMap<String, LogLevel> getCustomLogLevel() {
returncustomLogLevel;
}publicvoid setLogEntityService(LogEntityService logEntityService) {
this.logEntityService= logEntityService;
}@Override
publicvoid log(String message, LogLevel logLevel) {
log(message,null,logLevel);
}}


 

 

日志的业务逻辑的记录

Log:自定义注解

/***        */
@Documented
@Target({METHOD})
@Retention(RUNTIME)
public@interface Log {
/**** 日志信息* @return*/
Stringmessage();/**** 日志记录等级* @return*/
LogLevellevel() default LogLevel.TRACE;/**** 是否覆盖包日志等级* 1.为false不会参考level属性。* 2.为true会参考level属性。* @return*/
booleanoverride() default false;
}


 

LogUitl:将request放入ThreadLocal用于LOG_ARGUMENTS注入

/***将request放入ThreadLocal用于LOG_ARGUMENTS注入。        */
publicabstract class LogUitl {
//用于存储每个线程的request请求
privatestatic final ThreadLocal<HttpServletRequest> LOCAL_REQUEST = newThreadLocal<HttpServletRequest>();publicstatic void putRequest(HttpServletRequest request) {
LOCAL_REQUEST.set(request);
}publicstatic HttpServletRequest getRequest() {
returnLOCAL_REQUEST.get();
}publicstatic void removeRequest() {
LOCAL_REQUEST.remove();
}/*** 将LogMessageObject放入LOG_ARGUMENTS。* 描述* @param logMessageObject*/
publicstatic void putArgs(LogMessageObject logMessageObject) {
HttpServletRequestrequest = getRequest();
request.setAttribute(SecurityConstants.LOG_ARGUMENTS,logMessageObject);
}/*** 得到LogMessageObject。* 描述* @param logMessageObject*/
publicstatic LogMessageObject getArgs() {
HttpServletRequestrequest = getRequest();
return(LogMessageObject)request.getAttribute(SecurityConstants.LOG_ARGUMENTS);
}
}


 

TaskController :处理操作Task模块的用户请求逻辑

/***莫紧张,仅仅是一个例子。        */
@Controller
@RequestMapping("/management/sample/task")
publicclass TaskController {@Autowired
privateTaskService taskService;@Autowired
privateValidator validator;privatestatic final String CREATE = "management/sample/task/create";
privatestatic final String UPDATE = "management/sample/task/update";
privatestatic final String LIST = "management/sample/task/list";
privatestatic final String VIEW = "management/sample/task/view";@RequiresPermissions("Task:save")
@RequestMapping(value="/create",method=RequestMethod.GET)
publicString preCreate(Map<String, Object> map) {
returnCREATE;
}/*** LogMessageObject的write用法实例。*/
@Log(message="添加了{0}任务,LogMessageObject的isWritten为true。",level=LogLevel.INFO)
@RequiresPermissions("Task:save")
@RequestMapping(value="/create",method=RequestMethod.POST)
public@ResponseBody String create(Task task) {
BeanValidators.validateWithException(validator,task);
taskService.save(task);//加入一个LogMessageObject,该对象的isWritten为true,会记录日志。
LogUitl.putArgs(LogMessageObject.newWrite().setObjects(newObject[]{task.getTitle()}));
returnAjaxObject.newOk("任务添加成功!").toString();
}/*** LogMessageObject的ignore用法实例,ignore不会记录日志。*/
@Log(message="你永远不会看见该日志,LogMessageObject的isWritten为false。",level=LogLevel.INFO)
@RequiresPermissions("Task:edit")
@RequestMapping(value="/update/{id}",method=RequestMethod.GET)
publicString preUpdate(@PathVariable Long id, Map<String, Object> map) {
Tasktask = taskService.get(id);map.put("task",task);//加入一个LogMessageObject,该对象的isWritten为false,不会记录日志。
LogUitl.putArgs(LogMessageObject.newIgnore());
returnUPDATE;
}/*** Log的level用法实例*1.level分为三层,默认包层(rootLogLevel默认值TRACE),自定义包层(customLogLevel),具体方法层(@Log默认值TRACE)*2.参考顺序:默认包层->自定义包层->具体方法层->LogMessageObject* 3.有自定义包层的level等级会忽略默认包层* 4.@Log的level大于等于自定义包层或者默认的level会输出日志;小于则不会。*/
@Log(message="Log的level用法实例,LogLevel.TRACE小于自定义包层LogLevel.INFO,不会输出日志。",level=LogLevel.TRACE)
@RequiresPermissions("Task:edit")
@RequestMapping(value="/update",method=RequestMethod.POST)
public@ResponseBody String update(Task task) {
BeanValidators.validateWithException(validator,task);
taskService.update(task);returnAjaxObject.newOk("任务修改成功!").toString();
}/*** Log的override用法实例* 假如override为true,会忽略掉level** 批量删除展示*/
@Log(message="Log的override用法实例,override为true,会忽略掉level。删除了{0}任务。",level=LogLevel.TRACE, override=true)
@RequiresPermissions("Task:delete")
@RequestMapping(value="/delete",method=RequestMethod.POST)
public@ResponseBody String deleteMany(Long[] ids) {
String[]titles = new String[ids.length];
for(int i = 0; i < ids.length; i++) {
Tasktask = taskService.get(ids[i]);
taskService.delete(task.getId());titles[i]= task.getTitle();
}LogUitl.putArgs(LogMessageObject.newWrite().setObjects(newObject[]{Arrays.toString(titles)}));
returnAjaxObject.newOk("任务删除成功!").setCallbackType("").toString();
}@RequiresPermissions("Task:view")
@RequestMapping(value="/list",method={RequestMethod.GET, RequestMethod.POST})
publicString list(Page page, String keywords, Map<String, Object> map) {
List<Task>tasks = null;
if(StringUtils.isNotBlank(keywords)) {
tasks= taskService.find(page, keywords);
} else{
tasks= taskService.findAll(page);
}map.put("page",page);
map.put("tasks",tasks);
map.put("keywords",keywords);returnLIST;
}/*** 自定look权限,实例。* 描述* @param id* @param map* @return*/
@RequiresPermissions("Task:look")
@RequestMapping(value="/view/{id}",method={RequestMethod.GET})
publicString view(@PathVariable Long id, Map<String, Object> map) {
Tasktask = taskService.get(id);
map.put("task",task);
returnVIEW;
}
}


 

使用Filter拦截,将日志信息持久化

LogInterceptor:        继承HandlerInterceptorAdapter,覆盖三个方法实现

/***        继承HandlerInterceptorAdapter,覆盖三个方法实现*/publicclass LogInterceptor extends HandlerInterceptorAdapter {
privatefinal static Logger LOGGER = LoggerFactory.getLogger(LogInterceptor.class);privateLogAPI logAPI;/**  * 将request存入LogUitl中的LOCAL_REQUEST。* @param request* @param response* @param handler* @return* @throws Exception */
@Override
publicboolean preHandle(HttpServletRequest request,
HttpServletResponseresponse, Object handler) throws Exception {
LogUitl.putRequest(request);
returntrue;
}@Override
publicvoid postHandle(HttpServletRequest request,
HttpServletResponseresponse, Object handler,
ModelAndViewmodelAndView) throws Exception {if(!(handler instanceof HandlerMethod)) {
return;
}finalHandlerMethod handlerMethod = (HandlerMethod)handler;
Methodmethod = handlerMethod.getMethod();finalLog log = method.getAnnotation(Log.class);
if (log!= null) {
//得到LogMessageObject
finalLogMessageObject logMessageObject = LogUitl.getArgs();
//另起线程异步操作
newThread(new Runnable() {@Override
publicvoid run() {
try {
LogLevellastLogLevel = logAPI.getRootLogLevel();//先对自定义包等级做判断
Map<String,LogLevel> customLogLevel = logAPI.getCustomLogLevel();
if(!customLogLevel.isEmpty()) {
Class<?>clazz = handlerMethod.getBean().getClass();
StringpackageName = clazz.getPackage().getName();Set<String>keys = customLogLevel.keySet();
for(String key : keys) {
if(packageName.startsWith(key)) {
lastLogLevel= customLogLevel.get(key);
break;
}
}
}LogMessageObjectdefaultLogMessageObject = logMessageObject;
if(defaultLogMessageObject == null) {
defaultLogMessageObject= LogMessageObject.newWrite();
}if(defaultLogMessageObject.isWritten()) { // 判断是否写入log
//覆盖,直接写入日志
if(log.override()) {
logAPI.log(log.message(),defaultLogMessageObject.getObjects(), log.level());
}else {
//不覆盖,参考方法的日志等级是否大于等于最终的日志等级
if(!log.override() && log.level().compareTo(lastLogLevel) >= 0 ) {
logAPI.log(log.message(),defaultLogMessageObject.getObjects(), log.level());
}
}
}                                                
}catch (Exception e) {
LOGGER.error(Exceptions.getStackTraceAsString(e));
}
}
}).start();}}/*** 清除LogUitl中的LOCAL_REQUEST。* @param request* @param response* @param handler* @param ex* @throws Exception * @seeorg.springframework.web.servlet.handler.HandlerInterceptorAdapter#afterCompletion(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception) */
@Override
publicvoid afterCompletion(HttpServletRequest request,
HttpServletResponseresponse, Object handler, Exception ex)
throwsException {
LogUitl.removeRequest();
}publicvoid setLogAPI(LogAPI logAPI) {
this.logAPI= logAPI;
}



看看最终得到的日志数据

       





这篇关于日志处理 三:Filter+自定义注解实现 系统日志跟踪功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/828334

相关文章

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P