事情的起因:笔者在服务器中运行的 OLAP 程序有时会因意外的错误而 “悄悄” 地宕机。更糟糕的是,系统在宕机时没有任何机制提醒笔者上线进行故障排除,这导致积压在消息队列的 msg 迟迟得不到处理。为了解决这个问题,笔者构思了一个简单的方案:编写一个心跳检测的 Shell 脚本提交给 Linux 的 crontab 来定时维护,如果脚本在某一轮监测时判定到系统状态异常,会将错误日志发送至笔者的电子邮箱进行告警。流程如下:
告警的时机取决于开发者如何设计。比如,可以在系统重要业务的 try ... catch
块内安插守卫,当捕获到致命异常时,令守卫简单记录堆栈消息和错误原因,并在系统崩溃前发送一封 “SOS” 邮件。再比如,一些成熟的框架本身就提供统一的异常管理机制,告警逻辑也可以实现在这些钩子函数内部。
本文的重点是使用 java.mail
工具包实现发送电子邮件的功能,这需要保证服务器能够和外界进行网络通讯。
在下文的代码演示中,笔者使用了两个电子信箱地址:
- 在 163 官网注册的
jun****@163.com
,供脚本程序 发送邮件时 使用。 - 笔者平日使用的
37***@qq.com
,用于接收脚本发送的邮件。
如果没有 163 邮箱,可以去163网易 那里注册并获得一个免费的 @163.com
后缀的邮箱地址。
想要用程序发送电子邮件,只需要对 SMTP 协议有一个基本的认识。和电子邮件相关的还有 POP3,IMAP 协议,163 邮箱的帮助中心已经给了明确的介绍,见:帮助中心_常见问题 (163.com)。
程序必须获得 授权,然后才可以通过注册的 163 邮箱投递消息。我们首先需要登录到 163 邮箱页面开启 IMAP/SMTP 服务,然后获取 授权码。获取过程如下面的动图所示:
程序内部可能需要执行一些 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)
今天的文章通过 javax.mail 实现系统异常自动告警分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/22598.html