前言
在游戏开发过程中,日志是Debug非常重要和基础的方法,也是考验程序员基本功。
日志埋的好,很快就能定位问题所在,尤其面对偶现的BUG,更能突显重要性。
在UE游戏中,我们可以很方便的在Saved/Logs目录下取得日志文件。但是在团队协同时,面临的是日志的拷贝,传输等问题。
因此一个中央日志服务器来管理团队的日志,进行接收,保存,下载,分发就能很好的解决这些简单,但是繁琐的事情,而且能大大节省沟通时间,提升效率。
客户端
发送日志
在斟酌了众多传输文件的协议,本次使用Http协议的表数据来传输日志信息。因为其最常见,最为通用。
设计思路
- 读取日志文件
- 封装Http协议内容
- 发送对应Ip服务器和端口
代码段
.h文件声明
public:
UFUNCTION(BlueprintCallable)
bool SendLog(const FString& URL="", const bool IsServer=false, const int Port=80);
private:
void OnPossessCallback(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bConnectedSuccessfully);
.cpp文件声明
//发送日志
bool UFixManager::SendLog(const FString& URL, const bool IsServer, const int Port)
{
FString CurLogPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir()) / TEXT("Logs");
FString DefaultURL(TEXT("http://127.0.0.1"));
FString FinalURL(TEXT(""));
if (URL.IsEmpty())
{
FinalURL = FString::Printf(TEXT("%s:%d"), *DefaultURL, Port);
}
else
{
FinalURL = FString::Printf(TEXT("%s:%d"), *URL, Port);
}
FString Boundary = "---------------------------" + FString::FromInt(FDateTime::Now().GetTicks());
TArray<uint8> RequestContent;
TArray<uint8> FileArray;
// Begin Boundry
// FString BeginBoundry = "\r\n--" + Boundary + "\r\n";
FString BeginBoundary = "--" + Boundary + "\r\n";
RequestContent.Append((uint8*)TCHAR_TO_ANSI(*BeginBoundary), BeginBoundary.Len());
if (!IsServer)
{
FString ClientLog(CurLogPath / TEXT("MyProject.log"));
if (!FPaths::FileExists(ClientLog))
{
UE_LOG(LogTemp, Warning, TEXT("ClientPath is not exist"));
return false;
}
if (FFileHelper::LoadFileToArray(FileArray, *ClientLog, FILEREAD_AllowWrite))
{
FString ClientLogName(FString::Printf(TEXT("C_%s_%s"), *FDateTime::Now().ToString(),
*FPaths::GetCleanFilename(ClientLog)));
// File Header
FString FileHeader = "Content-Disposition: form-data;";
FileHeader.Append("name=\"file\";");
FileHeader.Append("filename=\"" + ClientLogName + "\"");
FileHeader.Append("\r\nContent-Type: application/octet-stream\r\n\r\n");
RequestContent.Append((uint8*)TCHAR_TO_ANSI(*FileHeader), FileHeader.Len());
RequestContent.Append(FileArray);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("FixManager, Load Client Log Failed"));
return false;
}
}
else
{
FString ServerLog(CurLogPath / TEXT("MyProject_2.log"));
if (!FPaths::FileExists(ServerLog))
{
UE_LOG(LogTemp, Warning, TEXT("ServerLog is not exist"));
return false;
}
if (FFileHelper::LoadFileToArray(FileArray, *ServerLog, FILEREAD_AllowWrite))
{
FString ServerLogName(FString::Printf(TEXT("S_%s_%s"), *FDateTime::Now().ToString(),
*FPaths::GetCleanFilename(ServerLog)));
// File Header
FString FileHeader = "Content-Disposition: form-data;";
FileHeader.Append("name=\"file\";");
FileHeader.Append("filename=\"" + ServerLogName + "\"");
FileHeader.Append("\r\nContent-Type: application/octet-stream\r\n\r\n");
RequestContent.Append((uint8*)TCHAR_TO_ANSI(*FileHeader), FileHeader.Len());
RequestContent.Append(FileArray);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("FixManager, Load Server Log Failed"));
return false;
}
}
// End Boundry
FString EndBoundary = "\r\n--" + Boundary + "--\r\n";
RequestContent.Append((uint8*)TCHAR_TO_ANSI(*EndBoundary), EndBoundary.Len());
auto HttpRequest = FHttpModule::Get().CreateRequest();
HttpRequest->SetURL(FinalURL);
HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("multipart/form-data; boundary="+Boundary));
HttpRequest->SetHeader(
TEXT("User-Agent"),
TEXT(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"));
HttpRequest->SetVerb(TEXT("POST"));
HttpRequest->SetContent(RequestContent);
HttpRequest->OnProcessRequestComplete().BindUObject(this, &UFixManager::OnPossessCallback);
UE_LOG(LogTemp, Display, TEXT("Begin Request, URL:%s"), *FinalURL);
return HttpRequest->ProcessRequest();
}
void UFixManager::OnPossessCallback(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bConnectedSuccessfully)
{
UE_LOG(LogTemp, Log, TEXT("FixManager, OnPossessCode: %d"), Response->GetResponseCode());
}
使用时机
- 在UE中注册一条命令为SendLog,主动通过命令行上传
- 在游戏出现致命crash时,被动触发上传日志
服务端
实现基本思路
- http服务器容器
- 能够上传,下载和查阅
接收与保存
接收:服务端首先需要能够接受到http请求,这里采用python来实现简单服务端。
保存:当服务端收到对应的http内容后,在通过文件内容,持久化到本地。
分发与下载
分发:(尚未完善)
下载:通过访问http服务器对应网址,进行下载
代码段
总结
整个工具的基础思路是对Http协议的上传与下载功能的封装。
此工具解决了拷贝和传递的痛点。