简体   繁体   English

通过电子邮件记录servlet异常时需要更多信息

[英]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对象的其他上下文信息,例如:

  • Who is the user that is logged in? 谁是登录用户?
  • What was the queryString, URL, URI, ContextPath, ServletPath, and getMethod of the request? 请求的queryString,URL,URI,ContextPath,ServletPath和getMethod是什么?
  • What were the header parameters? 标头参数是什么?
  • What were the parameters? 参数是什么?
  • What were the attribute names/values? 属性名称/值是什么?

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 . 我试图创建自己的类,该类同时实现ServletRequestListenerServletContextListener ,然后注册一个了解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>引用,该引用正确地调用了contextInitializedrequestInitialized当发生异常时, 永远不会调用日志记录处理程序的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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM