clientapm-service.js 4.77 KB
const msg2row = require('./msg2row');
const Sender = require('influx-batch-sender');
const MysqlSender = require('../lib/mysql-sender');
const ipService = require('./ip-service');
const useragent = require('useragent');

const _ = require('lodash');

const config = global.yoho.config;
const logger = global.yoho.logger;

const durationType = {
  dcl: 'domcontentload',
  ld: 'domreadyload',
  fs: 'firstscreen'
};

const routeInfluxSender = new Sender(config.reportRoute);

const slowRouterSqlSender = new MysqlSender(config.table.slow);
const errorRouterSqlSender = new MysqlSender(config.table.error);
const perfReportSqlSender = new MysqlSender(config.table.perf);

function handleClientError(scope, item) {
  // 无效数据
  if (!/cdn.yoho.cn/.test(item.sc)) {
    return;
  }

  if (!item.msg) {
    return;
  }

  if (_.startsWith(item.msg.toLowerCase(), 'script error')) {
    return;
  }

  if (item.msg.toLowerCase().charCodeAt(0) < 'a'.charCodeAt(0) ||
        item.msg.toLowerCase().charCodeAt(0) > 'z'.charCodeAt(0)) {
    return;
  }

  let data = {
    tags: {
      app: scope.app,
      route: item.r
    },
    fields: {
      reqID: item.rid,
      uid: item.u,
      udid: item.ud
    },
    time: new Date().getTime() * 1000000
  };

  _.merge(data, {
    measurement: 'error-report',
    tags: {
      type: 'client'
    },
    fields: Object.assign({
      message: item.msg,
      useragent: scope.useragent,
      stack: item.st ? item.st.replace(/"/g, '') : '',
      script: item.sc,
      line: _.parseInt(item.ln || 0),
      column: _.parseInt(item.cn || 0),
    })
  });

  const ipInfo = ipService.getIsp(scope.ip);
  const row = Object.assign(msg2row.errorRouter(data), ipInfo, {
    ip: scope.ip
  });

  logger.debug('[client] error info [%s]', JSON.stringify(row));
  errorRouterSqlSender.addMessage(row);
}

function handleClientFirst(scope, item) {
  let data = {
    tags: {
      app: scope.app,
      route: item.r
    },
    fields: {
      reqID: item.rid,
      uid: item.u,
      udid: item.ud
    },
    time: new Date().getTime() * 1000000
  };


  if (!item.t) {
    return;
  }

  const duration = _.parseInt(item.t);

  if (!duration || duration > 1000 * 60 * 10) {
    return;
  }

  _.merge(data, {
    measurement: 'route-info',
    tags: {
      type: durationType[item.tp],
    },
    fields: {
      duration: duration,
      useragent: scope.useragent
    }
  });

  routeInfluxSender.addMessage({
    measurement: 'client-info',
    tags: {
      app: scope.app,
      host: item.hostname,
      route: item.r,
      type: durationType[item.tp]
    },
    fields: {
      duration: duration
    }
  });

  logger.debug('[client] slow route info [%s]', JSON.stringify(data));

  if (duration > config.slowRoute.min && duration < config.slowRoute.max) {
    slowRouterSqlSender.addMessage(msg2row.slowRouter(data));
  }
}

async function handleClientTiming(scope, item) {
  const agent = useragent.lookup(scope.useragent);

  if (agent.family.toLowerCase() === 'baiduspider-render') {
    return;
  }

  const data = {
    app: scope.app,
    reqid: item.rid,
    uid: item.u,
    udid: item.ud,
    route: item.r,
    create_time: new Date().getTime(),
    domType: 'html',
    mineType: 'html',
    url: item.pt,
    dnsTime: _.parseInt(item.dt || '0'),
    tcpTime: _.parseInt(item.tt || '0'),
    responseTime: _.parseInt(item.rt || '0'),
    domParseTime: _.parseInt(item.domt || '0'),
    pageRenderTime: _.parseInt(item.et || '0'),
    pageInteractTime: _.parseInt(item.ot || '0'),
    pageLoadTime: _.parseInt(item.rrt || '0'),
    screenWidth: _.parseInt(item.sw || '0'),
    screenHeight: _.parseInt(item.sh || '0'),
    useragent: scope.useragent || '',
    os: item.pf || '',
    ip: scope.ip || '0.0.0.0',
    browserName: agent.family || 'unknown',
    browserVersion: agent.toVersion() || 'unknown',
    osName: agent.os.family || 'unknown',
    osVersion: agent.os.toVersion() || 'unknown'
  };

  if (!(data.responseTime >= 0 && data.responseTime <= 10000)) {
    return;
  }

  const ipInfo = ipService.getIsp(scope.ip);

  _.merge(data, ipInfo);

  logger.debug('[client] perf info [%s]', JSON.stringify(data));

  perfReportSqlSender.addMessage(data);
}

function handleClientResource(scope, item) {
  const data = {
    app: scope.app,
    reqid: item.rid,
    uid: item.u,
    udid: item.ud,
    route: item.r,
    create_time: new Date().getTime(),
    domType: 'resource',
    mineType: item.mtp,
    dnsTime: _.parseInt(item.dt || '0'),
    tcpTime: _.parseInt(item.tt || '0'),
    responseTime: _.parseInt(item.rt || '0'),
    pageLoadTime: _.parseInt(item.rrt || '0'),
  };

  logger.debug('[client] resource perf info [%s]', JSON.stringify(data));
  perfReportSqlSender.addMessage(data);
}

module.exports = {
  handleClientError,
  handleClientFirst,
  handleClientTiming,
  handleClientResource
};