一、需求描述:
每次系统出现异常(有系统异常,也有业务功能的异常)都需要让运维拉生产上的日志文件,查看哪个地方出问题了,根据打印的log日志定位问题点以及原因,比较浪费时间。为了解决这个问题,就想到:当系统出现异常时,将异常信息记录到数据库中,然后以短信或邮件的形式通知管理员登录到管理系统后台页面进行查看具体异常信息,从而快速定位和判断出现异常的位置和原因,直到修复。
二、实现思路:
1、创建一张数据表,专门用于存放异常信息,例如:远程访问IP、异常所在类、异常出现的方法名称、异常类型、异常发生时间以及异常的详细内容。
2、自定义一个异常处理器,当系统出现异常时就拦截该异常,并且获取必要信息,记录到数据库保存。
3、将异常信息记录到数据库之后,再发送邮件/短信通知系统管理员,提醒其登录后台系统进行查看。(后台系统做一个异常信息的查看页面,进行异常日志的查看和管理)
三、实现步骤:
1、创建数据表
CREATE TABLE t_exception_log (
id int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
ip varchar(20) DEFAULT NULL COMMENT '远程访问主机IP',
class_name varchar(255) DEFAULT NULL COMMENT '类名',
method_name varchar(120) DEFAULT NULL COMMENT '方法名',
exception_type varchar(255) DEFAULT NULL COMMENT '异常类型',
exception_msg text COMMENT '异常信息',
addtime datetime NOT NULL COMMENT '异常发生时间',
is_view tinyint(2) DEFAULT '1' COMMENT '是否查看,1:未查看、2:已查看',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='异常信息日志表';
2、自定义全局异常处理器,拦截并处理异常信息,发送通知邮件
package com.its.handler;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.mail.MessagingException;
import javax.mail.internet.AddressException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import com.its.common.utils.IPUtils;
import com.its.common.utils.mail.MailSenderFactory;
import com.its.entity.ExceptionLog;
import com.its.service.system.ExceptionLogService;
/**
* 全局异常处理器
*
* @author HY
* @date 创建时间:2017年2月24日
* @version
*/
public class GlobalExceptionHandler implements HandlerExceptionResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@Autowired
private ExceptionLogService exceptionLogService;
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
Map<String, Object> model = new HashMap<String, Object>();
model.put("ex", ex);
if (handler instanceof HandlerMethod) {
LOGGER.info(">>>>>>系统异常,记录异常信息到数据库------start------");
// 远程访问IP
String ip = IPUtils.getRemortIP(request);
HandlerMethod handlerMethod = (HandlerMethod) handler;
String className = handlerMethod.getBeanType().getName();
String methodName = handlerMethod.getMethod().getName();
StringWriter sw = new StringWriter();
ex.printStackTrace(new PrintWriter(sw, true));
// 插入异常日志到数据库
ExceptionLog log = new ExceptionLog();
log.setIp(ip);
log.setClassName(className);
log.setMethodName(methodName);
log.setExceptionType(ex.getClass().getSimpleName());
log.setExceptionMsg(sw.toString()); // 异常详细信息
log.setIsView((byte) 1); // 默认未读,1:为查看、2:已查看
log.setAddtime(new Date());
this.exceptionLogService.insertExceptionLogSelective(log);
LOGGER.info(">>>>>>系统异常,记录异常信息到数据库------end------");
// TODO 此处先写死。后期完善,接收人从数据库配置中获取
try {
String recipient = "zhangsan@abc.com";
String subject = "【XXXX系统异常通知】";
Object content = "管理员,您好:<br/> XXXX系统出现异常,请立即登录后台系统:“系统管理”--“异常日志管理”进行查看。<br/> "
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
MailSenderFactory.getSender().send(recipient, subject, content);
} catch (AddressException e) {
e.printStackTrace();
} catch (MessagingException e) {
e.printStackTrace();
}
}
String viewName = "error/500";
if (ex instanceof UnauthorizedException) { // 后台系统用的shiro做权限控制,该异常也是shiro的异常
viewName = "error/403";
}
return new ModelAndView(viewName, model);
}
}
3、在Spring配置文件中加上如下内容:
<!-- 全局异常处理器 -->
<bean id="exceptionResolver" class="com.xhh.ddhd.handler.GlobalExceptionHandler" />
其它代码:
ExceptionLog.java
package com.its.entity;
import java.util.Date;
/**
* 异常信息日志表
*
*/
public class ExceptionLog {
/**
* 主键id
*/
private Integer id;
/**
* 远程访问主机IP
*/
private String ip;
/**
* 类名
*/
private String className;
/**
* 方法名
*/
private String methodName;
/**
* 异常类型
*/
private String exceptionType;
/**
* 异常发生时间
*/
private Date addtime;
/**
* 是否查看,1:未查看、2:已查看
*/
private Byte isView;
/**
* 异常信息
*/
private String exceptionMsg;
// setter、getter方法略......
}
IPUtils.java
package com.its.common.utils;
import javax.servlet.http.HttpServletRequest;
/**
* IP地址操作工具类
*
* @author HY
* @date 创建时间:2017年2月24日
* @version
*/
public class IPUtils {
/**
* 获取远程访问主机ip地址
*
* 创建时间:2017年2月24日
*
* @author HY
* @param request
* @return
*/
public static String getRemortIP(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if ((ip == null) || (ip.length() == 0) || ("unknown".equalsIgnoreCase(ip))) {
ip = request.getHeader("Proxy-Client-IP");
}
if ((ip == null) || (ip.length() == 0) || ("unknown".equalsIgnoreCase(ip))) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if ((ip == null) || (ip.length() == 0) || ("unknown".equalsIgnoreCase(ip))) {
ip = request.getRemoteAddr();
}
return ip;
}
}
MailSenderFactory.java
package com.its.common.utils.mail;
/**
* 邮件发送工厂
*
* @author HY
* @date 创建时间:2017年2月28日
* @version
*/
public class MailSenderFactory {
private static SimpleMailSender getInstance = null;
private MailSenderFactory() {
}
public static SimpleMailSender getSender() {
if (getInstance == null) {
getInstance = new SimpleMailSender();
}
return getInstance;
}
}
SimpleMailSender.java
package com.its.common.utils.mail;
import java.util.List;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import com.its.common.context.Global;
/**
* 简单邮件(不带附件的邮件)发送器
*
* @author HY
* @date 创建时间:2017年2月28日
* @version
*/
public class SimpleMailSender {
private final transient Properties props = System.getProperties();
private transient MailAuthenticator authenticator;
private transient Session session;
// 从本地缓存中获取邮件相关配置信息
private static final String USERNAME = Global.getSysValue("mail_sender_username");
private static final String PASSWORD = Global.getSysValue("mail_sender_password");
private static final String MAIL_SMTP_HOST = Global.getSysValue("mail_smtp_host");
private static final String MAIL_SMTP_PORT = Global.getSysValue("mail_smtp_port");
private static final String MAIL_SMTP_AUTH = Global.getSysValue("mail_smtp_auth").equals("1") ? "true" : "false";
public SimpleMailSender() {
init();
}
/**
* 初始化
*
* 创建时间:2017年2月28日
*
* @author HY
* @param smtpHostName
* SMTP服务器地址
* @param username
* 发送邮件的用户名(邮箱地址)
* @param password
* 密码
*/
private void init() {
// 初始化Properties
props.put("mail.smtp.host", MAIL_SMTP_HOST);
props.put("mail.smtp.port", MAIL_SMTP_PORT);
props.put("mail.smtp.auth", MAIL_SMTP_AUTH);
// 验证发送者账户密码
authenticator = new MailAuthenticator(USERNAME, PASSWORD);
// 创建Session
session = Session.getInstance(props, authenticator);
}
/**
* 发送邮件(单发)
*
* 创建时间:2017年2月28日
*
* @author HY
* @param recipient
* 收件人邮箱地址
* @param subject
* 邮件主题
* @param content
* 邮箱内容
* @throws AddressException
* @throws MessagingException
*/
public void send(String recipient, String subject, Object content) throws AddressException, MessagingException {
// 1.创建mime类型邮件
final Message message = new MimeMessage(session);
// 2.设置发件人
message.setFrom(new InternetAddress(authenticator.getUsername()));
// 3.设置收件人
message.setRecipient(RecipientType.TO, new InternetAddress(recipient));
// 4.设置邮件主题
message.setSubject(subject);
// 5.设置邮件内容
message.setContent(content.toString(), "text/html;charset=utf-8");
// 6.发送
Transport.send(message);
}
/**
* 群发邮件
*
* 创建时间:2017年2月28日
*
* @author HY
* @param recipients
* 多个收件人(邮箱地址集合)
* @param subject
* 邮件主题
* @param content
* 邮件内容
* @throws AddressException
* @throws MessagingException
*/
public void send(List<String> recipients, String subject, Object content) throws AddressException, MessagingException {
// 1.创建mime类型邮件
final Message message = new MimeMessage(session);
// 2.设置发件人
message.setFrom(new InternetAddress(authenticator.getUsername()));
// 3.设置收件人
final int num = recipients.size();
InternetAddress[] addresses = new InternetAddress[num];
for (int i = 0; i < num; i++) {
addresses[i] = new InternetAddress(recipients.get(i));
}
message.setRecipients(RecipientType.TO, addresses);
// 4.设置邮件主题
message.setSubject(subject);
// 5.设置邮件内容
message.setContent(content.toString(), "text/html;charset=utf-8");
// 6.发送
Transport.send(message);
}
}
MailAuthenticator.java
package com.its.common.utils.mail;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
/**
* 邮箱服务器登录验证器
*
* @author HY
* @date 创建时间:2017年2月28日
* @version
*/
public class MailAuthenticator extends Authenticator {
/**
* 用户名(登录邮箱地址)
*/
private String username = null;
/**
* 密码
*/
private String password = null;
public MailAuthenticator() {
}
public MailAuthenticator(String username, String password) {
this.username = username;
this.password = password;
}
/**
* 账户认证
*/
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
4、验证
随便在controller中制造一个异常,然后看能否成功即可。此处就略过了...
注意:
我们项目用的MVC框架是SpringMVC,如果在springmvc配置文件中配置了SimpleMappingExceptionResolver的话,则会导致以上的自定义全局异常处理器无法正常
工作,若配置了,则将springmvc配置文件中的相关内容去掉即可。
5、实际效果:
页面:
邮件: