获取Java中的所有异常并远程发送它们

获取Java中的所有异常并远程发送它们,java,spring,aspect,spring-aspects,Java,Spring,Aspect,Spring Aspects,我有一个巨大的Java应用程序。我想截获所有Java异常并通过电子邮件发送它们。我无法添加everywhere代码,以便通过try catch发送代码,因此是否可以使用例如Aspect将异常拦截到低级类中并获取异常内容 或者是否有某种方法覆盖某个内部Java类并获取异常负载 什么是可能的?如果ht控件中有所有正在运行的线程,则可以使用Thread.UncaughtExceptionHandler的实现来标记它们。当然,如果应用程序具有深度多线程特性,那么可能会有点棘手。有关错误处理的信息,请阅读

我有一个巨大的Java应用程序。我想截获所有Java异常并通过电子邮件发送它们。我无法添加everywhere代码,以便通过
try catch
发送代码,因此是否可以使用例如Aspect将异常拦截到低级类中并获取异常内容

或者是否有某种方法覆盖某个内部Java类并获取异常负载


什么是可能的?

如果ht控件中有所有正在运行的线程,则可以使用Thread.UncaughtExceptionHandler的实现来标记它们。当然,如果应用程序具有深度多线程特性,那么可能会有点棘手。

有关错误处理的信息,请阅读以下内容

有关错误详细信息和发送电子邮件,请获取打印轨迹

public String printTraceMessage(Exception ex) {
    StringWriter errors = new StringWriter();
    ex.printStackTrace(new PrintWriter(errors));
    return errors.toString();
}

或者您可以使用单独的线程不阻止响应并发送电子邮件

您可以使用

这将拦截包
com.example
中未处理的所有异常。请注意,应用程序中处理(捕获)的异常不能被拦截


另一个解决方案是使用应用程序的日志框架。许多框架,如logback、log4j提供了内置配置,可以通过电子邮件发送日志。

因此,这就是我们使用基于Spring的webapp所做的

为了捕获所有意外异常,我们有一个异常servlet过滤器,它是过滤器链中的第一个/最后一个过滤器

此筛选器将捕获任何异常,然后向我们发送电子邮件。顺便说一句,我们有一个忽略例外列表,我们不报告。考虑客户端中止异常。对我们来说,真的没有任何理由报告这些

对于因用户请求而发生但不应干扰用户结果的任务,我们使用try/catch包装这些操作,然后在该副操作失败时发送电子邮件

例如,如果有人将新数据保存到数据库中,则会更新搜索索引。最终用户只想知道他们的项目已成功保存到数据库,但不需要知道搜索索引更新失败。我们(开发人员是这样做的),但总的来说,最终用户并不关心

然后,对于需要自己线程的后端任务,我们创建了一个线程,该线程执行try/catch语句,并在引发异常时发送电子邮件

类似这样的任务的一个例子是重新索引搜索索引。这可能是一个长时间运行的进程,我们不希望在该进程运行的整个时间内保持http连接的打开状态,因此我们创建了一个新线程,以便在其中运行重新索引。如果出了问题,我们想知道

下面是一些示例代码,向您展示如何实现我们的服务

@Transactional
public UUID saveRecord(RecordRequest recordRequest) {

    Record newRecord = this.recordFactory.create(recordRequest);

    this.recordRepository.add(newRecord);

    this.updateSearch(newRecord);
}

private void updateSearch(Record record) {

    try {

        this.searchIndex.add(record);

    catch(Exception e) {

        this.errorService.reportException(e);
    }
}
以下是异常处理过滤器的代码:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

    try {

        filterChain.doFilter(request, response);

    } catch (Throwable exception) {

        this.handleException(request, response, exception);
    }
}

private void handleException(ServletRequest request, ServletResponse response, Throwable throwable) {

    try {

        this.doHandleException(request, response, throwable);

    } catch (Exception handlingException) {

        LOG.error("This exception that was not handled by the UnhandledExceptionFilter", throwable);
        LOG.error("This exception occurred reporting an unhandled exception, please see the 'cause by' exception above", handlingException);
    }
}

