web-dev-qa-db-ja.com

Javaですべての例外を取得し、リモートで送信する

私は巨大なJavaアプリケーションです。すべてのインターセプトしたいJava例外があり、それらを電子メールで送信します。送信するためのコードをどこにも追加できませんtry-catch経由のコードでは、たとえばアスペクトを使用して例外を低レベルのクラスにインターセプトし、例外のコンテンツを取得できますか?

または、いくつかの内部Javaクラスをオーバーライドして例外ペイロードを取得する方法はありますか?

何が可能ですか?

9
Peter Penzov

spring-aop@AfterThrowingアドバイスを使用できます。

@Aspect
@Component
public class MailExceptionAspect {

    @AfterThrowing(value="execution(* com.example..*.*(..))", throwing="ex" )
    public void mailAfterThrowing(Throwable ex) {
        // do something to send an email
    }
}

これにより、パッケージcom.example内の処理されないすべての例外がインターセプトされます。アプリケーションで処理(キャッチ)される例外はインターセプトできないことに注意してください。

別の解決策は、アプリケーションのロギングフレームワークを使用することです。 logbackのような多くのフレームワーク、log4jは、ログを電子メールで送信できる組み込みの構成を提供します。

7
burna

春を見てください @ControllerAdvice アノテーション。私たちはそれを使用して、私があなたが望むと思うことを正確に行います。多数の@Controller@RestControllers。これにより、それらのコントローラーのメソッドによってエラーがスローされたときに、トリガーとなったリクエストに関する詳細をメールで送信します。 ClientAbortExceptionsのメールは送信されません。これは、リクエストの処理中にユーザーがブラウザーを閉じたときに頻繁に発生するためです。

@ControllerAdvice
public class GlobalExceptionHandler {

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

    private static final String ERROR_EMAIL_ADDRESS = "[email protected]";
    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);
    }

}
4
Andrew Mairose

最も簡単な方法は、(Webアプリケーションの場合)フィルターを作成してすべてのリクエストにマップし、filterChain.doFilterの周りにtry-catchを配置することです。これは単一です。必要なことを行う場所。

ロガーのメール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 {

            }
        }

 }

log4j2.xml

<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>
3

エラー処理については、こちらをお読みください

https://www.toptal.com/Java/spring-boot-rest-api-error-handling

エラーの詳細とメールの送信については、印刷用のトラックを入手してください

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

または、別のスレッドを使用して応答をブロックせず、メールを送信することができます

3
Nabeel Ahmed

これが、SpringベースのWebアプリケーションで行うことです。

意図しない例外をすべてキャッチするために、フィルターチェーンの最初/最後のフィルターである例外サーブレットフィルターがあります。

このフィルターはすべての例外をキャッチし、メールを送信します。ところで、報告しない例外のホワイトリストがあります。クライアントが例外を打ち切ると考えてください。私たちにとって、それらを報告する理由は本当にありません。

ユーザーのリクエストが原因で発生するが、ユーザーの結果を妨げないタスクの場合、それらのアクションを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;
}

ところで、本番環境のサービスから自分にメールを送信する場合、サービスが1分間に送信するメールの数をレート制限すること、および同じ種類の例外を1つのメールにまとめる方法があることは非常に重要です。

マネージャー、マネージャー、マネージャーから電話がかかってくるのは楽しいことではありません。そこでは、会社の電子メールサーバーに対するDOS(サービス拒否)攻撃を停止する必要があると言われます。 2回...

送信される電子メールの数を制限するために、Spring Integration(activemqバックアップキュー)を使用してこの問題を解決しました。

次に、カウント戦略を使用して、送信されている同じ例外の数を追跡し、それらの電子メールを1つの電子メールにまとめて、その特定の例外が発生した回数をカウントしようとしました。

3
hooknc

すべての実行中のスレッドがhtコントロールにある場合は、Thread.UncaughtExceptionHandlerの実装でそれらをすべてマークできます。もちろん、アプリに深いマルチスレッドの性質がある場合は、少し注意が必要かもしれません。

2
AlexGera

これらの手順に従って、エラーをリモートで送信できます。これをvm-file(Apache-Velocity-Template)に追加してhtmlを使用しています

enter image description here

APIデモ

