├── README.md ├── bj-wrap-annotated.js └── bj-report-annotated.js /README.md: -------------------------------------------------------------------------------- 1 | # badjs-report-annotated 2 | 在阅读badjs的过程中给badjs-report加了比较详细的中文注释,可结合[源项目的README](https://github.com/BetterJS/badjs-report)和[源码浅析文章](https://github.com/Q-Zhan/badjs-report-annotated/issues/1)一起阅读。 3 | 4 | -------------------------------------------------------------------------------- /bj-wrap-annotated.js: -------------------------------------------------------------------------------- 1 | // 由于badjs-report是通过改写window.onerror来进行错误捕获和上报, 2 | // 但当不同源的js文件抛出异常时,window.onerror无法获取具体的出错信息。 3 | /* 4 | 由于badjs-report是通过改写window.onerror来进行错误捕获和上报, 5 | 但当不同源的js文件抛出异常时,window.onerror无法获取具体的出错信息。 6 | 而badjs的解决方案是使用try-catch将可能出错的函数包裹起来,在catch里面 7 | 就把错误处理掉。 8 | 详见:https://github.com/BetterJS/badjs-report/issues/3 9 | */ 10 | 11 | (function(global) { 12 | 13 | if (!global.BJ_REPORT) { 14 | console.error("please load bg-report first"); 15 | return; 16 | } 17 | 18 | var _onthrow = function(errObj) { 19 | global.BJ_REPORT.push(errObj); 20 | }; 21 | 22 | var tryJs = {}; 23 | global.BJ_REPORT.tryJs = function(throwCb) { 24 | throwCb && (_onthrow = throwCb); 25 | return tryJs; 26 | }; 27 | 28 | // merge 29 | var _merge = function(org, obj) { 30 | for (var key in obj) { 31 | org[key] = obj[key]; 32 | } 33 | }; 34 | 35 | // function or not 36 | var _isFunction = function(foo) { 37 | return typeof foo === "function"; 38 | }; 39 | 40 | var timeoutkey; 41 | 42 | var cat = function(foo, args) { 43 | return function() { 44 | // 使用try-catch包裹 45 | try { 46 | return foo.apply(this, args || arguments); 47 | } catch (error) { 48 | 49 | _onthrow(error); 50 | 51 | //some browser throw error (chrome) , can not find error where it throw, so print it on console; 52 | if (error.stack && console && console.error) { 53 | console.error("[BJ-REPORT]", error.stack); 54 | } 55 | 56 | // hang up browser and throw , but it should trigger onerror , so rewrite onerror then recover it 57 | if (!timeoutkey) { 58 | var orgOnerror = global.onerror; 59 | // 先将window.onerror函数置为空函数,避免重复处理error事件 60 | global.onerror = function() { }; 61 | // catch函数处理完error后再还原window.onerror 62 | timeoutkey = setTimeout(function() { 63 | global.onerror = orgOnerror; 64 | timeoutkey = null; 65 | }, 50); 66 | } 67 | throw error; 68 | } 69 | }; 70 | }; 71 | // 包裹参数中的函数 72 | var catArgs = function(foo) { 73 | return function() { 74 | var arg, args = []; 75 | for (var i = 0, l = arguments.length; i < l; i++) { 76 | arg = arguments[i]; 77 | _isFunction(arg) && (arg = cat(arg)); 78 | args.push(arg); 79 | } 80 | return foo.apply(this, args); 81 | }; 82 | }; 83 | // 包裹setTimeout和setInterval 84 | var catTimeout = function(foo) { 85 | return function(cb, timeout) { 86 | // for setTimeout(string, delay) 87 | if (typeof cb === "string") { 88 | try { 89 | cb = new Function(cb); 90 | } catch (err) { 91 | throw err; 92 | } 93 | } 94 | var args = [].slice.call(arguments, 2); 95 | // for setTimeout(function, delay, param1, ...) 96 | cb = cat(cb, args.length && args); 97 | return foo(cb, timeout); 98 | }; 99 | }; 100 | 101 | /** 102 | * makeArgsTry 103 | * wrap a function's arguments with try & catch 104 | * @param {Function} foo 105 | * @param {Object} self 106 | * @returns {Function} 107 | */ 108 | var makeArgsTry = function(foo, self) { 109 | return function() { 110 | var arg, tmp, args = []; 111 | for (var i = 0, l = arguments.length; i < l; i++) { 112 | arg = arguments[i]; 113 | if (_isFunction(arg)) { 114 | if (arg.tryWrap) { 115 | arg = arg.tryWrap; 116 | } else { 117 | tmp = cat(arg); 118 | arg.tryWrap = tmp; 119 | arg = tmp; 120 | } 121 | } 122 | args.push(arg); 123 | } 124 | return foo.apply(self || this, args); 125 | }; 126 | }; 127 | 128 | /** 129 | * makeObjTry 130 | * wrap a object's all value with try & catch 131 | * @param {Function} foo 132 | * @param {Object} self 133 | * @returns {Function} 134 | */ 135 | var makeObjTry = function(obj) { 136 | var key, value; 137 | for (key in obj) { 138 | value = obj[key]; 139 | if (_isFunction(value)) obj[key] = cat(value); 140 | } 141 | return obj; 142 | }; 143 | 144 | /** 145 | * wrap jquery async function ,exp : event.add , event.remove , ajax 146 | * @returns {Function} 147 | */ 148 | tryJs.spyJquery = function() { 149 | var _$ = global.$; 150 | 151 | if (!_$ || !_$.event) { 152 | return tryJs; 153 | } 154 | 155 | var _add, _remove; 156 | if (_$.zepto) { 157 | _add = _$.fn.on, _remove = _$.fn.off; 158 | 159 | _$.fn.on = makeArgsTry(_add); 160 | _$.fn.off = function() { 161 | var arg, args = []; 162 | for (var i = 0, l = arguments.length; i < l; i++) { 163 | arg = arguments[i]; 164 | _isFunction(arg) && arg.tryWrap && (arg = arg.tryWrap); 165 | args.push(arg); 166 | } 167 | return _remove.apply(this, args); 168 | }; 169 | 170 | } else if (window.jQuery) { 171 | _add = _$.event.add, _remove = _$.event.remove; 172 | 173 | _$.event.add = makeArgsTry(_add); 174 | _$.event.remove = function() { 175 | var arg, args = []; 176 | for (var i = 0, l = arguments.length; i < l; i++) { 177 | arg = arguments[i]; 178 | _isFunction(arg) && arg.tryWrap && (arg = arg.tryWrap); 179 | args.push(arg); 180 | } 181 | return _remove.apply(this, args); 182 | }; 183 | } 184 | 185 | var _ajax = _$.ajax; 186 | 187 | if (_ajax) { 188 | _$.ajax = function(url, setting) { 189 | if (!setting) { 190 | setting = url; 191 | url = undefined; 192 | } 193 | makeObjTry(setting); 194 | if (url) return _ajax.call(_$, url, setting); 195 | return _ajax.call(_$, setting); 196 | }; 197 | } 198 | 199 | return tryJs; 200 | }; 201 | 202 | /** 203 | * wrap amd or commonjs of function ,exp : define , require , 204 | * @returns {Function} 205 | */ 206 | tryJs.spyModules = function() { 207 | var _require = global.require, 208 | _define = global.define; 209 | if (_define && _define.amd && _require) { 210 | global.require = catArgs(_require); 211 | _merge(global.require, _require); 212 | global.define = catArgs(_define); 213 | _merge(global.define, _define); 214 | } 215 | 216 | if (global.seajs && _define) { 217 | global.define = function() { 218 | var arg, args = []; 219 | for (var i = 0, l = arguments.length; i < l; i++) { 220 | arg = arguments[i]; 221 | if (_isFunction(arg)) { 222 | arg = cat(arg); 223 | //seajs should use toString parse dependencies , so rewrite it 224 | arg.toString = (function(orgArg) { 225 | return function() { 226 | return orgArg.toString(); 227 | }; 228 | }(arguments[i])); 229 | } 230 | args.push(arg); 231 | } 232 | return _define.apply(this, args); 233 | }; 234 | 235 | global.seajs.use = catArgs(global.seajs.use); 236 | 237 | _merge(global.define, _define); 238 | } 239 | 240 | return tryJs; 241 | }; 242 | 243 | /** 244 | * wrap async of function in window , exp : setTimeout , setInterval 245 | * @returns {Function} 246 | */ 247 | tryJs.spySystem = function() { 248 | global.setTimeout = catTimeout(global.setTimeout); 249 | global.setInterval = catTimeout(global.setInterval); 250 | return tryJs; 251 | }; 252 | 253 | /** 254 | * wrap custom of function , 255 | * @param obj - obj or function 256 | * @returns {Function} 257 | */ 258 | tryJs.spyCustom = function(obj) { 259 | if (_isFunction(obj)) { 260 | return cat(obj); 261 | } else { 262 | return makeObjTry(obj); 263 | } 264 | }; 265 | 266 | /** 267 | * run spyJquery() and spyModules() and spySystem() 268 | * @returns {Function} 269 | */ 270 | tryJs.spyAll = function() { 271 | tryJs 272 | .spyJquery() 273 | .spyModules() 274 | .spySystem(); 275 | return tryJs; 276 | }; 277 | 278 | }(window)); 279 | -------------------------------------------------------------------------------- /bj-report-annotated.js: -------------------------------------------------------------------------------- 1 | 2 | var BJ_REPORT = (function(global) { 3 | if (global.BJ_REPORT) return global.BJ_REPORT; 4 | 5 | var _log_list = []; 6 | var _log_map = {}; 7 | var _config = { 8 | id: 0, // 上报 id 9 | uin: 0, // user id 10 | url: "", // 上报 接口 11 | offline_url: "", // 离线日志上报 接口 12 | offline_auto_url: "", // 检测是否自动上报 13 | ext: null, // 扩展参数 用于自定义上报 14 | level: 4, // 错误级别 1-debug 2-info 4-error 15 | ignore: [], // 忽略某个错误, 支持 Regexp 和 Function 16 | random: 1, // 抽样 (0-1] 1-全量 17 | delay: 1000, // 延迟上报 combo 为 true 时有效 18 | submit: null, // 自定义上报方式 19 | repeat: 5, // 重复上报次数(对于同一个错误超过多少次不上报), 20 | offlineLog: false, //是否启用离线日志 21 | offlineLogExp: 5, // 离线日志过期时间 , 默认5天 22 | offlineLogAuto: false, //是否自动询问服务器需要自动上报 23 | }; 24 | 25 | var Offline_DB = { 26 | db: null, 27 | // 初始化数据库 28 | ready: function(callback) { 29 | var self = this; 30 | // 如果indexedDB数据库在该环境不兼容 31 | if (!window.indexedDB || !_config.offlineLog) { 32 | _config.offlineLog = false; 33 | return callback(); 34 | } 35 | // 已经初始化过 36 | if (this.db) { 37 | setTimeout(function() { 38 | callback(null, self); 39 | }, 0); 40 | 41 | return; 42 | } 43 | var version = 1; 44 | // 打开数据库 45 | var request = window.indexedDB.open("badjs", version); 46 | 47 | if (!request) { 48 | _config.offlineLog = false; 49 | return callback(); 50 | } 51 | 52 | request.onerror = function(e) { 53 | callback(e); 54 | _config.offlineLog = false; 55 | console.log("indexdb request error"); 56 | return true; 57 | }; 58 | // 打开成功 59 | request.onsuccess = function(e) { 60 | self.db = e.target.result; 61 | // 打开成功后执行回调 62 | setTimeout(function() { 63 | callback(null, self); 64 | }, 500); 65 | 66 | 67 | }; 68 | // 版本升级(初始化时会先触发upgradeneeded,再触发success) 69 | request.onupgradeneeded = function(e) { 70 | var db = e.target.result; 71 | if (!db.objectStoreNames.contains('logs')) { 72 | db.createObjectStore('logs', { autoIncrement: true }); 73 | } 74 | }; 75 | }, 76 | insertToDB: function(log) { 77 | var store = this.getStore(); 78 | store.add(log); 79 | }, 80 | addLog: function(log) { 81 | if (!this.db) { 82 | return; 83 | } 84 | this.insertToDB(log); 85 | }, 86 | addLogs: function(logs) { 87 | if (!this.db) { 88 | return; 89 | } 90 | // 遍历插入 91 | for (var i = 0; i < logs.length; i++) { 92 | this.addLog(logs[i]); 93 | } 94 | 95 | }, 96 | // 通过cursor指针遍历数据库 97 | getLogs: function(opt, callback) { 98 | if (!this.db) { 99 | return; 100 | } 101 | var store = this.getStore(); 102 | // 创建一个读取光标 103 | var request = store.openCursor(); 104 | var result = []; 105 | request.onsuccess = function(event) { 106 | var cursor = event.target.result; 107 | if (cursor) { 108 | // 满足opt对象要求的才能推入result数组回传 109 | if (cursor.value.time >= opt.start && cursor.value.time <= opt.end && cursor.value.id == opt.id && cursor.value.uin == opt.uin) { 110 | result.push(cursor.value); 111 | } 112 | // 读取下一个 113 | cursor["continue"](); 114 | } else { 115 | // 遍历完毕,执行回调 116 | callback(null, result); 117 | } 118 | }; 119 | 120 | request.onerror = function(e) { 121 | callback(e); 122 | return true; 123 | }; 124 | }, 125 | // 清除过期的离线日志 126 | clearDB: function(daysToMaintain) { 127 | if (!this.db) { 128 | return; 129 | } 130 | 131 | var store = this.getStore(); 132 | // 参数不存在则清除所有日志 133 | if (!daysToMaintain) { 134 | store.clear(); 135 | return; 136 | } 137 | // 计算过期时间 138 | var range = (Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000); 139 | var request = store.openCursor(); 140 | request.onsuccess = function(event) { 141 | var cursor = event.target.result; 142 | if (cursor && (cursor.value.time < range || !cursor.value.time)) { 143 | // 删除过期日志 144 | store["delete"](cursor.primaryKey); 145 | cursor["continue"](); 146 | } 147 | }; 148 | }, 149 | 150 | getStore: function() { 151 | // 打开logs数据库 152 | var transaction = this.db.transaction("logs", 'readwrite'); 153 | return transaction.objectStore("logs"); 154 | }, 155 | 156 | }; 157 | 158 | var T = { 159 | isOBJByType: function(o, type) { 160 | return Object.prototype.toString.call(o) === "[object " + (type || "Object") + "]"; 161 | }, 162 | 163 | isOBJ: function(obj) { 164 | var type = typeof obj; 165 | return type === "object" && !!obj; 166 | }, 167 | isEmpty: function(obj) { 168 | if (obj === null) return true; 169 | if (T.isOBJByType(obj, "Number")) { 170 | return false; 171 | } 172 | return !obj; 173 | }, 174 | extend: function(src, source) { 175 | for (var key in source) { 176 | src[key] = source[key]; 177 | } 178 | return src; 179 | }, 180 | // 格式化错误信息 181 | processError: function(errObj) { 182 | try { 183 | if (errObj.stack) { 184 | var url = errObj.stack.match("https?://[^\n]+"); 185 | url = url ? url[0] : ""; 186 | var rowCols = url.match(":(\\d+):(\\d+)"); 187 | if (!rowCols) { 188 | rowCols = [0, 0, 0]; 189 | } 190 | 191 | var stack = T.processStackMsg(errObj); 192 | return { 193 | msg: stack, 194 | rowNum: rowCols[1], 195 | colNum: rowCols[2], 196 | target: url.replace(rowCols[0], ""), 197 | _orgMsg: errObj.toString() 198 | }; 199 | } else { 200 | //ie 独有 error 对象信息,try-catch 捕获到错误信息传过来,造成没有msg 201 | if (errObj.name && errObj.message && errObj.description) { 202 | return { 203 | msg: JSON.stringify(errObj) 204 | }; 205 | } 206 | return errObj; 207 | } 208 | } catch (err) { 209 | return errObj; 210 | } 211 | }, 212 | // 进行格式转换 213 | processStackMsg: function(error) { 214 | var stack = error.stack 215 | .replace(/\n/gi, "") 216 | .split(/\bat\b/) 217 | .slice(0, 9) 218 | .join("@") 219 | .replace(/\?[^:]+/gi, ""); 220 | var msg = error.toString(); 221 | if (stack.indexOf(msg) < 0) { 222 | stack = msg + "@" + stack; 223 | } 224 | return stack; 225 | }, 226 | // 判断是否超过重复上报次数 227 | isRepeat: function(error) { 228 | if (!T.isOBJ(error)) return true; 229 | var msg = error.msg; 230 | var times = _log_map[msg] = (parseInt(_log_map[msg], 10) || 0) + 1; 231 | return times > _config.repeat; 232 | } 233 | }; 234 | // 保存原有的全局onerror事件 235 | var orgError = global.onerror; 236 | // 重写onerror事件 237 | global.onerror = function(msg, url, line, col, error) { 238 | var newMsg = msg; 239 | 240 | if (error && error.stack) { 241 | newMsg = T.processStackMsg(error); 242 | } 243 | if (T.isOBJByType(newMsg, "Event")) { 244 | newMsg += newMsg.type ? 245 | ("--" + newMsg.type + "--" + (newMsg.target ? 246 | (newMsg.target.tagName + "::" + newMsg.target.src) : "")) : ""; 247 | } 248 | // 将错误信息对象推入错误队列中,执行_process_log方法进行上报 249 | report.push({ 250 | msg: newMsg, 251 | target: url, 252 | rowNum: line, 253 | colNum: col, 254 | _orgMsg: msg 255 | }); 256 | 257 | _process_log(); 258 | // 调用原有的全局onerror事件 259 | orgError && orgError.apply(global, arguments); 260 | }; 261 | 262 | 263 | // 格式化log信息 264 | var _report_log_tostring = function(error, index) { 265 | var param = []; 266 | var params = []; 267 | var stringify = []; 268 | if (T.isOBJ(error)) { 269 | error.level = error.level || _config.level; 270 | for (var key in error) { 271 | var value = error[key]; 272 | if (!T.isEmpty(value)) { 273 | if (T.isOBJ(value)) { 274 | try { 275 | value = JSON.stringify(value); 276 | } catch (err) { 277 | value = "[BJ_REPORT detect value stringify error] " + err.toString(); 278 | } 279 | } 280 | stringify.push(key + ":" + value); 281 | param.push(key + "=" + encodeURIComponent(value)); 282 | params.push(key + "[" + index + "]=" + encodeURIComponent(value)); 283 | } 284 | } 285 | } 286 | 287 | // msg[0]=msg&target[0]=target -- combo report 288 | // msg:msg,target:target -- ignore 289 | // msg=msg&target=target -- report with out combo 290 | return [params.join("&"), stringify.join(","), param.join("&")]; 291 | }; 292 | 293 | 294 | 295 | var _offline_buffer = []; 296 | var _save2Offline = function(key, msgObj) { 297 | // 给msgObj添加额外信息 298 | msgObj = T.extend({ id: _config.id, uin: _config.uin, time: new Date - 0 }, msgObj); 299 | // 若数据库已初始化则直接推入 300 | if (Offline_DB.db) { 301 | Offline_DB.addLog(msgObj); 302 | return; 303 | } 304 | // 否则初始化后再推入 305 | if (!Offline_DB.db && !_offline_buffer.length) { 306 | Offline_DB.ready(function(err, DB) { 307 | if (DB) { 308 | if (_offline_buffer.length) { 309 | DB.addLogs(_offline_buffer); 310 | _offline_buffer = []; 311 | } 312 | 313 | } 314 | }); 315 | } 316 | _offline_buffer.push(msgObj); 317 | }; 318 | // 使用添加script的方式上报离线日志 319 | var _autoReportOffline = function() { 320 | var script = document.createElement("script"); 321 | script.src = _config.offline_auto_url || _config.url.replace(/badjs$/, "offlineAuto") + "?id=" + _config.id + "&uin=" + _config.uin; 322 | // 将主动上报函数reportOfflineLog上升为全局函数方法_badjsOfflineAuto 323 | window._badjsOfflineAuto = function(isReport) { 324 | if (isReport) { 325 | BJ_REPORT.reportOfflineLog(); 326 | } 327 | }; 328 | document.head.appendChild(script); 329 | }; 330 | 331 | 332 | 333 | var submit_log_list = []; 334 | var comboTimeout = 0; 335 | var _submit_log = function() { 336 | // 清除之前的延迟上报计时器 337 | clearTimeout(comboTimeout); 338 | // https://github.com/BetterJS/badjs-report/issues/34 339 | comboTimeout = 0; 340 | 341 | if (!submit_log_list.length) { 342 | return; 343 | } 344 | 345 | var url = _config._reportUrl + submit_log_list.join("&") + "&count=" + submit_log_list.length + "&_t=" + (+new Date); 346 | // 若用户自定义了上报方法,则使用自定义方法 347 | if (_config.submit) { 348 | _config.submit(url, submit_log_list); 349 | } else { 350 | // 否则使用img标签上报 351 | var _img = new Image(); 352 | _img.src = url; 353 | } 354 | 355 | submit_log_list = []; 356 | }; 357 | 358 | var _process_log = function(isReportNow) { 359 | if (!_config._reportUrl) return; 360 | // 取随机数,来决定是否忽略该次上报 361 | var randomIgnore = Math.random() >= _config.random; 362 | 363 | while (_log_list.length) { 364 | var isIgnore = false; 365 | // 循环遍历 366 | var report_log = _log_list.shift(); 367 | // 有效保证字符不要过长 368 | report_log.msg = (report_log.msg + "" || "").substr(0, 500); 369 | // 重复上报 370 | if (T.isRepeat(report_log)) continue; 371 | // 格式化log信息 372 | var log_str = _report_log_tostring(report_log, submit_log_list.length); 373 | // 若用户自定义了ignore规则,则按照规则进行筛选 374 | if (T.isOBJByType(_config.ignore, "Array")) { 375 | for (var i = 0, l = _config.ignore.length; i < l; i++) { 376 | var rule = _config.ignore[i]; 377 | if ((T.isOBJByType(rule, "RegExp") && rule.test(log_str[1])) || 378 | (T.isOBJByType(rule, "Function") && rule(report_log, log_str[1]))) { 379 | isIgnore = true; 380 | break; 381 | } 382 | } 383 | } 384 | // 通过了ignore规则 385 | if (!isIgnore) { 386 | // 若离线日志功能已开启,则将日志存入数据库 387 | _config.offlineLog && _save2Offline("badjs_" + _config.id + _config.uin, report_log); 388 | // level为20表示是offlineLog方法push进来的,只存入离线日志而不上报 389 | if (!randomIgnore && report_log.level != 20) { 390 | // 若可以上报,则推入submit_log_list,稍后由_submit_log方法来清空该队列并上报 391 | submit_log_list.push(log_str[0]); 392 | // 执行上报回调函数 393 | _config.onReport && (_config.onReport(_config.id, report_log)); 394 | } 395 | 396 | } 397 | } 398 | 399 | 400 | if (isReportNow) { 401 | _submit_log(); // 立即上报 402 | } else if (!comboTimeout) { 403 | comboTimeout = setTimeout(_submit_log, _config.delay); // 延迟上报 404 | } 405 | }; 406 | 407 | 408 | 409 | var report = global.BJ_REPORT = { 410 | push: function(msg) { // 将错误推到缓存池 411 | var data = T.isOBJ(msg) ? T.processError(msg) : { 412 | msg: msg 413 | }; 414 | // ext 有默认值, 且上报不包含 ext, 使用默认 ext 415 | if (_config.ext && !data.ext) { 416 | data.ext = _config.ext; 417 | } 418 | // 在错误发生时获取页面链接 419 | // https://github.com/BetterJS/badjs-report/issues/19 420 | if (!data.from) { 421 | data.from = location.href; 422 | } 423 | 424 | if (data._orgMsg) { 425 | var _orgMsg = data._orgMsg; 426 | delete data._orgMsg; 427 | data.level = 2; 428 | var newData = T.extend({}, data); 429 | newData.level = 4; 430 | newData.msg = _orgMsg; 431 | _log_list.push(data); 432 | _log_list.push(newData); 433 | } else { 434 | _log_list.push(data); 435 | } 436 | _process_log(); 437 | return report; 438 | }, 439 | // 主动进行上报 440 | report: function(msg, isReportNow) { 441 | msg && report.push(msg); 442 | 443 | isReportNow && _process_log(true); 444 | return report; 445 | }, 446 | // 主动上报info级别信息 447 | info: function(msg) { // info report 448 | if (!msg) { 449 | return report; 450 | } 451 | if (T.isOBJ(msg)) { 452 | msg.level = 2; 453 | } else { 454 | msg = { 455 | msg: msg, 456 | level: 2 457 | }; 458 | } 459 | report.push(msg); 460 | return report; 461 | }, 462 | // 主动上报debug级别信息 463 | debug: function(msg) { // debug report 464 | if (!msg) { 465 | return report; 466 | } 467 | if (T.isOBJ(msg)) { 468 | msg.level = 1; 469 | } else { 470 | msg = { 471 | msg: msg, 472 | level: 1 473 | }; 474 | } 475 | report.push(msg); 476 | return report; 477 | }, 478 | // 主动上报离线日志 479 | reportOfflineLog: function() { 480 | if (!window.indexedDB) { 481 | BJ_REPORT.info("unsupport offlineLog"); 482 | return; 483 | } 484 | Offline_DB.ready(function(err, DB) { 485 | if (!DB) { 486 | return; 487 | } 488 | // 日期要求是startDate ~ endDate 489 | var startDate = new Date - 0 - _config.offlineLogExp * 24 * 3600 * 1000; 490 | var endDate = new Date - 0; 491 | DB.getLogs({ 492 | start: startDate, 493 | end: endDate, 494 | id: _config.id, 495 | uin: _config.uin 496 | }, function(err, result) { 497 | var iframe = document.createElement("iframe"); 498 | iframe.name = "badjs_offline_" + (new Date - 0); 499 | iframe.frameborder = 0; 500 | iframe.height = 0; 501 | iframe.width = 0; 502 | iframe.src = "javascript:false;"; 503 | 504 | iframe.onload = function() { 505 | var form = document.createElement("form"); 506 | form.style.display = "none"; 507 | form.target = iframe.name; 508 | form.method = "POST"; 509 | form.action = _config.offline_url || _config.url.replace(/badjs$/, "offlineLog"); 510 | form.enctype.method = 'multipart/form-data'; 511 | 512 | var input = document.createElement("input"); 513 | input.style.display = "none"; 514 | input.type = "hidden"; 515 | input.name = "offline_log"; 516 | input.value = JSON.stringify({ logs: result, userAgent: navigator.userAgent, startDate: startDate, endDate: endDate, id: _config.id, uin: _config.uin }); 517 | iframe.contentDocument.body.appendChild(form); 518 | form.appendChild(input); 519 | // 通过form表单提交来上报离线日志 520 | form.submit(); 521 | 522 | setTimeout(function() { 523 | document.body.removeChild(iframe); 524 | }, 10000); 525 | 526 | iframe.onload = null; 527 | }; 528 | document.body.appendChild(iframe); 529 | }); 530 | }); 531 | }, 532 | // 记录离线日志,即只存入离线日志而不通过_process_log上报 533 | offlineLog: function(msg) { 534 | if (!msg) { 535 | return report; 536 | } 537 | if (T.isOBJ(msg)) { 538 | msg.level = 20; 539 | } else { 540 | msg = { 541 | msg: msg, 542 | level: 20 543 | }; 544 | } 545 | report.push(msg); 546 | return report; 547 | }, 548 | init: function(config) { // 初始化 549 | // 用配置参数的值覆盖_config的默认值 550 | if (T.isOBJ(config)) { 551 | for (var key in config) { 552 | _config[key] = config[key]; 553 | } 554 | } 555 | // 没有设置id将不上报 556 | var id = parseInt(_config.id, 10); 557 | if (id) { 558 | // set default report url and uin 559 | if (/qq\.com$/gi.test(location.hostname)) { 560 | if (!_config.url) { 561 | _config.url = "//badjs2.qq.com/badjs"; 562 | } 563 | 564 | if (!_config.uin) { 565 | _config.uin = parseInt((document.cookie.match(/\buin=\D+(\d+)/) || [])[1], 10); 566 | } 567 | } 568 | 569 | _config._reportUrl = (_config.url || "/badjs") + 570 | "?id=" + id + 571 | "&uin=" + _config.uin + 572 | // "&from=" + encodeURIComponent(location.href) + 573 | "&"; 574 | } 575 | 576 | // if had error in cache , report now 577 | if (_log_list.length) { 578 | _process_log(); 579 | } 580 | 581 | // init offlineDB 582 | if (!Offline_DB._initing) { 583 | Offline_DB._initing = true; 584 | Offline_DB.ready(function(err, DB) { 585 | if (DB) { 586 | setTimeout(function() { 587 | // 清除过期日志 588 | DB.clearDB(_config.offlineLogExp); 589 | setTimeout(function() { 590 | _config.offlineLogAuto && _autoReportOffline(); 591 | }, 5000); 592 | }, 1000); 593 | } 594 | 595 | }); 596 | } 597 | 598 | 599 | 600 | return report; 601 | }, 602 | 603 | __onerror__: global.onerror 604 | }; 605 | 606 | typeof console !== "undefined" && console.error && setTimeout(function() { 607 | var err = ((location.hash || "").match(/([#&])BJ_ERROR=([^&$]+)/) || [])[2]; 608 | err && console.error("BJ_ERROR", decodeURIComponent(err).replace(/(:\d+:\d+)\s*/g, "$1\n")); 609 | }, 0); 610 | 611 | return report; 612 | 613 | }(window)); 614 | 615 | if (typeof module !== "undefined") { 616 | module.exports = BJ_REPORT; 617 | } 618 | --------------------------------------------------------------------------------