Source: tool/LogWatcher.js

/**
 * Created by seeker910 on 2018/2/23.
 * 浏览器本地 日志监控程序,在当前页面中嵌入日志监控器,便于页面调试
 * 包含一个界面和一组方法,支持将日志提交到服务端
 */
window.Rsd = window.Rsd || {};

/*
* */
window.Rsd.startLog = function startLog(callback) {

    ///兼容FF,Google
    if (document.addEventListener) {
        document.addEventListener('DOMContentLoaded', function () {

            document.removeEventListener('DOMContentLoaded', arguments.callee, false);
            callback();
        }, false)
    }
    //兼容IE
    else if (document.attachEvent) {
        document.attachEvent('onreadystatechange', function () {
            if (document.readyState == "complete") {
                document.detachEvent("onreadystatechange", arguments.callee);
                callback();
            }
        })
    }
    else if (document.lastChild == document.body) {
        callback();
    }
}

/**
 * @constructor
 * @param {string} appId 绑定项目
 * @param {string}token 加密串
* */
window.Rsd.LogApi = function LogApi(appId,token,app) {

    this.projectId = appId;
    this.from = app;
    this.token = token;
    this.user = null;
    this.__errorEntries =  [];
    this.__warnEntries =  [];
    this.__debugEntries =  [];
    this.__infoEntries =  [];
    this.__pvEntries =  [];
    this.__clickEntries = [];
    this.__timing = null;
    this.__navigation = null;
    this.__memoryEntries = [];

};
/*
*
* */
window.Rsd.LogApi.prototype.setUser=function setUser(user) {
    this.user = user;
};
/*
 * @url 事件发生所在页面
 * */
window.Rsd.LogApi.prototype.error =function error(msg,ex) {
    if (msg || ex) {
        this.__errorEntries = this.__errorEntries || [];
        this.__errorEntries.push({
            data: JSON.stringify(ex),
            message: msg,
            projectId: this.projectId,
            logTime: new Date().getTime()
        });

        this.send(this.user || 'Redjs');
    }
    return this;
};
/*
 * @url 事件发生所在页面
 * */
window.Rsd.LogApi.prototype.warn = function warn(msg,data) {
    if (msg || data) {
        this.__warnEntries = this.__warnEntries || [];
        this.__warnEntries.push({
            data: JSON.stringify(data),
            message: msg,
            projectId: this.projectId,
            logTime: new Date().getTime()
        });
        this.send(this.user || 'Redjs');
    }
    return this;
};
/*
 * @url 事件发生所在页面
 * */
window.Rsd.LogApi.prototype.debug = function debug(msg,data) {
    if (msg || data) {
        this.__debugEntries = this.__debugEntries || [];
        this.__debugEntries.push({
            data: JSON.stringify(data),
            message: msg,
            projectId: this.projectId,
            logTime: new Date().getTime()
        });
        this.send(this.user || 'Redjs');
    }
    return this;
};
/*
 * @url 事件发生所在页面
 * */
window.Rsd.LogApi.prototype.info = function (msg,data) {
    if (msg || data) {
        this.__infoEntries = this.__infoEntries || [];
        this.__infoEntries.push({
            data: JSON.stringify(data),
            message: msg,
            projectId: this.projectId,
            logTime: new Date().getTime()
        });
        this.send(this.user || 'Redjs');
    }
    return this;
},
/*
* 开始执行 标记
* */
window.Rsd.LogApi.prototype.markStart = function markStart(name) {
    var _per = window.performance;
    if (_per) {
        _per.mark(name + '_start');
    }
    return this;
};
/**
 * 执行结束 标记
 * */
window.Rsd.LogApi.prototype.markEnd=function markEnd(name) {
    var _per = window.performance;
    if (_per) {
        _per.mark(name + '_end');
    }
    return this;
},
/**
* 计算 执行时长
* */
window.Rsd.LogApi.prototype.measureMark=function measureMark(name) {
    var _per = window.performance;
    if (_per) {
        _per.measure(name, name + '_start', name + '_end');
        this.send(this.user || 'Redjs');
    }
    return this;
};

/**
 * page view 日志
 * @page 当前访问的页面,URL 或 class
 * */
window.Rsd.LogApi.prototype.pv=function pv(page,text,data) {

    var _page = page||'unknown';
    var _className = _page.toString();

    this.__pvEntries = this.__pvEntries || [];
    this.__pvEntries.push({
        data: data || document.cookie,
        text: (text || document.title).toString(),
        page: _className || document.URL,
        projectId: this.projectId,
        logTime: new Date().getTime()
    });
    this.send(this.user || 'Redjs');
    return this;
};
/**
 * 点击事件日志
 * @eleType 点击的dom节点类型
 * @text 点击的dom节点文本描述
 * @url 目标URL
 * */
window.Rsd.LogApi.prototype.click=function click(eleType,text,data) {
    var _per = window.performance;
    var _now;
    if (_per) {
        _now = _per.now();
    }
    this.__clickEntries = this.__clickEntries || [];
    this.__clickEntries.push({
        elementType: eleType.toString(),
        elementText: text.toString(),
        data: data,
        ElementNow: _now,
        projectId: this.projectId,
        logTime: new Date().getTime()
    });
    this.send(this.user || 'Redjs');
    return this;
};
/**
 * 性能监控
 * */
window.Rsd.LogApi.prototype.timing=function timing() {
    var _per = window.performance;
    if (_per) {
        this.__timing = _per.timing;
        this.send(this.user || 'Redjs');
    }
    return this;
};
/**
 * */
window.Rsd.LogApi.prototype.navigation=function navigation() {
    var _per = window.performance;
    if (_per) {
        this.__navigation = _per.navigation;
        this.send(this.user || 'Redjs');
    }
    return this;
};
/**
 * 记录内存信息
 * */
window.Rsd.LogApi.prototype.memory=function memory() {
    this.__memoryEntries = this.__memoryEntries||[];
    var _per = window.performance;
    if(_per && _per.memory) {

        this.__memoryEntries.push(_per.memory);
        this.send(this.user||'Redjs');
    }
    return this;
};
/**
 * 提交日志,在onLoad之后提交
 * */
