我刚刚扩展了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。
答案 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自己的FullAjaxExceptionHandler
和FacesExceptionFilter
来记录所有异常以及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->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个优点: