OmniFaces FullAjaxExceptionHandler 日志记录专业化
OmniFaces FullAjaxExceptionHandler logging specialization
我刚刚扩展了 OminFaces 的 FullAjaxExceptionHandler
class 以添加我的项目所需的简单功能。
我所要做的就是简单地用我的记录器记录所有异常,并在日志和请求中添加一个唯一的错误标识符(具有特定的属性名称),该标识符将显示在错误页面中与其他信息。
这将允许快速检索日志中的错误(用户只需将此 "identifier" 发送给支持团队)。
我重写了 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。
好吧,这似乎行得通...但我不知道这里是否适合做此类事情。我宁愿重载 only logException()
方法,因为我想要专攻的是日志记录而不是其他行为。我做得好还是有更好的解决方案?
我正在使用 vanilla Java EE 7(所以是 JSF 2.2)、OmniFaces 2.6.9 和 Payara 5.181 以及 Java SE 1.8.0u170。
很抱歉没有回答你的直接问题,但也许我可以给你一个更好的整体解决方案,我们在生产中使用它,让你避免编辑 OmniFaces 代码,并使用 servlet 过滤器为你提供更强大的日志记录。
使用 servlet 过滤器,您可以利用 Log4J 或 SLF4J 映射设备上下文,将变量放在其使用的线程上。就像您一样,我们也使用 UUID 为每次 HTTP 请求生成一个唯一的 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>
它有两个优点:
- 您可以在错误页面中使用 "javax.servlet.error.uuid",因为它已被填充到请求或响应中。
- 您日志中的所有日志消息都有 UUID,因此您可以跟踪整个请求,从客户通过您的所有逻辑提交到响应(或错误),快速轻松。如果客户在他们的错误页面上使用该 UUID 调用,您可以 GREP 您的日志并查看他们的整个事件链,包括堆栈跟踪。
FullAjaxExceptionHandler
不会记录 非-Ajax JSF 请求的异常。非 Ajax JSF 请求(和非 JSF 请求!)的异常通常会到达 servlet Filter
。
findErrorPageLocation()
方法确实在 FullAjaxExceptionHandler
检查它是否是 Ajax 请求之前调用,因此您还可以记录非 Ajax JSF 请求的异常那里。
但是如果您想要记录非 JSF 请求的异常,那么您仍然需要一个 servlet Filter
。在这种情况下,非 Ajax JSF 请求的异常最终将被记录两次。第一次通过自定义异常处理程序的 findErrorPageLocation()
方法,第二次通过 servlet Filter
.
理想情况下,当您想为每个可能抛出的异常微调日志记录时,您最好同时拥有 自定义 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)肯定是一个有用的改进。这确实不是一个不常见的业务需求。它将在下一个版本中出现。
我刚刚扩展了 OminFaces 的 FullAjaxExceptionHandler
class 以添加我的项目所需的简单功能。
我所要做的就是简单地用我的记录器记录所有异常,并在日志和请求中添加一个唯一的错误标识符(具有特定的属性名称),该标识符将显示在错误页面中与其他信息。
这将允许快速检索日志中的错误(用户只需将此 "identifier" 发送给支持团队)。
我重写了 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。
好吧,这似乎行得通...但我不知道这里是否适合做此类事情。我宁愿重载 only logException()
方法,因为我想要专攻的是日志记录而不是其他行为。我做得好还是有更好的解决方案?
我正在使用 vanilla Java EE 7(所以是 JSF 2.2)、OmniFaces 2.6.9 和 Payara 5.181 以及 Java SE 1.8.0u170。
很抱歉没有回答你的直接问题,但也许我可以给你一个更好的整体解决方案,我们在生产中使用它,让你避免编辑 OmniFaces 代码,并使用 servlet 过滤器为你提供更强大的日志记录。
使用 servlet 过滤器,您可以利用 Log4J 或 SLF4J 映射设备上下文,将变量放在其使用的线程上。就像您一样,我们也使用 UUID 为每次 HTTP 请求生成一个唯一的 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>
它有两个优点:
- 您可以在错误页面中使用 "javax.servlet.error.uuid",因为它已被填充到请求或响应中。
- 您日志中的所有日志消息都有 UUID,因此您可以跟踪整个请求,从客户通过您的所有逻辑提交到响应(或错误),快速轻松。如果客户在他们的错误页面上使用该 UUID 调用,您可以 GREP 您的日志并查看他们的整个事件链,包括堆栈跟踪。
FullAjaxExceptionHandler
不会记录 非-Ajax JSF 请求的异常。非 Ajax JSF 请求(和非 JSF 请求!)的异常通常会到达 servlet Filter
。
findErrorPageLocation()
方法确实在 FullAjaxExceptionHandler
检查它是否是 Ajax 请求之前调用,因此您还可以记录非 Ajax JSF 请求的异常那里。
但是如果您想要记录非 JSF 请求的异常,那么您仍然需要一个 servlet Filter
。在这种情况下,非 Ajax JSF 请求的异常最终将被记录两次。第一次通过自定义异常处理程序的 findErrorPageLocation()
方法,第二次通过 servlet Filter
.
理想情况下,当您想为每个可能抛出的异常微调日志记录时,您最好同时拥有 自定义 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)肯定是一个有用的改进。这确实不是一个不常见的业务需求。它将在下一个版本中出现。