[Unity]错误日志处理

        游戏上线测试,会有很多错误日志生成,也不可能一个个去点过来,因为错误信息是根据日期和账号来区分的,便于管理。所以就需要来处理下,其实很简单,就是把后台PHP存储的txt格式的报错信息写入到一个CSV文件中。

        其实这功能我之前就有写过,那时候是在Unity上写了个工具,所以用的是C#,最近为了锻炼一下自己的C++,就写了份C++的,写是写出来了,但是各种不好用(主要是没有去重和分割,C++的字符串处理真TM难),就只好换回C#来。

        为了不放弃C++代码,把C++的一部分代码写成了DLL,还是遇到各种问题,到最后还是放弃了使用DLL,因为BROWSEINFO这个选择文件夹的结构体会在DLL中一直卡死,没有报错和异常。所以就用了网上搜到的一个方法来实现选择文件夹功能。
        虽然没有用上DLL,但是也学习到了不少东西,也明白了extern "C" __declspec(dllexport)的意思,这玩意经常看到但不知道什么意思,现在知道是为了处理C++符号修饰别名(mangled name)。
        C#调用DLL提示"试图加载格式不正确的程序"处理方法是32位和64位的问题:

[Unity]错误日志处理_第1张图片
        C#调试C++DLL的方法:

[Unity]错误日志处理_第2张图片
        代码保留一份,避免下次再进行重复工作:

