通过电子邮件记录 servlet 异常时需要更多信息
Need more info when logging servlet exceptions through email
环境
我运行 一些使用 Servlet 的应用程序,包括那些基于 JSF 和 JAX-WS 的应用程序以及我自己的一些自定义 servlet。我正在使用 Tomcat 7.x 作为我的网络容器。我正在使用 java.util.logging 来记录消息。
现状
对于记录异常,我一直在使用 SMTPHandler,效果很好。以下是我的 logging.properties 文件的相关摘录:
handlers = {... other handlers ...},08SMTP.smtphandler.SMTPHandler
08SMTP.smtphandler.SMTPHandler.level=SEVERE
08SMTP.smtphandler.SMTPHandler.smtpHost=smtp.somedomain.com
08SMTP.smtphandler.SMTPHandler.to=developers@somedomain.com
08SMTP.smtphandler.SMTPHandler.from=developers@somedomain.com
08SMTP.smtphandler.SMTPHandler.subject=MyApplication error message
08SMTP.smtphandler.SMTPHandler.bufferSize=512
08SMTP.smtphandler.SMTPHandler.formatter=java.util.logging.SimpleFormatter
此设置的唯一问题是电子邮件仅包含一个例外。没有关于发生错误的上下文的其他信息。
我想看什么
我希望电子邮件包含来自 ServletRequest
/ HttpServletRequest
object 的其他上下文信息,例如:
- 登录的用户是谁?
- 请求的 queryString、URL、URI、ContextPath、ServletPath 和 getMethod 是什么?
- header 参数是什么?
- 参数是什么?
- 属性是什么names/values?
尝试的解决方案
由 logging.properties
文件配置的日志记录 Handler
无法访问应用程序的其他部分,除非通过静态变量,所以我想我会尝试创建一个日志记录 Handler
以编程方式。我试图制作一个处理程序,但没有办法让它知道在异常发生时处于活动状态的 HttpServletRequest
。
我尝试创建自己的 class 来实现 ServletRequestListener
和 ServletContextListener
,然后注册一个自定义日志记录 Handler
,它知道 [=21] =] 变量,然后在 ServletRequestListener
中设置和清除 ThreadLocal
变量。在正确调用 contextInitialized
和 requestInitialized
的 web.xml
文件中添加 <listener>
引用后, 我的日志记录处理程序的 publish
方法 在发生异常时永远不会被调用。
代码在这里。
public class LoggingWebListener
implements ServletRequestListener, ServletContextListener
{
public static class FtSmtpHandler
extends Handler
{
private final ServletContext sc;
private final ThreadLocal<ServletRequest> servletReqLocal;
public FtSmtpHandler(ServletContext servletContext, ThreadLocal<ServletRequest> servletReqLocal)
{
this.sc = servletContext;
this.servletReqLocal = servletReqLocal;
}
@Override public void publish(LogRecord record)
{
if (record.getLevel().intValue() < Level.WARNING.intValue())
return;
// Don't try to send email if the emailer fails and logs an exception
if (record.getLoggerName().equals(MyEmailHelper.class.getName()))
return;
// CODE TO SEND EMAIL GOES HERE
}
@Override public void flush()
{
}
@Override public void close()
throws SecurityException
{
}
}
public static final Logger glogger = Logger.getGlobal();
public static final Logger logger = Logger.getLogger(LoggingWebListener.class.getName());
private final ThreadLocal<ServletRequest> servletReqLocal = new ThreadLocal<>();
private FtSmtpHandler handler;
public LoggingWebListener()
{
}
@Override public void contextInitialized(ServletContextEvent evt)
{
logger.log(Level.INFO, "Initializing context for " + getClass().getName());
ServletContext servletContext = evt.getServletContext();
handler = new FtSmtpHandler(servletContext, servletReqLocal);
glogger.addHandler(handler);
}
@Override public void contextDestroyed(ServletContextEvent arg0)
{
glogger.removeHandler(handler);
handler = null;
}
@Override public void requestInitialized(ServletRequestEvent evt)
{
ServletRequest servletRequest = evt.getServletRequest();
// logger.log(Level.INFO, "Initializing request for request " + servletRequest);
servletReqLocal.set(servletRequest);
}
@Override public void requestDestroyed(ServletRequestEvent evt)
{
servletReqLocal.remove();
}
}
我做的有没有小错误?这是完全错误的方法吗?是否有一个已经存在的模块可以完成我想要但我还没有找到的模块?还有其他方法可以做我想做的事吗?
This post 提出了一种类似于我所采取的方法,但没有详细信息。
创建一个自定义 servlet 过滤器,所有调用该应用程序时都会触发该过滤器。然后创建一个知道如何格式化请求属性的自定义格式化程序。在过滤器内部,捕获当前请求并将其发送到您安装在 SMTPHandler 上的自定义格式化程序,以获取对请求对象的访问权限。
public class RequestContextFilter implements javax.servlet.Filter {
private static final String CLASS_NAME = MdcFilter.class.getName();
private static final Logger logger = Logger.getLogger("");
private volatile Handler emailer;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
emailer = new SMTPHandler();
//etc...
emailer.setFormatter(new ContextFormatter());
logger.addHandler(emailer);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ContextFormatter.CTX.set(request);
try {
chain.doFilter(request, response);
} finally {
ContextFormatter.CTX.remove();
}
}
@Override
public void destroy() {
logger.removeHandler(emailer);
emailer.close();
}
private static class ContextFormatter extends Formatter {
static final ThreadLocal<ServletRequest> CTX = new ThreadLocal();
private final Formatter txt = new SimpleFormatter();
@Override
public String format(LogRecord record) {
HttpServletRequest req = (HttpServletRequest) CTX.get();
return req.getRequestURI() + " " + txt.format(record);
}
}
}
由于这是使用本地线程,因此如果记录器和过滤器之间存在线程切换,它将无法工作。
环境
我运行 一些使用 Servlet 的应用程序,包括那些基于 JSF 和 JAX-WS 的应用程序以及我自己的一些自定义 servlet。我正在使用 Tomcat 7.x 作为我的网络容器。我正在使用 java.util.logging 来记录消息。
现状
对于记录异常,我一直在使用 SMTPHandler,效果很好。以下是我的 logging.properties 文件的相关摘录:
handlers = {... other handlers ...},08SMTP.smtphandler.SMTPHandler
08SMTP.smtphandler.SMTPHandler.level=SEVERE
08SMTP.smtphandler.SMTPHandler.smtpHost=smtp.somedomain.com
08SMTP.smtphandler.SMTPHandler.to=developers@somedomain.com
08SMTP.smtphandler.SMTPHandler.from=developers@somedomain.com
08SMTP.smtphandler.SMTPHandler.subject=MyApplication error message
08SMTP.smtphandler.SMTPHandler.bufferSize=512
08SMTP.smtphandler.SMTPHandler.formatter=java.util.logging.SimpleFormatter
此设置的唯一问题是电子邮件仅包含一个例外。没有关于发生错误的上下文的其他信息。
我想看什么
我希望电子邮件包含来自 ServletRequest
/ HttpServletRequest
object 的其他上下文信息,例如:
- 登录的用户是谁?
- 请求的 queryString、URL、URI、ContextPath、ServletPath 和 getMethod 是什么?
- header 参数是什么?
- 参数是什么?
- 属性是什么names/values?
尝试的解决方案
由 logging.properties
文件配置的日志记录 Handler
无法访问应用程序的其他部分,除非通过静态变量,所以我想我会尝试创建一个日志记录 Handler
以编程方式。我试图制作一个处理程序,但没有办法让它知道在异常发生时处于活动状态的 HttpServletRequest
。
我尝试创建自己的 class 来实现 ServletRequestListener
和 ServletContextListener
,然后注册一个自定义日志记录 Handler
,它知道 [=21] =] 变量,然后在 ServletRequestListener
中设置和清除 ThreadLocal
变量。在正确调用 contextInitialized
和 requestInitialized
的 web.xml
文件中添加 <listener>
引用后, 我的日志记录处理程序的 publish
方法 在发生异常时永远不会被调用。
代码在这里。
public class LoggingWebListener
implements ServletRequestListener, ServletContextListener
{
public static class FtSmtpHandler
extends Handler
{
private final ServletContext sc;
private final ThreadLocal<ServletRequest> servletReqLocal;
public FtSmtpHandler(ServletContext servletContext, ThreadLocal<ServletRequest> servletReqLocal)
{
this.sc = servletContext;
this.servletReqLocal = servletReqLocal;
}
@Override public void publish(LogRecord record)
{
if (record.getLevel().intValue() < Level.WARNING.intValue())
return;
// Don't try to send email if the emailer fails and logs an exception
if (record.getLoggerName().equals(MyEmailHelper.class.getName()))
return;
// CODE TO SEND EMAIL GOES HERE
}
@Override public void flush()
{
}
@Override public void close()
throws SecurityException
{
}
}
public static final Logger glogger = Logger.getGlobal();
public static final Logger logger = Logger.getLogger(LoggingWebListener.class.getName());
private final ThreadLocal<ServletRequest> servletReqLocal = new ThreadLocal<>();
private FtSmtpHandler handler;
public LoggingWebListener()
{
}
@Override public void contextInitialized(ServletContextEvent evt)
{
logger.log(Level.INFO, "Initializing context for " + getClass().getName());
ServletContext servletContext = evt.getServletContext();
handler = new FtSmtpHandler(servletContext, servletReqLocal);
glogger.addHandler(handler);
}
@Override public void contextDestroyed(ServletContextEvent arg0)
{
glogger.removeHandler(handler);
handler = null;
}
@Override public void requestInitialized(ServletRequestEvent evt)
{
ServletRequest servletRequest = evt.getServletRequest();
// logger.log(Level.INFO, "Initializing request for request " + servletRequest);
servletReqLocal.set(servletRequest);
}
@Override public void requestDestroyed(ServletRequestEvent evt)
{
servletReqLocal.remove();
}
}
我做的有没有小错误?这是完全错误的方法吗?是否有一个已经存在的模块可以完成我想要但我还没有找到的模块?还有其他方法可以做我想做的事吗?
This post 提出了一种类似于我所采取的方法,但没有详细信息。
创建一个自定义 servlet 过滤器,所有调用该应用程序时都会触发该过滤器。然后创建一个知道如何格式化请求属性的自定义格式化程序。在过滤器内部,捕获当前请求并将其发送到您安装在 SMTPHandler 上的自定义格式化程序,以获取对请求对象的访问权限。
public class RequestContextFilter implements javax.servlet.Filter {
private static final String CLASS_NAME = MdcFilter.class.getName();
private static final Logger logger = Logger.getLogger("");
private volatile Handler emailer;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
emailer = new SMTPHandler();
//etc...
emailer.setFormatter(new ContextFormatter());
logger.addHandler(emailer);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ContextFormatter.CTX.set(request);
try {
chain.doFilter(request, response);
} finally {
ContextFormatter.CTX.remove();
}
}
@Override
public void destroy() {
logger.removeHandler(emailer);
emailer.close();
}
private static class ContextFormatter extends Formatter {
static final ThreadLocal<ServletRequest> CTX = new ThreadLocal();
private final Formatter txt = new SimpleFormatter();
@Override
public String format(LogRecord record) {
HttpServletRequest req = (HttpServletRequest) CTX.get();
return req.getRequestURI() + " " + txt.format(record);
}
}
}
由于这是使用本地线程,因此如果记录器和过滤器之间存在线程切换,它将无法工作。