clientapm-service.js 5.29 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 (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
};