通过 javax.mail 实现系统异常自动告警[亲测有效]

通过 javax.mail 实现系统异常自动告警[亲测有效]事情的起因:笔者在服务器中运行的 OLAP 程序有时会因意外的错误而 “悄悄” 地宕机。更糟糕的是,系统在宕机时没有任何机制提醒笔者上线进行故障排除,这导致积压在消息队列的 msg 迟迟得不到处理。为

事情的起因:笔者在服务器中运行的 OLAP 程序有时会因意外的错误而 “悄悄” 地宕机。更糟糕的是,系统在宕机时没有任何机制提醒笔者上线进行故障排除,这导致积压在消息队列的 msg 迟迟得不到处理。为了解决这个问题,笔者构思了一个简单的方案:编写一个心跳检测的 Shell 脚本提交给 Linux 的 crontab 来定时维护,如果脚本在某一轮监测时判定到系统状态异常,会将错误日志发送至笔者的电子邮箱进行告警。流程如下:

sys_watcher.png

告警的时机取决于开发者如何设计。比如,可以在系统重要业务的 try ... catch 块内安插守卫,当捕获到致命异常时,令守卫简单记录堆栈消息和错误原因,并在系统崩溃前发送一封 “SOS” 邮件。再比如,一些成熟的框架本身就提供统一的异常管理机制,告警逻辑也可以实现在这些钩子函数内部。

本文的重点是使用 java.mail 工具包实现发送电子邮件的功能,这需要保证服务器能够和外界进行网络通讯

在下文的代码演示中,笔者使用了两个电子信箱地址:

  1. 在 163 官网注册的 jun****@163.com,供脚本程序 发送邮件时 使用。
  2. 笔者平日使用的 37***@qq.com ,用于接收脚本发送的邮件。

如果没有 163 邮箱,可以去163网易 那里注册并获得一个免费的 @163.com 后缀的邮箱地址。

想要用程序发送电子邮件,只需要对 SMTP 协议有一个基本的认识。和电子邮件相关的还有 POP3,IMAP 协议,163 邮箱的帮助中心已经给了明确的介绍,见:帮助中心_常见问题 (163.com)

程序必须获得 授权,然后才可以通过注册的 163 邮箱投递消息。我们首先需要登录到 163 邮箱页面开启 IMAP/SMTP 服务,然后获取 授权码。获取过程如下面的动图所示:

auto_mail.gif

程序内部可能需要执行一些 Shell 命令和操作系统进行交互。因此,笔者在这里选择了 Groovy 编写脚本。感兴趣的可以参考笔者的 Groovy 专栏:Groovy 从入门到 MOP – 花花子的专栏 – 掘金 (juejin.cn)

javax.* 并不是 Java 标准库的一部分。对于 Java 开发者,javax.mail 工具需要从 Maven 那里下载并添加到依赖中。也可以手动在此 github 链接 JavaMail (javaee.github.io) 下载现成的 jar 包之后放入 CLASS_PATH 下,或在运行时通过 -cp 导入它。

如果是 Groovy 脚本,使用 @Grab 注解就能直接下载需要的依赖,而不需要借助其它的依赖管理工具:

@Grab(group = "javax.mail",module = "mail",version = "1.4.7")
import javax.mail.*

@Grab 注解还有更简洁的表述方式:

@Grab("javax.mail:mail:1.4.7")

更多的用法,见:groovy中如何使用grab自动下载jar包? (findsrc.com)

163 邮箱官网给出了三种协议的服务器地址和端口号,见下面的表格:

服务器名称 服务器地址 SSL协议端口号 非SSL协议端口号
IMAP imap.126.com 993 143
SMTP smtp.126.com 465/994 25
POP3 pop.126.com 995 110

我们的脚本只用来发送邮件,这里只需要根据 SMTP 服务进行配置即可:

properties = new Properties();
properties.put("mail.smtp.auth","true")
properties.put("mail.transport.protocol","smtp")
properties.put("mail.smtp.host","smtp.163.com")
// 下面两项是 SSL 相关配置。如果缺省,那么会走默认非加密的 25 端口。
properties.put("mail.smtp.socketFactory.port","465")
properties.put("mail.smtp.socketFactory.class","javax.net.ssl.SSLSocketFactory")

同时,SMTP 协议需要进行身份认证。创建一个抽象 Authenticator 类的匿名实例,实现 getPasswordAuthentication 方法,将 163 邮箱地址及其之前在网页获取的 授权码 ( 注意,授权码不是登录 163 邮箱时使用的密码) 包装成一个 PasswordAuthentication 实例并返回。

