统一封装Logger对象并获取正确的位置信息

平时项目中,我们一般会在每一个类中定义一个logger对象,一般是使用日志门面系统slf4j去获取日志对象

    private static final Logger logger = LoggerFactory.getLogger(xx.class);

但是考虑到项目文件如果非常之多,可能会非常麻烦,当然,可以使用lombok(这真是太好用了).还有一种方案就是自己定义一个Logger工具类,封装logger对象,但是这个弊端就是会使得打印位置信息的时候,全都定位到了这个工具类.

这里就提供一下比较常见的日志系统怎么来解决这个问题.害,空下来就光研究这些东西了.

log4j,公司的项目就是用的这个,very old,所以我今天就在琢磨想给他升级成logback/log4j2.

log4j自己的Logger对象有一个方法:public void log(String callerFQCN, Priority level, Object message, Throwable t)

这个方法可以将调用者的类名传入 FQCN (Full Qualified Class Name),然后在位置信息中有一段代码.

public LocationInfo(Throwable t, String fqnOfCallingClass) {
      if(t == null || fqnOfCallingClass == null)
	return;
      if (getLineNumberMethod != null) {
          try {
              Object[] noArgs = null;
              Object[] elements =  (Object[]) getStackTraceMethod.invoke(t, noArgs);
              String prevClass = NA;
              for(int i = elements.length - 1; i >= 0; i--) {
                  String thisClass = (String) getClassNameMethod.invoke(elements[i], noArgs);
                  if(fqnOfCallingClass.equals(thisClass)) {
                      int caller = i + 1;
                      if (caller < elements.length) {
                          className = prevClass;
                          methodName = (String) getMethodNameMethod.invoke(elements[caller], noArgs);
                          fileName = (String) getFileNameMethod.invoke(elements[caller], noArgs);
                          if (fileName == null) {
                              fileName = NA;
                          }
                          int line = ((Integer) getLineNumberMethod.invoke(elements[caller], noArgs)).intValue();
                          if (line < 0) {
                              lineNumber = NA;
                          } else {
                              lineNumber = String.valueOf(line);
                          }
                          StringBuffer buf = new StringBuffer();
                          buf.append(className);
                          buf.append(".");
                          buf.append(methodName);
                          buf.append("(");
                          buf.append(fileName);
                          buf.append(":");
                          buf.append(lineNumber);
                          buf.append(")");
                          this.fullInfo = buf.toString();
                      }
                      return;
                  }
                  prevClass = thisClass;
              }
              return;
          } catch(IllegalAccessException ex) {
              LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
          } catch(InvocationTargetException ex) {
              if (ex.getTargetException() instanceof InterruptedException
                      || ex.getTargetException() instanceof InterruptedIOException) {
                  Thread.currentThread().interrupt();
              }
              LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
          } catch(RuntimeException ex) {
              LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
          }
      }

      String s;
      // Protect against multiple access to sw.
      synchronized(sw) {
	t.printStackTrace(pw);
	s = sw.toString();
	sw.getBuffer().setLength(0);
      }
      //System.out.println("s is ["+s+"].");
      int ibegin, iend;

      // Given the current structure of the package, the line
      // containing "org.apache.log4j.Category." should be printed just
      // before the caller.

      // This method of searching may not be fastest but it's safer
      // than counting the stack depth which is not guaranteed to be
      // constant across JVM implementations.
      ibegin = s.lastIndexOf(fqnOfCallingClass);
      if(ibegin == -1)
	return;

      //
      //   if the next character after the class name exists
      //       but is not a period, see if the classname is
      //       followed by a period earlier in the trace.
      //       Minimizes mistakeningly matching on a class whose
      //       name is a substring of the desired class.
      //       See bug 44888.
      if (ibegin + fqnOfCallingClass.length() < s.length() &&
              s.charAt(ibegin + fqnOfCallingClass.length()) != '.') {
          int i = s.lastIndexOf(fqnOfCallingClass + ".");
          if (i != -1) {
              ibegin = i;
          }
      }


      ibegin = s.indexOf(Layout.LINE_SEP, ibegin);
      if(ibegin == -1)
	return;
      ibegin+= Layout.LINE_SEP_LEN;

      // determine end of line
      iend = s.indexOf(Layout.LINE_SEP, ibegin);
      if(iend == -1)
	return;

      // VA has a different stack trace format which doesn't
      // need to skip the inital 'at'
      if(!inVisualAge) {
	// back up to first blank character
	ibegin = s.lastIndexOf("at ", iend);
	if(ibegin == -1)
	  return;
	// Add 3 to skip "at ";
	ibegin += 3;
      }
      // everything between is the requested stack item
      this.fullInfo = s.substring(ibegin, iend);
    }

他会将传入的类名与调用者去进行比较,然后得到正确的位置信息.至此,所以我们只要使用org.apache.log4j.Logger对象的log方法,然后在pattern中使用%l/location就可以得到调用这个封装的日志方法的位置信息.

logback,spring boot 官方在用的日志框架,害,我也一直挺喜欢,因为spring boot里面 这个可以带颜色,花里胡哨,2333333.但今天去看了好多博客,发现的确好像性能是不如log4j2,但这里我也说一下.自己研究出来的比较蠢的替代方案吧.

因为logback是slf4j的一个实现方案,所以他自己已经封装好了FQCN信息,我无法去修改,但看了好久的文档(的确 英文水平不是特别过关哈).是有一个%caller,可以通过%caller得到调用栈.

但,其实我只需要得到调用者的调用者,也就是偏移了一位的caller ,那么就是使用%caller{1..2}这种方式,可以只得到Caller+1

但还不够,这也太难看了吧.用%replace正则替换

%replace(%replace(%caller{1..2}){'\w+\+\d+\s+at\s+',''}){'\s*',''}

结果就是 

[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] - %replace(%replace(%caller{1..2}){'\w+\+\d+\s+at\s+',''}){'\s*',''} %m%n

测试一下

得到了正确的位置信息.

Log4j2其实与Log4j差不多,但是我们需要取到Logger对象的具体实现类,在log4j.core里面的Logger对象,他有一个方法:

public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message)

看到这个fqcn没有,直接传入工具类的class的名字.

最后他会在Log4jLogEvent里面的calcLocation方法中获取到调用者的信息.

public static StackTraceElement calcLocation(final String fqcnOfLogger) {
        if (fqcnOfLogger == null) {
            return null;
        }
        // LOG4J2-1029 new Throwable().getStackTrace is faster than Thread.currentThread().getStackTrace().
        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
        StackTraceElement last = null;
        for (int i = stackTrace.length - 1; i > 0; i--) {
            final String className = stackTrace[i].getClassName();
            if (fqcnOfLogger.equals(className)) {
                return last;
            }
            last = stackTrace[i];
        }
        return null;
    }

那么,在Pattern上用上%l/location,舒舒服服 简简单单,完成.

猜你喜欢

转载自blog.csdn.net/hexiaodiao/article/details/103690525