美文网首页
react-native-xlog开发回顾

react-native-xlog开发回顾

作者: 逆水处行舟 | 来源:发表于2017-02-13 14:04 被阅读0次

    背景简介

    微信最近开源了mars,其中的xlog模块在兼顾安全性、流畅性、完整性和容错性的前提下,达到了:高性能高压缩率、不丢失任何一行日志、避免系统卡顿和CPU波峰。我们项目正在用react-native开发,也需要一个日志模块能够较好的处理JS端的日志,xlog的出现,是我们项目的不错选择,所以有了react-native-xlog的实现。

    日志场景分析

    从RN的视角来看,可以分为JS端日志和native端日志。

    JS端日志

    1.打到控制台的日志

    调试RN项目,无论是通过adb logcat或是直接命令行执行react-natie log-android,都可以看到我们项目中调用console.trace/log/warn/error()的地方,都会有对应的日志输出,且包含字符串“ReactNativeJS”,这是在JNI层做的,其底层是直接用android的log实现,详细的此处不做分析。如果要在这边重定向日志到xlog,要么改jni层替代android的log调用,要么改js层的console.log的行为。前者,本人对c++的不熟,不在考虑范围。后者,改默认实现,很hack的行为,应该是可以实现,目前JS研究不够深,实现起来估计也不够直观。

    //参见JSLogging.cpp 
    JSValueRef nativeLoggingHook(
        JSContextRef ctx,   
        JSObjectRef function,
        JSObjectRef thisObject,
        size_t argumentCount,
        const JSValueRef arguments[], JSValueRef *exception) {
      android_LogPriority logLevel = ANDROID_LOG_DEBUG;
      if (argumentCount > 1) {
        int level = (int)Value(ctx, arguments[1]).asNumber();
        // The lowest log level we get from JS is 0. We shift and cap it to be
        // in the range the Android logging method expects.
        logLevel = std::min(
            static_cast<android_LogPriority>(level + ANDROID_LOG_DEBUG),
            ANDROID_LOG_FATAL);
      }
      if (argumentCount > 0) {
        String message = Value(ctx, arguments[0]).toString();
        FBLOG_PRI(logLevel, "ReactNativeJS", "%s", message.str().c_str());// <-- 就在这边
      }
      return Value::makeUndefined(ctx);
    }
    
    

    2.传给native(java)的日志

    调试的时候,JS端代码bug,就经常会遇到红色弹框。其实,JS会在每次和原生通信的时候都会捕获异常信息,传给native

    //摘自MessageQueue.js
    ...
    const guard = (fn) => {
     try {
       fn();
     } catch (error) {
       ErrorUtils.reportFatalError(error);
     }
    };
    ...
     callFunctionReturnFlushedQueue(module: string, method: string, args: Array<any>) {
       guard(() => {
         this.__callFunction(module, method, args);
         this.__callImmediates();
       });
    
       return this.flushedQueue();
     }
    ...
    
    

    这边的ErrorUtils.reportFatalError最终会调用ExceptionsManager.js中的reportException方法,可以看到在reportException方法,会根据isFatal会调用原生模块的ExceptionsManager.reportFatalException和ExceptionsManager.reportSoftException

    //摘自ExceptionsManager.js   
    /**
     * Handles the developer-visible aspect of errors and exceptions
     */
    let exceptionID = 0;
    function reportException(e: Error, isFatal: bool) {
      const {ExceptionsManager} = require('NativeModules');
      if (ExceptionsManager) {
        const parseErrorStack = require('parseErrorStack');
        const stack = parseErrorStack(e);
        const currentExceptionID = ++exceptionID;
        if (isFatal) {
          ExceptionsManager.reportFatalException(e.message, stack, currentExceptionID);
        } else {
          ExceptionsManager.reportSoftException(e.message, stack, currentExceptionID);
        }
        if (__DEV__) {
          const symbolicateStackTrace = require('symbolicateStackTrace');
          symbolicateStackTrace(stack).then(
            (prettyStack) => {
              if (prettyStack) {
                ExceptionsManager.updateExceptionMessage(e.message, prettyStack, currentExceptionID);
              } else {
                throw new Error('The stack is null');
              }
            }
          ).catch(
            (error) => console.warn('Unable to symbolicate stack trace: ' + error.message)
          );
        }
      }
    }
    
    ...
    /**
     * Logs exceptions to the (native) console and displays them
     */
    function handleException(e: Error, isFatal: boolean) {
      // Workaround for reporting errors caused by `throw 'some string'`
      // Unfortunately there is no way to figure out the stacktrace in this
      // case, so if you ended up here trying to trace an error, look for
      // `throw '<error message>'` somewhere in your codebase.
      if (!e.message) {
        e = new Error(e);
      }
      if (console._errorOriginal) {
        console._errorOriginal(e.message);
      } else {
        console.error(e.message);
      }
      reportException(e, isFatal); //将console.error也传给native
    }
    

    从贴出来的ExceptionsManager.js源码片段,JS端调用console.error时,也会将错误信息传给native的。

    native(java)端的日志

    native端的日志,我们只关心java,jni层的不考虑,我们目前的项目没有具体使用场景。
    java端的日志,我们可以暂且这么分,一种是RN的日志,一种是我们自己封装的模块的日志。

    1.RN的日志

    RN java端的日志接口类是FLog,官方提供了一个默认的实现类FLogDefaultLoggingDelegate,同时,也暴露了一个口子,让我们自己实现

    public class FLog {
     ...
    private static LoggingDelegate sHandler = FLogDefaultLoggingDelegate.getInstance();
    
    /**
     * Sets the logging delegate that overrides the default delegate.
     *
     * @param delegate the delegate to use
     */
    public static void setLoggingDelegate(LoggingDelegate delegate) {
      if (delegate == null) {
        throw new IllegalArgumentException();
      }
      sHandler = delegate;
    }
    
    

    回顾下前面说到的JS端传递异常到native端的代码,我们看下native端的实现,可以发现reportFatalException会抛出JavascriptException,RN并未做处理,这就会导致crash,而crash也是我们关心的;而reportSoftException会调用FLog.e进行记录

    public class ExceptionsManagerModule extends BaseJavaModule {
      ...
      @ReactMethod
      public void reportFatalException(String title, ReadableArray details, int exceptionId) {
        showOrThrowError(title, details, exceptionId);
      }
    
      @ReactMethod
      public void reportSoftException(String title, ReadableArray details, int exceptionId) {
        if (mDevSupportManager.getDevSupportEnabled()) {
          mDevSupportManager.showNewJSError(title, details, exceptionId);
        } else {
          FLog.e(ReactConstants.TAG, stackTraceToString(title, details));
        }
      }
      
      private void showOrThrowError(String title, ReadableArray details, int exceptionId) {
        if (mDevSupportManager.getDevSupportEnabled()) {
          mDevSupportManager.showNewJSError(title, details, exceptionId);
        } else {
          throw new JavascriptException(stackTraceToString(title, details));
        }
      }
     ...
     }
    

    2.自己封装模块的日志

    顾名思义,自己封装的,你可以自己选择各种实现。当然也有可能抛出各种异常的情况存在。

    react-native-xlog设计

    JS端

    接口

    • Xlog.open/close(), 开启/关闭xlog。
    • 使用封装的方法Xlog.verbose/debug/info/warn/error/fatal('your custom tag','log message here'),基本和android系统的log级别一致。

    实现

    具体实现通过封装原生模块完成。

    native端:

    接口

    • 两个初始化接口init/initWithNativeCrashInclude, 后者多包含了crash日志的记录
    • 暴露给JS端的接口,和前面JS端的对应。

    实现

    • 自定义FLog的实现类FLogging2XLogDelegate,将Flog的日志打到xlog
    • JS端对应的桥接方法,直接打到xlog
    • 自定义UncaughtExceptionHandler的实现类XLogCustomCrashHandler,覆盖系统默认的hander

    相关文章

      网友评论

          本文标题:react-native-xlog开发回顾

          本文链接:https://www.haomeiwen.com/subject/upazittx.html