username = "jun***@163.com" // 163 的邮箱地址就是账户
pwd = "******"		  	   // 授权码
auth = new Authenticator(){
    @Override
    protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username,pwd) }
}

Groovy 本身作为一个动态语言,可以使用 Map 类型作为抽象类或者接口的实现:

auth = ["getPasswordAuthentication" : {-> new PasswordAuthentication(username,pwd)}] as Authenticator

在准备好配置信息和认证信息之后,便可以创建一个 MimeMessage 实例作为消息容器了。其中,session.debug 是一个可选项,主要用于排查邮件发送失败时的错误原因。在测试成功之后,可以将这行代码从脚本中注释掉。

session = Session.getInstance(properties, auth)
session.debug = true
message = new MimeMessage(session)
from = new InternetAddress("jun****@163.com")
to = new InternetAddress("37****@qq.com")
message.setFrom(from)
message.setRecipients(Message.RecipientType.TO,to)

将邮件内容设置为 text/html;charset=UTF-8 的 MIME 消息类型 ( 常见于 HTTP 请求的 Header )。这里是一个简单的例子:将本机的 JDK 环境打印出来,然后写到邮件并投递。

// 设置电子邮件的标题
message.setSubject("Check your server")

// 调用系统 shell.
// java -version 会将消息写入到 err 流。
report = "cmd /c java -version".execute().err.text
content = """
    <h2>Check your jdk</h2>
    Java version: <br>
    ${report}
"""
// 注意,GString 需要显式调用 `toString` 方法转换为 Java String。
message.setContent(content.toString(),"text/html;charset=UTF-8")
Transport.send(message)

或许,我们还希望脚本可以将系统 ( 出错时 ) 生成的日志文件作为 附件 添加到电子邮件并发送。首先,创建多个 MimeBodyPart 实例分别装入邮件信封的内容和附件,然后创建一个 MimeMultipart 封装为一个完整的邮件。过程如下:

// 创建 message 和 session 的过程和前文相同,这里略。
mainPart = new MimeMultipart();

body = new MimeBodyPart()
body.setContent(content.toString(),"text/html;charset=UTF-8")

attach = new MimeBodyPart()
attach.attachFile(new File("C:\\Users\\i\\Desktop\\sys.log"))

mainPart.addBodyPart(body)
mainPart.addBodyPart(attach)

message.setContent(mainPart)
Transport.send(message)

下面是完整的代码演示:

@Grab(group = "javax.mail",module = "mail",version = "1.4.7")
import javax.mail.*
import javax.mail.internet.InternetAddress
import javax.mail.internet.MimeBodyPart
import javax.mail.internet.MimeMessage
import javax.mail.internet.MimeMultipart

report = "cmd /c java -version".execute().err.text

properties = new Properties();
properties.put("mail.smtp.auth","true")
properties.put("mail.transport.protocol","smtp")
properties.put("mail.smtp.host","smtp.163.com")

// 发送 SSL 层加密邮件,否则是不走 SSL。
properties.put("mail.smtp.socketFactory.port","465")
properties.put("mail.smtp.socketFactory.class","javax.net.ssl.SSLSocketFactory")

//auth = new Authenticator(){
//
//    String username = "jun****@163.com"
//    String pwd = "*******"
//
//    @Override
//    protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username,pwd) }
//}

// groovy 支持使用 Map 动态实现接口或者抽象类
au = ["getPasswordAuthentication" : {-> new PasswordAuthentication("jun****@163.com","CDPG*********PG")}]
        as Authenticator

session = Session.getInstance(properties, au)
session.debug = true
message = new MimeMessage(session)
from = new InternetAddress("jun****@163.com")
to = new InternetAddress("3767****@qq.com")

message.setFrom(from)
message.setRecipients(Message.RecipientType.TO,to)

// MIME type 不支持发送 GString,需要转换 toString
// 在 text/html 下,可以发送 HTML 文档。
content = """
    <h2>Java jdk</h2>
    ${report}
"""

message.setSubject("请检查您的程序状态")

//----------------------------------
mainPart = new MimeMultipart();

body = new MimeBodyPart()
body.setContent(content.toString(),"text/html;charset=UTF-8")

attach = new MimeBodyPart()
attach.attachFile(new File("C:\\Users\\i\\Desktop\\sys.log"))

mainPart.addBodyPart(body)
mainPart.addBodyPart(attach)
//---------------------------------------

message.setContent(mainPart)
Transport.send(message)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13582.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注