Simulating spdlog in Unreal Engine
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. ...