天眼FAQ

1、如何上报自定义事件?

目前有两种方式可以上报自定义事件

利用HTML标签的自定义属性将click事件替换为自定义事件

<button>保存</button>

在点击上述HTML结构中的button标签时,触发的点击事件内容会包含如下信息

{
  ...此处省略其他字段
  et: 'button',
  ec: '保存',
  e: 'click',
  l: ''
}

利用申明式日志采集配置(log_content_attribute和log_event_attribute)做以下调整

<button data-log="销售数据仪表盘" data-event="保存仪表盘">保存</button>

再次触发的点击事件内容会包含如下信息

{
  ...此处省略其他字段
  et: 'button',
  ec: '保存',
  e: '保存仪表盘',
  l: '销售数据仪表盘'
}

如果在初始化配置中将log_event_attribute配置为data-action,那么HTML结构中也应该进行调整

<button data-log="销售数据仪表盘" data-action="保存仪表盘">保存</button>

代码中埋点调用report接口上报

在上述方式不满足的情况下,可以直接调用探针提供的report接口上报

__myWebLogTracker__ && __myWebLogTracker__.report('事件名称', '事件内容');

事件名称不能与内置事件冲突

事件内容可以是字符串或JSON对象

2、如何在日志中添加业务系统用户标识(租户编码、用户账号、用户分组)?

我们提供了3个字段供标识这些信息

  • 租户编码
  • 用户账号
  • 用户分组 目前有两种方法可以在日志中添加业务系统用户的相关信息

使用自动查询当前租户、用户名、用户分组的配置参数实现(2.1.0或更高版本)

详见配置参数 - tenant_code_query、user_account_query和user_group_query

使用registUser接口写入

详见接口方法 - registUser

3、怎样才能看到完整的前端错误堆栈信息?

需要看到前端错误堆栈信息需要满足以下条件:

  • 探针版本不低于2.0.4(必须)
  • 上传sourcemap(如不上传无法看到错误在源码中的位置)

4、如何开启混合APP的崩溃日志的上报?

需要看到混合APP的崩溃日志信息必须同时满足以下条件:

  • 探针版本不低于2.0.4
  • 在appcloud平台为应用添加MLogCollection插件
  • 在探针配置中的collect_event_types勾选crash事件

5、页面加载时间是如何计算的?

页面加载时间目前按照以下兼容性优先级依次进行计算:

  • window.performance.getEntriesByType('navigation')[0].duration
  • window.performance.timing.loadEventStart - window.performance.timing.navigationStart
  • window.performance.timing.loadEventStart - 探针JS加载的时间(当window.performance.timing.navigationStart为0的时候使用)

在浏览器控制台开启状态下重新加载页面,可以看到下方有一个Load时间,与上述的前两个获取方法得到的值是一致的

6、API是如何被探针全局捕获到的?

浏览器发出请求,必须通过XMLHttpRequest及fetch,探针通过对其重新包装,从而拦截到应用内的所有请求。

XMLHttpRequest

基本的修改原理参照以下代码片段

const originOpen = XMLHttpRequest.prototype.open;
const originSend = XMLHttpRequest.prototype.send;

// 改写open
function (a, u, ...args) {
  const r = arguments.length === 1 ? [a] : [a, u, ...args];
  let apiUrl = u || '';
  // 检查是否属于忽略的API
  if (apiUrl && isIgnoreApi(apiUrl, $self$.conf.api_ignore_urls)) {
    apiUrl = '';
  }
  // 在实例中写入请求方式与请求地址
  this.__api_method__ = (a || 'get').toUpperCase();
  this.__api_url__ = apiUrl;
  // 创建xhr
  try {
    return originOpen.apply(this, r);
  } catch (error) {
    return Function.prototype.apply.apply(originOpen, [this, ...r]);
  }
};

