Using the Visual Logger

When using Unreal Engine, there are many different ways to help you understand what is going on in your game. To name a few:

Today we will cover the Visual Logger.

Overview

So what can the Visual Logger do?

  • Draw debug shapes in space while the game is running.
  • Print formatted debug text on the state of objects at a point in time (e.g. HP, inventory, selected AI behaviour).
  • Record and move through a recorded log after gameplay has stopped, seeing how the state of the game changed over time.
  • Save the recording to an external file, perfect for attaching to bug tickets.

Now let's compare it to other debugging related tools in Unreal:

System Text Shapes Written to log View recordings Interactive
Plain logs :heavy_check_mark: :x: :heavy_check_mark: :x: :x:
DrawDebug shapes :x: :heavy_check_mark: :x: :heavy_check_mark: :x:
Visual Logger :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: :x:
Gameplay Debugger :heavy_check_mark: :heavy_check_mark: :x: :x: :heavy_check_mark:

So we can see that the Visual Logger lives up to its name: it is great for creating a visual and spatial log of what happened during gameplay. It's perfect for debugging things that have a spatial element; movement, collisions, projectiles.

Opening the Visual Logger

To open the Visual Logger window:

  • Unreal 5.0 or newer: Tools > Debug > Visual Logger.
  • Unreal 4.27 or older: Windows > Developer Tools > Visual Logger.

At any point, before starting your game or during, click Start to begin recording. You may want to change Unreal's settings so that the log is cleared every time you press Play, in that case select Editor Preferences > Advanced - Visual Logger > Reset Data with New Session. With this you do not have to start and stop the Visual Logger every time you start or end a Play In Editor session.

The documentation for the Visual Logger is great for getting started, I thoroughly recommend reading it and then coming back here for advanced usage and tips on usage.

Actor Snapshot

Every time a shape, event or text is added to the visual logger for an actor, the visual logger can also get a snapshot of any of that actor's state that we might want to log. For example if we had an ABUIPlant actor, we might want to log its Height, its current number of Leaves, so we know what was happening to it when the log event happened.

We provide this snapshot by implementing the IVisualLoggerDebugSnapshotInterface interface, which consists of a single function void GrabDebugSnapshot(FVisualLogEntry* Snapshot) const.

The snapshot information is shown in the bottom-left of the Visual Logger. It's a hierarchy of nested categories and strings.

BUIPlant.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "VisualLogger/VisualLoggerDebugSnapshotInterface.h"
#include "BUIPlant.generated.h"

UCLASS()
class ABUIPlant : public AActor, public IVisualLoggerDebugSnapshotInterface
{
	GENERATED_BODY()
	// ...
#if ENABLE_VISUAL_LOG
    virtual void GrabDebugSnapshot(FVisualLogEntry* Snapshot) const override;
#endif
};

BUIPlant.cpp

#if ENABLE_VISUAL_LOG
void ABUIPlant::GrabDebugSnapshot(FVisualLogEntry* Snapshot) const
{
	IVisualLoggerDebugSnapshotInterface::GrabDebugSnapshot(Snapshot);
	const int32 CatIndex = Snapshot->Status.AddZeroed();
	FVisualLogStatusCategory& Category = Snapshot->Status[CatIndex];
	Category.Category = TEXT("Plant");
	Category.Add(TEXT("Height"), FString::Printf(TEXT("%4.2f"), Height));
	Category.Add(TEXT("Nutrients"), FString::Printf(TEXT("%4.2f"), Nutrients));
	Category.Add(TEXT("Leaves"), FString::Printf(TEXT("%d"), Leaves));

	Snapshot->AddText("This is some text", "Some category", ELogVerbosity::Verbose);
	Snapshot->AddText("More text", "Another category", ELogVerbosity::Warning);

	const float VisualScale = 50.0f;
	Snapshot->AddArrow(GetActorLocation(), GetActorLocation() + FVector::UpVector * Height * VisualScale, "Height",
	                   ELogVerbosity::Verbose, FColor::Green, "Plant height");

	// Subcategories are auto-closed whenever you select a new entry so they
	// kind of suck from a usability standpoint.
	FVisualLogStatusCategory SubCategory(TEXT("Sub category"));
	SubCategory.Add(TEXT("Some key"), "Some value");
	SubCategory.Add(TEXT("Another key"), "Some value");
	Category.AddChild(SubCategory);
}

