私は巨大なJavaアプリケーションです。すべてのインターセプトしたいJava例外があり、それらを電子メールで送信します。送信するためのコードをどこにも追加できませんtry-catch
経由のコードでは、たとえばアスペクトを使用して例外を低レベルのクラスにインターセプトし、例外のコンテンツを取得できますか?
または、いくつかの内部Javaクラスをオーバーライドして例外ペイロードを取得する方法はありますか?
何が可能ですか?
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は、ログを電子メールで送信できる組み込みの構成を提供します。
春を見てください @ControllerAdvice
アノテーション。私たちはそれを使用して、私があなたが望むと思うことを正確に行います。多数の@Controller
砂 @RestController
s。これにより、それらのコントローラーのメソッドによってエラーがスローされたときに、トリガーとなったリクエストに関する詳細をメールで送信します。 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);
}
}
最も簡単な方法は、(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>
エラー処理については、こちらをお読みください
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();
}
または、別のスレッドを使用して応答をブロックせず、メールを送信することができます
これが、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つの電子メールにまとめて、その特定の例外が発生した回数をカウントしようとしました。
すべての実行中のスレッドがhtコントロールにある場合は、Thread.UncaughtExceptionHandlerの実装でそれらをすべてマークできます。もちろん、アプリに深いマルチスレッドの性質がある場合は、少し注意が必要かもしれません。
これらの手順に従って、エラーをリモートで送信できます。これをvm-file(Apache-Velocity-Template)に追加してhtmlを使用しています
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();
}
独自の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 ##############");
}
すべてのコンストラクターを変更すると、たとえば、例外のすべてのインスタンスに対応できます。