我有一个巨大的 Java 应用程序。我想拦截所有 Java 异常并通过电子邮件发送它们。我无法在任何地方添加用于发送代码的代码,try-catch
因此是否可以使用例如 Aspect 将异常拦截到低级类并获取异常内容?
或者有什么方法可以覆盖一些内部 Java 类并获取异常有效负载?
什么是可能的?
我有一个巨大的 Java 应用程序。我想拦截所有 Java 异常并通过电子邮件发送它们。我无法在任何地方添加用于发送代码的代码,try-catch
因此是否可以使用例如 Aspect 将异常拦截到低级类并获取异常内容?
或者有什么方法可以覆盖一些内部 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
}
}
这将拦截 package 中所有未处理的异常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 Integration(带有 activemq 支持的队列)来限制发送的电子邮件数量来解决这个问题。
然后我们使用计数策略来跟踪发送了多少相同的异常,然后尝试将这些电子邮件捆绑到一封电子邮件中,并计算该特定异常发生的次数。
查看 Spring 的@ControllerAdvice
注释。我们用它来做我认为你想要的。我们有一个包含多个@Controller
s 和@RestController
s 的 Web 应用程序。这将发送一封电子邮件,其中包含有关在这些控制器中的任何方法引发错误时触发它的请求的许多详细信息。我们不会为 发送电子邮件ClientAbortExceptions
,因为这些电子邮件通常发生在用户在处理请求时关闭浏览器时。
@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);
}
}
最简单的方法,我将如何做到这一点(如果它是一个 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 {
}
}
}
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();
}
或者您可以使用不阻止响应并发送电子邮件的单独线程
您可以按照这些步骤远程发送错误。我通过将其添加到 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();
}
如果您在 ht 控制中拥有所有正在运行的线程,则可以使用 Thread.UncaughtExceptionHandler 的实现将它们全部标记。当然,如果应用程序具有深度多线程性质,这可能会有点棘手。
可以实现自己的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 ##############");
}
例如,通过修改所有构造函数,您可以对所有异常实例做出反应。