异常日志 异常:

三类:
1系统无法支撑导致异常(错误);
2代码逻辑异常,无法继续运行下去导致异常;
3不符合开发者业务要求导致异常。
系统错误:error(系统支持不下去) 。OutOfMemoryError、StackOverflowError、IllegalAccessError。jvm运行错误、内存不足等等。
代码逻辑异常:代码执行过程中,因为用户代码问题,导致代码执行不下去。
两类:
1 RuntimeException;
NullPointException等,代码逻辑中比较容易避免而没有避免掉的一些异常。用户代码逻辑可以很容易直接校验处理的异常。
2 CheckedException;
IOException等;
用户写代码时不容易检测因为代码问题导致运行时可以出现的异常。
需要有预先可能出现的异常的处理方式的一类异常。
Java的设计哲学:没有完善错误处理的代码根本没有机会被执行。
业务异常:用户自定义异常,不符合用户需要的业务处理逻辑。就是程序执行到某个地方,如果继续往下,则不符合业务要求的逻辑,那么用户就自定义一个异常。
(一) 异常处理
1、RuntimeException不用try{}catch(){}
/**
* GY
* 2017年9月22日
* RuntimeException 可以通过预先检查进行规避
* 不用try-catch
*/
@Test
public void runtimeExceptionNotUseTryCatch(){
String str = null;
m1(str);//空指针了
System.out.println("over");
}
@Test
public void runtimeExceptionNotUseTryCatch2(){
String str = null;
m2(str);
System.out.println("over");
}
//异常
public void m1(String str){
int i = str.indexOf("111");
}
//建议
//两个原因:1、性能及开销;2、这类异常本就不应该在运行中出现(编程中就能规避),而不是等出现异常后再处理异常。
public void m2(String str){
if(str == null){
return;//不太业务场景不同处理方式
}
int i = str.indexOf("111");
//略
}
2、不要用try-catch来控制程序的流程
/**
* 追求卓越成功自然相随
* 2017年9月24日
* 功能:不要用是否异常做条件控制
*/
//错误使用
@Test
public void dontUseExceptionControlProcedure(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String strDate = "2017|09|16";
Date date = null;
try {
date = sdf.parse(strDate);
System.out.println("sdf类型");
} catch (Exception e) {
e.printStackTrace();
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd");
try {
date = sdf2.parse(strDate);
System.out.println("sdf2类型");
} catch (Exception e2) {
e2.printStackTrace();
SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy.MM.dd");
try {
date = sdf3.parse(strDate);
System.out.println("sdf3类型");
} catch (Exception e3) {
e3.printStackTrace();
System.out.println("无法转化");
return;
}
}
}
//......后续使用date对象
System.out.println(date.getTime());
}
/**
* 使用代码条件控制流程
* 不用异常控制流程
* GY
* 2017年9月25日
*/
//推荐使用 定义正则表达式判断 因为异常的处理效率比条件分支低
//出现异常后再执行某段代码 跟 提前 判断进入某段代码的 区别。
@Test
public void dontUseExceptionControlProcedure2(){
String strDate = "2017-02-18";
String regDate = "[0-9]{4}[\\-\\/\\s\\年\\.]?((0[1-9])|(1[0-2]))[\\-\\/\\s\\月\\.]?(([0-2][1-9])|(3[0|1]))[日\\s]?";
Pattern pattern = Pattern.compile(regDate);
Matcher m = pattern.matcher(strDate);
if(!m.matches()){
System.out.println("字符串无法转化日期");//抛出自定义异常
return;
}
SimpleDateFormat sdf = null;
if((strDate.indexOf("年")) != -1){
sdf = new SimpleDateFormat("yyyy年MM月dd日");
System.out.println("年月日");
}
if((strDate.indexOf("-")) != -1 && sdf == null){
sdf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println("yyyy-MM-dd");
}
if((strDate.indexOf(".")) != -1 && sdf == null){
sdf = new SimpleDateFormat("yyyy.MM.dd");
System.out.println("yyyy.MM.dd");
}
if((strDate.indexOf("/")) != -1 && sdf == null){
sdf = new SimpleDateFormat("yyyy/MM/dd");
System.out.println("yyyy/MM/dd");
}
if(sdf == null){
sdf = new SimpleDateFormat("yyyyMMdd");
System.out.println("yyyyMMdd");
}
Date date = null;
try {
date = sdf.parse(strDate);
} catch (ParseException e) {
e.printStackTrace();
System.out.println("太奇葩!");
return;
}
//......后续使用date对象
System.out.println(date.getTime());
}
3、不同异常不同catch处理
/**
* try代码块能小则小
* 稳定代码不应包含在try中
* 不稳定的代码catch区分异常种类
* 因为不同的异常类型不同的处理方式
* GY
* 2017年9月25日
*/
//(...)代表瞎bb
//原因: try执行代码的效率会有所下降,try得越多另外开辟的空间耗损越多(...) 见上例
// 不同的异常类型不同的处理方式,方便分析异常日志,针对不同的业务异常,解决方案更加具体(...)
@Test
public void minTryCatchBlock(){
//这里演示不同异常不同解决方案
//参数异常不计录,业务异常进行记录
try {
handleService1("GY","1",new BigDecimal(500),123L);
} catch (MyParameterException e) {
e.printStackTrace();
return;
} catch (MyBusinessException e) {
e.printStackTrace();
//记录日志.........业务异常记录日志
Long userId = (Long)(e.getData());
System.out.println("异常用户id:"+userId);
return;
}
}
/**
* @throws MyParameterException
* @throws MyBusinessException
* GY
* 2017年9月25日
* 针对某个业务的处理类
*/
public void handleService1(String name, String loanState, BigDecimal money, Long userId)
throws MyParameterException, MyBusinessException{
met1(name);
met2(loanState, money,userId);
}
/**
* @param s
* @throws MyParameterException
* GY
* 2017年9月25日
* 操作1
*/
public void met1(String name) throws MyParameterException{
if(StringUtils.isBlank(name)){
throw new MyParameterException("0001");
}
//.....
}
/**
* @param loanState
* @param money
* @throws MyBusinessException
* GY
* 2017年9月25日
* 操作2
*/
public void met2(String loanState, BigDecimal money, Long userId) throws MyBusinessException{
if("1".equals(loanState)){
MyBusinessException e = new MyBusinessException("9002");
e.setData(userId);
throw e;
}
//.....
}
/**
* @author GY
* 2017年9月25日
* 入参校验异常类
*/
@SuppressWarnings("serial")
class MyParameterException extends Exception{
/**
* 异常码
*/
private String code;
/**
* 异常错误提示
*/
private String msg;
/**
* 异常对象
*/
private Object data;//比如异常json对象 后期捕获异常后处理的数据依据
/**
* 可维护异常信息map
*/
private static final HashMap MY_PARAMETER_EXCEPTION_CODE_MSG_MAP = new HashMap();
static{
MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("0001", "参数不能为空");//必要字段传空
MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("0002", "参数不符合业务逻辑");//比如不可能为负数的字段为负数
MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("0003", "参数内容不合法");//比如日期格式不合规
MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("0004", "不符合接口参数结构");//比如接口json报文解析失败
}
/**
* @param code异常码
*/
public MyParameterException(String code){
this.code = code;
this.msg = MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.get(this.code);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
@Override
public void printStackTrace() {
System.out.println(this.code+":"+this.msg);
}
}
/**
* @author GY
* 2017年9月25日
* 业务处理异常类
*/
@SuppressWarnings("serial")
class MyBusinessException extends Exception{
/**
* 异常码
*/
private String code;
/**
* 异常错误提示
*/
private String msg;
/**
* 异常对象
*/
private Object data;//比如异常json对象
/**
* 可维护异常信息map
*/
private static final HashMap MY_PARAMETER_EXCEPTION_CODE_MSG_MAP = new HashMap();
static{
MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("9001", "借款信息不存在");//不存在该用户的欠款信息
MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("9002", "用户已经还款");//数据库已经还了又调了还款接口还钱
}
/**
* @param code异常码
*/
public MyBusinessException(String code){
this.code = code;
this.msg = MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.get(this.code);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static HashMap getMyParameterExceptionCodeMsgMap() {
return MY_PARAMETER_EXCEPTION_CODE_MSG_MAP;
}
@Override
public void printStackTrace() {
System.out.println(this.code+":"+this.msg);
}
}
4、事物方法中异常被catch处理后,需要手动将事物回滚
if(userSave){
try {
userDao.save(user);
userCapabilityQuotaDao.save(capabilityQuota);
} catch (Exception e) {
logger.info("能力开通接口,开户异常,异常信息:"+e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
默认spring事务只在发生未被捕获的 runtimeexcetpion时才回滚。 在ServiceB的method2方法上注解,告诉spring当该方法抛出自定义异常CustomException时,不要回滚事务,这样当该方法抛出异常时,spring不会标记事务为回滚状态。
@Transactional(noRollbackFor=CustomException.class)
public void method2() throws CustomException{
}
@Transactional(rollbackFor = Throwable.class)//异常回滚绑定
这种设置是因为Spring的默认回滚RuntimeException,如果想要回滚Exception时,要设置@Transactional(rollbackFor = Exception.class),而且Exception还要抛出。
操作数据库异常:SQLException 属于RuntimeException
5、捕获了异常就得处理这个异常,不做处请不要捕获。最外层调用者必须捕获
最外层捕获异常后,将异常信息转换成用户可以理解的内容。
6、
/**
* finally中需要释放打开的资源
*/
@Test
public void finallyNeedCloseResource(){
File file = new File("F:\\javaeeWorkspace\\exceptionLog\\test.txt");
if(file.exists()){
file.delete();
}
FileOutputStream fos = null;
PrintStream isr = null;
PrintWriter pr = null;
try {
fos = new FileOutputStream(file);
isr = new PrintStream (fos);
pr = new PrintWriter(isr);
pr.print("7777777777");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if(pr != null){
pr.close();
}
if(isr != null){
isr.close();
}
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* jdk1.7及以上可以用try-with-resources
* 出try体资源即被关闭
*/
@Test
public void finallyNeedCloseResource2(){
File file = new File("F:\\javaeeWorkspace\\exceptionLog\\test.txt");
if(file.exists()){
file.delete();
}
try(FileOutputStream fos = new FileOutputStream(file);
PrintWriter pr = new PrintWriter(fos)){
pr.print("7777777777");
System.out.println("000");
return;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//return;
System.out.println("try-catch块无论异常与否的终点");
}
System.out.println("出现异常,异常被处理后才会执行的代码");
}
/*
* 8、
* 对于异常处理机制一样的不通的异常可以catch这两种异常的父类异常
* 最好抛什么异常捕获(catch)什么异常,统一用Exception不方便区分,不便于区分处理方式
*/
/**
* 9、
* 可以为null
* 必需说明白,方法什么情况会返回null
*/
@Test
public void returnNull(){
//比如数据库删除一条记录,在进行删除完对象对应的数据库数据操作后,可以将对象置为null
//return 这个引用(null)
}
/**
* 10、
* 防止出现NPE
*/
@Test
public void avoidNPE(){
method1(999L);
}
//反例 错误 解决:返回值类型跟方法返回类型保持一致
public long method1(Long taskId){
//Long userId = repayDao.findUserIdByTaskId(taskId);
//如果没有这个字段或没有这条数据 数据库查询结果为null
Long userId = null;
return userId;//会有警告提示
}
//JDK8 Optional obj.isPresent() 与 obj != null 无任何分别
/**
* 10、
* 集合里的元素isNotEmpty 取出的这个元素仍然有可能为null
* GY
* 2017年9月26日
*/
@Test
public void avoidNPE2(){
List list = new ArrayList();
list.add(null);
boolean isEmpty = (list.isEmpty());
System.out.println(isEmpty); //false 非空
System.out.println(list.get(0)); //null 集合中含有一个null元素,集合非空
}
/*
* 11、
* 避免直接抛出 new RuntimeException()
* unchecked异常 运行时异常 控制组 数组越界 (....) 代码规避,不让出现
* checked 异常 Java的设计哲学:没有完善错误处理的代码根本没有机会被执行。 需要具备处理这种异常的代码
* 更不允许抛出 Exception 或者 Throwable
*/
/*
* 12、
* RPC调用:远程过程调用协议
* 一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
*
*/
/*
* 13、
* DRY 原则
* 不要重复自己"。
* 强调的意思就是在进行编程时相同的代码不要重复写,最好只写一次,然后可以在其他地方直接引用。
* 一个类中有多个 public 方法,都需要进行数行相同的参数校验操作,这个时候可以抽取成一个公共校验方法
* 必须在某项操作前校验(执行某段代码(即使代码为空)),也有必要加上。面向切面编程。(....) 资金类交易
*/
package exceptionlog;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExceptionLog {
private static final Logger logger = LoggerFactory.getLogger(ExceptionLog.class);
//log file sync等待时间发生在redo log从log buffer写入到log file期间。
/**
* 不可以直接使用Log4j、Logback自带的api记录自己业务系统的日志
* 应该依赖使用日志框架 SLF4J 中的 API,比较统一日志格式,便于后续日志分析
* 有利于维护和各个类的日志处理方式统一
* 1、添加依赖包logback使用需要和slf4j一起使用,所以总共需要添加依赖的包有slf4j-api
* logback使用需要和slf4j一起使用,所以总共需要添加依赖的包有slf4j-api.jar,logback-core.jar,logback-classic.jar。
* logback-access.jar这个暂时用不到所以不添加依赖了
* GY
* 2017年9月26日
*/
@Test
public void runtimeExceptionNotUseTryCatch(){
//trace: 是追踪,就是程序推进以下,你就可以写个trace输出,所以trace应该会特别多,不过没关系,我们可以设置最低日志级别不让他输出。一般没人用
logger.trace("trace");
logger.debug("debug");
logger.info("info");
logger.warn("warn");
logger.error("error");
//fatal: 级别比较高了。重大错误,这种级别你可以直接停止程序了,是不应该出现的错误么!不用那么紧张,其实就是一个程度的问题。SLF4J 中的 API没有
}
/*
* 3、
* 日志命名规范化
* mppserver 应用中单独监控时区转换异常,
* 如:mppserver_monitor_timeZoneConvert.log
* 说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于 通过日志对系统进行及时监控。
*/
/*
* 4、
* 必须使用条件输出形式或者使用占位符的方式 1、拼接;2、{},传参数
* 如果代码中所打印的日志,没有达到日志级别的要求,应该要考虑 打印日志 所占用的系统资源,
* 执行了上述操作(代码),最终日志却没有打印
*
*/
/*
* 5、
* 避免打印重复日志
* 尽量避免循环打印日志
* 浪费磁盘空间
* 避免重复打印的一个方面的正例子:
* log4j.xml 中设置 additivity=false
*
* 它是 子Logger是否继承 父Logger 的 输出源(appender) 的标志位。
* 具体说,默认情况下子Logger会继承父Logger的appender,也就是说子Logger会在父Logger的appender里输出。
* 若是additivity设为false,则子Logger只会在自己的appender里输出,而不会在父Logger的appender里输出。
*/
/*
* 6、
* 异常信息应该包括两类信息:
* 案发现场信息和异常堆栈信息
* logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e);
*/
@Test
public void exceptionInfoLog(){
File file = new File("I:\\javaeeWorkspace\\exceptionLog\\test.txt");
try(FileOutputStream fos = new FileOutputStream(file)){
} catch (FileNotFoundException e) {
//e.printStackTrace();
logger.error("文件找不到:"+e.getMessage(), e);
} catch (IOException e) {
e.printStackTrace();
}
//FileOutputStream fos = null;
/*try {
fos = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
logger.error("文件找不到"+e.getMessage(), e);
} finally {
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}*/
}
/*
* 7、
* 生产环境禁止输出 debug 日志;有选择地输出 info 日志;】
* 如果使 用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,
* 避免把服务器磁盘 撑爆,并记得及时删除这些观察日志。
*/
/*
* 8、
* 可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从
* error 级别只记录系统逻辑出错、异常等重要的错误信息
*/
}
logback.xml
${APP_NAME}
${ENCODER_PATTERN}
${LOG_HOME}/output.%d{yyyy-MM-dd}.log
7
${ENCODER_PATTERN}
${LOG_HOME}/error.%d{yyyy-MM-dd}.log
15
${ENCODER_PATTERN}
WARN
${LOG_HOME}/sync.%d{yyyy-MM-dd}.log
7
${ENCODER_PATTERN}