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

标签 java servlets tomcat7 java.util.logging

环境

我正在运行许多使用 Servlet 的应用程序,包括基于 JSF 和 JAX-WS 的应用程序以及我自己的一些自定义 servlet。我使用 Tomcat 7.x 作为我的 Web 容器。我正在使用 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对象例如:

  • 登录的用户是谁?
  • 请求的 queryString、URL、URI、ContextPath、ServletPath 和 getMethod 是什么?
  • header 参数是什么?
  • 参数是什么?
  • 属性名称/值是什么?

尝试的解决方案

记录Handlerlogging.properties 配置除了通过静态变量之外,文件无法访问应用程序的其他部分,所以我想我应该尝试创建一个日志 Handler以编程方式。我尝试制作一个处理程序,但没有办法让它知道 HttpServletRequest在异常发生时处于 Activity 状态。

我尝试创建自己的类来实现 ServletRequestListenerServletContextListener ,然后注册自定义日志记录 Handler知道 ThreadLocal<ServletRequest>变量,然后设置并清除 ThreadLocal ServletRequestListener 中的变量。添加<listener>后引用我的web.xml正确调用 contextInitialized 的文件和requestInitialized我的日志处理程序的 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);
            }
        }
    }

由于这是使用本地线程,因此如果记录器和过滤器之间存在线程切换,它将无法工作。

关于java - 通过电子邮件记录 servlet 异常时需要更多信息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33397154/

相关文章:

java - 实体 : Conditionals + Component members vs. O(1) 策略的哈希查找

java - JNDI 在服务器初始化函数中抛出 Servlet 异常

java - 如何将我的 JAR 库添加到 ubuntu 中的 tomcat7 安装

java - tomcat 7重启后恢复用户登录

java - 注销后我无法重新登录,继续重定向到 "mysite/remove-group-member.icepush"。为什么?

java - 如何使用 GWT RPC 在客户端保存文件?

java - 多对多关系中没有序号键的枚举

java - 从 redshift 异步批量卸载

java - 如何让servlet容器(Tomcat)中断/销毁servlet请求?

javascript - 如何使用 javascript 将系统时区发送到 servlet