Shapes Within Snapshots

It is also possible to set up visual logger shapes from within this GrabDebugSnapshot function, as well as by using UE_VLOG.

I think it's best to put these shape calls inside GrabDebugSnapshot if it's some information that you always want to know about the actor.

// These both do the same thing; draw an arrow

// Inside GrabDebugSnapshot
Snapshot->AddArrow(GetActorLocation(), GetActorLocation() + FVector::UpVector * Height, "Height", ELogVerbosity::Verbose, FColor::Green, "Plant height");

// Or inside tick or an event
UE_VLOG_ARROW(this, LogTemp, Verbose, GetActorLocation(), GetActorLocation() + FVector::UpVector * Height, FColor::Green, TEXT("Plant height"));

Sub-categories

It's possible to create sub categories but they are auto-closed whenever you select a new entry so it kind of sucks from a usability standpoint.

FVisualLogStatusCategory Category(TEXT("Sub category"));
AnotherPlaceableCategory.Add(TEXT("Some key"), "Some value");
AnotherPlaceableCategory.Add(TEXT("Another key"), "Some value");

Components

Implementing IVisualLoggerDebugSnapshotInterface on Actors causes them to automatically show up in the Visual Logger, but the same is not true for components. However I still like to implement the interface in components and then call them in this way from the actor:

#if ENABLE_VISUAL_LOG
void ABUIPlant::GrabDebugSnapshot(FVisualLogEntry* Snapshot) const
{
	for (UActorComponent* Comp : GetComponentsByInterface(UVisualLoggerDebugSnapshotInterface::StaticClass()))
	{
		IVisualLoggerDebugSnapshotInterface* Interface = Cast<IVisualLoggerDebugSnapshotInterface>(Comp);
		Interface->GrabDebugSnapshot(Snapshot);
	}
}

Then from within the component's GrabDebugSnapshot function, I can create new categories or add information to the last-added category:

#if ENABLE_VISUAL_LOG
void UBUIHealthComponent::GrabDebugSnapshot(FVisualLogEntry* Snapshot) const
{
	FVisualLogStatusCategory& Category = Snapshot->Status.Last();
	Category.Add(TEXT("Health"), FString::Printf(TEXT("%d/%d"), CurrentHealth, MaxHealth));
}

Text

UE_VLOG(this, LogTemp, Verbose, TEXT("Character hit zero HP"));

Text log entries are shown as piece of text associated with a single-frame. I would use them for making it clear when events happen if they are related to spatial logging. For example when a character hits zero HP and starts their death animation. Use UE_VLOG_UELOG to also write to the regular log.

UE_VLOG_UELOG(this, LogTemp, Verbose, TEXT("Ran out of nutrients"));

Shapes

The main purpose of the visual logger is to draw 3D shapes in the world, so it has support for a bunch of different shapes.

There's not much more to say other than check the official documentation for most of the functions, and check the source for all of the functions.

Events

The Visual Logger also has the concept of Events. They appear much as text does, as a single-frame entry. I can't really tell how they are treated differently to text, but they are used in a few places within the Unreal codebase.

DEFINE_VLOG_EVENT(EventTest, All, "Simple event for vlog tests")

const FName EventTag1 = TEXT("ATLAS_C_0");
const FName EventTag2 = TEXT("ATLAS_C_1");

UE_VLOG_EVENT_WITH_DATA(World, EventTest, EventTag1, EventTag2);

Histogram

The visual logger can also display two-dimensional charts that they call histograms. They are displayed in full-screen and seem to be useful for showing 2D data that changes over time. The graph seems to scale itself to fit all the data for that frame. I can't think of any particularly common use-cases

UE_VLOG_HISTOGRAM(GetOwner(), LogTemp, Verbose, "Location", "Some 2D info", FVector2D(20, 30));

Interestingly the Gameplay Abilities System has a call to it for visualizing attributes changing over time.

Conclusion

From what I've heard from people at Epic the future of the Visual Logger is a little in question, but I've found it incredibly useful for debugging collisions, movement and AI.

For a point of comparison, the Gameplay Debugger is under more active development and is more suited for multiplayer and realtime interactive debugging.

Posted: