程序异常和日志的设计方法

【程序异常和日志的设计方法】恢弘志士之气,不宜妄自菲薄。这篇文章主要讲述程序异常和日志的设计方法相关的知识,希望能为你提供帮助。

任何一套程序都少不了异常和日志,从纯技术编码的角度来讲处理起来并不复杂。但这两块编码的目的更多是围绕非正常情况来设计的,所以从业务和运维的角度来讲想处理好日志和异常其实并不简单。所有的团队设计异常和日志的初衷都是好的,但有些团队在落地执行时并不会达到所预想的目的,甚至是为了制定而制定,产生大量无用编码的同时无形中增加了开发同学的工作量,同时也会影响程序的性能和代码的可读性。
  • 异常的目的:记录程序中异常数据,重在结果;
  • 日志的目的:记录程序中流程数据,重在过程;
以上是笔者个人的归纳,下文中也会围绕这两个核心点从理论到场景适配展开讲述。因每套系统的业务情况和复杂度都不太一样,所以建议按需设计,同时文中所描述的是笔者个人的观点,能力有限,不妥的地方欢迎指导留言以免带偏。
首先还是来一些基础,复盘一下java的API。
一、异常如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行行交给异常处理器。java程序中一般会有编译错误、逻辑错误、运行时错误(异常处理)这几种非正常的情况。

1.1、异常的分类
1.1.1、Error 指系统错误,java 运行时系统的内部错误和资源耗尽错误,应用程序不会抛出该类对象。如果出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。
1.1.2、Exception 指程序异常,异常有两个分支,一个是运行时异常 RuntimeException,一个是CheckedException。
CheckedException外部错误,这种异常发生在代码编译阶段,Java 编译器会强制程序去捕获此类异常,然后要求把这段可能出现异常的程序进行 try catch包装(如果在代码中没有找到相关的catch语句会抛出ThreadGroup.uncaughtException()方法,在字节码编程时会出现这种情况)。
需查异常是在方法定义时用throws关键字声明的再用catch捕获后可以进行恢复,在catch中最简单的处理方式是声明e.printStackTrace()语句。使用时两个三点:
  1. throws时不要直接抛出Exception,这样系统会从Exception子类一个一个搜索match很费时间;
  2. catch时要分层catch即要从子类到父类一层层catch;
private static void throwOldException() throws IOException
throw new IOException("a forced exception"); //需查异常

RuntimeException如字面意思,指运行时错误,这种异常都发生在程序运行阶段,程序员的错误造成。API都需要继承RuntimeException,由程序员设计的异常规则。不需查异常是被设计来强化方法的约定(方法的约定指方法使用者和调用者之间的一种约定,包括调用方法和返回结果的约定等)的。
因为执行了异常检查的语块比没执行异常的语块运行速度要慢很多,所以不建议把整个方法的实现通通包装起来,在使用时几点建议可以参考:
  1. 用时间频度来确定,也就是说不会发生异常的地方不要加入catch语句;
  2. 包装可选方法,?不允许调用的方法不实现具体的功能,直接抛出UnsupportedOperationException来强制不能调用此方法。
  3. 对经常需要调用的语句不要进行异常处理,在逻辑上也显然不能作为异常来处理。可以有两种方法来代替异常处理功能:1、是使用特殊的返回值;二、是前导检查法,例如string.length在使用前先判断是否越界了。
private static void throwNestedException() throws NestedException
try
String s = "abc";
if(s.equals("abc"))
throwOldException(); //不需查异常

catch (IOException e)
throw new NestedException(e);


1.1.3、throws和throws的区别位置不同
throws 用在函数上,后面跟的是异常类(可以跟多个), throw 用在函数内,后面跟的是异常对象,一次只能一个对象。
功能不同
  1. throws 用来声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方式; throw 抛出具体的问题对象,执行到 throw,功能就已经结束了,跳转到调用者,并将具体的问题对象抛给调用者。也就是说 throw 语句独立存在时,下面不要定义其他语句,因为执行不到;
  2. throws 表示出现异常的一种可能性,并不一定会发生这些异常; throw 则是抛出了异常,执行 throw 则一定抛出了某种异常对象;
  3. 两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理;
1.2、自定义异常的方法
1.2.1、自定义参数
class MyException2 extends Exception
private int x;
public MyException2()
public MyException2(String msg)super(msg);
public MyException2(String msg, int x)
super(msg);
this.x = x;

public int val()return x;
public String getMessage()
return "Detail Message: "+ x + " "+ super.getMessage();


1.2.2、重新捕获异常
public class Rethrowing
public static void f() throws Exception
System.out.println("originating the exception in f()");
throw new Exception("thrown from f()");

public static void g() throws Exception
try
f();
catch(Exception e)
System.out.println("Inside g(),e.printStackTrace()");
e.printStackTrace(System.out);
throw e;


public static void main(String[] args)
try
g();
catch(Exception e)
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);



1.2.3、异常链想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这称为异常链。如果把自定义的异常链起来,应该使用initCause()方法,而不是原始的cause参数。
class DynamicFieldsException extends Exception

public class DynamicFields

public Object setField(String id, Object value) throws DynamicFieldsException
DynamicFieldsException dfe = new DynamicFieldsException();
dfe.initCause(new NullPointerException()); //用此方法封装了原始的异常信息
throw dfe;

public static void main(String[] args)
DynamicFields df = new DynamicFields();
try
Object field = df.setField("d", null); // Exception
catch(DynamicFieldsException e)
e.printStackTrace(System.out);



1.2.4、异常掩盖
class VeryImportantException extends Exception
public String toString()
return "A very important exception!";



class HoHumException extends Exception
public String toString()
return "A trivial exception";



public class LostMessage
void f() throws VeryImportantException
throw new VeryImportantException();

void dispose() throws HoHumException
throw new HoHumException();

public static void main(String[] args)
try
LostMessage lm = new LostMessage();
try
lm.f();
finally
lm.dispose();

catch(Exception e)
System.out.println(e); //此处的异常被finally给替换了



1.2.5、把CheckedException转换为RuntimeException
class WrapCheckedException
void throwRuntimeException(int type)
try
switch(type)
case 0: throw new FileNotFoundException();
case 1: throw new IOException();
case 2: throw new RuntimeException("Where am I?");
default: return;

catch(Exception e)// Adapt to unchecked:
throw new RuntimeException(e);


//这种技术不会导致异常丢失也不会破坏异常链

1.3、异常处理程序的架构设计
一些互联网系统的特点一般都是分布式的,而且每个应用的集群节点都不少。这类系统的问题基本全是通过日志(包含异常日志信息和业务日志信息)来排查的,但如果从几百上千台服务中找到所需要的日志信息也并不简单,虽然有些公司会提供线上日志的搜索功能,但这个搜索功能相对通用。如果在架构设计时采用必要的手段可以做下互补收益还是很大的,本小节描述一下笔者一直使用的异常处理的一种方法,此设计围绕异常发现和处理来设计的,大体的设计流程如下:
下述的设计是用在一个相对比较复杂

    推荐阅读