// 改写send
function (...args) {
  const tnow = new Date();
  const r = [...args];
  // send发送前为xhr实例绑定readystatechange事件
  if (this) {
    addEvent(this, 'readystatechange', (evt) => {
      const targetXhr = evt.target;
      if (targetXhr && targetXhr.__api_url__ && targetXhr.readyState === 4) {
        const ti = new Date() - tnow;
        // 处理API URL
        const apiTrimUrl = cutAPIUrl(targetXhr.__api_url__, $self$.conf.api_property_cb);
        // 获取响应状态和内容
        const { status, responseText } = targetXhr;
        // 响应内容的长度
        const contentLen = responseText ? (responseText.length || 0) : 0;
        if (status >= 200 && status < 300) {
          // 上报
        } else {
          // 上报
        }
      }
    })
  }

  try {
    return originSend.apply(this, r);
  } catch (error) {
    return Function.prototype.apply.apply(originSend, [this, ...r]);
  }
};

fetch

基本的修改原理参照以下代码片段

// 原始fetch
const originFetch = window.fetch;
// 改写后的fetch
function (u, i, ...args) {
  const s = arguments.length === 1 ? [u] : [u, i, ...args];

  if (!i || i.method === 'HEAD' || i.mode === 'no-cors') {
    return originFetch.apply(g, s);
  }
  const apiUrl = (u && typeof u !== 'string' ? u.url : u) || '';
  // 检查API请求是否在忽略的url中
  if (apiUrl && isIgnoreApi(apiUrl, $self$.conf.api_ignore_urls)) {
    return originFetch.apply(g, s);
  }
  // 处理API URL
  const apiTrimUrl = cutAPIUrl(apiUrl, $self$.conf.api_property_cb);
  // 记录调用前的时间戳
  const tnow = new Date();
  // 执行fetch
  return originFetch.apply(g, s).then((p) => {
    const a = p.clone();
    // 此处得到请求耗时
    const ti = new Date() - tnow;
    if (a.ok) {
      a.arrayBuffer().then((re) => {
        // 上报
      });
    } else {
      a.arrayBuffer().then((re) => {
        // 上报
      });
    }
    return p;
  });
};
};

7、API响应时间是如何计算的?

通过performance.getEntriesByType('resource')获取

调用window.performance.getEntriesByType('resource')得到initiatorType为xmlhttprequest或fetch的PerformanceEntry对象,取其duration作为API响应时间,同时可以将其中的多个时间戳对整个请求周期进行分段

  • Queueing、Stalled:PerformanceEntry.connectStart - PerformanceEntry.fetchStart
  • Initial connection:PerformanceEntry.connectEnd - PerformanceEntry.connectStart
  • Waiting:PerformanceEntry.responseStart - PerformanceEntry.requestStart
  • Content Download:PerformanceEntry.responseEnd - PerformanceEntry.responseStart
  • Total:PerformanceEntry.duration 或 PerformanceEntry.responseEnd - PerformanceEntry.fetchStart

具体可对照控制台Timing

需要注意以下几点:

  • Waiting、Content Download得到的值与控制台中显示的值存在略微误差
  • 因浏览器兼容性的关系,有可能出现仅有Total的情况(其他时间戳均为0,导致计算异常)
  • 该方式无法获取到API METHOD
  • 仅能获取到成功的请求,如401、403、404、500、501、502、503、504等异常状态因不会进入window.performance.getEntriesByType('resource'),因此无法获取
  • 这种方法对于响应内容长度的获取正常

通过改写后的XMLHttpRequest及fetch获取(查看原理

XMLHttpRequest.send或fetch在真正被调用前,记录一个时间戳,响应结束后用当前时间减去记录的时间戳得到请求的完整时间

需要注意以下几点:

  • 此种方法得到的请求时间通常略微偏大,原因在于记录起始时间戳到真正的send或fetch的执行会有短暂的延迟
  • 这种方法只能得到请求总时长
  • 这种方法能够获取到API METHOD
  • 这种方式对于API STATUS没有限制
  • 这种方式在获取响应内容长度时可能会存在问题,尤其是在请求跨域的情况下有可能引发浏览器警告(这不是JS错误,不会阻塞应用)

results matching ""

    No results matching ""