[英]Need more info when logging servlet exceptions through email
The environment 环境
I'm running a number of applications that use Servlets, including those based on JSF and JAX-WS and some of my own custom servlets. 我正在运行许多使用Servlet的应用程序,包括基于JSF和JAX-WS的应用程序以及一些自己的自定义Servlet。 I'm using Tomcat 7.x as my web container.
我正在使用Tomcat 7.x作为我的Web容器。 I'm using java.util.logging for logging messages.
我正在使用java.util.logging记录消息。
Current Situation 现在的情况
For logging exceptions, I have been using SMTPHandler which has worked very well. 对于记录异常,我一直在使用SMTPHandler ,该方法效果很好。 Here are the relevant excerpts from my logging.properties file:
以下是我的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
The only problem with this setup is that the email only contains an exception. 此设置的唯一问题是电子邮件仅包含一个例外。 There is no other information about the context in which the error happened.
没有关于错误发生的上下文的其他信息。
What I'd Like To See 我想看到的
I'd like the email to contain other contextual information from the ServletRequest
/ HttpServletRequest
object such as: 我希望电子邮件包含来自
ServletRequest
/ HttpServletRequest
对象的其他上下文信息,例如:
The Attempted Solution 尝试的解决方案
Logging Handler
s configured by the logging.properties
file don't have access to other parts of the application except through static variables, so I thought I'd try to create a logging Handler
programatically. 由
logging.properties
文件配置的Logging Handler
只能通过静态变量访问应用程序的其他部分,因此我认为我将尝试以编程方式创建Logging Handler
。 I've tried to make a handler, but there is not a way for it to know about the HttpServletRequest
that is active at the time of the exception. 我试图创建一个处理程序,但是没有办法让它知道异常发生时处于活动状态的
HttpServletRequest
。
I've tried to create my own class that implements both ServletRequestListener
and ServletContextListener
, then registers a custom logging Handler
that knows about a ThreadLocal<ServletRequest>
variable, and then set and clear that ThreadLocal
variable in the ServletRequestListener
. 我试图创建自己的类,该类同时实现
ServletRequestListener
和ServletContextListener
,然后注册一个了解ThreadLocal<ServletRequest>
变量的自定义日志记录Handler
,然后在ServletRequestListener
设置并清除该ThreadLocal
变量。 After adding a <listener>
reference in my web.xml
file which correctly calls contextInitialized
and requestInitialized
, my logging Handler's publish
method is never called when an exception happens . 在我的
web.xml
文件中添加了一个<listener>
引用,该引用正确地调用了contextInitialized
和requestInitialized
, 当发生异常时, 永远不会调用日志记录处理程序的publish
方法 。
The code for this is here. 代码在这里。
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();
}
}
Is there a small mistake in what I'm doing? 我的工作有小错误吗? Is it the totally wrong approach?
这是完全错误的方法吗? Is there an already existing module that will do what I want that I haven't found?
是否有一个已经存在的模块可以执行我尚未找到的所需功能? Is there another way to do what I want to do?
还有另一种方法可以做我想做的事吗?
This post suggests an approach similar to what I've taken, but does not have the details. 这篇文章提出了一种与我所采取的方法类似的方法,但是没有细节。
Create a custom servlet filter that will be triggered for all calls the the application. 创建一个自定义servlet过滤器,该过滤器将为应用程序的所有调用触发。 Then create a custom formatter that knows how to format the properties of your request.
然后创建一个自定义格式化程序,该格式化程序知道如何格式化请求的属性。 Inside the filter, capture the current request and send it over to the custom formatter that you installed on the SMTPHandler to gain access to the request object.
在过滤器内部,捕获当前请求,并将其发送到安装在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);
}
}
}
Since this is using a thread local it won't work if there is a thread handoff between the logger and filter. 由于这使用的是本地线程,因此如果记录器和过滤器之间存在线程切换,则将无法使用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.