(function(window) {
var LOG_LEVELS = ["DEBUG", "LOG", "INFO", "WARN", "ERROR"], //日誌級別
//TODO 是否可以做成隊列閾值和倒計時時間由UI配置?
THRESHOLD = 200, // 觸發日誌上傳的閾值,設置大一點,避免高頻的請求導致併發問題
TIMER = 1000, // 倒計時,間隔發送時間
URL = location.protocol + "//__rosin__.qq.com",
KEY = +new Date() + '_' + parseInt(Math.random() * 1e8); // 每個頁面生成一個唯一key
// https的請求,使用另外的通信協議
if(location.protocol === "https:") {
URL = "https://" + location.host + '/?__rosin__';
}
/**
* 上傳任務隊列
* 隊列長度達到閾值觸發POST日誌
* @type {{_queueArr: Array, add: add, _post: _post}}
*/
var queue = {
_queueArr: [], //數組模擬隊列
add: function() {
Array.prototype.push.apply(this._queueArr, arguments);
clock.start();
//隊列達到閾值就觸發上傳
if (this._queueArr.length >= THRESHOLD) {
this._post(this._queueArr.splice(0, this._queueArr.length));
return;
}
},
_post: function(logArr) {
var headers = {};
var setHeader = function(name, value) {
headers[name.toLowerCase()] = [name, value]
};
var xhr = new XMLHttpRequest();
var nativeSetHeader = xhr.setRequestHeader;
setHeader('Accept', '*/*');
setHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader = setHeader;
xhr.onreadystatechange = function(){}; // do nothing
xhr.open('POST', URL, true);
// chrome 控制檯會看到一個報錯
// A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true.
// xhr.withCredentials = true;
for (name in headers) nativeSetHeader.apply(xhr, headers[name]);
xhr.send(JSON.stringify(logArr));
}
},
/**
* 自定義日誌對象
* @type {{DELIMITER: string, _record: _record, debug: debug, log: log, info: info, warn: warn, error: error}}
*/
FConsole = {
DELIMITER: " ", //分隔符
JSON_TAG_LEFT: '\uFFFE', // 用於標識json對象字符串的等寬字符
JSON_TAG_RIGHT: '\uFEFF',
_record: function(logLevel) {
var log = {
"key": KEY,
"level": logLevel, //日誌級別
"time": new Date().getTime() //日誌時間,時間戳
// "url" : window.location.href //記錄日誌的URL
},
content = ""; //日誌內容
for (var i = 1; i < arguments.length; i++) {
if (Array.isArray(arguments[i])) {
content += (i === 1 ? "" : FConsole.DELIMITER) + FConsole.JSON_TAG_LEFT + "[" + arguments[i] + "]" + FConsole.JSON_TAG_RIGHT;
} else if (typeof arguments[i] === "object") {
content += (i === 1 ? "" : FConsole.DELIMITER) + FConsole.JSON_TAG_LEFT + JSON.stringify(arguments[i]) + FConsole.JSON_TAG_RIGHT;
} else {
content += (i === 1 ? "" : FConsole.DELIMITER) + arguments[i];
}
}
log["content"] = content;
//日誌記錄加入隊列
queue.add(log)
},
debug: function() {
Array.prototype.unshift.call(arguments, LOG_LEVELS[0]);
FConsole._record.apply(FConsole, arguments);
},
log: function() {
Array.prototype.unshift.call(arguments, LOG_LEVELS[1]);
FConsole._record.apply(FConsole, arguments);
},
info: function() {
Array.prototype.unshift.call(arguments, LOG_LEVELS[2]);
FConsole._record.apply(FConsole, arguments);
},
warn: function() {
Array.prototype.unshift.call(arguments, LOG_LEVELS[3]);
FConsole._record.apply(FConsole, arguments);
},
error: function() {
Array.prototype.unshift.call(arguments, LOG_LEVELS[4]);
FConsole._record.apply(FConsole, arguments);
}
},
/**
* 全局定時器,監聽隊列
* @type {{timeout: null, start: Function, clear: Function}}
*/
clock = {
timeout: null,
start: function() {
var self = this;
if (!this.timeout) this.timeout = setInterval(function() {
if (queue._queueArr.length > 0) {
queue._post(queue._queueArr.splice(0, queue._queueArr.length));
}
}, TIMER);
},
clear: function() {
clearTimeout(this.timeout);
this.timeout = null;
}
};
function _extendObj(obj) {
if (typeof obj !== 'object') return obj;
var source, prop;
for (var i = 1, length = arguments.length; i < length; i++) {
source = arguments[i];
for (prop in source) {
if (Object.prototype.hasOwnProperty.call(source, prop)) {
// 不覆蓋原方法執行,只是加個殼
(function(obj, prop) {
if (typeof obj[prop] === "function") {
var oldFun = obj[prop].bind(obj);
obj[prop] = function() {
source[prop].apply(source, arguments);
// oldFun.apply(obj, arguments);
oldFun(arguments[0]);
};
} else {
obj[prop] = source[prop];
}
})(obj, prop);
}
}
}
return obj;
};
//將自定義的日誌API覆蓋到原生console中
// _extendObj(window.console, FConsole);
})(window);