一 前言 :
最近公司希望我做一個日誌系統,用來排查手手遊Bug用的。因爲前些時候實現了vConsole在手機上的顯示,所以覺得是輕車熟路了。麻煩的是 : 需要玩家在出現bug後打開記錄截圖給我方策劃,供前端開發人員分析,Low是Low了點,但是好實現。但是沒過幾天新的情況出現了:遊戲閃退。Oh , My God!玩家截圖的機會都沒有了。只有硬着頭皮搞正真的Log方案了。
我在GitHub上搜到Log4js,但是專門爲node.js做的類庫,放在Egret前端上折騰了大半天,因爲Log4js依賴太多的node.js庫,所以無賴放棄。只有在互聯網上自找其他出路。
二 方案(H5前端):
Ⅰ:Log數據的來源
①,重寫window.console方法
②,監聽window.onerror方法
思想:當日志信息的條數達到一定的數量,或者有重要日誌信息,立即向Web服務器發送請求,要求服務器保存日誌。
如下代碼:
(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;
};
Ⅱ:請求服務器保存Log
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# .Net Core):
之所以選擇C# , 是因爲我對C#比較熟悉。
Ⅰ:寫Log的思想如下圖所示:
①,平臺 ,服務器ID , 玩家ID都是H5客戶端傳來的
②,生成TXT日誌文件,需要注意的是:同一天的放在都一個Txt中。
③,過期Txt日誌文件需要刪除,以釋放服務器的空間。
Ⅱ:C#後端處理H5前端的下載請求
[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並返回