Calmer的文章

  • 首页
  • 文章归档
  • 关于页面

  • 搜索
体验游戏 笔记 推荐 工具链 工具使用 小游戏 插件 UI 软件 教程

远程日志工具

发表于 2023-05-09 | 分类于 游戏开发 | 0 | 阅读次数 1120

前言

在游戏开发过程中,日志是Debug非常重要和基础的方法,也是考验程序员基本功。
日志埋的好,很快就能定位问题所在,尤其面对偶现的BUG,更能突显重要性。

在UE游戏中,我们可以很方便的在Saved/Logs目录下取得日志文件。但是在团队协同时,面临的是日志的拷贝,传输等问题。

因此一个中央日志服务器来管理团队的日志,进行接收,保存,下载,分发就能很好的解决这些简单,但是繁琐的事情,而且能大大节省沟通时间,提升效率。


客户端

发送日志

在斟酌了众多传输文件的协议,本次使用Http协议的表数据来传输日志信息。因为其最常见,最为通用。

设计思路

  1. 读取日志文件
  2. 封装Http协议内容
  3. 发送对应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());
}

使用时机

  1. 在UE中注册一条命令为SendLog,主动通过命令行上传
  2. 在游戏出现致命crash时,被动触发上传日志

服务端

实现基本思路

  1. http服务器容器
  2. 能够上传,下载和查阅

接收与保存

接收:服务端首先需要能够接受到http请求,这里采用python来实现简单服务端。
保存:当服务端收到对应的http内容后,在通过文件内容,持久化到本地。

分发与下载

分发:(尚未完善)
下载:通过访问http服务器对应网址,进行下载

代码段

python日志服务器

总结

整个工具的基础思路是对Http协议的上传与下载功能的封装。
此工具解决了拷贝和传递的痛点。

  • 本文作者: Calmer
  • 本文链接: https://mytechplayer.com/archives/ue5远程日志工具
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
# 工具链
UE5内存管理原理
UE5命令行的扩展
  • 文章目录
  • 站点概览
Calmer

Calmer

88 日志
7 分类
10 标签
RSS
Creative Commons
0%
© 2020 — 2025 Calmer
由 Halo 强力驱动
蜀ICP备20010026号-1川公网安备51019002006543
Copyright © 2020-2025 Calmer的文章