配置 logback.xml

logback.xml新增SMTPAppender配置

    <appender name="MAIL" class="ch.qos.logback.classic.net.SMTPAppender">
        <smtpHost>smtp.mxhichina.com</smtpHost>
        <smtpPort>465</smtpPort>
        <SSL>true</SSL>
        <to>to@test.com</to>
        <from>ABC Finance Manager &lt;from@test.com&gt;</from>
        <username>from@test.com</username>
        <password>from_password</password>
        <subject>ERROR: %logger{20} - %m</subject>
        <evaluator class="ch.qos.logback.classic.boolex.OnErrorEvaluator" />
        <layout class="ch.qos.logback.classic.html.HTMLLayout"/>
        <cyclicBufferTracker class="ch.qos.logback.core.spi.CyclicBufferTracker">
            <bufferSize>1</bufferSize>
        </cyclicBufferTracker>
    </appender>
    <root level="DEBUG">
        <appender-ref ref="MAIL"/>
    </root>

说明:

  • STMPAppender类是logback=classic模块内的实现类
  • to:可以有多个,所以可以同时给多个邮箱发送日志信息。
  • from:有两种格式,一种是带别名的,上面的配置就是使用了这个中配置方式。另一种是不带别名,直接填入发送人的邮件地址。如果username不为空的话,需要与这个字段填入的邮箱地址一致。
  • username:非必填。这个字段有一个比较隐藏的用处,就是用来判断目标的邮件服务器是否需要做认证检查。该字段需要填入发送人的邮件地址。
  • password:非必填。如果username不为空,那么这个字段必定不为空,填入发送人邮箱密码。
  • subject:该字段是邮件的标题内容的格式。
  • evaluator:非必填。这个字段主要是用于日志的appender层过滤,当前使用的是logback官方实现的OnErrorEvaluatorOnErrorEvaluator是用于过滤非错误级别的日志,当且仅当日志为错误级别的时候才调用该appender。如配置中根记录器的级别设置成DEBUG,但是我只希望发送ERROR级别的日志,并且对所有记录器有效,这个时候evaluator字段就成了一个快捷的解决方案了。当然开发者也可以自己实现evaluator。
  • layout:邮件内容的排版方式。
  • cyclicBufferTracker:非必填。主要用于自定义每封邮件包含的日志条数。

以上配置即可实现错误级别日志通过from@test.com发送邮件到to@test.com。发送结果如下:

5bdde9d4ef1a487aae28bf10ccf41cb5.png

配置Spring的异常统一处理

方法一:实现HandlerExceptionResolver接口

@Component
public class ExceptionResolver implements HandlerExceptionResolver {
    private Logger logger = LoggerFactory.getLogger(ExceptionResolver.class);
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        logger.error(e.getMessage(), e);
        return null;
    }
}

方法二:使用@ControllerAdvice注解

@ControllerAdvice(annotations = {RestController.class,Controller.class})
public class ExceptionInterceptor {
    private Logger logger = LoggerFactory.getLogger(ExceptionInterceptor.class);

    @ExceptionHandler(value = {Exception.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ModelAndView exception(Exception exception, WebRequest request) {
        logger.error(exception.getMessage(), exception);
        return  new ModelAndView("error/errorPage");
    }
}

说明:

  • @ControllerAdvice(annotations = {RestController.class})配置你需要拦截的类,@ControllerAdvice(basePackages = “com.demo”) 这也可以。
  • @ExceptionHandler配置需要处理的异常类型。
  • @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 告诉浏览器这是什么错误类型
  • 最后就可以记录日志,根据需求做一些处理,返回对应的ModelAndView跳转到对应的错误页面
  • 需要注意的是,上面这些配置,只能配置你的程序中抛出的错误
  1. 如果是用户请求了一个不存在的页面,没有对应的@RequestMapping,此时Spring的DispatcherServlet就会处理掉返回404,不会进入任何一个controller
  2. 还有比如spring security之类的权限管理模块,如果用户的密码正确,但是该账户的权限组没有权限访问当前页面,此时权限模块会有自己的AccessDeniedHandler处理,也不会进入刚才配置的@ControllerAdvice

未映射的接口异常抛出

针对上述问题1的解决方案是:我们可以配置Spring在没有对应的@RequestMapping时,不要自行处理,让他抛出一个NoHandlerFoundException的异常,从而让我们配置的@ControllerAdvice进行统一处理。

web.xml文件中配置修改如下:

    <servlet>
        <servlet-name>springServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/config-servlet.xml</param-value>
        </init-param>
        <init-param>
            <param-name>throwExceptionIfNoHandlerFound</param-name>
            <param-value>true</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

配置Spring Security

针对上述问题2的解决方法如下:

@Override  
protected void configure(HttpSecurity http) throws Exception {  
      
    http  
        .authorizeRequests()  
            .antMatchers("/resources/**")  
            .permitAll()  
        .anyRequest()  
            .authenticated()  
            .and()  
        .exceptionHandling()  
            .accessDeniedPage("/error/accessDenied.html")  
            .and()  
        .formLogin()  
            .loginPage("/login")  
            .permitAll()  
            .and()  
        .logout()  
            .permitAll();  
  
}  

这个方法未亲测,仅记录未下次遇到时作为首选方案。

——————————————————————————
行路不知花开处,蓦然回首芷兰香。