Last active
April 12, 2025 22:29
-
-
Save Solessfir/eb0df57297f8a61f0c598629b0a78865 to your computer and use it in GitHub Desktop.
Single header UE5 Log library with automatic type deduction
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright (c) Solessfir under MIT license | |
* | |
* Usage: | |
* | |
* #define LOG_CATEGORY_NAME LogMyCustomCategory // Optional, defaulted to EasyLog if not defined | |
* #include "EasyLog.h" | |
* | |
* LOG_DISPLAY("This Actor name is: {0}, expecting {1}", this, "Foo"); | |
* LOG_WARNING("Vector is: {0}", FVector(1.f, 2.f, 3.f)); | |
* LOG_ERROR("Error with object: {0}", SomeObject); // "None" if nullptr | |
* LOG_WARNING_EX(-1, 10.f, "Updating value: {0}", SomeValue); | |
* | |
* No need for GetName() or ToString(), etc. Type will be automatically deduced. | |
* | |
* Reference: | |
* Laura's Unreal Blog | |
* https://landelare.github.io/2022/04/28/better-ue_log.html | |
* https://github.com/landelare/llog | |
*/ | |
#pragma once | |
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) | |
// Public logging macros | |
#define LOG_DISPLAY(Format, ...) INTERNAL_LOG(Display, UNIQUE_KEY_HASH, 10.f, TEXT(Format), ##__VA_ARGS__) | |
#define LOG_WARNING(Format, ...) INTERNAL_LOG(Warning, UNIQUE_KEY_HASH, 10.f, TEXT(Format), ##__VA_ARGS__) | |
#define LOG_ERROR(Format, ...) INTERNAL_LOG(Error, UNIQUE_KEY_HASH, 10.f, TEXT(Format), ##__VA_ARGS__) | |
#define LOG_DISPLAY_EX(Key, Duration, Format, ...) INTERNAL_LOG(Display, Key, Duration, TEXT(Format), ##__VA_ARGS__) | |
#define LOG_WARNING_EX(Key, Duration, Format, ...) INTERNAL_LOG(Warning, Key, Duration, TEXT(Format), ##__VA_ARGS__) | |
#define LOG_ERROR_EX(Key, Duration, Format, ...) INTERNAL_LOG(Error, Key, Duration, TEXT(Format), ##__VA_ARGS__) | |
// Internal macro to handle logging logic | |
#define INTERNAL_LOG(Verbosity, Key, Duration, Fmt, ...) \ | |
do { \ | |
FStringFormatOrderedArguments OrderedArguments; \ | |
FillArgs(OrderedArguments, __VA_ARGS__); \ | |
const FString Message = FString::Format(Fmt, MoveTemp(OrderedArguments)); \ | |
const FString FullMessage = FString::Printf(TEXT("%s | %s"), *Message, *GET_LOG_LOCATION); \ | |
if (GEngine && GAreScreenMessagesEnabled && Duration > 0.f) { \ | |
FColor Color = ELogVerbosity::Verbosity == ELogVerbosity::Error ? FColor::Red : \ | |
ELogVerbosity::Verbosity == ELogVerbosity::Warning ? FColor::Orange : FColor::White; \ | |
GEngine->AddOnScreenDebugMessage(Key, Duration, Color, FullMessage); \ | |
} \ | |
UE_LOG(LOG_CATEGORY_NAME, Verbosity, TEXT("%s"), *FullMessage); \ | |
} while (false) | |
// Log call location Class::Function::Line | |
#define GET_LOG_LOCATION (FString(__FUNCTION__) + "::" + FString::FromInt(__LINE__)) | |
// Generate unique hash based on the function name and line for AddOnScreenDebugMessage() | |
#define UNIQUE_KEY_HASH static_cast<int32>((FCrc::MemCrc32(__FUNCTION__, sizeof(__FUNCTION__) - 1) ^ (static_cast<uint32>(__LINE__) << 15)) & 0x7FFFFFFF) | |
// Allows to pass LOG_CATEGORY_NAME to DEFINE_LOG_CATEGORY_STATIC | |
#define INTERNAL_LOG_CATEGORY_DEFINE(CategoryName) DEFINE_LOG_CATEGORY_STATIC(CategoryName, Log, All) | |
#ifndef LOG_CATEGORY_NAME | |
#define LOG_CATEGORY_NAME EasyLog | |
#endif | |
// Define Log Category | |
INTERNAL_LOG_CATEGORY_DEFINE(LOG_CATEGORY_NAME); | |
// Type traits for automatic type deduction | |
template<typename T, typename = int32> | |
struct THasToString : std::false_type {}; | |
template<typename T> | |
struct THasToString<T, decltype(std::declval<T>().ToString(), 0)> : std::true_type {}; | |
template<typename T, typename = int32> | |
struct THasActorNameOrLabel : std::false_type {}; | |
template<typename T> | |
struct THasActorNameOrLabel<T, decltype(std::declval<T>()->GetActorNameOrLabel(), 0)> : std::true_type {}; | |
template<typename T, typename = int32> | |
struct THasGetName : std::false_type {}; | |
template<typename T> | |
struct THasGetName<T, decltype(std::declval<T>()->GetName(), 0)> : std::true_type {}; | |
template<typename T, typename = int32> | |
struct THasLexToString : std::false_type {}; | |
template<typename T> | |
struct THasLexToString<T, decltype(LexToString(std::declval<T>()), 0)> : std::true_type {}; | |
template<typename T> | |
struct TIsUObjectPointer : std::false_type {}; | |
template<typename T> | |
struct TIsUObjectPointer<T*> : std::is_base_of<UObject, T> {}; | |
template<typename... T> | |
void FillArgs(FStringFormatOrderedArguments& Arguments, T&&... Parameters) | |
{ | |
([&]<typename F>(F&& Argument) | |
{ | |
if constexpr (std::is_constructible_v<FStringFormatArg, F>) | |
{ | |
Arguments.Add(Forward<F>(Argument)); | |
} | |
else if constexpr (THasToString<F>::value) | |
{ | |
Arguments.Add(Argument.ToString()); | |
} | |
else if constexpr (THasActorNameOrLabel<F>::value) | |
{ | |
Arguments.Add(IsValid(Argument) ? Argument->GetActorNameOrLabel() : TEXT("None")); | |
} | |
else if constexpr (THasGetName<F>::value) | |
{ | |
if constexpr (TIsUObjectPointer<F>::value) | |
{ | |
Arguments.Add(IsValid(Argument) ? Argument->GetName() : TEXT("None")); | |
} | |
else | |
{ | |
Arguments.Add(Argument ? Argument->GetName() : TEXT("None")); | |
} | |
} | |
else if constexpr (THasLexToString<F>::value) | |
{ | |
Arguments.Add(LexToString(Forward<F>(Argument))); | |
} | |
else | |
{ | |
static_assert(std::is_void_v<F> && false, "Unsupported type"); | |
} | |
}(Forward<T>(Parameters)), ...); | |
} | |
#else | |
// Disable all logging in Shipping builds | |
#define LOG_DISPLAY(Format, ...) | |
#define LOG_WARNING(Format, ...) | |
#define LOG_ERROR(Format, ...) | |
#define LOG_DISPLAY_EX(Key, Duration, Format, ...) | |
#define LOG_WARNING_EX(Key, Duration, Format, ...) | |
#define LOG_ERROR_EX(Key, Duration, Format, ...) | |
#define UNIQUE_KEY_HASH | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment