L5 : capteur d'exceptions de script Blueprint (variables locales au moment de l'erreur)

Option -LPTraceScriptEx/-LPTraceLocals/-LPTraceMax, inactive par defaut.
Valide en -game (releve et reecriture des piles d'appel).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
edujiro5972 2026-09-29 17:04:53 +02:00
parent 0949db6c2c
commit 86c38a9f3a
3 changed files with 85 additions and 0 deletions

View file

@ -299,3 +299,5 @@ Build `D:\Game\LifePlay_Build_5.5_L5a` (instrumentée) : éditeur et jeu compil
Suite : session 4.27 jouant les mêmes scènes de quête, puis comparaison normalisée.
Note de traçabilité : la sauvegarde `L5-instr` de `LPSessionLogSubsystem.*` a été prise après modification ; la version antérieure est dans git (`0baf4cf`).
| CHG-028 | 2026-09-29 | L5 | `Source/LifePlay/LPSessionLogSubsystem.h/.cpp` | capteur d'exceptions de script (-LPTraceScriptEx, -LPTraceLocals, -LPTraceMax) : relève les variables locales du cadre Blueprint au moment de l'erreur ; actif seulement avec l'option (le moteur supprime sa pile d'appel quand un écouteur est branché : elle est réécrite). Validé en -game. | `Tools\Restore.ps1 -Lot L5-trace` |

View file

@ -11,6 +11,9 @@
#include "Misc/CommandLine.h"
#include "Misc/Parse.h"
#include "UObject/UnrealType.h"
#include "UObject/Script.h"
#include "UObject/Stack.h"
#include "Blueprint/BlueprintExceptionInfo.h"
#include "Misc/EngineVersion.h"
#include "Misc/OutputDevice.h"
#include "Misc/Paths.h"
@ -129,6 +132,70 @@ void ULPSessionLogSubsystem::Initialize(FSubsystemCollectionBase& Collection)
LogStartupSummary();
DumpRequestedVariables(TEXT("demarrage"));
StartScriptExceptionTrace();
}
void ULPSessionLogSubsystem::StartScriptExceptionTrace()
{
FString Filter;
if (!FParse::Value(FCommandLine::Get(), TEXT("LPTraceScriptEx="), Filter, false) || Filter.IsEmpty())
{
return;
}
FString LocalsList;
FParse::Value(FCommandLine::Get(), TEXT("LPTraceLocals="), LocalsList, false);
TArray<FString> Keywords;
LocalsList.ParseIntoArray(Keywords, TEXT(","), true);
int32 Max = 3;
FParse::Value(FCommandLine::Get(), TEXT("LPTraceMax="), Max);
UE_LOG(LogLifePlay, Display, TEXT("Trace des exceptions de script : filtre '%s', variables [%s], %d releve(s) au plus"), *Filter, *LocalsList, Max);
TSharedRef<int32> Count = MakeShared<int32>(0);
ScriptExceptionHandle = FBlueprintCoreDelegates::OnScriptException.AddLambda(
[Filter, Keywords, Max, Count](const UObject* ActiveObject, const FFrame& StackFrame, const FBlueprintExceptionInfo& Info)
{
// Brancher un ecouteur supprime la pile d'appel que le moteur ecrit d'ordinaire :
// on la reecrit pour toutes les exceptions, pas seulement celles relevees.
const FString StackTrace = StackFrame.GetStackTrace();
UE_LOG(LogScript, Warning, TEXT("%s"), *StackTrace);
const FString Description = Info.GetDescription().ToString();
if (*Count >= Max || !Description.Contains(Filter) || !StackFrame.Node || !StackFrame.Locals)
{
return;
}
++*Count;
UE_LOG(LogLifePlay, Warning, TEXT("[trace %d/%d] %s | objet %s | fonction %s | offset %d"),
*Count, Max, *Description, *GetNameSafe(ActiveObject), *StackFrame.Node->GetName(),
StackFrame.Code && StackFrame.Node->Script.Num() ? (int32)(StackFrame.Code - StackFrame.Node->Script.GetData()) : -1);
int32 Shown = 0;
for (TFieldIterator<FProperty> It(StackFrame.Node); It && Shown < 200; ++It)
{
const FProperty* Prop = *It;
FString TypeName = Prop->GetCPPType();
if (const FStructProperty* SP = CastField<FStructProperty>(Prop)) { TypeName = SP->Struct->GetName(); }
if (const FArrayProperty* AP = CastField<FArrayProperty>(Prop))
{
if (const FStructProperty* ISP = CastField<FStructProperty>(AP->Inner)) { TypeName = TEXT("TArray<") + ISP->Struct->GetName() + TEXT(">"); }
}
const FString PropName = Prop->GetName();
const bool bMatch = Keywords.ContainsByPredicate([&](const FString& K)
{ return PropName.Contains(K) || TypeName.Contains(K); });
if (!bMatch)
{
continue;
}
FString Value;
Prop->ExportTextItem_Direct(Value, Prop->ContainerPtrToValuePtr<void>(StackFrame.Locals), nullptr, nullptr, PPF_None);
UE_LOG(LogLifePlay, Warning, TEXT("[trace %d] %s (%s) = %s"), *Count, *PropName, *TypeName, *Value.Left(500));
++Shown;
}
});
}
void ULPSessionLogSubsystem::DumpRequestedVariables(const TCHAR* When) const
@ -178,6 +245,11 @@ void ULPSessionLogSubsystem::DumpRequestedVariables(const TCHAR* When) const
void ULPSessionLogSubsystem::Deinitialize()
{
DumpRequestedVariables(TEXT("fin"));
if (ScriptExceptionHandle.IsValid())
{
FBlueprintCoreDelegates::OnScriptException.Remove(ScriptExceptionHandle);
ScriptExceptionHandle.Reset();
}
LogSessionSummary();
if (Counter.IsValid())

View file

@ -38,6 +38,17 @@ private:
*/
void DumpRequestedVariables(const TCHAR* When) const;
/**
* Diagnostic (lot L5) : avec -LPTraceScriptEx=<texte> -LPTraceLocals=<mot1,mot2>, a chaque
* exception de script Blueprint dont la description contient <texte> (3 premieres
* seulement, -LPTraceMax=N pour changer), ecrit la pile d'appel et les variables locales
* du cadre dont le nom ou le type de structure contient l'un des mots.
* Sans l'option, rien n'est branche : le moteur garde son comportement de journal normal.
*/
void StartScriptExceptionTrace();
FDelegateHandle ScriptExceptionHandle;
TSharedPtr<FLPLogCounter> Counter;
double StartTime = 0.0;
};