Android StackTraceElement与Log工具类设计

StackTraceElement引入 StackTraceElement这个类在开发中并不常见,但是做过Java2EE开发的人员肯定不陌生 java util logging Logg

StackTraceElement引入

StackTraceElement这个类在开发中并不常见,但是做过Java2EE开发的人员肯定不陌生 java.util.logging.Logger

类或log4j工具包,当我们使用日志功能的时候,系统为我们打印很丰富的信息,格式如下:

运行时间] [当前类名] [方法名]

INFO: [用户信息]

java.util.logging.Logger打印log信息

十月 24, 2016 9:06:33 下午 com.demo.MainTestmain

警告: helloworldwarning

十月 24, 2016 9:06:33 下午 com.demo.MainTestmain

信息: helloworldinfo

使用log4j的tomcat日志

十月 24, 2016 9:04:24 下午 org.apache.catalina.startup.VersionLoggerListenerlog

信息: Serverversion:        ApacheTomcat/7.0.70

十月 24, 2016 9:04:24 下午 org.apache.catalina.startup.VersionLoggerListenerlog

信息: Serverbuilt:          Jun 15 2016 16:27:45 UTC

十月 24, 2016 9:04:24 下午 org.apache.catalina.startup.VersionLoggerListenerlog

信息: Servernumber:        7.0.70.0

十月 24, 2016 9:04:24 下午 org.apache.catalina.startup.VersionLoggerListenerlog

信息: OSName:              Windows 7

相比较而言Android的日志输出就有些简单粗暴了,虽然时间日期可有LogCat自动输出,可是方法名或类名都没有,需要我们自己将方法名或者类名手动输入tag标签或者msg才可以。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

问题来了,为什么 java.util.logging.Logger

类或log4j工具包可以输出很规范详细的信息呢?接下来我们查看一下源码设计来追本溯源。

在JDK的源代码里面java.util.logging.LogRecord类的inferCaller方法。

private void inferCaller() {

needToInferCaller = false;

JavaLangAccessaccess = SharedSecrets.getJavaLangAccess();

Throwablethrowable = new Throwable();

int depth = access.getStackTraceDepth(throwable);

 

boolean lookingForLogger = true;

for (int ix = 0; ix < depth; ix++) {

// Calling getStackTraceElement directly prevents the VM

// from paying the cost of building the entire stack frame.

StackTraceElementframe =

access.getStackTraceElement(throwable, ix);

String cname = frame.getClassName();

boolean isLoggerImpl = isLoggerImplFrame(cname);

if (lookingForLogger) {

// Skip all frames until we have found the first logger frame.

if (isLoggerImpl) {

lookingForLogger = false;

}

} else {

if (!isLoggerImpl) {

// skip reflection call

if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) {

  // We've found the relevant frame.

  setSourceClassName(cname);

  setSourceMethodName(frame.getMethodName());

  return;

}

}

}

}

// We haven't found a suitable frame, so just punt.  This is

// OK as we are only committed to making a "best effort" here.

}

log4j中LocationInfo类中一个静态代码块设计如下:

static {

      try {

          Class[] noArgs = null;

          getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs);

          Class stackTraceElementClass = Class.forName("java.lang.StackTraceElement");

          getClassNameMethod = stackTraceElementClass.getMethod("getClassName", noArgs);

          getMethodNameMethod = stackTraceElementClass.getMethod("getMethodName", noArgs);

          getFileNameMethod = stackTraceElementClass.getMethod("getFileName", noArgs);

          getLineNumberMethod = stackTraceElementClass.getMethod("getLineNumber", noArgs);

      } catch(ClassNotFoundExceptionex) {

          LogLog.debug("LocationInfo will use pre-JDK 1.4 methods to determine location.");

      } catch(NoSuchMethodExceptionex) {

          LogLog.debug("LocationInfo will use pre-JDK 1.4 methods to determine location.");

      }

  }

两个日志工具类都使用了一个类StackTraceElement,只是获取方式不一样,下面我们介绍一下该类。

StackTraceElement介绍

