PART 10 - 강의 3/6
로깅 시스템
커스텀 로그 카테고리, UE_LOG 레벨별 사용법
01
UE_LOG 기초
언리얼 엔진의 기본 로깅 시스템
기본 사용법
Basic Logging
// 기본 형식
UE_LOG(LogCategory, Verbosity, TEXT("Format string"), Args...);
// 예제
UE_LOG(LogTemp, Log, TEXT("Simple message"));
UE_LOG(LogTemp, Warning, TEXT("Player health: %f"), Health);
UE_LOG(LogTemp, Error, TEXT("Failed to load: %s"), *AssetPath);
// 조건부 로깅
UE_CLOG(bCondition, LogTemp, Warning, TEXT("Condition is true!"));
로그 Verbosity 레벨
| Verbosity | 설명 | 색상 | 사용 시점 |
|---|---|---|---|
Fatal |
치명적 오류 (크래시 발생) | 빨강 | 복구 불가능한 오류 |
Error |
오류 | 빨강 | 기능 실패, 예외 상황 |
Warning |
경고 | 노랑 | 잠재적 문제, 비정상 상황 |
Display |
중요 정보 | 회색 | 항상 표시해야 할 정보 |
Log |
일반 로그 | 회색 | 일반적인 정보 |
Verbose |
상세 정보 | 회색 | 디버깅용 상세 정보 |
VeryVerbose |
매우 상세 | 회색 | 극단적 디버깅 |
Verbosity 표시 설정
기본적으로 Verbose 이하는 출력되지 않습니다. 콘솔에서 설정:
Log CategoryName Verbose
02
커스텀 로그 카테고리
프로젝트에 맞는 전용 로그 카테고리 생성
로그 카테고리 선언
MyGame.h
#pragma once
#include "CoreMinimal.h"
// 로그 카테고리 선언 (헤더에서)
// DECLARE_LOG_CATEGORY_EXTERN(CategoryName, DefaultVerbosity, CompileTimeVerbosity)
// 게임 전체 로그
DECLARE_LOG_CATEGORY_EXTERN(LogMyGame, Log, All);
// 시스템별 로그
DECLARE_LOG_CATEGORY_EXTERN(LogCombat, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogInventory, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogQuest, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogAI, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogNetwork, Log, All);
// 성능 프로파일링용 (기본 비활성)
DECLARE_LOG_CATEGORY_EXTERN(LogPerformance, Verbose, All);
로그 카테고리 정의
MyGame.cpp
#include "MyGame.h"
// 로그 카테고리 정의 (소스 파일에서)
DEFINE_LOG_CATEGORY(LogMyGame);
DEFINE_LOG_CATEGORY(LogCombat);
DEFINE_LOG_CATEGORY(LogInventory);
DEFINE_LOG_CATEGORY(LogQuest);
DEFINE_LOG_CATEGORY(LogAI);
DEFINE_LOG_CATEGORY(LogNetwork);
DEFINE_LOG_CATEGORY(LogPerformance);
사용 예제
Usage Examples
// 전투 시스템
void UCombatComponent::ApplyDamage(float Damage, AActor* Source)
{
UE_LOG(LogCombat, Log, TEXT("Damage applied: %.1f from %s"),
Damage, *GetNameSafe(Source));
if (Damage > MaxHealth)
{
UE_LOG(LogCombat, Warning,
TEXT("Damage %.1f exceeds MaxHealth %.1f"), Damage, MaxHealth);
}
CurrentHealth -= Damage;
if (CurrentHealth <= 0)
{
UE_LOG(LogCombat, Display, TEXT("%s has been defeated"),
*GetOwner()->GetName());
}
}
// 인벤토리 시스템
bool UInventoryComponent::AddItem(const FItemData& Item)
{
UE_LOG(LogInventory, Verbose, TEXT("Attempting to add item: %s x%d"),
*Item.ItemId.ToString(), Item.Quantity);
if (IsFull())
{
UE_LOG(LogInventory, Warning, TEXT("Inventory full, cannot add %s"),
*Item.ItemId.ToString());
return false;
}
Items.Add(Item);
UE_LOG(LogInventory, Log, TEXT("Added %s to inventory"),
*Item.ItemId.ToString());
return true;
}
// 네트워크
void AMyGameMode::OnPlayerJoined(APlayerController* PC)
{
UE_LOG(LogNetwork, Display, TEXT("Player joined: %s"),
*PC->GetPlayerState<APlayerState>()->GetPlayerName());
}
로그 카테고리 설계 가이드
- 시스템별 분리: Combat, Inventory, Quest 등 기능 단위로 분리
- 적절한 기본 레벨: 일반 로그는 Log, 상세 디버깅은 Verbose
- 일관된 네이밍: Log 접두사 + 시스템명
- 필터링 용이성: 콘솔에서 카테고리별 필터링 가능
03
로그 포맷팅과 고급 기능
효과적인 로그 메시지 작성법
포맷 스트링
Format Strings
// 기본 타입
UE_LOG(LogTemp, Log, TEXT("Int: %d"), IntValue);
UE_LOG(LogTemp, Log, TEXT("Float: %f (2dp: %.2f)"), FloatValue, FloatValue);
UE_LOG(LogTemp, Log, TEXT("Bool: %s"), bValue ? TEXT("true") : TEXT("false"));
// FString - * 연산자로 TCHAR* 변환
FString MyString = TEXT("Hello");
UE_LOG(LogTemp, Log, TEXT("String: %s"), *MyString);
// FName
FName MyName = FName(TEXT("ItemName"));
UE_LOG(LogTemp, Log, TEXT("Name: %s"), *MyName.ToString());
// FVector
FVector Location = FVector(100, 200, 300);
UE_LOG(LogTemp, Log, TEXT("Location: %s"), *Location.ToString());
// 객체 이름 (null 안전)
UE_LOG(LogTemp, Log, TEXT("Actor: %s"), *GetNameSafe(MyActor));
// 클래스 이름
UE_LOG(LogTemp, Log, TEXT("Class: %s"), *GetClass()->GetName());
// 전체 경로
UE_LOG(LogTemp, Log, TEXT("Full path: %s"), *GetPathName());
함수/파일 정보 포함
Context Information
// 함수명 포함
UE_LOG(LogMyGame, Log, TEXT("[%s] Message"), ANSI_TO_TCHAR(__FUNCTION__));
// 함수명 + 라인 번호
UE_LOG(LogMyGame, Log, TEXT("[%s:%d] Message"),
ANSI_TO_TCHAR(__FUNCTION__), __LINE__);
// 커스텀 매크로 정의
#define LOG_FUNC() \
UE_LOG(LogMyGame, Log, TEXT("[%s]"), ANSI_TO_TCHAR(__FUNCTION__))
#define LOG_WARN(Format, ...) \
UE_LOG(LogMyGame, Warning, TEXT("[%s] ") TEXT(Format), \
ANSI_TO_TCHAR(__FUNCTION__), ##__VA_ARGS__)
// 사용
void UMyClass::SomeFunction()
{
LOG_FUNC(); // "[UMyClass::SomeFunction]"
LOG_WARN("Value is %d", Value); // "[UMyClass::SomeFunction] Value is 42"
}
화면 출력 (디버그용)
On-Screen Debug
// GEngine 화면 메시지
if (GEngine)
{
// AddOnScreenDebugMessage(Key, Duration, Color, Message)
// Key: -1 = 새 메시지, 양수 = 같은 키 덮어쓰기
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::White,
TEXT("일반 메시지"));
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green,
FString::Printf(TEXT("Health: %.1f"), Health));
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red,
TEXT("경고!"));
// 같은 키로 업데이트되는 메시지 (HUD 느낌)
GEngine->AddOnScreenDebugMessage(1, 0.0f, FColor::Yellow,
FString::Printf(TEXT("FPS: %.1f"), 1.0f / DeltaTime));
}
// 커스텀 매크로
#define SCREEN_LOG(Format, ...) \
if (GEngine) { \
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Cyan, \
FString::Printf(TEXT(Format), ##__VA_ARGS__)); \
}
// 사용
SCREEN_LOG("Player took %d damage", DamageAmount);
04
로그 필터링과 출력 제어
콘솔 명령과 설정을 통한 로그 관리
콘솔 명령어
Console Commands
// 특정 카테고리의 Verbosity 변경
Log LogCombat Verbose // Verbose까지 출력
Log LogCombat Warning // Warning 이상만 출력
Log LogCombat off // 로그 비활성화
Log LogCombat break // Error에서 중단점
// 현재 로그 설정 확인
Log List
// 로그 파일 출력
Log LogCombat VeryVerbose OutputLogFile
// 모든 로그 필터링
Log * Warning // 모든 카테고리 Warning만
DefaultEngine.ini 설정
DefaultEngine.ini
[Core.Log]
; 전역 로그 레벨
Global=Warning
; 카테고리별 설정
LogCombat=Log
LogInventory=Log
LogQuest=Verbose
LogPerformance=off
; 개발 빌드에서만 상세 로그
#if !UE_BUILD_SHIPPING
LogAI=Verbose
LogNetwork=Verbose
#endif
코드에서 동적 제어
Dynamic Log Control
// 런타임에 로그 레벨 변경
void UMyDebugSettings::EnableVerboseLogging(bool bEnable)
{
if (bEnable)
{
UE_SET_LOG_VERBOSITY(LogCombat, Verbose);
UE_SET_LOG_VERBOSITY(LogInventory, Verbose);
}
else
{
UE_SET_LOG_VERBOSITY(LogCombat, Log);
UE_SET_LOG_VERBOSITY(LogInventory, Log);
}
}
// 콘솔 명령으로 노출
static FAutoConsoleCommand CmdEnableVerbose(
TEXT("MyGame.EnableVerboseLog"),
TEXT("Enable verbose logging for game systems"),
FConsoleCommandDelegate::CreateLambda([]()
{
UE_SET_LOG_VERBOSITY(LogCombat, Verbose);
UE_SET_LOG_VERBOSITY(LogInventory, Verbose);
UE_LOG(LogMyGame, Display, TEXT("Verbose logging enabled"));
})
);
// 조건부 컴파일
#if !UE_BUILD_SHIPPING
// 개발 빌드에서만 상세 로그
UE_LOG(LogPerformance, Verbose, TEXT("Frame time: %f ms"), DeltaTime * 1000);
#endif
05
로깅 베스트 프랙티스
효과적인 로그 작성 가이드라인
로그 레벨 선택 가이드
Fatal / Error
- 복구 불가능한 상태
- 필수 리소스 로드 실패
- 크리티컬 시스템 오류
Warning
- 예상치 못한 상황
- 성능 저하 가능성
- Fallback 사용 시
Display / Log
- 중요 이벤트 기록
- 상태 변화 추적
- 시스템 초기화/종료
Verbose
- 상세 디버깅 정보
- 루프 내 상태
- 성능 측정 데이터
좋은 로그 메시지 예시
Good vs Bad Logging
// 나쁜 예 - 정보 부족
UE_LOG(LogTemp, Error, TEXT("Failed"));
UE_LOG(LogTemp, Log, TEXT("Done"));
UE_LOG(LogTemp, Warning, TEXT("Problem"));
// 좋은 예 - 컨텍스트 포함
UE_LOG(LogInventory, Error,
TEXT("Failed to add item %s: inventory full (current: %d, max: %d)"),
*ItemId.ToString(), CurrentCount, MaxCapacity);
UE_LOG(LogQuest, Log,
TEXT("Quest '%s' completed by player %s in %.1f seconds"),
*QuestName, *PlayerName, CompletionTime);
UE_LOG(LogCombat, Warning,
TEXT("Damage calculation overflow: raw=%.0f, clamped to %.0f for %s"),
RawDamage, ClampedDamage, *GetNameSafe(Target));
// 구조화된 로깅
UE_LOG(LogAI, Verbose,
TEXT("[AI:%s] State: %s -> %s | Target: %s | Distance: %.1f"),
*GetOwner()->GetName(),
*UEnum::GetValueAsString(PreviousState),
*UEnum::GetValueAsString(CurrentState),
*GetNameSafe(CurrentTarget),
DistanceToTarget);
성능 고려사항
로깅 성능 주의사항
- Tick 함수 내 과도한 로깅은 성능 저하 유발
- FString::Printf 호출도 비용이 있음
- Shipping 빌드에서 Verbose 로그 제거 고려
Performance-Aware Logging
// 조건부 컴파일로 Shipping 빌드에서 제외
#if !UE_BUILD_SHIPPING
UE_LOG(LogPerformance, Verbose, TEXT("Tick: %f ms"), DeltaTime * 1000);
#endif
// 또는 CLOG로 조건부 실행
static int32 FrameCounter = 0;
UE_CLOG(++FrameCounter % 60 == 0, LogPerformance, Log,
TEXT("60 frames processed"));
// 로그 레벨 체크 후 문자열 생성
#if !NO_LOGGING
if (UE_LOG_ACTIVE(LogAI, Verbose))
{
// 비용이 큰 문자열 생성은 로그가 활성화될 때만
FString DetailedState = BuildDetailedStateString();
UE_LOG(LogAI, Verbose, TEXT("%s"), *DetailedState);
}
#endif
SUMMARY
핵심 요약
- DECLARE_LOG_CATEGORY_EXTERN과 DEFINE_LOG_CATEGORY로 커스텀 로그 카테고리 생성
- Verbosity 레벨: Fatal, Error, Warning, Display, Log, Verbose, VeryVerbose
- UE_CLOG로 조건부 로깅, GEngine->AddOnScreenDebugMessage로 화면 출력
- 콘솔 명령 Log CategoryName Level로 런타임 필터링
- 컨텍스트 정보(함수명, 객체명, 값)를 포함한 의미있는 로그 메시지 작성
다음 강의 예고
다음 강의에서는 Visual Logger를 사용하여 AI와 게임플레이를 시각적으로 디버깅하는 방법을 학습합니다.
PRACTICE
도전 과제
배운 내용을 직접 실습해보세요
실습 1: 커스텀 로그 카테고리 정의
DECLARE_LOG_CATEGORY_EXTERN과 DEFINE_LOG_CATEGORY로 LogRPGCombat, LogRPGInventory, LogRPGQuest 카테고리를 만드세요. UE_LOG에서 카테고리별로 로그 레벨(Log, Warning, Error)을 구분하세요.
실습 2: 조건부 로깅 시스템
UE_CLOG로 조건부 로깅을 구현하고, Shipping 빌드에서 로그가 제거되는 것을 확인하세요. FMsg::Logf, GEngine->AddOnScreenDebugMessage도 활용하세요.
심화 과제: 로그 파싱 및 분석 도구
Output Log의 로그를 파싱하여 에러/경고를 자동 분류하는 스크립트를 작성하세요. 또한 커스텀 로그 카테고리별 출력 파일을 분리하여 디버깅 효율을 높이세요.