Need to use third-party libraries that depend on spdlog
in your Unreal project? Here’s how to simulate the spdlog
API using Unreal’s native UE_LOG
system, to let you avoid adding extra dependencies while keeping your code clean.
We’ll create a logging utility class that matches the spdlog
interface, handling type conversions to FString
and formatting along the way.
Implementation
Here’s the code that bridges spdlog
and Unreal’s logging system:
#pragma once
#include "Containers/UnrealString.h"
#include "Internationalization/Text.h"
#include "Templates/EnableIf.h"
#include "Templates/IsIntegral.h"
#include "Templates/IsFloatingPoint.h"
#include "Logging/LogMacros.h"
#include <string>
DEFINE_LOG_CATEGORY_STATIC(LogPiperLibrary, Log, All);
/**
* Class to simulate the spdlog API in Unreal Engine
* spdlog is used in the original piper
*/
class spdlog
{
private:
// Template function for integral types
template <typename T, typename TEnableIf<TIsIntegral<T>::Value, bool>::Type = 0>
static FString ToFString(T value)
{
// Most integral types won't be larger than signed 64-bit integer
// TODO: Add support for larger integral types supported by UE (uint64)
return FString::Printf(TEXT("%lld"), static_cast<int64>(value));
}
// Template function for floating-point types
template <typename T, typename TEnableIf<TIsFloatingPoint<T>::Value, bool>::Type = 0>
static FString ToFString(T value)
{
return FString::Printf(TEXT("%f"), static_cast<double>(value));
}
// Specialize for FString
static FString ToFString(const FString& Value)
{
return Value;
}
// Specialize for std::string
static FString ToFString(const std::string& Value)
{
return StringCast<TCHAR>(Value.c_str(), Value.size()).Get();
}
// Convert the format string to UTF-16 for Unreal Engine
static FText ConvertFormatString(const char* FormatString)
{
// Convert the format string to FText
return FText::FromString(StringCast<TCHAR>(FormatString).Get());
}
// Convert a single argument to FFormatArgumentValue
template <typename T>
static FFormatArgumentValue ToFormatArgumentValue(const T& value)
{
return FFormatArgumentValue(FText::FromString(ToFString(value)));
}
// Specialize for FString
static FFormatArgumentValue ToFormatArgumentValue(const FString& value)
{
return FFormatArgumentValue(FText::FromString(value));
}
// Specialize for std::string
static FFormatArgumentValue ToFormatArgumentValue(const std::string& value)
{
return FFormatArgumentValue(FText::FromString(StringCast<TCHAR>(value.c_str(), value.size()).Get()));
}
/**
* Replace {} placeholders with {0}, {1}, etc
* This is necessary because FText::Format does not support {} placeholders
* @param FormatString The format string to convert
* @return The converted format string
*/
static FString ConvertBracedFormatString(const FString& FormatString)
{
FString ConvertedFormatString = FormatString;
int32 PlaceholderIndex = 0;
while (ConvertedFormatString.Contains(TEXT("{}")))
{
FStringBuilderBase Builder;
Builder = ConvertedFormatString;
Builder.ReplaceAt(ConvertedFormatString.Find(TEXT("{}")), 2,
FString::Printf(TEXT("{%d}"), PlaceholderIndex++));
ConvertedFormatString = Builder.ToString();
}
return ConvertedFormatString;
}
// Format log message with provided arguments
template <typename... Args>
static FString FormatLogMessage(const char* FormatString, Args... args)
{
// Convert the format string to FText and replace placeholders
FText FormatText = ConvertFormatString(FormatString);
FText ConvertedFormatText = FText::FromString(ConvertBracedFormatString(FormatText.ToString()));
// Create an array to hold the arguments for formatting
TArray<FFormatArgumentValue> FormatArgs;
(FormatArgs.Add(ToFormatArgumentValue(args)), ...);
// Use FText to format the string with arguments
FText FormattedText = FText::Format(ConvertedFormatText, FormatArgs);
return FormattedText.ToString();
}
public:
//~ Functions for simulating the spdlog API
template <typename... Args>
static void error(const char* FormatString, Args... args)
{
FString LogMessage = FormatLogMessage(FormatString, args...);
UE_LOG(LogPiperLibrary, Error, TEXT("%s"), *LogMessage);
}
template <typename... Args>
static void warn(const char* FormatString, Args... args)
{
FString LogMessage = FormatLogMessage(FormatString, args...);
UE_LOG(LogPiperLibrary, Warning, TEXT("%s"), *LogMessage);
}
template <typename... Args>
static void debug(const char* FormatString, Args... args)
{
FString LogMessage = FormatLogMessage(FormatString, args...);
UE_LOG(LogPiperLibrary, Log, TEXT("%s"), *LogMessage);
}
template <typename... Args>
static void info(const char* FormatString, Args... args)
{
FString LogMessage = FormatLogMessage(FormatString, args...);
UE_LOG(LogPiperLibrary, Log, TEXT("%s"), *LogMessage);
}
// @formatter:off
// Dummy functionality to simulate the spdlog API
struct level { constexpr static bool debug = false; };
static bool should_log(bool) { return true; }
// Add more functions as needed
// @formatter:on
};
Usage
The class lets you use familiar spdlog
calls like spdlog::info
, spdlog::debug
, spdlog::warn
, and spdlog::error
in your code. Behind the scenes, it routes everything through Unreal’s logging system, so you can avoid adding the spdlog
dependency to your project.