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

来源:互联网 发布:c语言培训深圳 编辑:程序博客网 时间:2024/05/06 21:21

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


一般情况下,对来自浏览器的请求的拦截,是利用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) */@Overridepublicvoid 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()  */@OverridepublicLogLevel getRootLogLevel() {returnrootLogLevel;} publicvoid setCustomLogLevel(Map<String, LogLevel> customLogLevel) {this.customLogLevel= customLogLevel;} @OverridepublicMap<String, LogLevel> getCustomLogLevel() {returncustomLogLevel;} publicvoid setLogEntityService(LogEntityService logEntityService) {this.logEntityService= logEntityService;} @Overridepublicvoid 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 { @AutowiredprivateTaskService taskService; @AutowiredprivateValidator 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  */@Overridepublicboolean preHandle(HttpServletRequest request,HttpServletResponseresponse, Object handler) throws Exception {LogUitl.putRequest(request);returntrue;} @Overridepublicvoid 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) {//得到LogMessageObjectfinalLogMessageObject logMessageObject = LogUitl.getArgs();//另起线程异步操作newThread(new Runnable() { @Overridepublicvoid 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)  */@Overridepublicvoid afterCompletion(HttpServletRequest request,HttpServletResponseresponse, Object handler, Exception ex)throwsException {LogUitl.removeRequest();} publicvoid setLogAPI(LogAPI logAPI) {this.logAPI= logAPI;}



看看最终得到的日志数据