堆栈跟踪中的元素,它由Throwable.getStackTrace()返回。每个元素表示单独的一个堆栈帧。所有的堆栈帧(堆栈顶部的那个堆栈帧除外)都表示一个方法调用。堆栈顶部的帧表示生成堆栈跟踪的执行点。通常,这是创建对应于堆栈跟踪的 throwable 的点。

StackTraceElement是JDK1.4开始引入的,而java.util.logging.Logger类也是JDK1.4开始引入的,某种意义上来讲StackTraceElement就是为java.util.logging.Logger的实现而设计的。在初始引入是是通过Throwable类通过 getStackTrace()

方法获取一个StackTraceElement数组,在JDK1.5中Thread类引入了 getStackTrace()

getAllStackTraces()

两个方法。这下子,我们不用 (new Throwable()).getStackTrace()

;可以调用 Thread.getCurrentThread().getStackTrace()

来获得当前线程的运行栈信息。不仅如此,只要权限允许,还可以获得其它线程的运行栈信息。

Throwable类部分代码:

public StackTraceElement[] getStackTrace() {

return getOurStackTrace().clone();

}

private synchronized StackTraceElement[] getOurStackTrace() {

// Initialize stack trace field with information from

// backtrace if this is the first call to this method

if (stackTrace == UNASSIGNED_STACK ||

(stackTrace == null && backtrace != null) /* Out of protocol state */) {

int depth = getStackTraceDepth();

stackTrace = new StackTraceElement[depth];

for (int i=0; i < depth; i++)

stackTrace[i] = getStackTraceElement(i);

} else if (stackTrace == null) {

return UNASSIGNED_STACK;

}

return stackTrace;

}

Thread类部分代码:

public StackTraceElement[] getStackTrace() {

if (this != Thread.currentThread()) {

// check for getStackTrace permission

SecurityManagersecurity = System.getSecurityManager();

if (security != null) {

security.checkPermission(

SecurityConstants.GET_STACK_TRACE_PERMISSION);

}

// optimization so we do not call into the vm for threads that

// have not yet started or have terminated

if (!isAlive()) {

return EMPTY_STACK_TRACE;

}

StackTraceElement[][] stackTraceArray = dumpThreads(new Thread[] {this});

StackTraceElement[] stackTrace = stackTraceArray[0];

// a thread that was alive during the previous isAlive call may have

// since terminated, therefore not having a stacktrace.

if (stackTrace == null) {

stackTrace = EMPTY_STACK_TRACE;

}

return stackTrace;

} else {

// Don't need JVM help for current thread

return (new Exception()).getStackTrace();

}

}

public static Map<Thread, StackTraceElement[]> getAllStackTraces() {

// check for getStackTrace permission

//...

// Get a snapshot of the list of all threads

Thread[] threads = getThreads();

StackTraceElement[][] traces = dumpThreads(threads);

Map<Thread, StackTraceElement[]> m = new HashMap<>(threads.length);

for (int i = 0; i < threads.length; i++) {

StackTraceElement[] stackTrace = traces[i];

if (stackTrace != null) {

m.put(threads[i], stackTrace);

}

// else terminated so we don't put it in the map

}

return m;

}

上面已经说了StackTraceElement就是一个堆栈帧,在方法栈中每个方法都有一个StackTraceElement,每个StackTraceElement中保存了方法的相关信息,如方法所处的类、方法名、方法所处类文件名称以及该方法执行行号。

  • String getClassName()

    返回类的完全限定名,该类包含由该堆栈跟踪元素所表示的执行点。

  • String getFileName()

    返回源文件名,该文件包含由该堆栈跟踪元素所表示的执行点。

  • int getLineNumber()

    返回源行的行号,该行包含由该堆栈该跟踪元素所表示的执行点。

  • String getMethodName()

    返回方法名,此方法包含由该堆栈跟踪元素所表示的执行点。

public static void main(String[] args) {

test();

}

private static void test(){

StackTraceElement[] caller = new Throwable().getStackTrace();

for(int i=0;i<caller.length;i++){

System.out.println("====> "+caller[i].getClassName());

System.out.println("====> "+caller[i].getMethodName());

System.out.println("====> "+caller[i].getFileName());

System.out.println("====> "+caller[i].getLineNumber());

System.out.println("----------------------------------");

}

}

