[UE5] Debugging with Print String and the Output Log

Created: 2025-12-12Last updated: 2026-09-05

An introduction to confirming flow and variable values in UE5. Learn Print String and the Output Log through a small hands-on investigation into why HP isn't dropping, plus Key-based overwriting and C++'s UE_LOG.

The attack looks like it landed, but the enemy's HP does not drop. In that situation, appearance alone cannot tell you whether "the damage logic was never called" or "it was called but treated as invincible."

So we print short messages along the way. "Got this far." "Damage is 25." "Invincible, so skipping." Once what happened internally is visible, you can narrow down where to look next. Investigating behavior and fixing problems like this is debugging, and the records you leave as clues are logs.

In this article we try printing messages with Blueprint's Print String and reading them back in the editor's Output Log. The hands-on needs only one Actor placed, so you can proceed even without weapon or enemy systems.

A blue figure following footprints with a magnifying glass and arriving at a log screen

What You'll Learn

  • Confirming that logic was reached and what a variable holds with Print String
  • Finding your own messages in the Output Log
  • Narrowing down causes by "was it called, what was the value, which way did it go"
  • Choosing between Key for repeated display and C++ log output

Sponsored

Print String is a node that outputs a specified string. A string is a run of characters such as Hello or HP: 100. You can choose to show it in the top-left of the screen, record it in the Output Log, or both.

Print String for quick on-screen checks, and the Output Log for careful investigation of records

Let's build the small check Actor we also use in the hands-on. An Actor is something you can place in a level. Since this one exists to print messages, we give it no visual model.

  1. Create a "Blueprint Class" in the content browser with "Actor" as parent class and name it BP_DebugEnemy.
  2. Open it and go to the "Event Graph." That is the workspace where you connect nodes representing logic.
  3. Drag from the white exec pin on the right of Event BeginPlay, search Print String, and add it. BeginPlay is the event called when this Actor starts running in the game.
  4. Enter [DebugEnemy] Start in Print String's "In String." From the expand arrow at the bottom of the node, turn on "Print to Screen" and "Print to Log" and set "Duration" to 5.0.
  5. Press "Compile" and "Save," then drag exactly one BP_DebugEnemy from the content browser into the level. Save the level too and press "Play."

[DebugEnemy] Start in the top-left means success. [DebugEnemy] is a marker you added yourself, used later to search for this experiment's logs. Since the Actor has no model, no enemy appearing on screen is fine.

White lines represent the order in which logic runs. Merely placing Print String does nothing; a white line from BeginPlay must be connected.

SettingIts role here
In StringThe string to print
Print to ScreenDisplay it in the game view
Print to LogEmit it to the Output Log as a normal log
Text ColorThe color of the on-screen text
DurationSeconds it stays on screen. 5.0 means 5 seconds
KeyA name for updating the same on-screen message. Left None here

Read vanished messages back in the Output Log

Print String's on-screen display disappears over time. When you missed it, or want to read several operations in order, open the Output Log. It is the screen where you can see your own messages plus warnings and errors UE emitted.

On-screen text disappears in seconds, but records sent to the Output Log can be searched later
  1. Stop the game with "Stop" and open "Window → Output Log."
  2. Enter DebugEnemy in the search box.
  3. Find [DebugEnemy] Start. Reading back text that vanished on screen means success.

If you cannot find it, check whether Print String's "Print to Log" is on and whether the Output Log's filter is narrowed to errors and warnings only. Even when a log appears absent, it may just be hidden by a filter.

The LogTemp and similar at the start of a log line is the category, the name separating "which kind of logic emitted this record." Rather than memorizing categories early on, searching from your own marker as we did here is easier.

Sponsored

Hands-On: investigating why HP isn't dropping

Next we add logic to BP_DebugEnemy that subtracts 25 from HP. But we deliberately turn "invincible" on at first. Read the log, confirm why HP is not dropping, then fix it.

A scene where the bullet appears to hit but HP does not drop. Appearance alone cannot tell you where the logic stopped

The diagram shows the in-game situation. In this exercise we build no attack or collision and run the damage logic once from BeginPlay. That lets us focus on preparing to investigate the damage calculation.

Preparation: three values to investigate