private void doHandleException(ServletRequest request, ServletResponse response, Throwable throwable) throws Exception {

    this.errorResponse.send(request, response);

    this.reportException(request, response, throwable);

}

/**
 * Report exception.
 *
 * @param request   the request
 * @param response  the response
 * @param throwable the throwable
 */
protected void reportException(ServletRequest request, ServletResponse response, Throwable throwable) {

    UnhandledException unhandledException = this.setupExceptionDetails((HttpServletRequest) request, (HttpServletResponse) response, throwable);

    this.exceptionHandlingService.handleUnexpectedException(unhandledException);
}

private UnhandledException setupExceptionDetails(HttpServletRequest request, HttpServletResponse response, Throwable throwable) {

    UnhandledException unhandledException = new UnhandledException(throwable);

    if (response.isCommitted()) {
        unhandledException.put("Session Id", "response already committed, cannot get Session Id");
    } else {
        unhandledException.put("Session Id", request.getSession().getId());
    }
    unhandledException.put("Remote Address", request.getRemoteAddr());
    unhandledException.put("User Agent", request.getHeader(HttpHeaderConstants.USER_AGENT));
    unhandledException.put("Server Name", request.getServerName());
    unhandledException.put("Server Port", "" + request.getServerPort());
    unhandledException.put("Method", request.getMethod());
    unhandledException.put("URL", request.getRequestURI());
    unhandledException.put("Referer", request.getHeader(HttpHeaderConstants.REFERRER));

    Cookie[] cookies = request.getCookies();

    if (cookies != null && cookies.length != 0) {

        for (Cookie cookie : cookies) {

            unhandledException.put(cookie.getName(), cookie.getValue());
        }
    }

    unhandledException.put("Query String", request.getQueryString());

    Enumeration parameterNames = request.getParameterNames();

    while (parameterNames.hasMoreElements()) {

        String parameterName = (String) parameterNames.nextElement();

        String parameterValue = request.getParameter(parameterName);

        if (parameterName.equals("j_password") || parameterName.equals("password") || parameterName.equals("confirmationPassword") || parameterName.equals("oldPassword") || parameterName.equals("confirmNewPassword")) {

            parameterValue = "********";
        }

        unhandledException.put(parameterName, "'" + parameterValue + "'");
    }

    return unhandledException;
}
顺便说一句,当您从生产服务向自己发送电子邮件时,限制您的服务在一分钟内发送的电子邮件数量是非常重要的,并且有一种方法可以将相同类型的异常捆绑到一封电子邮件中

接到经理、经理、经理的电话并不好玩,他们告诉你必须停止对公司电子邮件服务器的DOS(拒绝服务)攻击。两次

我们通过使用Spring集成(带有activemq支持的队列)来限制发送的电子邮件数量,从而解决了这个问题

然后,我们使用计数策略跟踪发送了多少相同的异常,然后尝试将这些电子邮件捆绑到一封电子邮件中,并计算该特定异常发生的次数。

查看Spring的注释。我们用它来做你想做的事。我们有一个web应用程序,它有许多
@Controller
s和
@RestController
s。这将发送一封电子邮件,其中包含有关在这些控制器中的任何方法引发错误时触发它的请求的许多详细信息。我们不会为
客户端BortExceptions
发送电子邮件,因为当用户在处理请求时关闭浏览器时,通常会发生这种情况

@ControllerAdvice
public class GlobalExceptionHandler {

