最近公司但願我作一個日誌系統,用來排查手手遊Bug用的。由於前些時候實現了vConsole在手機上的顯示,因此以爲是輕車熟路了。麻煩的是 : 須要玩家在出現bug後打開記錄截圖給我方策劃,供前端開發人員分析,Low是Low了點,可是好實現。可是沒過幾天新的狀況出現了:遊戲閃退。Oh , My God!玩家截圖的機會都沒有了。只有硬着頭皮搞正真的Log方案了。
我在GitHub上搜到Log4js,可是專門爲node.js作的類庫,放在Egret前端上折騰了大半天,由於Log4js依賴太多的node.js庫,因此無賴放棄。只有在互聯網上自找其餘出路。前端
Ⅰ:Log數據的來源
①,重寫window.console方法
②,監聽window.onerror方法
思想:當日志信息的條數達到必定的數量,或者有重要日誌信息,當即向Web服務器發送請求,要求服務器保存日誌。
以下代碼:node
(function(){ var ___log___ = console.log; var ___error___ = console.error; var ___warn___ = console.warn; var ___info___ = console.info; var ___trace___ = console.trace; console.error = function(errMessage){ _cacheLog( "error" , errMessage ); ___error___.apply(console,arguments); }; console.log = function(logMessage){ _cacheLog( "log" , logMessage ); ___log___.apply(console,arguments); }; console.warn = function(warnMessage){ _cacheLog( "warn" , warnMessage ); ___warn___.apply(console,arguments); }; console.info = function (infoMessage) { _cacheLog( "info" , infoMessage ); ___info___.apply( console , arguments ); }; console.trace = function ( traceMessage ) { _cacheLog( "trace" , traceMessage ); ___trace___.apply( console , arguments ); } })(); window.onerror = function(msg, url, line, col, error) { var extra = !col ? '' : '\ncolumn: ' + col; extra += !error ? '' : '\nerror: ' + error; _cacheLog( "system_error" , msg + "\nurl: " + url + "\nline: " + line + extra ); var suppressErrorAlert = true; return suppressErrorAlert; };
Ⅱ:請求服務器保存Logjson
var _saveLog = function ( $logList = null ) { var $logMsg = _getLogList( $logList ); if( $logMsg ){ var $jsonTotal = ___config___["data_json"]; $jsonTotal = $jsonTotal.replace("{1}" , ___playerID___); $jsonTotal = $jsonTotal.replace("{2}" , ___platfrom___); $jsonTotal = $jsonTotal.replace("{3}" , ___serverID___); $jsonTotal = $jsonTotal.replace("{4}" , $logMsg); // var $blob = new Blob([$jsonTotal], { type: "application/json" });//text/plain;charset=utf-8 var $oXHR = new XMLHttpRequest(); $oXHR.responseType = "text"; $oXHR.open(___config___["upload_method"], ___servicePath___); $oXHR.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" );//application/x-www-form-urlencoded $oXHR.addEventListener('load', function(event){ console.log("upload--ok--"); }, true); $oXHR.send( $jsonTotal );//$blob } }
Ⅲ:請求服務器提供相關的Log
思想:服務器將某一個渠道旗下的一個服務器下的某一個玩家的全部的日誌文件達成一個Zip壓縮包 ,供H5前端客戶下載後端
//放到公司的管理網站上 var _downLog = function () { var $jsonTotal = ___config___["down_json"]; $jsonTotal = $jsonTotal.replace("{1}" , ___playerID___); $jsonTotal = $jsonTotal.replace("{2}" , ___platfrom___); $jsonTotal = $jsonTotal.replace("{3}" , ___serverID___); var xhr = new XMLHttpRequest(); xhr.open('post', "https://localhost:44370/Home/DownLog", true); xhr.responseType = 'blob'; xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { // var name = xhr.getResponseHeader('content-disposition'); // var filename = name.substring(20, name.length); var blob = new Blob([xhr.response]); let link = document.createElement('a'); let url = URL.createObjectURL(blob); link.style.display = 'block'; link.href = url; link.download = "Log.zip"; document.body.appendChild(link); link.click(); } } xhr.send($jsonTotal); }
須要注意的是 , content-disposition header字段受到了限制。調用就會報Refused to get unsafe header的錯誤。服務器
之因此選擇C# , 是由於我對C#比較熟悉。
Ⅰ:寫Log的思想以下圖所示:
①,平臺 ,服務器ID , 玩家ID都是H5客戶端傳來的
②,生成TXT日誌文件,須要注意的是:同一天的放在都一個Txt中。
③,過時Txt日誌文件須要刪除,以釋放服務器的空間。
Ⅱ:C#後端處理H5前端的下載請求app
[HttpPost] public ActionResult DownLog(string data) { LogTxt log = JsonConvert.DeserializeObject<LogTxt>(data); string path = $"{this._settings.Value.Root}/pf_{log.Platform}/server_{log.ServerID}/player_{log.PlayerID}"; byte[] bts = null; System.Net.Mime.ContentDisposition cd = null; if (Directory.Exists(path)) { ZipFile.CreateFromDirectory( path, $"{path}.zip" ); bts = System.IO.File.ReadAllBytes($"{path}.zip"); System.IO.File.Delete($"{path}.zip"); cd = new System.Net.Mime.ContentDisposition { FileName = $"player_{log.PlayerID}.zip", Inline = false // false = prompt the user for downloading; true = browser to try to show the file inline }; } else { if (!System.IO.File.Exists($"{this._error.Value.Root}/{this._error.Value.Sub}.zip")) { IO.Instance.CreateFolder(this._error.Value.Root); IO.Instance.CreateFolder($"{this._error.Value.Root}/{this._error.Value.Sub}"); DirectoryInfo root = new DirectoryInfo($"{this._error.Value.Root}/{this._error.Value.Sub}"); FileInfo[] files = root.GetFiles(); if (files == null || files.Length == 0) { IO.Instance.CreateFile($"{this._error.Value.Root}/{this._error.Value.Sub}/error.txt"); IO.Instance.WriteCommon($"{this._error.Value.Root}/{this._error.Value.Sub}/error.txt", this._error.Value.Txt, true); } ZipFile.CreateFromDirectory( $"{this._error.Value.Root}/{this._error.Value.Sub}", $"{this._error.Value.Root}/{this._error.Value.Sub}.zip" ); } bts = System.IO.File.ReadAllBytes($"{this._error.Value.Root}/{this._error.Value.Sub}.zip"); cd = new System.Net.Mime.ContentDisposition { FileName = $"{this._error.Value.Sub}.zip", Inline = false // false = prompt the user for downloading; true = browser to try to show the file inline }; } Response.Headers.Add("Content-Disposition", cd.ToString()); Response.Headers.Add("X-Content-Type-Options", "nosniff"); return File(bts, "application/zip"); }
思想 :
①,根據前端提供的平臺 ,服務器ID , 玩家ID來尋找相關日誌
②,若是找到則將日誌所在的整個文件夾打包壓縮
③,若是沒找到,查找有無error.zip 若有,則返回error.zip,沒有就構建error.zip並返回ide