Stop Play and create the following variables from the "+" in BP_DebugEnemy's "My Blueprint → Variables." Variables remember values such as HP. After "Compile," set each variable's initial value in "Default Value."

VariableTypeDefaultMeaning
HealthFloat100.0Current HP
IncomingDamageFloat25.0The damage received this time
bIsInvincibleBooleantrue (checked)true when invincible, false to take damage

Float is a number that can hold decimals, and Boolean is a type holding only true / false. Here we represent "am I invincible" with a Boolean. The leading b in the variable name is a marker showing it is a Boolean; it has no special behavior.

The three things to investigate in order: was it called, what was the value, which way did it go

1. Was it called this far, and what is the value

The [DebugEnemy] Start you made first shows the logic arrived from BeginPlay. Add a Print String after it that prints the damage amount.

Displaying Start and Damage in order from BeginPlay, converting IncomingDamage to a string and passing it to Append's B

For readability, the diagram draws value lines uniformly in blue. In the actual editor, colors vary by value type. "Next: Branch" points to the next diagram; it is not a node to add.

  1. Connect from the first Print String's white output pin to a second Print String.
  2. Drag IncomingDamage into the graph and choose "Get." Get reads the value stored in a variable.
  3. Place an Append node that joins strings and enter [DebugEnemy] Damage: in A.
  4. Connect IncomingDamage's output to Append's B. A node converting the number to a string is inserted automatically. Connect Append's "Return Value" to the second Print String's "In String."

Return Value is the result that node produced. Here it holds [DebugEnemy] Damage: 25, combining text and a number. Decimal display depends on the conversion settings, so 25 and 25.0 are the same value.

Turn on screen and log output on the second one too, set Duration to 5.0, Compile, Save, and Play. Search DebugEnemy in the Output Log and confirm the Start and Damage lines in order. So far you know "the logic started and the damage value was 25."

2. Which condition did it take

Stop Play and place a Branch after the second Print String. Branch is a node that splits where logic goes based on a condition. Connect a Get of bIsInvincible to "Condition."

Since we ask "am I invincible" directly, the True side skips and the False side applies damage.

Passing bIsInvincible to Branch's Condition, showing skip text on True and proceeding to Set Health on False
  • True side: connect to a Print String with In String [DebugEnemy] Skip: Invincible.
  • False side: connect to Set Health. Drag the Health variable into the graph and choose "Set" to place it. Set writes a new value into a variable.

Build the value fed into Set Health with a Float subtraction node. Drag from Health's Get and add Subtract (-), with Health on the top input and IncomingDamage on the bottom. Connect the result to Set Health's value input.

Passing Health minus IncomingDamage into Set Health. The exec line comes from Branch's False

Finally add a Print String from Set Health's white output. Place an Append as before with A set to [DebugEnemy] HP: and B connected to Set Health's value output. Insert the number-to-string conversion and pass Append's Return Value to Print String's In String. That displays HP after the write. Turn on screen and log output and set Duration to 5.0 on this Print String too.

After Set Health runs, converting the written value to a string, labeling it HP, and displaying it

The Set Health in the diagram is the same node as in the previous one. There is no need to place another.

3. Compare the logs and fix exactly one thing

Compile, Save, and Play, and at first these three lines appear. New lines may stack on top on screen, so confirm the order in the Output Log. Logs from previous Plays remain too, so look at this run's lines at the end.

[DebugEnemy] Start
[DebugEnemy] Damage: 25
[DebugEnemy] Skip: Invincible

The damage amount was 25, but it took the invincible branch. HP did not drop this time because it skipped before the calculation.

Stop Play and change the Blueprint's bIsInvincible default to false (unchecked). Compile, Save, and Play again, and instead of Skip you get [DebugEnemy] HP: 75. HP with 25 subtracted from 100 was actually written.

Next, stop Play and change only IncomingDamage's default to 40.0. Play again and Damage should be 40 with an updated HP of 60. Since you restart Play each time, Health starts from its default of 100.

Investigate your own attack logic in the same order

In a real game, put a Start-equivalent log at the head of the logic that runs when damage is taken. If you use UE's damage feature, the entry point is Event AnyDamage. Hit, which reports collisions, and Overlap, which reports overlaps, have different roles, so match your own logic's entry point (→ building health and damage).

What you confirmedWhere to look next
The first log doesn't appearDisplay settings, log filters, whether the target Actor exists, the connections to the event
Logic arrived but damage is 0The side passing damage, and where the value is read
The value is right but it skipsConditions such as invincibility, and the logic changing that value
Updated HP drops but the HP bar doesn'tThe display update logic, and which enemy the display references

One log does not reveal the whole cause. Even so, you can push the investigation past the point you confirmed, which saves you from changing unrelated settings in bulk.

Sponsored

Update the same on-screen line with Key

Sometimes you want to follow a constantly changing value, such as movement speed, for a short time. Put a Print String on Event Tick, called every frame — every screen update — and the screen fills with lines of text.

That is what Key is for. Specify a name other than None, such as PlayerSpeed, and screen messages with the same Key overwrite each other.

Without a Key lines stack up; with the same Key one on-screen line updates

Key is for organizing the on-screen display. It is not a feature for collapsing Output Log records into one line or for keeping text past Duration. When watching a value temporarily on Tick, turn "Print to Log" off to avoid flooding the normal log. For values like HP where you want to know the moment of change, printing right after the change logic is enough to start.

Logging in C++: UE_LOG

To record values the same way in C++, use UE_LOG. If you are working in Blueprint, come back when you need it.

For example, adding these two lines after the existing Super::BeginPlay(); inside your own Actor's .cpp prints HP to the log.

const float HealthForDebug = 100.0f;
UE_LOG(LogTemp, Log, TEXT("[DebugEnemy] HP: %.1f"), HealthForDebug);

LogTemp is a category usable for temporary checks, and the second Log is the record's severity. The %.1f slot takes the number to one decimal place, printing HP: 100.0. TEXT(...) is UE's way of writing strings. Preparing your own category can wait until logs multiply.

Verbosity is the classification of a log's severity and detail. Separating logs indicating problems from routine records makes filtering in the Output Log easier.

Using Error, Warning, Display, and Log to separate problems from routine records
LevelRule of thumb for use
ErrorProblems such as failing to load required data
WarningStates worth noting, such as correcting an unexpected value
DisplayInformation you also want in the console, such as startup settings
LogRoutine behavior checks, like the HP here

Error records a problem; it is not by itself an instruction to stop the game. Fatal, which records a fatal error and crashes, is a separate level. And while printf can print text in some situations, UE_LOG is what fits UE's categories, severities, and log window.

Tips for leaving investigable logs

  • Pair the name with the value: Damage: 25 reads better later than just 25. When investigating several enemies, include the Actor name too.
  • Change one thing at a time: here, fix the invincible default first, then change the damage amount. You never lose track of why a result changed.
  • Also show normal records: when hunting your own Print Strings, clear filters limited to Error and Warning. Conversely, narrow to those when hunting UE-side problems.
  • Clean up your check logic: after the experiment, remove BP_DebugEnemy from the working level. Delete temporary logs you added to your own game once they have served their purpose.

Bonus: Good to Know Up Front

Confirm positions and volumes with diagrams. Even when a log tells you "the attack query ran," whether the query volume reaches the enemy is hard to follow in text alone. Combining it with Draw Debug and the Visual Logger, which draws lines and spheres in the game view, makes it easier to confirm.

  • What Accessed None means: it is a clue that there was no usable reference, such as a target you tried to operate on not being set. Open the Blueprint and node reported in the log and investigate from the logic that retrieves the target.
  • Print String is for development checks: it is unavailable in normal Shipping builds. Do not use it as a substitute for a shipping HP display or player notifications.
  • You can read it from files too: in normal editor runs, logs are saved under the project's Saved/Logs/. To investigate after closing, check the file matching the date and time.
  • There is also a stop-and-inspect method: once you have narrowed the location, the Blueprint Debugger, which pauses logic so you can look at variables, helps.

Summary

Put markers along your logic with Print String and read back the order and values in the Output Log. Combining the two lets you follow behavior that appearance alone cannot reveal.

The first things to confirm are the three: "was it called this far," "what was the value," and "which way did it go." As with the invincible setting here, try small changes and compare results to narrow down where to look.

Further Reading

Unreal Engine Notes in this section98