@RequestMapping(value = "/xyz", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public APIResponse xyz(@RequestBody String json) {
    Long startTime = System.currentTimeMillis();
    try {
    } catch (Exception ex) {
        logger.error("Error := " + ex);
        // add Constructor in ErrorVo
        // profileType mean the server like (staging|prod)
        ErrorVo apiError = new ErrorVo("/xyz", this.profileType, "XYZRestApi", "method-name", LocalDateTime.now(), this.extUtil.printTraceMessage(ex));
        this.extUtil.sendErrorEmail(apiError);
    }
    logger.info("Response Time :== {} ms ==:", System.currentTimeMillis() - startTime);
    return this.apiResponse;
}

これらの依存関係をPom.xmlファイルに追加します

<dependency>
    <groupId>org.Apache.velocity</groupId>
    <artifactId>velocity</artifactId>
    <version>1.7</version>
</dependency>

<dependency>
    <groupId>org.Apache.velocity</groupId>
    <artifactId>velocity-tools</artifactId>
    <version>2.0</version>
</dependency>

Error.vmにHTMLを追加し、resource/templateフォルダーの下に配置します

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>
<body style="background:#ededed;padding:0;margin:20px 0;font-family: Calibri, sans-serif, serif, EmojiFont;">
<div style="border:1px solid #0056B3;background:#fff;width:650px;margin:0 auto;">

    <div style="width:100%;overflow:hidden;margin-bottom:10px;margin-top:10px;">
        <h1 style="color:#0056B3;font-size:16px;font-weight:bold;margin:10px 15px;">Api Break Alert.</h1>
        <hr style="border: 0;height: 0;border-top: 1px solid rgba(0, 0, 0, 0.1);border-bottom: 1px solid rgba(255, 255, 255, 0.3);margin:0 15px;" />
        <div style="overflow:hidden;margin-bottom:10px;margin:15px;">
        <p style="padding:0;margin:0;">Please Contact with the Support Team ASAP For Resolving the issue.</p>
      <table width="100%" border="0" align="center" cellpadding="0" cellspacing="0" style="width:100%;border:1pt solid #F1F1F1;margin-top:15px;">
          <tbody>
            <tr>
              <td width="100%" height="30" align="left" valign="middle" style="padding:3.75pt;border:1px solid #f1f1f1;font-size:14px;font-weight:bold;">
                  <p style="margin:0;padding:0;">Contact Phone: <span style="font-weight:normal;">$request.getPhoneNumber()</span></p>
              </td>
            </tr>
            <tr>
              <td width="100%" height="30" align="left" valign="middle" style="padding:3.75pt;border:1px solid #f1f1f1;font-size:14px;font-weight:bold;">
                  <p style="margin:0;padding:0;">Email: <span style="font-weight:normal;"><a href="#">$request.getEmails()</a></span></p>
              </td>
            </tr>
            <tr>
              <td width="100%" height="30" align="left" valign="middle" style="padding:3.75pt;border:1px solid #f1f1f1;font-size:14px;font-weight:bold;">
                  <p style="margin:0;padding:0;">End Point: <span style="font-weight:normal;">$request.getEndPoint()</span></p>
              </td>
            </tr>
            <tr>
              <td width="100%" height="30" align="left" valign="middle" style="padding:3.75pt;border:1px solid #f1f1f1;font-size:14px;font-weight:bold;">
                  <p style="margin:0;padding:0;">Running On: <span style="font-weight:normal;">$request.getStage()</span></p>
              </td>
            </tr>
            <tr>
              <td width="100%" height="30" align="left" valign="middle" style="padding:3.75pt;border:1px solid #f1f1f1;font-size:14px;font-weight:bold;">
                  <p style="margin:0;padding:0;">Service Type: <span style="font-weight:normal;">$request.getServiceType()</span></p>
              </td>
            </tr>
            <tr>
              <td width="100%" height="30" align="left" valign="middle" style="padding:3.75pt;border:1px solid #f1f1f1;font-size:14px;font-weight:bold;">
                  <p style="margin:0;padding:0;">Method Name: <span style="font-weight:normal;">$request.getMethodName()</span></p>
              </td>
            </tr>
            <tr>
              <td width="100%" height="30" align="left" valign="middle" style="padding:3.75pt;border:1px solid #f1f1f1;font-size:14px;font-weight:bold;">
                  <p style="margin:0;padding:0;">Exception Time: <span style="font-weight:normal;">$request.getExceptionTime()</span></p>
              </td>
            </tr>
            <tr>
              <td width="100%" height="100" align="left" valign="top" style="padding:3.75pt;border:1px solid #f1f1f1;font-size:14px;font-weight:bold;">
                  <p style="margin:0;padding:0;">Exception: <span style="font-weight:normal;">$request.getError()</span></p>
              </td>
            </tr>
          </tbody>
      </table>
      </div>
    </div>
</div>
</body>
</html>

エラーの詳細があるCrete ErrorVoクラス

public class ErrorVo {

    private String phoneNumber;
    private String emails;
    private String endPoint;
    private String stage;
    private String serviceType;
    private String methodName;
    private String exceptionTime;
    private String error;

    public ErrorVo() { }

    public ErrorVo(String endPoint, String stage, String serviceType, String methodName, String exceptionTime, String error) {
        this.endPoint = endPoint;
        this.stage = stage;
        this.serviceType = serviceType;
        this.methodName =  methodName;
        this.exceptionTime = exceptionTime;
        this.error = error;
    }

    public String getPhoneNumber() { return phoneNumber; }
    public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber;     }

    public String getEmails() { return emails; }
    public void setEmails(String emails) { this.emails = emails; }

    public String getEndPoint() { return endPoint; }
    public void setEndPoint(String endPoint) { this.endPoint = endPoint; }

    public String getStage() { return stage; }
    public void setStage(String stage) { this.stage = stage; }

    public String getServiceType() { return serviceType; }
    public void setServiceType(String serviceType) { this.serviceType = serviceType; }

    public String getMethodName() { return methodName; }
    public void setMethodName(String methodName) { this.methodName = methodName; }

    public String getExceptionTime() { return exceptionTime; }
    public void setExceptionTime(String exceptionTime) { this.exceptionTime = exceptionTime; }

    public String getError() { return error; }
    public void setError(String error) { this.error = error; }

    @Override
    public String toString() { return new Gson().toJson(this); }

}

テンプレートタイプを追加

public enum TemplateType {
    ERROR_TEMPLATE
}

Error.vmファイルを提供するTemplateFactoryクラスを追加します。

@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; }

}

Vmファイルを取得し、vmファイルにエラーを書き込むVelocityManagerを追加します

@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; }
}

Utilクラスをいくつか作成し、メトエの下に追加します

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();
}
2
Nabeel Ahmed

独自のJava.lang.Throwableクラスを実装することが可能です。 JVMで使用するには、プロセスの開始時にJVMブートクラスパスを設定する必要があります。 Java 8 on Windowsの例:

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 ##############");
 }

すべてのコンストラクターを変更すると、たとえば、例外のすべてのインスタンスに対応できます。

1
geri