上面方法打印结果如下:

====> com.demo.MainTest

====> test

====> MainTest.java

====> 11

----------------------------------

====> com.demo.MainTest

====> main

====> MainTest.java

====> 7

----------------------------------

在MainTest类中首先定义了一个test方法,然后在main方法中调用,由打印信息知道该次执行方法栈深度为2,方法调用的相关信息也如数打印了出来。上述方法是使用Throwable类获取的方法栈信息,如果使用Thread获取会多出来一个层级,示例代码如下:

public static void main(String[] args) {

test01();

System.out.println("---------------------------------------");

test02();

}

private static void test01(){

StackTraceElement[] caller = new Throwable().getStackTrace();

for(int i=0;i<caller.length;i++){

System.out.println("Throwable: "+caller[i].toString());

}

}

private static void test02(){

StackTraceElement[] caller = Thread.currentThread().getStackTrace();

for(int i=0;i<caller.length;i++){

System.out.println("Thread: "+caller[i].toString());

}

}

Throwable: com.demo.MainTest.test01(MainTest.java:39)

Throwable: com.demo.MainTest.main(MainTest.java:7)

---------------------------------------

Thread: java.lang.Thread.getStackTrace(UnknownSource)

Thread: com.demo.MainTest.test02(MainTest.java:45)

Thread: com.demo.MainTest.main(MainTest.java:9)

Android Log的设计

在Android的原生Log需要两个参数一个是tag标签一个是msg,我们只需要将tag重新设计一下然后作为原生Log相关方法中tag标签,从LogCat中我们开发中最想知道的信息给出来就可以了,如类名、方法名还有方法调用的行数,时间信息我们不需要从新赋值,因为LogCat会自动打印出Log执行时的时间信息。

整个类的设计很简单,核心就在tag的设计上面,LogUtils类的代码如下:

public class LogUtils {

 

private static final boolean DEBUG = true;

public static String tagPrefix = "";

 

private LogUtils() {

}

 

private static String generateTag() {

StackTraceElementcaller = new Throwable().getStackTrace()[2];

String tag = "%s.%s(L:%d)";

String callerClazzName = caller.getClassName();

callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1);

tag = String.format(tag, callerClazzName, caller.getMethodName(), caller.getLineNumber());

tag = TextUtils.isEmpty(tagPrefix) ? tag : tagPrefix + ":" + tag;

return tag;

}

 

public static void d(String content) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.d(tag, content);

}

 

public static void d(String content, Throwabletr) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.d(tag, content, tr);

}

 

public static void e(String content) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.e(tag, content);

}

 

public static void e(String content, Throwabletr) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.e(tag, content, tr);

}

 

public static void i(String content) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.i(tag, content);

}

 

public static void i(String content, Throwabletr) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.i(tag, content, tr);

}

 

public static void v(String content) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.v(tag, content);

}

 

public static void v(String content, Throwabletr) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.v(tag, content, tr);

}

 

public static void w(String content) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.w(tag, content);

}

 

public static void w(String content, Throwabletr) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.w(tag, content, tr);

}

 

public static void w(Throwabletr) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.w(tag, tr);

}

 

public static void wtf(String content) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.wtf(tag, content);

}

 

public static void wtf(String content, Throwabletr) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.wtf(tag, content, tr);

}

 

public static void wtf(Throwabletr) {

if (!DEBUG)

return;

String tag = generateTag();

 

Log.wtf(tag, tr);

}

}

写一个简单的测试类验证一下,测试类代码如下:

public class MainActivity extends Activity {

protected void onCreate(BundlesavedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

LogUtils.tagPrefix="test";

LogUtils.d("hello world01");

test01();

}

private void test01(){

LogUtils.d("hello world02");

}

}

10-25 23:00:24.063: D/test:MainActivity.onCreate(L:13): helloworld01

10-25 23:00:24.063: D/test:MainActivity.test01(L:17): helloworld02

参考资料

JDK Logging模块深入分析

Log信息获取调用类和调用方法名的实现原理

StackTraceElement类使用说明

xUtils LogUtil类设计

未登录用户
全部评论0
到底啦