游戏一到测试阶段,就想获取游戏测试的报错日志,因为玩家基数大,一个开发团队不可能测试到方方面面,所以收集客户端报错日志就是必须的,有用过腾讯的bugly,官方文档写的云里雾里的,有Unity插件导入,还有安卓工程导入,都没说是两边都需要导入还是怎样,要靠猜,太累。接好后发现只能获取java层的报错,没系统学过,表示压力巨大。
只好自己写一个来收集报错日志,利用Unity自身的Application.logMessageReceived方法来实现,代码很简单,就是拼接自己想要的报错日志上传。也加了个接口,可以通过lua热更实现关闭上传。
public static void SetIsOpenCollcetLog(bool bIsCollcetLog)
{
LogCollection.bIsCollcetLog = bIsCollcetLog;
}
/// <summary>
/// 初始化
/// </summary>
/// <param name="postUrl">上传url</param>
/// <param name="bIsPostServer">是否上传</param>
public static void Initialize(bool bIsShowStackTrace = true)
{
LogCollection.bIsCollcetLog = true;
LogCollection.bIsShowStackTrace = bIsShowStackTrace;
Application.logMessageReceived += HandleMessage;
LogCollection.logFilePath = Application.persistentDataPath + "/log.txt";
}
private static void HandleMessage(string logString, string logStackTrace, LogType logType)
{
if (!LogCollection.bIsCollcetLog) return;
if (string.IsNullOrEmpty(logString) && string.IsNullOrEmpty(logStackTrace)) return;
if (lastLogString.Equals(logString) && lastLogStackTrace.Equals(logStackTrace) && lastLogType == logType) return;
lastLogString = logString;
lastLogStackTrace = logStackTrace;
lastLogType = logType;
switch (logType)
{
case LogType.Assert:
tempLog = string.Format("Assert: {0}\nstack trace: {1}", logString, LogCollection.bIsShowStackTrace ? logStackTrace : string.Empty);
break;
case LogType.Error:
tempLog = string.Format("Error: {0}\nstack trace: {1}", logString, LogCollection.bIsShowStackTrace ? logStackTrace : string.Empty);
break;
case LogType.Exception:
tempLog = string.Format("deviceName: {0}\ndeviceModel: {1}\noperatingSystem:{2}\ngraphicsDeviceName: {3}\n", SystemInfo.deviceName, SystemInfo.deviceModel, SystemInfo.operatingSystem, SystemInfo.graphicsDeviceName);
tempLog = string.Format("device: {0}\nException: {1}\nstack trace: {2}", tempLog, logString, LogCollection.bIsShowStackTrace ? logStackTrace : string.Empty);
break;
default:
tempLog = string.Format("default: {0}\nstack trace: {1}", logString, LogCollection.bIsShowStackTrace ? logStackTrace : string.Empty);
break;
}
if (!string.IsNullOrEmpty(tempLog))
{
PostToFtp(tempLog);
}
}
ftp上传代码是之前写的,不是最新的,大致思想都在:
private static void PostToFtp(string message)
{
if (string.IsNullOrEmpty(message)) return;
try
{
FileStream fileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write);
StreamWriter sw = new StreamWriter(fileStream);
sw.WriteLine(tempLog);
sw.Flush();
sw.Close();
// 上传到Ftp
FtpUploadFile(logFilePath);
}
catch(Exception e)
{
}
}
private static List<string> directoryList;
private static bool bIsGetDirectoryList = false;
private static int requetTimeOut = 5000;
/// <summary>
/// ftp上传文件
/// </summary>
/// <param name="file"></param>
private static void FtpUploadFile(string file)
{
if (string.IsNullOrEmpty(file)) return;
System.DateTime nowTime = System.DateTime.Now;
string curDay = nowTime.ToString("yyyyMMdd");
FtpCheckDirectoryExist(curDay);
FtpWebRequest req = null;
FileInfo fileInfo = null;
FileStream fileStream = null;
Stream stream = null;
try
{
fileInfo = new FileInfo(file);
string curTime = (nowTime.ToString("HH_mm_ss_fff"));
string destPath = string.Format("{0}{1}/{2}.txt", ftpUrl, curDay, curTime);
req = (FtpWebRequest)WebRequest.Create(new Uri(destPath));
req.UseBinary = true;
req.Credentials = new NetworkCredential(ftpUserName, ftpPassword);
req.KeepAlive = false;
req.Method = WebRequestMethods.Ftp.UploadFile;
req.Timeout = requetTimeOut;
req.ContentLength = fileInfo.Length;
int buffLength = 2048;
byte[] buff = new byte[buffLength];
int contentLen;
fileStream = fileInfo.OpenRead();
stream = req.GetRequestStream();
contentLen = fileStream.Read(buff, 0, buffLength);
while (contentLen != 0)
{
stream.Write(buff, 0, contentLen);
contentLen = fileStream.Read(buff, 0, buffLength);
}
}
catch(Exception e)
{
}
finally
{
if(stream != null)
stream.Close();
if (fileStream != null)
fileStream.Close();
if (fileInfo != null)
fileInfo.Delete();
if (req != null)
req.Abort();
}
}
/// <summary>
/// 检测ftp文件夹是否存在,不存在则创建
/// </summary>
/// <param name="destFilePath"></param>
private static void FtpCheckDirectoryExist(string destFilePath)
{
if (string.IsNullOrEmpty(destFilePath)) return;
FtpInitDirectoryList();
bool bIsExit = false;
for(int i = 0; i < directoryList.Count; ++i)
{
if (destFilePath.Equals(directoryList[i]))
{
bIsExit = true;
break;
}
}
if (bIsExit) return;
FtpCreateDirectory(destFilePath);
}
/// <summary>
/// 获取ftp服务器上文件夹列表
/// </summary>
private static void FtpInitDirectoryList()
{
if (bIsGetDirectoryList) return;
bIsGetDirectoryList = true;
if (null == directoryList)
directoryList = new List<string>();
else
directoryList.Clear();
FtpWebRequest req = null;
WebResponse response = null;
StreamReader reader = null;
req = (FtpWebRequest)WebRequest.Create(ftpUrl);
req.UseBinary = true;
req.Credentials = new NetworkCredential(ftpUserName, ftpPassword);
req.KeepAlive = false;
req.Method = WebRequestMethods.Ftp.ListDirectory;
req.Timeout = requetTimeOut;
try
{
response = req.GetResponse();
reader = new StreamReader(response.GetResponseStream(), Encoding.Default);
string line = reader.ReadLine();
while(line != null)
{
directoryList.Add(line);
line = reader.ReadLine();
}
reader.Close();
response.Close();
}
catch(Exception e)
{
}
finally
{
if (reader != null)
reader.Close();
if (response != null)
response.Close();
if(req != null)
req.Abort();
}
}
/// <summary>
/// ftp服务器上创建文件夹
/// </summary>
private static void FtpCreateDirectory(string destFilePath)
{
if (string.IsNullOrEmpty(destFilePath)) return;
FtpWebRequest req = null;
WebResponse response = null;
req = (FtpWebRequest)WebRequest.Create(ftpUrl + destFilePath);
req.Credentials = new NetworkCredential(ftpUserName, ftpPassword);
req.KeepAlive = false;
req.Method = WebRequestMethods.Ftp.MakeDirectory;
req.Timeout = requetTimeOut;
try
{
response = req.GetResponse();
response.Close();
if (null == directoryList)
directoryList = new List<string>();
directoryList.Add(destFilePath);
}
catch(Exception e)
{
}
finally
{
if (response != null)
response.Close();
if (req != null)
req.Abort();
}
}
注意在Application.logMessageReceived方法中就不能使用Debug.Log等方法,这是Unity防止死循环做的处理。