I’m making a custom logging library, to work around the fact that UE force-disables logs in “Shipping” build config (I really don’t want to deal with building the engine from its source code). Problem: the IFileHandle::Write function only takes a uint8* byte sequence, and the length of that sequence, as its inputs. It’s up to me to convert my input FString into bytes and then to know how big the resulting buffer is.
I have searched online, and found only two approaches. The first is to use a fixed-size byte array, and just hope that messages never exceed the length of that array. This isn’t great, logs could get truncated. The second is to use the TCHAR_TO_ANSI macro and then blithely assume that the length of the FString you pass into the macro is the same as the length of the byte array it returns. This is awful, because that assumption is routinely invalid, you’ll end up writing the wrong number of bytes and corrupting your logfile.
UE’s own documentation on the macros specifically warns against using the macros this way, and suggests that you use the helper classes. Its example specifically mentions using FTCHARToANSI. Problem: that helper class does not exist as far as I can tell in my version of UE (5.3.2). FTCHARToWCHAR exists, FTCHARToUTF8 exists, etc etc but not the one that gives me a uint8*. I can of course reinterpret_cast them to force the type, but then I’m not confident that I’ll get the right sequence length out of the helper.
Any advice? Everything I’ve found online only deals with entire files at a time (e.g. “write this string to disk” or “real this file as a string”), which isn’t workable as a logging solution. I feel like I’m falling into an edge case that the UE string system doesn’t really properly account for.
Here’s what I’m using right now, but I have nearly zero confidence that the reinterpret_cast in the Write call will give me correct results.
void THLog::MessageInternal(const FString& message, const FString& prefix, int flushInterval) {
Init();
const auto formatted = UKismetMathLibrary::Now().ToFormattedString(TEXT("%Y_%m_%d_%H_%M_%S ")) + prefix + ": " + message + "\r\n";
FTCHARToUTF8 convert(formatted);
if (handle != nullptr) {
handle->Write(reinterpret_cast<const uint8*>(convert.Get()), convert.Length());
linesSinceLastFlush++;
if (linesSinceLastFlush > flushInterval) {
handle->Flush();
linesSinceLastFlush = 0;
}
}
else {
// This is pointless in builds, because the Shipping build config doesn't have UE logs anyway.
UE_LOG(LogTemp, Error, TEXT("pqr logfile handle went away!"));
}
}