Code Snippet: Custom message in the "Play In Editor" Message Log

Heya,

Didn’t find any posts on this so I thought I’d share this nice little tidbit. Often times I would have my gameplay code spit out a warning or error that I don’t see right away because it happens right at the start and subsequently gets buried in all the startup log spam you get whenever you press the play button.

To that end I was looking for a way to display the “There have been errors during play” toast message that pops up when you have errors in your Blueprints (or rather, when there are error messages in the PIE category of the Message Log). I eventually found the code snippet below, it works like a charm:




#include "MessageLog.h"
#include "TokenizedMessage.h"

...

FFormatNamedArguments Arguments;
Arguments.Add(TEXT("SomeArgument"), 42);
FMessageLog("PIE").Error()
	->AddToken(FTextToken::Create(FText::Format(LOCTEXT("Test_Error", "Something went wrong: {SomeArgument}"), Arguments)));

Obviously you don’t need the arguments, they’re there just for demonstration purposes. The downside of this is that it’s localized text, and localizing all your code errors seems like a major overkill for me. However, it does produce the toast notification when you finish playing.

The most optimal solution obviously is if we could have PIE produce the error toast if there are ANY errors, including code UE_LOG messages marked with the Error verbosity. But that’s just wishful thinking now…

Nice, I think @Allar was looking for this some time ago.

I’ve tried looking around the code to see what engine changes would be necessary to facilitate the toast popping up even with UE_LOG messages marked with Warnings / Errors. Unfortunately it’s not quite easy, as the toast is intrinsically linked with the Message Log (Which is localized), so it’s either that or nothing.

This is awesome! :slight_smile:

Now I can track function calls and function exceptions from PIE sessions in my plugin code:

.H:



#pragma once

#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "UObject/WeakObjectPtr.h" 
#include "UFSM_Log.generated.h"


DECLARE_LOG_CATEGORY_EXTERN(UFSM,Log,All);


#define FFIND(Name) FindFunction(Name)

#define PIE_Warning(Message,Token) FMessageLog("PIE").Warning(FText::FromString(FString::Printf(TEXT("{FSM}:: %s --> "),*Message)))->AddToken(Token)
#define PIE_Error(Message,Token) FMessageLog("PIE").Error(FText::FromString(FString::Printf(TEXT("{FSM}:: %s --> "),*Message)))->AddToken(Token)
#define PIE_Critical(Token) FMessageLog("PIE").Message(EMessageSeverity::CriticalError)->AddToken(Token)
#define PIE_Message(Token) FMessageLog("PIE").Message(EMessageSeverity::Info)->AddToken(Token);


UENUM()
enum class ESeverity : uint8 {
	CriticalError		= 0,
	Error				= 1,
	Warning				= 2,
	Info				= 3
};


UFSM_API void LOG_FSM(const bool Debug, const bool Logs, const float Duration, const FColor Color, const FString Message);
UFSM_API void LOG_PIE(const bool Debug, const ESeverity Severity, const UObject* Owner, const UFunction* Function, const FString Message = TEXT(""));
UFSM_API void LOG_PIE(const ESeverity Severity, const UObject* Owner, const UFunction* Function, const FString Message = TEXT(""));
UFSM_API void LOG_PIE(const UObject* Owner, const UFunction* Function, const FString Message = TEXT(""));
UFSM_API void LOG_PIE(const UFunction* Function, const UObject* Owner);
UFSM_API void LOG_PIE(const UFunction* Function);


.CPP:



#if WITH_EDITOR
	#include "MessageLog.h"
	#include "TokenizedMessage.h"
#endif


DEFINE_LOG_CATEGORY(UFSM);


#define LOCTEXT_NAMESPACE "MyEditorStuff"


void LOG_PIE(const bool Debug, const ESeverity Severity, const UObject* Owner, const UFunction* Function, const FString Message) {
	if (!Debug)  {return;}
	if (!Function)  {return;}
	if (!Owner)  {return;}
	//
#if WITH_EDITOR
	FFormatNamedArguments ARG;
	FFormatArgumentValue AFunction = Function->GetDisplayNameText();
	FFormatArgumentValue AInfo = FText::FromString(Function->GetFullName());
	FFormatArgumentValue APackage = FText::FromString(Owner->GetFullName());
	//
	ARG.Add(TEXT("Function"),AFunction);
	ARG.Add(TEXT("Package"),APackage);
	ARG.Add(TEXT("Details"),AInfo);
	//
	const auto Token = FTextToken::Create(FText::Format(LOCTEXT("LOG_PIE","{Function}: {Details} at ({Package})]"),ARG));
	//
	switch (Severity) {
		case ESeverity::Info:
			PIE_Message(Token); break;
		case ESeverity::Warning:
			PIE_Warning(Message,Token); break;
		case ESeverity::Error:
			PIE_Error(Message,Token); break;
		case ESeverity::CriticalError:
			PIE_Critical(Token); break;
	default: break;}
#endif
}

UFSM_API void LOG_PIE(const ESeverity Severity, const UObject* Owner, const UFunction* Function, const FString Message) {
	LOG_PIE(true,Severity,Owner,Function,Message);
}

UFSM_API void LOG_PIE(const UObject* Owner, const UFunction* Function, const FString Message) {
	LOG_PIE(ESeverity::Warning,Owner,Function,Message);
}

UFSM_API void LOG_PIE(const UFunction* Function, const UObject* Owner) {
	LOG_PIE(Owner,Function,FString(TEXT("Something went wrong!")));
}

UFSM_API void LOG_PIE(const UFunction* Function) {
	LOG_PIE(ESeverity::Info,Function,Function,TEXT(""));
}


#undef LOCTEXT_NAMESPACE
#undef PIE_Message
#undef PIE_Warning
#undef PIE_Critical
#undef PIE_Error
#undef FFIND


Calling from within a Game function; it identifies the calling function using its “Display Name” metadata, like the screenshot above:



void UStateMachineComponent::Client_SetState_Implementation(const FName Name, const bool Validate) {

	//(...) // do stuff...
	
	LOG_PIE(FFIND(TEXT("Client_SetState")));
	LOG_PIE(FFIND(TEXT("Client_SetState")),GetOwner());
	LOG_PIE(GetOwner(),FFIND(TEXT("Client_SetState")),TEXT("Message from Game Module!"));
	LOG_PIE(ESeverity::Warning,GetOwner(),FFIND(TEXT("Client_SetState")),TEXT("Warning from Game Module!"));
	LOG_PIE(Debug,ESeverity::Error,GetOwner(),FFIND(TEXT("Client_SetState")),TEXT("Error from Game Module!"));
	//LOG_PIE(Debug,ESeverity::**CriticalError**,GetOwner(),FFIND(TEXT("Client_SetState")),TEXT("Oops!"));  <-- This will crash the Editor and launch Bug Report window with traces up to where it locates and displays the 'Token' created.

}


Oh man, neat macros! This will definitely come in useful. I really need that blurb for any code that the non-programmers (like e.g. level designers) will have to interact with that has to point them to potential errors they might do (or forget to do).

That’s really neat! Thank you for sharing this snippet, I’m adding this to my codebase as I was searching for something similar…

[MENTION=434]BrUnO XaVIeR[/MENTION]

We’ve been duped this whole time!

Yeah that is quite the same, but not exactly the same ‘liberty’ I was looking for lol