什么是通知管理员有关Java应用程序的新异常的最佳方法?

时间:2014-05-14 05:13:53

标签: java java-ee exception-handling struts2 jms

我的问题是,最好的方法是跟踪应用程序管理员的异常。 (出于维护目的,通知管理员抛出的异常)。

对于系统用户,我认为应该捕获异常并显示相应的错误消息。 对于系统管理员,我想,最好的方法是让消息系统将每个异常的详细信息作为消息发送给接收方。一旦接收方收到新的错误消息,它就会在数据库中持续显示,或者向管理员发送一封电子邮件,其中包含异常的详细信息。

try{
  ....
}
catch(Exception e){
   //what to do here? how to notify admin?
}

9 个答案:

答案 0 :(得分:10)

我建议使用配置了SMTPAppender致命日志的log4j。然后,只需记录到达全局try / catch块的任何未处理异常的致命级别消息(包含您可以获得的任何有用信息)。

另请参阅:What is the proper way to configure SMTPAppender in log4j?

答案 1 :(得分:9)

企业解决方案:

使用SL4J并将所有邮件保存到日志中。

对日志消息使用MDC to add tags。让这些标签描述应该通知谁以及错误的性质:

2014-05-24 [SystemCAD][NOTIFY=ADMIN], [ACCOUNTID=123], [SEVERITY=SEVERE], [MESSAGE="Cannot contact Google.com"]  
2014-05-24 [SystemCAD][NOTIFY=USER], [ACCOUNTID=123], [SEVERITY=SEVERE], [MESSAGE="Could not save document to Google. Support has been notified."]  

获取Splunk或类似于索引所有日志的产品,以便于搜索和创建可用于通知管理员的事件。使用PagerDutty通知您的管理员并创建升级,避免重复,创建触发器等。

答案 2 :(得分:5)

首先,不要尝试解决应用程序本身的通知问题。

建议的方法是在应用程序中的适当位置捕获异常,并生成捕获故障详细信息(包括异常)的日志事件。主要日志记录应使用标准日志记录系统完成。有许多可行的选项(例如java.util.logginglog4jlogbacklog4j2slf4j),每个选项都包含专业版和con&# 39; s,但最重要的是不要试图自己推动#34;。

这很容易。

困难的部分是弄清楚如何以适当的方式从日志系统获取通知给管理员。有许多事情需要考虑:

  • 管理员不会在凌晨2点被一个报告办公室冷水机温度过高的页面吵醒。

  • 管理员不希望50条短信都报告同样的问题。系统需要能够过滤掉重复的内容。

  • 管理员需要能够告诉系统"关闭"关于某个问题/问题。

  • 系统需要认识到某些事件比其他事件更重要,而营业时间与非工作时间会影响优先级。

  • 通知管理员的最合适方式是什么?电子邮件?短信?寻呼机?

  • 升级 - 如果主要(待命)管理员没有回应通知怎么办?

  • 系统还需要与其他监控集成;例如检查服务可用性,网络连接,文件系统级别,CPU /负载平均度量,检查重要事件是否发生。

  • 所有这些都需要是可配置的,与首先生成事件的应用程序无关。

  • 理想情况下,您需要与运营问题跟踪系统集成...以帮助管理员将事件与之前的问题相关联等。

这是一个非常大的问题空间。幸运的是,那里有产品可以做这种事情。 Too many to list here

(IMO,为您推荐一个解决方案是没有意义的。我们不了解您的组织的要求。这是需要与运营人员一起解决的问题。 & management。)

答案 3 :(得分:4)

我在使用spring AOP的应用程序中完成了异常通知。

例如

@Aspect
public class ExceptionAspect {

   @AfterThrowing(
      pointcut = "execution(* com.suren.customer.bo.CustomerBo.addCustomerThrowException(..))",
      throwing= "error")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
    // Notify admin in email
    sendEmail(joinPoint,error);

    }
}

Common AspectJ注释:

@Before – Run before the method execution
@After – Run after the method returned a result
@AfterReturning – Run after the method returned a result, intercept the returned result as well.
@AfterThrowing – Run after the method throws an exception
@Around – Run around the method execution, combine all three advices above.

答案 4 :(得分:2)

您应该使用日志记录工具记录文件系统中的每个异常,以便管理员希望他们可以通过文件系统查看它。

<强> ErrorUtil

public class ErrorLogUtil {

public static File createErrorFile(String fileName, String productName,
        String regionName) {
    File fileErrorLogs = new File("Error Logs");
    if (!fileErrorLogs.isDirectory()) {
        fileErrorLogs.mkdir();
    }
    File fileProductName = new File(fileErrorLogs, productName);
    if (!fileProductName.isDirectory()) {
        fileProductName.mkdir();
    }

    File fileDate = null;

    if (regionName != null && regionName.trim().length() != 0) {
        File fileRegionName = new File(fileProductName, regionName);
        if (!fileRegionName.isDirectory()) {
            fileRegionName.mkdir();
        }

        fileDate = new File(fileRegionName, new SimpleDateFormat(
                "dd-MM-yyyy").format(new Date()));
        if (!fileDate.isDirectory()) {
            fileDate.mkdir();
        }
    } else {
        fileDate = new File(fileProductName, new SimpleDateFormat(
                "dd-MM-yyyy").format(new Date()));
        if (!fileDate.isDirectory()) {
            fileDate.mkdir();
        }
    }

    File errorFile = new File(fileDate, fileName + "-errors.txt");
    try {
        if (!errorFile.exists()) {
            errorFile.createNewFile();
            System.out.println("New Error File created=>"+errorFile.getAbsolutePath());
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return errorFile;
}

public static void writeError(File errorFile, String error) {
    try {
        FileOutputStream fileOutputStream = new FileOutputStream(errorFile,
                true);
        DataOutputStream out = new DataOutputStream(fileOutputStream);
        BufferedWriter bufferedWriter = new BufferedWriter(
                new OutputStreamWriter(out));
        bufferedWriter.append((new Date())+" - "+error);
        bufferedWriter.newLine();
        bufferedWriter.flush();
        bufferedWriter.close();
        fileOutputStream.flush();
        fileOutputStream.close();
        out.flush();
        out.close();


    } catch (IOException e) {
        e.printStackTrace();
    }
}

public static void printStackTrace(File errorFile, String message, Throwable error) {
    try {
        FileOutputStream fileOutputStream = new FileOutputStream(errorFile,
                true);
        DataOutputStream out = new DataOutputStream(fileOutputStream);
        PrintWriter bufferedWriter = new PrintWriter(
                new BufferedWriter(new OutputStreamWriter(out)));

        bufferedWriter.println(new Date() + " : "+ message);        

        error.printStackTrace(bufferedWriter);

        bufferedWriter.println();
        bufferedWriter.close();

    } catch (IOException e) {
        e.printStackTrace();
    }
}

}

发送邮件不会很好,因为它可能会填充管理员的邮箱,但如果您真的需要这样,您可以创建一个MailUtil并向用户发送电子邮件或将其保存在日志中。

<强> MailUtil

public class MailUtil {
public static void sendEmail(String messageString, String subject, Properties props) {

    try {
        Session mailSession = null;
        final String userName = props.getProperty("mail.from");
        final String password = props.getProperty("mail.from.password");
        mailSession = Session.getInstance(props, new javax.mail.Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(userName, password);
            }
        });

        Transport transport = mailSession.getTransport();

        MimeMessage message = new MimeMessage(mailSession);

        message.setSubject(subject);
        message.setFrom(new InternetAddress(props.getProperty("mail.from")));
        String[] to = props.getProperty("mail.to").split(",");
        for (String email : to) {

            message.addRecipient(Message.RecipientType.TO, new InternetAddress(email));
        }

        String body = messageString;
        message.setContent(body, "text/html");
        transport.connect();

        transport.sendMessage(message, message.getRecipients(Message.RecipientType.TO));
        transport.close();
    } catch (Exception exception) {
        exception.printStackTrace();
    }
}

public static void sendEmail(String subject, String messageString) {
    try {
        Session mailSession = null;
        Properties props=new Properties();
        FileInputStream fileInputStream = new FileInputStream(new File("mail-config.properties"));
        props.load(fileInputStream);
        fileInputStream.close();

        final String fromUsername = props.getProperty("mail.from");
        final String fromPassword = props.getProperty("mail.from.password");

        mailSession = Session.getInstance(props, new javax.mail.Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(fromUsername, fromPassword);
            }
        });