    private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    private static final String ERROR_EMAIL_ADDRESS = "foo@bar.com";
    private static final String APPLICATION_ERROR_SUBJECT = "Foo Error Occurred";
    private static final String USER_AGENT = "user-agent";

    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseEntity defaultErrorHandler(final HttpServletRequest request, final Principal principal, final Exception e) {
        final String userTime = principal.getName() + " triggered an error at " + new Date();
        final String userAgent = "User-Agent: " + StringUtils.trimToEmpty(request.getHeader(USER_AGENT));
        final String url = "URL: " + StringUtils.trimToEmpty(request.getRequestURL().toString());
        final String httpMethod = "HTTP method: " + request.getMethod();

        final StringBuilder emailSb = new StringBuilder();
        emailSb.append(userTime).append("\n");
        emailSb.append(userAgent).append("\n");
        emailSb.append(url).append("\n");
        emailSb.append(httpMethod).append("\n");

        if(e instanceof ClientAbortException){
            logger.debug("Not sending email for socketExceptions");
        }else {
            emailSb.append(ExceptionUtils.getStackTrace(e));
            //just a simple util class we use to send emails with javax.mail api
            EmailUtil.sendEmail(ERROR_EMAIL_ADDRESS, ERROR_EMAIL_ADDRESS, APPLICATION_ERROR_SUBJECT,
                                emailSb.toString());
        }

        return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
    }

}

您可以按照以下步骤远程发送错误。我使用html将其添加到vm文件(Apache Velocity模板)中

Api演示

将这些依赖项添加到Pom.xml文件中

添加模板类型

添加提供error.vm文件的TemplateFactory类

添加VelocityManager,获取vm文件并将错误写入vm文件

创建一些Util类并添加到methoe下面


最简单的方法,我将如何做(如果它是一个web应用程序)是创建一个过滤器,并将其映射到所有请求,并在filterChain.doFilter周围放置一个try catch,这将是一个执行所需内容的地方

您可以使用logger的mail Appender发送邮件,而无需编写任何额外代码。我的log4j2.xml中的代码片段

  public class ApplicationErrorLoggingFilter extends OncePerRequestFilter{

        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
            try {
                    filterChain.doFilter(requestCopier, responseCopier);

                }
            catch(Exception e){

                logger.error("Error Message",e)
                throw e;
            }

            finally {

            }
        }

 }
<Appenders>

    <SMTP name="MailAppender" subject="Error Alert on server"
        to="?" 
        from="?"
        smtpHost="smtp.gmail.com" smtpPort="465"
        smtpUsername="?" 
        smtpPassword="?"
        smtpProtocol="smtps"
        smtpDebug="true"
        bufferSize="1">
        <ThresholdFilter level="ERROR" onMatch="ACCEPT"
            onMismatch="DENY" />
        <PatternLayout>
            <Pattern>${MAIL_LOG_PATTERN}</Pattern>
        </PatternLayout>
    </SMTP>

</Appenders>
log4j2.xml

  public class ApplicationErrorLoggingFilter extends OncePerRequestFilter{

        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
            try {
                    filterChain.doFilter(requestCopier, responseCopier);

                }
            catch(Exception e){

                logger.error("Error Message",e)
                throw e;
            }

            finally {

            }
        }

 }
<Appenders>

    <SMTP name="MailAppender" subject="Error Alert on server"
        to="?" 
        from="?"
        smtpHost="smtp.gmail.com" smtpPort="465"
        smtpUsername="?" 
        smtpPassword="?"
        smtpProtocol="smtps"
        smtpDebug="true"
        bufferSize="1">
        <ThresholdFilter level="ERROR" onMatch="ACCEPT"
            onMismatch="DENY" />
        <PatternLayout>
            <Pattern>${MAIL_LOG_PATTERN}</Pattern>
        </PatternLayout>
    </SMTP>

</Appenders>

${MAIL\u LOG\u PATTERN}

您可以实现自己的
java.lang.Throwable
类。要让JVM使用它,必须在启动进程时设置JVM引导类路径。Windows上的Java 8示例:

java.exe -Xbootclasspath/p:C:\..\ReplceJavaLangClasses\bin -classpath ... MyApp
在本例中,文件夹
C:\..\ReplaceJavaLangClasses\bin
包含原始
java.lang Throwable.java
代码的修改副本的类,通常在适当的包子文件夹
java/lang/Throwable.class
中。现在,您可以添加自己的异常管理操作,例如:

  ...
  public Throwable(String message) {
    fillInStackTrace();
    detailMessage = message;
    System.out.println("################ my additional code ##############");
 }

例如,通过修改所有构造函数,您可以对异常的所有实例作出反应。

