【问题标题】:Whats the best way to notify admin about new exceptions of the Java application?通知管理员有关 Java 应用程序的新异常的最佳方式是什么?
【发布时间】:2014-07-02 00:06:04
【问题描述】:

我的问题是,跟踪应用程序管理员异常的最佳方式是什么。 (将抛出的异常通知管理员以进行维护)。

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

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

【问题讨论】:

  • 通知人们异常情况的方式不限。没有“最佳”方式,这取决于您的实际需求、现有基础设施、相关异常的严重性等等。
  • @DaveNewton 会抛出不同类型的异常,其中一些应 24/7 全天候监控,其他应记录以在计划维护中考虑。关键的应该通过电子邮件通知,但其余的可以保存在日志中。
  • 电子邮件不足以处理严重异常,真的。
  • 那么你的建议是什么?你能给我提供更多细节吗?似乎许多其他用户也在寻找答案。
  • 如果它是一个 critical 异常然后发送一个文本消息。如果只是“哦,亲爱的”,那么电子邮件就可以了。

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


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

    推荐的方法是在应用程序的适当位置捕获异常,并生成一个日志事件来捕获失败的详细信息(包括异常)。应该使用标准的日志系统来完成主要的日志记录。有许多可行的选项(例如java.util.logginglog4jlogbacklog4j2slf4j),每个选项都有优点和缺点,但最重要的是不要试图“自己动手” ”。

    这是最简单的部分。

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

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

    • 管理员不希望 50 条 SMS 消息都报告相同的问题。系统需要能够过滤掉重复项。

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

    • 系统需要识别某些事件比其他事件更重要,并且工作时间与非工作时间影响优先级。

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

    • 上报 - 如果主要(待命)管理员未响应通知怎么办?

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

    • 所有这些都需要可配置,独立于最初生成事件的应用程序。

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

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

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

    【讨论】:

      【解决方案3】:

      企业解决方案:

      使用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 通知您的管理员并创建升级、避免重复、创建触发器等。

      【讨论】:

      • 你对济慈的信息有何看法?
      • 对于非常小的系统来说这是一个不错的解决方案,但是一旦您的日志开始增长,您就需要考虑对它们进行索引和搜索。这就是 Splunk 提供的。另一个问题是当管理员想要配置通知传递时。例如,如果是工作时间,则给我发电子邮件,但如果是下班后,则给我发短信。或者,如果我正在度假……而您根本无法要求 SMTPAppender 提供所需的所有灵活性。这就是您需要 PagerDutty 的原因。
      • 为了完整性考虑使用slf4j.org/api/org/slf4j/Marker.html作为事件stackoverflow.com/questions/16813032/…的分层类型系统
      【解决方案4】:

      对我来说,将这种行为直接放在应用程序的代码中并不是一个好主意。很明显,简单地调用在 catch 子句中发送电子邮件的函数是简单、快速和直接的。如果你没有那么多时间,那就去吧。

      但是你会意识到这会产生一些你需要的意想不到的附带影响

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

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

      【讨论】:

        【解决方案5】:

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

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

        用户定义的异常

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

        管理员对这些类型的异常不感兴趣,也不需要警报。您可以简单地将其记录为信息。

        意外的系统异常

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

        根据 Effective Java 中的 Joshua Bloch,建议不要捕获系统异常,因为这样做弊大于利。而是让它传播到最高级别(Web 层)。

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

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

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

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

            【解决方案7】:

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

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

            【讨论】:

            • 只要确保管理员控制 log4j 配置,这样他们就可以防止收件箱拥挤。
            • 您对亚历山大·桑托斯的回答有何看法?
            【解决方案8】:

            您可以创建异常日志表。在那里,编写代码以将具有“待处理”状态的异常插入数据库中,无论应用程序中引发什么异常。 创建一个cron job (linux) 或quartz scheduler,它将在特定时间段内触发,并将预定义格式的“待定”状态异常邮件发送给管理员用户。 将数据库条目更新为“已发送”状态,使其不再发送。

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

            class UserDao extends CommonDao
            {
            
              try
               {
            
               }catch(Exception e)
               {
                  saveException(e);
               }
            }
            
            class CommonDao
            {
            
              public void saveException(Exception e)
              {
                //write code to insert data into database
              }
            }
            

            【讨论】:

            • 待定状态和cron作业是什么意思,它们都与linux有关吗? windows平台呢?
            • 待定状态意味着,在表中,您有要记录异常的列数。您使用状态栏来管理已经发送的任何异常,不再发送。您也可以编写石英调度程序来触发。参考mkyong.com/java/quartz-scheduler-example
            【解决方案9】:

            我已经使用 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.
            

            【讨论】:

            • @JackMoore 两者并不是互斥的; S2 的 Spring 集成非常紧密。
            • @DaveNewton 这是否意味着这个答案是该问题的最佳解决方案?
            • 对我来说,它看起来不像是一个强大的全局错误处理解决方案。它只涵盖了某些应用程序方法,即使您尝试涵盖所有方法,有时也会在框架内甚至在 JSP 中发生错误。
            猜你喜欢
            • 1970-01-01
            • 2010-09-09
            • 2011-05-16
            • 2011-02-10
            • 2015-02-11
            • 2013-05-18
            • 2020-04-03
            • 2014-06-01
            相关资源
            最近更新 更多