class Program
    {
        #region 选择文件夹功能
        [DllImport("E:\\ZP\\Utility\\x64\\Release\\Utility.dll", EntryPoint = "BrowseDirectory")]
        public static extern bool BrowseDirectory(IntPtr hwnd, StringBuilder title, ref StringBuilder result);
        [DllImport("E:\\ZP\\Utility\\x64\\Release\\Utility.dll", EntryPoint = "Add")]
        public static extern int Add(int v1, int v2);
        [DllImport("shell32.dll")]
        static extern IntPtr SHBrowseForFolder(ref BROWSEINFO lpbi);
        [DllImport("shell32.dll")]
        public static extern Int32 SHGetPathFromIDList(IntPtr pidl, StringBuilder pszPath);
        public delegate int BrowseCallBackProc(IntPtr hwnd, int msg, IntPtr lp, IntPtr wp);
        struct BROWSEINFO
        {
            public IntPtr hwndOwner;
            public IntPtr pidlRoot;
            public string pszDisplayName;
            public string lpszTitle;
            public uint ulFlags;
            public BrowseCallBackProc lpfn;
            public IntPtr lParam;
            public int iImage;
        }

        public static string SelectDirectoryFromCpp(StringBuilder title)
        {
            StringBuilder inputPath = new StringBuilder();
            try
            {
                IntPtr hwnd = Process.GetCurrentProcess().MainWindowHandle;
                if (BrowseDirectory(hwnd, title, ref inputPath))
                {
                    Console.WriteLine("获取错误日志存放目录成功");
                }
                else
                {
                    Console.WriteLine("获取错误日志存放目录失败");
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            return inputPath.ToString();
        }
        public static string SelectDirectory(StringBuilder title)
        {
            StringBuilder sb = new StringBuilder(256);
            IntPtr pidl = IntPtr.Zero;
            BROWSEINFO bi;
            bi.hwndOwner = Process.GetCurrentProcess().MainWindowHandle; ;
            bi.pidlRoot = IntPtr.Zero;
            bi.pszDisplayName = null;
            bi.lpszTitle = title.ToString();
            bi.ulFlags = 0; // BIF_NEWDIALOGSTYLE | BIF_SHAREABLE;
            bi.lpfn = null; // new BrowseCallBackProc(OnBrowseEvent);
            bi.lParam = IntPtr.Zero;
            bi.iImage = 0;

            try
            {
                pidl = SHBrowseForFolder(ref bi);
                if (0 == SHGetPathFromIDList(pidl, sb))
                {
                    return null;
                }
            }
            finally
            {
                // Caller is responsible for freeing this memory.
                Marshal.FreeCoTaskMem(pidl);
            }
            return sb.ToString();
        }
        #endregion

        /// 
        /// 错误日志路径列表
        /// 
        private static List errorFileList;
        /// 
        /// 错误日志存放目录
        /// 
        private static string inputPath;
        /// 
        /// 错误日志输出目录
        /// 
        private static string outputPath;
        /// 
        /// 已经报过的错误
        /// 
        private static List existErrorList;

        static void Main(string[] args)
        {
            bool result;

            result = Initialize();
            if (result)
            {
                ErrorTxtConverToCSV();
            }

            Console.ReadKey();
        }

        private static void ErrorTxtConverToCSV()
        {
            if (null == errorFileList || errorFileList.Count <= 0) return;
            string outputFilePath = outputPath + "\\error.csv";
            if (File.Exists(outputFilePath))
                File.Delete(outputFilePath);
            var file = File.Create(outputFilePath);
            if(file != null)
            {
                file.Close();
                file = null;
            }
            int size = errorFileList.Count;
            int count = 0;
            StreamReader reader = null;
            StreamWriter writer = null;
            writer = new StreamWriter(outputFilePath, true, Encoding.GetEncoding("gb2312"));//解决中文写入乱码问题
            if (null == writer) return;
            for (int i = 0; i < size; ++i)
            {
                ++count;
                Console.WriteLine("处理进度: " + count + "/" + size);
                try
                {
                    reader = new StreamReader(errorFileList[i]);
                    if (null == reader) continue;
                    var content = reader.ReadToEnd().Trim();
                    reader.Close();

                    var result = OperateString(content);
                    if (writer != null)
                    {
                        for (int j = 0; j < result.Length; ++j)
                        {
                            if (BIsNewError(result[j]))
                            {
                                writer.WriteLine(result[j]);
                                writer.Flush();
                            }
                        }
                    }
                }
                catch(Exception e)
                {
                    Console.WriteLine("Exception: " + e.ToString());
                }
                finally
                {
                    if(reader != null)
                    {
                        reader.Close();
                        reader = null;
                    }
                }
            }

            if (writer != null)
            {
                writer.Close();
                writer = null;
            }
            Console.WriteLine("转换完成!");
        }

        /// 
        /// 是否有重复错误
        /// 
        /// 
        /// 
        private static bool BIsNewError(string error)
        {
            bool bIsHaveSameError = false;

            if (null == existErrorList)
                existErrorList = new List();

            if (string.IsNullOrEmpty(error))
                bIsHaveSameError = true;
            else
            {
                for (int i = 0; i < existErrorList.Count; ++i)
                {
                    if (error.Equals(existErrorList[i]))
                    {
                        bIsHaveSameError = true;
                        break;
                    }
                }
            }

            if (!bIsHaveSameError)
                existErrorList.Add(error);

            return !bIsHaveSameError;
        }

        private static string[] OperateString(string content)
        {
            //处理CSV中写入的一些特殊符号
            content = content.Replace("\r", ",");
            content = content.Replace("\n", ",");
            content = content.Replace(",", ",");
            content = content.Replace("\t", ",");
            content = content.Replace(" ", ",");
            //使用正则表达式分割字符串,蛋疼的是不能使用(.*)来处理,如果用(.*),只会分割出最后的字符串
            string[] result = Regex.Split(content, @"\[错误\(...................\)\]:");
            
            var resultList = result.ToList();
            for (int i = resultList.Count - 1; i >= 0; --i)
            {
                if (string.IsNullOrEmpty(resultList[i]))
                    resultList.RemoveAt(i);
            }
            return resultList.ToArray();
        }

        private static bool Initialize()
        {
            var v = Add(5, 10);
            StringBuilder title = new StringBuilder("请选择错误日志存放目录");
            //var inputPath = SelectDirectoryFromCpp(title);//调用C++ DLL
            inputPath = SelectDirectory(title);

            title = new StringBuilder("请选择错误日志输出目录");
            outputPath = SelectDirectory(title);

            if (string.IsNullOrEmpty(inputPath) || string.IsNullOrEmpty(outputPath))
            {
                Console.WriteLine("错误日志存放目录或者输出目录有误");
                return false;
            }

            if (null == errorFileList)
                errorFileList = new List();
            else
                errorFileList.Clear();

            FindFile(inputPath);

            return true;
        }

        private static void FindFile(string inputPath)
        {
            if (!Directory.Exists(inputPath))
                return;

            DirectoryInfo dirInfo = new DirectoryInfo(inputPath);
            var files = dirInfo.GetFileSystemInfos();
            for (int i = 0; i < files.Length; ++i)
            {
                var fileName = files[i].Name;
                if (fileName.Contains("."))//文件
                {
                    if (files[i].Name.Contains(".txt"))
                    {
                        errorFileList.Add(files[i].FullName);
                    }
                }
                else//文件夹
                {
                    if(!files[i].Name.Equals("PC"))//过滤UnityEditor开发版本的错误
                        FindFile(files[i].FullName);
                }  
            }
        }
    }

        C++DLL中的代码:

#include "stdafx.h"
#include 

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
	DWORD curProcessID = *((DWORD*)lParam);
	DWORD tempProcessID = 0;

	GetWindowThreadProcessId(hwnd, &tempProcessID);
	if (tempProcessID == curProcessID && nullptr == GetParent(hwnd))
	{
		*((HWND *)lParam) = hwnd;
		return true;
	}

	return false;
}

HWND GetMainWindow()
{
	DWORD curProcessID = GetCurrentProcessId();
	if (!EnumWindows(EnumWindowsProc, (LPARAM)&curProcessID))
	{
		return (HWND)curProcessID;
	}
	return nullptr;
}

extern "C" __declspec(dllexport) bool BrowseDirectory(HWND hwnd, char* title, char** resultStr)
{
	bool result;
	BROWSEINFO bi;
	LPITEMIDLIST pidl;
	int length;
	WCHAR* tempTitle;
	WCHAR* tempResult;
	char* tempResult2;

	result = (nullptr == title || nullptr == resultStr);
	if (result)
		return false;

	length = MultiByteToWideChar(CP_ACP, 0, title, -1, nullptr, 0);
	tempTitle = new WCHAR[length + 1];
	memset(tempTitle, 0, sizeof(tempTitle));
	MultiByteToWideChar(CP_ACP, 0, title, -1, tempTitle, length);

	memset(&bi, 0, sizeof(BROWSEINFO));
	//HMODULE hModule = GetModuleHandle(nullptr);
	//HWND hwnd = GetMainWindow();
	bi.hwndOwner = hwnd;
	bi.pidlRoot = nullptr;
	bi.lpszTitle = tempTitle;
	bi.ulFlags = BIF_BROWSEFORCOMPUTER | BIF_EDITBOX | BIF_NEWDIALOGSTYLE;
	bi.lpfn = nullptr;
	bi.lParam = 0;
	bi.iImage = 0;
	pidl = SHBrowseForFolder(&bi);
	result = pidl != nullptr;
	if (!result)
		return false;

	tempResult = new WCHAR[1024];
	memset(tempResult, 0, sizeof(tempResult));
	SHGetPathFromIDList(pidl, tempResult);

	length = WideCharToMultiByte(CP_ACP, 0, tempResult, -1, nullptr, 0, nullptr, nullptr);
	tempResult2 = new char[length + 1];
	WideCharToMultiByte(CP_ACP, 0, tempResult, -1, tempResult2, length, nullptr, nullptr);

	strcpy_s(*resultStr, strnlen_s(tempResult2, 1024), tempResult2);

	return true;
}

extern "C" __declspec(dllexport) int Add(int v1, int v2)
{
	return v1 + v2;
}

        最后再留一份之前C++实现的,望以后能继续用C++修改出一份完美的。

#include 
#include 
#include 
#include 
#include 

#define MAX_PATH_LENGTH  1024
#define TXT_EXTENSION TEXT("txt")

TCHAR* inputPath = new TCHAR[MAX_PATH_LENGTH];		//错误日志存放目录
TCHAR* outputPath = new TCHAR[MAX_PATH_LENGTH];		//错误日志输出目录
std::list errorFileList;					//错误日志文件路径

/* 初始化 */
bool Initialize();
/* 将txt内容写入csv中 */
void ErrorTxtConverToCSV();
/* 将UTF8格式转成ANSI格式 */
char* ConvertUTF8ToANSI(char* text);
/* 替换字符串 */
char* DeleteChar(char* text, const char* deleteChar);
/* 查找文件并存放到list中 */
void FindFile(const TCHAR* inputPath, std::list &fileList);
/* 判断文件名后缀是否匹配 */
bool IsFileHaveSameExtension(const TCHAR* fileName, const TCHAR* extension);

int main()
{
	bool result;

	result = Initialize();
	if (result)
	{
		ErrorTxtConverToCSV();
	}

	getchar();

	return 0;
}

bool Initialize()
{
	BROWSEINFO bi;
	LPITEMIDLIST pidl;
	bool result;

	inputPath[0] = TEXT('\0');
	outputPath[0] = TEXT('\0');

	memset(&bi, 0, sizeof(BROWSEINFO));
	bi.ulFlags = BIF_BROWSEFORCOMPUTER | BIF_EDITBOX | BIF_NEWDIALOGSTYLE;
	WCHAR msg[32];
	wsprintf(msg, TEXT("%d"), bi.ulFlags);
	MessageBox(nullptr, msg, TEXT("bi.ulFlags"), MB_OK);
	bi.lpszTitle = TEXT("请选择错误日志存放目录");
	pidl = SHBrowseForFolder(&bi);
	result = pidl != nullptr;
	if (!result)
		return false;
	SHGetPathFromIDList(pidl, inputPath);
	pidl = nullptr;

	bi.lpszTitle = TEXT("请选择错误日志输出目录");
	pidl = SHBrowseForFolder(&bi);
	result = pidl != nullptr;
	if (!result)
		return false;
	SHGetPathFromIDList(pidl, outputPath);
	pidl = nullptr;

	if (TEXT('\0') == inputPath[0] || TEXT('\0') == outputPath[0])
	{
		MessageBox(nullptr, TEXT("错误日志存放目录或者输出目录有误"), TEXT("错误"), MB_OK | MB_ICONERROR);
		return false;
	}

	errorFileList.clear();

	FindFile(inputPath, errorFileList);
	
	result = errorFileList.size() > 0;

	return result;
}

void ErrorTxtConverToCSV()
{
	TCHAR* outputFilePath = new TCHAR[MAX_PATH_LENGTH];

	lstrcpy(outputFilePath, outputPath);
	lstrcat(outputFilePath, TEXT("\\error.csv"));
	std::ofstream writer(outputFilePath, std::ios::out);
	if (!writer) return;

	std::list::iterator listIter;
	int count = 0;
	int size = (int)errorFileList.size();
	for (listIter = errorFileList.begin(); listIter != errorFileList.end(); ++listIter)
	{
		++count;
		std::cout << "处理进度: " << count << "/" << size << std::endl;
		std::ifstream reader((*listIter), std::ios::in);
		if (!reader)
		{
			std::cout << "文件不能打开: " << (*listIter) << std::endl;
		}
		else
		{
			while (!reader.eof())
			{
				char ch1[50000];
				char* temp;
				reader.getline(ch1, 50000);
				temp = ConvertUTF8ToANSI(ch1);
				temp = DeleteChar(temp, ",");
				temp = DeleteChar(temp, "\t");
				temp = DeleteChar(temp, " ");
				writer << temp;
			}
			writer << std::endl;
		}
		reader.close();
	}
	writer.close();

	std::cout << "转换完成!" << std::endl;
}

char* ConvertUTF8ToANSI(char* text)
{
	int wcsLen = MultiByteToWideChar(CP_UTF8, 0, text, (int)strlen(text), nullptr, 0);
	wchar_t* wszString = new wchar_t[wcsLen + 1];
	MultiByteToWideChar(CP_UTF8, 0, text, (int)strlen(text), wszString, wcsLen);
	wszString[wcsLen] = '\0';

	int ansiLen = WideCharToMultiByte(CP_ACP, 0, wszString, (int)wcslen(wszString), nullptr, 0, nullptr, nullptr);
	char* szAnsi = new char[ansiLen + 1];
	WideCharToMultiByte(CP_ACP, 0, wszString, (int)wcslen(wszString), szAnsi, ansiLen, nullptr, nullptr);
	szAnsi[ansiLen] = '\0';
	return szAnsi;
}

char* DeleteChar(char* text, const char* deleteChar)
{
	char* st = text;
	char* s1 = nullptr;
	const char* s2 = nullptr;
	while (*st && *deleteChar)
	{
		s1 = st;
		s2 = deleteChar;
		while (*s1 && *s2 && !(*s1 - *s2))
		{
			s1++;
			s2++;
		}
		if (!*s2)
		{
			while (*st++ = *s1++);
			st = text;
		}
		st++;
	}
	return text;
}

void FindFile(const TCHAR* inputPath, std::list& fileList)
{
	TCHAR* path = new TCHAR[MAX_PATH_LENGTH];
	WIN32_FIND_DATA findFileData;
	HANDLE handle;

	lstrcpy(path, inputPath);
	lstrcat(path, TEXT("\\*"));

	handle = FindFirstFile(path, &findFileData);
	if (INVALID_HANDLE_VALUE == handle)
	{
		MessageBox(nullptr, path, TEXT("错误日志存放目录空目录"), MB_OK | MB_ICONERROR);
		return;
	}

	
	while (true)
	{
		if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			if (findFileData.cFileName[0] != TEXT('.'))
			{
				if (findFileData.cFileName[0] != TEXT('P') && findFileData.cFileName[0] != TEXT('C'))//过滤UnityEditor开发版本的错误
				{
					TCHAR* tempPath = new TCHAR[MAX_PATH_LENGTH];
					lstrcpy(tempPath, inputPath);
					lstrcat(tempPath, TEXT("\\"));
					lstrcat(tempPath, (TCHAR*)(findFileData.cFileName));
					FindFile(tempPath, fileList);
				}
			}
		}
		else
		{
			if (IsFileHaveSameExtension(findFileData.cFileName, TXT_EXTENSION))
			{
				TCHAR* tempPath = new TCHAR[MAX_PATH_LENGTH];
				lstrcpy(tempPath, inputPath);
				lstrcat(tempPath, TEXT("\\"));
				lstrcat(tempPath, findFileData.cFileName);
				fileList.push_back(tempPath);
			}
		}
		
		if (!FindNextFile(handle, &findFileData))
			break;
	}

	FindClose(handle);
}

bool IsFileHaveSameExtension(const TCHAR* fileName, const TCHAR* extension)
{
	TCHAR* tempFile = new TCHAR[MAX_PATH_LENGTH];
	TCHAR* tempExtension = new TCHAR[MAX_PATH_LENGTH];
	int fileLength, extensionLength;
	int difference;

	lstrcpy(tempFile, fileName);
	lstrcpy(tempExtension, extension);

	fileLength = lstrlen(tempFile);
	extensionLength = lstrlen(tempExtension);
	difference = fileLength - extensionLength;
	if (difference < 2 || fileName[difference - 1] != TEXT('.')) return false;

	int count = 0;
	for (int i = 0; i < extensionLength; ++i)
	{
		if (tempExtension[i] == fileName[difference + i])
			++count;
		else
			break;
	}

	return count == extensionLength;
}


        参考:
        在VS2015中用C++创建DLL并用C#调用且同时实现对DLL的调试
        使用C#调用C++类库

你可能感兴趣的:(Unity)