正如我所说,这取决于应用程序的性质。您将找到创建任何线程(如果有的话)的所有点。如果主线程中只有一个线程,只需添加以下线程:thread.setDefaultUncaughtExceptionHandler(globalExceptionHandler);当然,它只适用于未处理的例外情况
@Component
@Scope("prototype")
public class TemplateFactory {

    private Logger logger = LogManager.getLogger(TemplateFactory.class);

    public final String ERROR_TEMPLATE_PATH = "templates/error.vm";

    private Template template;
    private VelocityEngine engine;

    public TemplateFactory() { }

    public Template getTemplate(TemplateType templateType) {
        this.engine = this.getEngine();
        this.engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
        this.engine.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
        this.engine.init();
        switch (templateType) {
            case ERROR_TEMPLATE:
                logger.debug("Error-Template Path :- " + this.getERROR_TEMPLATE_PATH());
                this.template = this.engine.getTemplate(this.getERROR_TEMPLATE_PATH());
                break;
        }
        return template;
    }

    private VelocityEngine getEngine() { return new VelocityEngine(); }

    public String getERROR_TEMPLATE_PATH() { return ERROR_TEMPLATE_PATH; }

}
@Component
@Scope("prototype")
public class VelocityManager {

    private final Logger logger = LogManager.getLogger(VelocityManager.class);

    @Autowired
    private TemplateFactory templateFactory;
    /*  create a context and add data */
    private VelocityContext context;
    /* now render the template into a StringWriter */
    private StringWriter writer;

    public VelocityContext getContext() { return context; }
    public void setContext(VelocityContext context) { this.context = context; }

    public String getResponseMessage(TemplateType templateType, Object object) throws Exception {
        String responseMessage = null;
        this.setWriter(new StringWriter());
        this.setContext(new VelocityContext());
        if(templateType.equals(ERROR_TEMPLATE)) {
            logger.info("Request Content :- " + object);
            this.context.put("request", (ErrorVo) object);
            responseMessage = this.getWriterResponse(templateType).toString();
        }
        return responseMessage;
    }

    private StringWriter getWriterResponse(TemplateType templateType) throws Exception {
        Template template = this.templateFactory.getTemplate(templateType);
        if(template != null) {
            template.merge(this.getContext(), this.getWriter());
            logger.info("Response Content :- " + this.getWriter().toString().replaceAll("\\s+",""));
            return this.getWriter();
        }
        throw new NullPointerException("Template Not Found");
    }

    public StringWriter getWriter() { return writer; }
    public void setWriter(StringWriter writer) { this.writer = writer; }
}
public void sendErrorEmail(ErrorVo apiError) {
    String htmlWithErroDetail = this.velocityManager.getResponseMessage(ERROR_TEMPLATE, apiError);
    // Note :- Now you have html with error. i'm using aws-ses email. you go with your option like (java-email, aws-ses, sendgrid)

}

public String printTraceMessage(Exception ex) {
    StringWriter errors = new StringWriter();
    ex.printStackTrace(new PrintWriter(errors));
    return errors.toString();
}
  public class ApplicationErrorLoggingFilter extends OncePerRequestFilter{

        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
            try {
                    filterChain.doFilter(requestCopier, responseCopier);

                }
            catch(Exception e){

                logger.error("Error Message",e)
                throw e;
            }

            finally {

            }
        }

 }
<Appenders>

    <SMTP name="MailAppender" subject="Error Alert on server"
        to="?" 
        from="?"
        smtpHost="smtp.gmail.com" smtpPort="465"
        smtpUsername="?" 
        smtpPassword="?"
        smtpProtocol="smtps"
        smtpDebug="true"
        bufferSize="1">
        <ThresholdFilter level="ERROR" onMatch="ACCEPT"
            onMismatch="DENY" />
        <PatternLayout>
            <Pattern>${MAIL_LOG_PATTERN}</Pattern>
        </PatternLayout>
    </SMTP>

</Appenders>
java.exe -Xbootclasspath/p:C:\..\ReplceJavaLangClasses\bin -classpath ... MyApp
  ...
  public Throwable(String message) {
    fillInStackTrace();
    detailMessage = message;
    System.out.println("################ my additional code ##############");
 }