Integrating third-party libraries that rely on spdlog
in Unreal Engine can pose challenges without directly adding that dependency. This short guide provides steps to simulate the spdlog
API using Unreal’s native logging system (UE_LOG
), which ensures compatibility while avoiding unnecessary dependencies.
To create a logging utility that mimics spdlog
, we can create a class with methods for logging at various severity levels. This class will convert different types to FString
for logging and formatting.
Implementation
Here’s the code snippet that simulates the spdlog
API in Unreal Engine:
#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
This class allows you and your libraries to call spdlog::info
, spdlog::debug
, spdlog::warn
, and spdlog::error
with the same syntax as spdlog
. The messages will be logged using Unreal Engine’s native logging system, which will ensure compatibility with Unreal Engine projects.