Skip to content

Instantly share code, notes, and snippets.

@Solessfir
Last active September 8, 2025 19:58
Show Gist options
  • Save Solessfir/eb0df57297f8a61f0c598629b0a78865 to your computer and use it in GitHub Desktop.
Save Solessfir/eb0df57297f8a61f0c598629b0a78865 to your computer and use it in GitHub Desktop.
Single header UE5 Log library with automatic type deduction
/**
* Copyright (c) Solessfir under MIT license
*
* Usage:
*
* #define LOG_CATEGORY_NAME LogMyCustomCategory // Optional, defaults to EasyLog
* #include "EasyLog.h"
*
* LOG_DISPLAY("Actor name: {0}, expected {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);
*
* UENUM Support (fallback to numeric values for raw enums)
* LOG_WARNING("UENUM name is: {0}", UEnum::GetValueAsString(MyEnum));
*
* No need for GetName() or ToString(), etc. Type is deduced automatically.
*
* 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__)
// Extended versions with exposed Key and Duration
#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 for unified logging
#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)
// Call location Class::Function::Line
#define GET_LOG_LOCATION (FString(__FUNCTION__) + "::" + FString::FromInt(__LINE__))
// Hash for AddOnScreenDebugMessage()
#define UNIQUE_KEY_HASH static_cast<int32>((FCrc::MemCrc32(__FUNCTION__, sizeof(__FUNCTION__) - 1) ^ (static_cast<uint32>(__LINE__) << 15)) & 0x7FFFFFFF)
// Allow overriding log category
#ifndef LOG_CATEGORY_NAME
#define LOG_CATEGORY_NAME EasyLog
#endif
// Define log category once
DEFINE_LOG_CATEGORY_STATIC(LOG_CATEGORY_NAME, Log, All);
inline void FillArgs(FStringFormatOrderedArguments&) {}
template<typename F, typename... Rest>
void FillArgs(FStringFormatOrderedArguments& Args, F&& First, Rest&&... More)
{
if constexpr (requires { FStringFormatArg(Forward<F>(First)); })
{
Args.Add(Forward<F>(First));
}
else if constexpr (requires { First.ToString(); })
{
Args.Add(First.ToString());
}
else if constexpr (requires { First->GetActorNameOrLabel(); })
{
Args.Add(IsValid(First) ? First->GetActorNameOrLabel() : TEXT("None"));
}
else if constexpr (requires { First->GetName(); })
{
if constexpr (std::is_pointer_v<F> && std::is_base_of_v<UObject, std::remove_pointer_t<F>>)
Args.Add(IsValid(First) ? First->GetName() : TEXT("None"));
else
Args.Add(First ? First->GetName() : TEXT("None"));
}
else if constexpr (requires { LexToString(Forward<F>(First)); })
{
Args.Add(LexToString(Forward<F>(First)));
}
else if constexpr (std::is_enum_v<std::remove_cvref_t<F>>)
{
using Underlying = std::underlying_type_t<std::remove_cvref_t<F>>;
Args.Add(LexToString(static_cast<Underlying>(First)));
}
else
{
static_assert([]{return false;}(), "Unsupported type passed to FillArgs()");
}
FillArgs(Args, Forward<Rest>(More)...);
}
#else
// Disable logging in Shipping/Test
#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