        Transport transport = mailSession.getTransport();

        MimeMessage message = new MimeMessage(mailSession);

        message.setSubject(subject);
        message.setFrom(new InternetAddress(fromUsername));
        String[] to = props.getProperty("mail.to").split(",");
        for (String email : to) {
            message.addRecipient(Message.RecipientType.TO, new InternetAddress(email));
        }

        String body = messageString;
        message.setContent(body, "text/html");
        transport.connect();

        transport.sendMessage(message, message.getRecipients(Message.RecipientType.TO));
        transport.close();
    } catch (Exception exception) {
        exception.printStackTrace();
    }
}

}

您应该使用属性来管理是否需要邮件,以便将来只需更改属性文件即可停止邮件。

答案 5 :(得分:2)

设计应用程序时,需要考虑两种类型的异常

  • 用户定义的业务例外
  • 意外的系统异常

用户定义的例外

用户定义的异常用于将负面条件从一个层传递到另一个层(服务到Web)。例如,在银行应用程序中,如果帐户中没有余额,并且您尝试提取资金,则WithdrawService可能会抛出NoBalanceException。 Web层将捕获此异常并向用户显示相应的消息。

这些类型的例外对管理员不感兴趣,不需要警报。您只需将其记录为信息即可。

意外的系统异常

意外的系统异常是数据库连接或JMS一致性或NullPointException等异常或从外部系统收到的无效消息。基本上任何意外(非业务)异常都被归类为系统异常。

根据有效Java中的Joshua Bloch所说,建议不要捕获系统异常,因为你可能弊大于利。而是允许它传播到最高级别(Web层)。

在我的应用程序中,我在Web层提供了一个全局异常处理程序(由Spring / Struts 2支持),并向ops团队发送详细的电子邮件,包括异常堆栈跟踪,并将请求重定向到标准错误页面,类似“发生了意外的内部错误。请再试一次”。

使用此选项更安全,因为它不会在任何情况下向用户公开丑陋的异常堆栈跟踪。

Struts2参考: http://struts.apache.org/release/2.3.x/docs/exception-handling.html

答案 6 :(得分:1)

您可以创建例外日志表。在那里,写一个代码来插入异常&#34; Pending&#34;在应用程序中引发异常的状态。 创建一个cron job(linux)或quartz scheduler将在某段时间内触发并发送&#34;待处理的&#34;管理员用户使用预定义格式的状态异常。 使用&#34;发送&#34;更新数据库条目状态,所以它不会再发送。

在代码中,要保存异常,请创建超类,即

class UserDao extends CommonDao
{

  try
   {

   }catch(Exception e)
   {
      saveException(e);
   }
}

class CommonDao
{

  public void saveException(Exception e)
  {
    //write code to insert data into database
  }
}

答案 7 :(得分:1)

考虑使用标准日志记录(如log4j)并使用适合您的appender - 前面提到的SMTP或自定义日志。存在称为日志服务器的解决方案 - 它们在通知,过滤,存储,处理等方面提供高度灵活性。开始阅读和调查的好地方是ScribeFlume。可以找到关于这个主题的很好的讨论here

还有一些云解决方案可用,从SentryLogDigger(您自己的安装)等自动化解决方案,再到Amazon SQS等更低级别的设置。

答案 8 :(得分:0)

对我来说,将这种行为直接放在应用程序的代码中并不是一个好主意。很明显,只需调用一个在catch子句中发送电子邮件的函数即可轻松,快速和直接。如果你没有那么多时间去追求它。

但是你会意识到这将产生一些你需要的预期副作用

  • 控制异常解析时的性能
  • 控制通知哪些异常以及哪些异常
  • 控件不会发送大量电子邮件,因为应用程序中的错误会不断产生异常。

为此我更喜欢使用http://logstash.net/这允许将所有日志放在一个通用的noSQL数据库中,然后您可以使用logstash制作仪表板,甚至可以创建自己的应用程序来发送有关特定事件的精心设计的报告。它在开始时需要更多的工作,但在此之后我相信你会更多地控制在日志中看到的重要内容以及什么不是。