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.