OmniFaces FullAjaxExceptionHandler日志记录专门化

时间:2018-07-27 07:58:14

标签: jsf jsf-2.2 omnifaces

我刚刚扩展了OminFaces的FullAjaxExceptionHandler类,以添加项目所需的简单功能。

我要做的就是简单地使用记录器记录所有异常,并在日志和请求中添加唯一的错误标识符(具有特定的属性名称),该标识符将与其他页面一起显示在错误页面上信息。

这将允许快速检索日志中的错误(用户只需要将此“标识符”发送给支持团队)。 我都覆盖了logException()方法(都尝试了两个重载),但是它们从未被调用。在GitHub上查看FullAjaxExceptionHandler的源代码后,我发现在某些特定情况下会调用它。 因此,我改写了findErrorPageLocation()方法(这是初步的测试代码):

    public final static String ERROR_UUID = "javax.servlet.error.uuid";

    @Override
    protected String findErrorPageLocation(FacesContext context, Throwable exception) {            
        // Retrieve error page location from base class
        String location = super.findErrorPageLocation(context, exception); 
        // generate a unique error identifier
        String uuid = UUID.randomUUID().toString();
        // log into my logger
        logException(context, exception, location, "["+uuid+"]: "+exception.getMessage());
        // Retrieve request, put the new attribute to be shown into page and return the location
        HttpServletRequest req = (HttpServletRequest)context.getExternalContext().getRequest();
        req.setAttribute(ERROR_UUID, uuid);
        return location;
    }

在我刚刚添加的页面中

<li>Error UUID: #{requestScope['javax.servlet.error.uuid']}</li>

显示错误uuid。

好吧,这似乎行得通...但是我不知道这是做这种事情的正确地方。我宁愿仅重载logException()方法,因为 我想专门研究的是日志记录,而没有其他行为。我做得好还是有更好的解决方案?

我正在使用香草Java EE 7(因此是JSF 2.2),带有Payara 5.181的OmniFaces 2.6.9和Java SE 1.8.0u170。

2 个答案:

答案 0 :(得分:3)

FullAjaxExceptionHandler不会记录 -Ajax JSF请求的异常。非Ajax JSF请求(和非JSF请求!)的异常通常会到达servlet Filter

findErrorPageLocation()确实在FullAjaxExceptionHandler检查它是否是Ajax请求之前被调用,因此您也可以在此记录非Ajax JSF请求的异常。

但是,如果您想记录非JSF请求的异常,那么您仍将需要一个servlet Filter。在这种情况下,非Ajax JSF请求的异常最终将被记录两次。第一次通过您的自定义异常处理程序的findErrorPageLocation()方法,第二次通过servlet Filter

理想情况下,每当要针对Web应用程序引发的每个可能的异常微调日志记录时,最好同时具有 和自定义FullAjaxExceptionHandler和自定义FacesExceptionHandler

因此,一个用于JSF Ajax请求期间的异常:

public class CustomExceptionHandler extends FullAjaxExceptionHandler {

    public CustomExceptionHandler(ExceptionHandler wrapped) {
        super(wrapped);
    }

    @Override
    protected void logException(FacesContext context, Throwable exception, String location, LogReason reason) {
        String uuid = UUID.randomUUID().toString();
        String ip = FacesLocal.getRemoteAddr(context);
        FacesLocal.setRequestAttribute(context, "UUID", uuid);
        super.logException(context, exception, location, "[%s][%s] %s", uuid, ip, reason.getMessage(), location);
    }

    public static class Factory extends FullAjaxExceptionHandlerFactory {
        public Factory(ExceptionHandlerFactory wrapped) {
            super(wrapped);
        }

        @Override
        public ExceptionHandler getExceptionHandler() {
            return new CustomExceptionHandler(getWrapped().getExceptionHandler());
        }
    }
}

faces-config.xml中映射为

<factory>
    <exception-handler-factory>com.example.CustomExceptionHandler$Factory</exception-handler-factory>
</factory>

另一个是在所有其他请求期间的异常:

@WebFilter(filterName="customExceptionFilter")
public class CustomExceptionFilter extends FacesExceptionFilter {

    private static final Logger logger = Logger.getLogger(CustomExceptionHandler.class.getName());

    @Override
    public void doFilter(HttpServletRequest request, HttpServletResponse response, HttpSession session, FilterChain chain) throws ServletException, IOException {
        try {
            super.doFilter(request, response, session, chain);
        }
        catch (Exception e) {
            String uuid = UUID.randomUUID().toString();
            String ip = Servlets.getRemoteAddr(request);
            request.setAttribute("UUID", uuid);
            logger.log(Level.SEVERE, String.format("[%s][%s]", uuid, ip), e);
            throw e;
        }
    }
}

web.xml中映射为第一个<filter-mapping>条目:

<filter-mapping>
    <filter-name>customExceptionFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

拥有OmniFaces自己的FullAjaxExceptionHandlerFacesExceptionFilter来记录所有异常以及UUID(和IP)肯定是有用的改进。这确实不是一个不常见的业务需求。它将在下一个版本中提供。

答案 1 :(得分:1)

很抱歉没有回答您的直接问题,但是也许我可以为您提供一个更好的整体解决方案,以便我们在生产环境中使用它,从而避免编辑OmniFaces代码,并使用servlet过滤器为您提供更强大的日志记录。

使用servlet过滤器,您可以利用Log4J或SLF4J映射的设备上下文,该上下文将变量放在要使用的线程上。就像您一样,我们每次每次HTTP请求都使用UUID生成唯一ID。

过滤器:

import java.io.IOException;
import java.util.UUID;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.HttpHeaders;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;


/**
 * Servlet filter used to attach incoming HTTP request values into the MDC so
 * they can be used throughout the entire request-&gt;response cycle for
 * logging.
 */
public class MDCLogFilter implements Filter {

   private static final Logger LOG = LoggerFactory.getLogger(MDCLogFilter.class);

   @Override
   public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
            throws IOException, ServletException {

      // add remote ip of incoming request
      MDC.put("ipv4", request.getRemoteAddr());

      // check for any specific Servlet values
      if (request instanceof HttpServletRequest) {
         final HttpServletRequest httpRequest = (HttpServletRequest) request;
         // try and get the username
         String username = httpRequest.getRemoteUser();

         // if not found then use their IP address as their username
         username = StringUtils.defaultIfBlank(username, request.getRemoteAddr());

         // uppercase the username for consistency
         MDC.put("user", StringUtils.upperCase(username));

         // first look for one passed in from the caller on the header
         String correlationId = httpRequest.getHeader("uuid");
         correlationId = StringUtils.defaultIfBlank(correlationId, UUID.randomUUID().toString());

         // unique ID for this thread context
         MDC.put("uuid", correlationId);
         request.setAttribute("javax.servlet.error.uuid", uuid);

         // get the browser user agent
         MDC.put("user-agent", httpRequest.getHeader(HttpHeaders.USER_AGENT));
      }

      try {
         chain.doFilter(request, response);
      } finally {
         MDC.clear();
      }
   }


}

在您的web.xml中,使用*将这个过滤器应用于每个传入的HTTP请求。

web.xml

<!-- Inject additional logging attributes into each servlet request -->
<filter>
    <filter-name>MDCFilter</filter-name>
    <filter-class>com.melloware.servlet.MDCLogFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MDCFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Logback和Log4J然后可以在日志输出中使用您的MDC值,以便将其附加到每个日志行。我使用SLF4J和Logback,所以下面的示例使用%X表示法在每个日志行中显示唯一ID和用户...

日志设置:

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender" >
    <file>application.log</file>
    <encoder>
        <pattern>%-5level - [id=%X{uuid} user=%X{user}] %logger{35} - %msg%n</pattern>
    </encoder>
</appender>

它有2个优点:

  1. 您可以在错误页面中使用“ javax.servlet.error.uuid”,因为它已被填充在请求或响应中。
  2. 您日志中的所有日志消息都具有UUID,因此您可以从客户通过您的所有逻辑提交之时起,轻松快捷地跟踪整个请求,直至响应(或错误)。如果客户在其错误页面上使用该UUID进行呼叫,则可以为此记录GREP,并查看其整个事件链,包括堆栈跟踪。