apm2.js 5 KB
const queryParse = require('../lib/query-parse');
const Sender = require('influx-batch-sender');
const MysqlSender = require('../lib/mysql-sender');
const _ = require('lodash');
const logger = global.yoho.logger;
const config = global.yoho.config;
const msg2row = require('./msg2row');
const routeDecode = require('./route-decode');
const crypto = global.yoho.crypto;

const decodeUid = _.memoize(function(r) {
    r = r.replace(/ /g, '+');
    return crypto.decrypt(null, decodeURIComponent(r));
});

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);

module.exports = (req, res, next) => {
    if (!req.query.l) {
        logger.error('query l is undefined');
        return res.send();
    }
    const app = req.query.s || 'unknown';

    try {
        const report = queryParse.parse(req.query.l);

        if (!report) {
            logger.error('report can not be parse:', req.query.l);
            return res.send();
        }
        report.forEach(item => {
            if (item.pt && item.pt.indexOf('openby:yohobuy') >= 0) {
                item.pt = item.pt.substring(0, item.pt.indexOf('openby:yohobuy'));
            }

            if (item.u) {
                try {
                    item.u = decodeUid(item.u);
                    item.u = parseInt(item.u);
                } catch (e) {
                    logger.error('decode uid error ', item.u);
                    item.u = '0';
                }
            }

            if (item.r) {
                try {
                    item.r = decodeURIComponent(item.r);
                    item.r = routeDecode(item.r) || 'null';
                } catch (e) {
                    logger.warn('decodeURIComponent route wrong', item.r);
                }
            } else {
                item.r = 'null';
            }

            if (item.tp === 'err') {
                let data = {
                    tags: {
                        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: req.get('user-agent'),
                        stack: item.st ? item.st.replace(/"/g, '') : '',
                        script: item.sc,
                        line: _.parseInt(item.ln || 0),
                        column: _.parseInt(item.cn || 0),
                    })
                });

                logger.info('client:error =>', item);

                if (/cdn.yoho.cn/.test(item.sc)) {
                    errorRouterSqlSender.addMessage(msg2row.errorRouter(data));
                }

            } else {
                let data = {
                    tags: {
                        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) {
                    return;
                }

                if (duration < 1000 * 60 * 10) {
                    _.merge(data, {
                        measurement: 'route-info',
                        tags: {
                            type: durationType[item.tp],
                        },
                        fields: {
                            duration: duration,
                            useragent: req.get('user-agent')
                        }
                    });

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

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

        res.json();
    } catch (e) {
        return next(e);
    }
};