window.Rsd.LogApi.prototype.send =function send(user) {
    //
    var me = this;
    if( me.__isSending)
    {
        return;
    }
    me.__isSending = true;

    setTimeout(function () {
        var _per = window.performance;
        if(_per)
        {
            me.__perEntries = _per.getEntries();
            _per.clearMarks();
            _per.clearMeasures();
            _per.clearResourceTimings();
        }
        var timer = 100;
        if(me.__debugEntries.length > 0)
        {
            me.commit('Debug',me.__debugEntries.splice(0,100),user,timer+=50) ;
        }

        if(me.__infoEntries.length > 0)
        {
            me.commit('Info',me.__infoEntries.splice(0,100),user,timer+=50) ;
        }

        if(me.__warnEntries.length > 0)
        {
            me.commit('Warn',me.__warnEntries.splice(0,100),user,timer+=50) ;
        }

        if(me.__errorEntries.length > 0 )
        {
            me.commit('Error',me.__errorEntries.splice(0,100),user,timer+=50) ;
        }
        //所有资源请求的时间数据
        if(me.__perEntries.length > 0)
        {
            var list = me.__perEntries.splice(0,100);
            for(var i in list)
            {
                list[i] = list[i];
            }
            me.commit('PerformanceEntry',list,user,timer+=50) ;

        }

        if(me.__timing)
        {
            if(me.__navigation)
            {
                me.__timing.NavType = me.__navigation.type;
                me.__timing.NavRedirectCount = me.__navigation.redirectCount;
            }
            me.commit('PageTiming', [me.__timing]) ;
        }

        if(me.__memoryEntries.length > 0)
        {
            me.commit('ClientMemory',me.__memoryEntries.splice(0,100),user,timer+=50) ;
        }

        if(me.__clickEntries.length > 0)
        {
            me.commit('PageEvent',me.__clickEntries.splice(0,100),user,timer+=50) ;
        }

        if(me.__pvEntries.length > 0)
        {
            me.commit('PageView',me.__pvEntries.splice(0,100),user,timer+=50) ;
        }

        me.__timing = null;
        me.__navigation = null;
        me.__isSending = false;
    },500);

    return this;
};
/**
* 转化为Jquery参数格式
* */
window.Rsd.LogApi.prototype.toString = function toString(obj,pre) {


    var _pre = pre || '';
    if (obj == undefined || obj==null) {

        return '';
    }

    if (typeof obj == "string") {
        return "" + obj.replace(/([\"\\])/g, "\\$1").replace(/(\n)/g, "\\n").replace(/(\r)/g, "\\r").replace(/(\t)/g, "\\t") + "";
    }
    if (obj instanceof Array) {
        var r = [];
        for (var i = 0; i < obj.length; i++) {
            if (typeof (obj[i]) == 'function') {
                continue;
            }
            if (typeof obj[i] == 'object') {
                var _t = this.toString(obj[i], _pre + '[' + i + ']');
                 Array.prototype.push.apply(r, _t);
            }
            else {
                r.push(this.toString(obj[i], _pre + '[' + i + ']'))
            }

            if (!_pre) {
                r = r.join('&');
            }
        }
        return r;
    }

    if (typeof obj == "object") {
        var r = [];

        for (var k in obj) {
            if (typeof (obj[k]) == 'function') {
                continue;
            }
            if ( obj[k]==null || typeof  obj[k] == "string") {


                if(_pre)
                {
                    r.push(_pre + '[' + k + ']' + "=" +  this.toString(obj[k]));
                }else
                {
                    r.push(k + "=" +  this.toString(obj[k]));
                }
                continue;
            }
            if(typeof obj[k] == 'object' )
            {
                if(_pre)
                {
                    var _t = this.toString(obj[k],_pre + '[' + k + ']');
                    Array.prototype.push.apply(r , _t);
                }else
                {
                    var _t = this.toString(obj[k], k);
                    Array.prototype.push.apply(r , _t);
                }
            }
            else
            {
                if(_pre)
                {
                    r.push(_pre + '[' + k + ']' + "=" +  this.toString(obj[k]));
                }else
                {
                    r.push(k + "=" +  this.toString(obj[k]));
                }

            }

        }
        if(!_pre)
        {
            r = r.join('&');
        }
        return r;
    }

    return obj.toString().replace(/\"\:/g, '":""');
};
/**
*
* */
window.Rsd.LogApi.prototype.ajax = function ajax(config){

        var _c = config ||{type:'GET', url:'', data:null, success:null, failed:null};
        _c.type = _c.type|| 'GET';

        // 创建ajax对象
        var xhr = null;
        if(window.XMLHttpRequest){
            xhr = new XMLHttpRequest();
        } else {
            xhr = new ActiveXObject('Microsoft.XMLHTTP')
        }

        xhr.timeout = _c.timeout||3000;
        xhr.ontimeout = function (event) {
            console.error(event);
        };

        var type = _c.type.toUpperCase();
        // 用于清除缓存
        var random = Math.random();

        if(type == 'GET'){

            if(_c.data){
                var _data = this.toString(_c.data);
                xhr.open('GET', _c.url + '?' + _data, _c.async == undefined ? true : _c.async);
            } else {
                xhr.open('GET', _c.url + '?t=' + random, _c.async == undefined ? true : _c.async);
            }
            xhr.send();

        }
        if(type == 'POST'){
            xhr.open('POST', _c.url, true);
            //setRequestHeader()添加http 头。
            xhr.setRequestHeader("Content-type", _c.contentType||'application/json; charset=utf-8');
            var _data = this.toString(_c.data);
            xhr.send(_data);
        }


        return ;//日志提交 不做结果处理

        //处理返回数据
        xhr.onreadystatechange = function(){
            if(xhr.readyState == 4){
                if(xhr.status == 200){

                } else {
                    console.error('send log fail:'+xhr.status)
                }
            }
        }
};
/**
* 提交日志
* */
window.Rsd.LogApi.prototype.commit = function commit(api,data,user,timer) {
    var me = this;
    if (me.projectId == undefined || me.projectId == null || me.projectId == '')
    {
        return;
    }
    var _data = {type:api,token:me.token, projectId:me.projectId,cookies:document.cookie, from:me.from,logTime:new Date().getTime(), user:user || me.user ||'Redjs',logData:data};

    //异步发送日志数据
    setTimeout(function () {
        try {
            me.ajax({
                url:window.location.protocol + '//prophet.redmicro.cn/LogService.asmx/Log?___api='+ api +'&___key='+new Date().getTime(),
                //url:'http://log.redmicro.cn:8060/LogService.asmx/Log?___key='+new Date().getTime(),
                type:'POST',
                contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
                dataType: 'text',
                timeout:1000,
                async:true,
                data:_data
            });
        }
        catch(error)
        {
            console.error('提交'+api+'日志失败。');
            console.error(error);
        }

    },timer||100);

};
/**
* */
window.onerror = function error(message, url, lineNo, columnNo, error) {
    window.Rsd.Logger.error( message + '['+url+'](L:'+lineNo+',C:'+columnNo+')',error);
};

/**
* */
window.onunload = function unload() {
    
    window.Rsd.Logger.info('页面退出',{url:window.location.href,title:document.title});

};

 (function () {
    //初始化
     var scriptArgs = document.getElementById('RedjsLoggerAPI');
     if(scriptArgs)
     {
         window.Rsd.Logger = new Rsd.LogApi(scriptArgs.getAttribute('appId'),scriptArgs.getAttribute('token'),scriptArgs.getAttribute('from'));
     }
     else
     {
         console.error('未发现id为RedjsLoggerAPI的script标签,请正确引用RedjsLoggerAPI');
     }

})();

/**
* 
* */
window.Rsd.startLog(function(){

    if(window.Rsd.Logger){

        window.Rsd.Logger.setUser('Redjs');
        window.Rsd.Logger.timing();
        window.Rsd.Logger.navigation();
        window.Rsd.Logger.memory();
        var form = document.getElementsByTagName('form');
        window.Rsd.Logger.pv(document.baseURI,document.title,form instanceof HTMLFormElement ? new FormData(form):null);
    }

});

//Example:
//<script type="text/javascript" src="./tool/LogWatcher.js" appId="b110c126-f428-489f-82fe-a8fcd12ba99a" token="token"></script>