[UE5] Unreal Engine 5 길라잡이 - 5. 로깅 방법

세동네·2022년 6월 11일
1
post-thumbnail

이 시리즈는 이득우의 언리얼 C++ 게임 개발의 정석을 바탕으로 작성되었습니다.

언리얼 엔진은 코딩의 흐름을 파악하고 디버깅을 용이하게 만들어주는 '로깅' 기능을 제공한다. 이는 일반적으로 UE_LOG라는 매크로를 활용하며, 다음과 같은 형태로 사용할 수 있다.

· 로그 매크로 형태

// UE_LOG(카테고리, 로깅 수준, 형식 문자열, 인자..)

UE_LOG(LogTemp, Warning, TEXT("Hello, %s"), *name);

로그 매크로를 구성하는 각 매개변수에 대한 설명은 다음과 같다.

· 로그 카테고리

모든 로그는 분류를 위해 카테고리를 지정한다.

LogPlayLavel, LogUObjectHash, LogSavePackage와 같이 모든 로그 앞에는 해당 로그가 어떤 상황에 발생한 것인지 분류해주는데, 이것을 카테고리라 한다.

그리고 사용자가 직접 로그를 발생시키는 경우 해당 로그를 어떤 분류에 놓을 것인지 직접 카테고리를 지정해주는데, 그 종류는 CoreGlobals.h에서 확인할 수 있다.


테스트 등의 역할로 가장 대중적으로 사용되는 로그 카테고리는 LogTemp이며, 사용자가 직접 로그 카테고리를 지정해줄 수도 있다.

- 커스텀 로그 카테고리

로그 카테고리를 직접 생성하기 위해선 게임의 모듈명으로 된 헤더 파일을 찾는다. 언리얼 에디터의 상단 탭에서 [ - Visual Studio 열기]를 따라가면 프로젝트의 솔루션 파일을 열 수 있고, 다음과 같은 경로에 모듈명으로 된 h, cpp 파일이 존재한다.

해당 파일들에 다음과 같은 구문을 추가한다.

// 모듈명.h

#include "EngineMinimal.h" // 원래 "CoreMinimal.h"일텐데, 필자가 임의로 변경한 것이다. 신경쓰지 말자.

DECLARE_LOG_CATEGORY_EXTERN(UE5Practice, Log, All); // +

DECLARE_LOG_CATEGORY_EXTERN 매크로의 각 매개변수는 ( CategoryName, DefaultVerbose, CompileTimeVerbose )이다. Verbose는 정보가 얼마나 상세한지의 수준을 말한다. ELogVerbosity::Type 네임스페이스에는 다음과 같은 상세 수준 목록을 제공하며, 로그를 어떻게 출력할 것인지의 정보이다.

namespace ELogVerbosity
{
    enum Type
    {
        NoLogging        = 0,
        Fatal,							// 파일 및 에디터 로그에 빨간색으로 출력, 프로그램 종료
        Error,							// 파일 및 에디터 로그에 빨간색으로 출력
        Warning,						// 파일 및 에디터 로그에 노란색으로 출력
        Display,						// 파일 및 콘솔에 출력
        Log,							// 파일 및 에디터 로그에 출력
        Verbose,						// 로그 파일에 출력
        VeryVerbose,					// 로그 파일에 상세 내용을 포함하여 출력
        All              = VeryVerbose,
        NumVerbosity,
        VerbosityMask    = 0xf,
        SetColor         = 0x40,
        BreakOnLog       = 0x80,
    }
}

DefaultVerbose는 런타임 동안의 Verbosity(상세 정도)를, CompileTimeVerbose는 컴파일타임 동안의 Verbosity를 얼마나 확장하여 조사할 것인지를 기재하는 것이다.

우리의 UE5Practice 로그 타입은 런타임에선 에디터 로그 패널에 결과를 보여주는 Log 수준, 컴파일타임에선 파일에서 로그의 상세 정보를 확인할 수 있는 All 수준의 로깅으로 선택했다. 보통 위와 같은 상세 수준을 많이 적용하기 때문에 이 형태를 기억하는 것이 편하다.

// 모듈명.cpp

#include "UE5Practice.h"
#include "Modules/ModuleManager.h"

IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, UE5Practice, "UE5Practice" );

DEFINE_LOG_CATEGORY(UE5Practice); // +
 

헤더에 커스텀 로그 카테고리를 선언했으니 cpp에서 정의해주는 과정이라고 보면 된다. 해당 구문을 추가해주어야 실제 액터 등에서 새로운 로그 카테고리를 사용할 수 있다.

이후 해당 로그 카테고리를 사용할 액터의 헤더 파일로 이동하여 기존에 포함한 헤더 모음(CoreMinimal.h, EngineMinimal.h 등) 대신 모듈.h로 변경해주자.

// Fountain.h

#include "UE5Practice.h"
#include "GameFramework/Actor.h"
#include "Fountain.generated.h"

UCLASS()
class UE5PRACTICE_API AFountain : public AActor
{
	GENERATED_BODY()
	
public:	
	...

이때 모듈.h도 헤더 모음을 포함하기 때문에 동작에는 이상이 없다. 필자가 다루는 Fountain.h는 헤더 모음으로 EngineMinimal.h를 선택했기 때문에 모듈.h 내부의 헤더 모음을 CoreMinimal.h에서 EngineMinimal.h로 변경해줬던 것이다.

이후 다음과 같이 로그 카테고리를 이용할 수 있게 된다.

// Fountain.cpp

// Called when the game starts or when spawned
void AFountain::BeginPlay()
{
	Super::BeginPlay();
    
    UE_LOG(LogTemp, Log, TEXT("Test log");
	UE_LOG(UE5Practice, Warning, TEXT("Actor Name : %s, ID : %d, Location X : %.3f"), *GetName(), ID, GetActorLocation().X);
}

스크립트를 저장하고 에디터에서 컴파일 후 게임을 플레이하면 다음과 같은 로그를 확인할 수 있다.

로그 출력 구문을 Fountain 액터의 BeginPlay() 함수 구현부에 삽입했기 때문에 해당 액터가 반드시 레벨에 배치된 상태여야 하며, 게임 플레이 버튼을 눌렀을 때 BeginPlay()함수가 호출되어 로그가 출력된다.

중요한 것 중 하나는 UE_LOG 매크로에 매개변수를 사용하는 방법인데,

UE_LOG(UE5Practice, Warning, TEXT("Actor Name : %s, ID : %d, Location X : %.3f"), *GetName(), ID, GetActorLocation().X);

마치 printf() 함수를 사용하는 것처럼 서식 지정자를 활용해 문자열에 변수를 포함한다. 기본 서식 지정자와 동일한 형태이지만, 각 서식 지정자에 대응될 변수를 UE_LOG 매개변수로 전달한다는 것에 유의해야 하며, 문자열 변수에 포인터(*)를 포함해야 한다는 것을 반드시 숙지하자.

· 커스텀 로그 매크로

특정한 형태를 가진 로그 매크로를 직접 만들 수도 있다.

예를 들어 "코드가 들어있는 함수 이름과 라인 정보를 추가해 UE5Practice 카테고리로 로그를 남기는 로그"를 만들어 보자. 로그를 사용한 함수의 실행 시점을 파악할 때 유용하다. 모듈.h 파일로 이동하여 다음 구문을 추가한다.

// 모듈.h

#pragma once

#include "EngineMinimal.h"

DECLARE_LOG_CATEGORY_EXTERN(UE5Practice, Log, All);

#define ABLOG_CALLINFO (FString(__FUNCTION__) + TEXT("(") + FString::FromInt(__LINE__) + TEXT(")"))
#define ABLOG_S(Verbosity) UE_LOG(UE5Practice, Verbosity, TEXT("%s"), *ABLOG_CALLINFO)
#define ABLOG(Verbosity, Format, ...) UE_LOG(UE5Practice, Verbosity, TEXT("%s %s"), *ABLOG_CALLINFO, *FString::Printf(Format, ##__VA_ARGS__))

먼저 매크로의 형태를 잘 볼 필요가 있다. 매크로는 '긴 문자열을 짧은 문자열로 대체'하는 용도로 사용된다. 하지만 매개변수를 전달하여 호출하는 것도 가능하다. ABLOG_CALLINFO는 매개변수가 없이 단순히 문자열을 대체하는 매크로, 아래 두 개는 매개변수를 포함하여 로그를 출력하는 로그 매크로이다. 하나씩 살펴보자.

#define ABLOG_CALLINFO (FString(__FUNCTION__) + TEXT("(") + FString::FromInt(__LINE__) + TEXT(")"))

앞서 말한 것처럼 이 매크로는 로그 매크로가 아니며, 긴 문자열을 간략하게 표현하기 위한 용도이다.

ABLOG_CALLINFO라는 매크로가 대체되는 문자열은 해당 코드를 호출한 함수의 식별자(이름)와 코드를 호출한 위치가 소스 파일의 몇 번째 라인인지를 알려주는 것이다. __FUNCTION____LINE__이 각각 그 역할을 하며, C/C++에서 기본적으로 제공하는 표준 식별자 및 매크로다.

#define ABLOG_S(Verbosity) UE_LOG(UE5Practice, Verbosity, TEXT("%s"), *ABLOG_CALLINFO)

ABLOG_SUE5Practice 카테고리로 ABLOG_CALLINFO가 다루는 내용을 Verbosity라는 상세 수준으로 로그를 출력하는 매크로를 호출한다.

#define ABLOG(Verbosity, Format, ...) UE_LOG(UE5Practice, Verbosity, TEXT("%s %s"), *ABLOG_CALLINFO, *FString::Printf(Format, ##__VA_ARGS__))

ABLOGABLOG_S와 동일하지만 사용자가 원하는 문자열과 추가적인 인자를 함께 출력할 수 있다. 이 매크로들을 실제 스크립트에서 다음과 같이 사용할 수 있다.

// Fountain.cpp

...

void AFountain::BeginPlay()
{
	...
	ABLOG_S(Warning);
	ABLOG(Warning, TEXT("Actor Name : %s, ID : %d, Location X : %.3f"), *GetName(), ID, GetActorLocation().X);
}

그리고 Fountain 액터를 레벨에 배치하고 게임을 플레이하면 다음과 같은 로그를 확인할 수 있다.

로그를 호출하는 매크로가 BeginPlay() 함수의 49, 50번째 라인에 작성되었고, 이 정보가 정상적으로 로그에 출력되었다. 또한, 추가적인 문자열과 함께 출력도 잘 작동하였다.


· 참고

[Unreal Engine Document] 로그 상세 수준 목록
로그의 상세 수준 설명
안드로이드의 Log 목록
[Microsoft Document] 미리 정의된 식별자, 매크로

0개의 댓글