Console Output & Debugging
System.out.println() is the world's most underrated debugging tool — and the most misused one in FRC. This lesson covers how to use console output strategically, how it interacts with SmartDashboard, what to do when the output floods the console, and how to clean up before competition so your logs stay useful.
By the end of this lesson, you will:
- Place console output strategically to verify program flow, variable values, and state transitions
- Explain why printing in
periodic()without throttling floods the console and how to prevent it - Access the robot's live console output from VS Code's RioLog panel
- Decide for each print statement whether it belongs in production code, should be removed, or should be migrated to SmartDashboard or WPILOG
- Apply the 2910 standard: no temporary debug prints in competition code
Three Questions Console Output Answers
Every print statement exists to answer one question. If you can't state the question, you don't need the print statement. The three useful questions are:
- "Did this code path run?" — Verifying control flow. A print in an
ifbranch confirms whether the branch executed. - "What is the value of this variable right now?" — Verifying data. Printing an encoder value, a sensor state, or a calculated output to confirm it's what you expect.
- "When did this event happen?" — Verifying timing. Printing at state transitions, command start/end, or loop boundaries to understand sequencing.
System.out.println("here");
setState(HOLDING);
}
System.out.println(
"[Intake] Piece detected → HOLDING");
setState(HOLDING);
}
The Flood Problem: Printing in periodic()
teleopPeriodic() runs 50 times per second. A single System.out.println() inside it generates 3,000 log entries per minute. The Driver Station console can't display them all. Recent messages scroll off the screen before you can read them. The system also has to serialize and transmit all that data, which consumes loop time.
// Generates 3000 lines/minute
System.out.println(
"Encoder: " + getDistance());
}
// Updates live, no console flood
SmartDashboard.putNumber(
"Drive/Distance", getDistance());
}
The rule: if you need to watch a value continuously, use SmartDashboard. If you need to know when a specific event occurred (one-time state change, error condition), use a targeted print statement. Print statements in periodic() are almost always the wrong tool unless they're gated by a state-change condition.
Sometimes you want to sample a value periodically without flooding. A counter field works: declare private int m_printCounter = 0;, then inside periodic(): if (++m_printCounter % 50 == 0) { System.out.println(...); }. This fires once per second (every 50 loops). This is a development tool — remove it before competition. SmartDashboard is better for anything you want to see long-term.
Strategic Print Placement: Debug Scenarios
Select a debugging scenario to see the recommended console strategy.
Accessing the Console: RioLog in VS Code
The Driver Station console (Messages tab) shows a filtered, formatted view. For raw output from System.out.println(), the VS Code RioLog panel gives you direct access to the robot's stdout stream. Access it via Ctrl+Shift+P → WPILib: Start RioLog while tethered to the robot.
The difference matters: the DS console may delay or truncate long messages. RioLog shows everything, unformatted, in real time — useful during initial bringup and for high-frequency debugging where the DS console is too slow.
The Console Cleanup Standard
The rule on Team 2910: no temporary debug prints in code pushed to main. This is enforced at pull request review. The reasoning: a flooded console in competition hides real errors. If every loop prints "running" and an actual WARNING fires, it scrolls off before anyone reads it.
The system for this:
| Print Statement Type | Where It Belongs | Before Competition |
|---|---|---|
| Temporary variable check during development | Local dev branch only | Delete before merge |
| State machine transition confirmation | OK in feature branch | Migrate to SmartDashboard.putString() |
| Error condition message | Any branch | Convert to DriverStation.reportWarning() |
| Startup/init confirmation | OK in main | Keep — fires once, not in a loop |
| Sensor value sampling in periodic() | Local dev only | Remove — use SmartDashboard or DataLog |
When I connect to a robot and open the DS console, if I see thousands of lines per minute of "encoder: 0.00, encoder: 0.00, encoder: 0.00," I know this team is going to miss a real error in competition because it'll be buried. When I see a clean console — startup messages, then silence punctuated only by meaningful warnings — I know this team has thought about what information they actually need. Debugging output is a development tool. A cluttered console in competition is a liability.
Run this checklist before any competition deploy:
- Search the entire project for
System.out.println(Ctrl+Shift+F in VS Code). For every occurrence: decide whether it's temporary debug output (delete it), a permanent condition (convert toreportWarning()), or a startup message (keep it). - Enable the robot and watch the DS console for 30 seconds during TeleOp. Count how many lines per second appear. The target for competition code: fewer than 1 line per second during normal operation.
- If you see "Loop time of Xs overran the Ys period" warnings, this is an emergency — find and fix the slow call before competing. Run the robot in Test mode and watch for the warning to identify which periodic method is slow.
Knowledge Check
1. Your robot's arm isn't reaching its target position. You add System.out.println("arm angle: " + m_arm.getAngle()) to teleopPeriodic(). What problem does this create, and what's the better approach?
2. You want to confirm that the EJECTING state is only entered once per B button press (not multiple times per press). Where should you place the print statement, and what should it say?
3. A teammate pushes code to main that contains 15 System.out.println() statements in three different periodic() methods. What is the right response?
Console Audit and Strategic Debug Placement
- Run a project-wide search for
System.out.println. Categorize every result: temporary debug (delete), permanent condition (convert toreportWarning), or startup message (keep). Make the changes and deploy. Watch the DS console for 30 seconds and count lines per second — target under 1/sec. - In your IntakeSubsystem state machine, add a targeted print on every state transition:
System.out.printf("[Intake] %s → %s%n", m_state, newState). Run through a full intake/eject cycle and confirm each transition fires exactly once. Then migrate this toSmartDashboard.putString("Intake/State", m_state.toString())and remove the print. - Deliberately add a 25 ms blocking call to
teleopPeriodic():Timer.delay(0.025). Enable and watch the DS console for the loop overrun warning. Confirm it appears. Remove the delay. This lets you recognize a loop overrun warning in a real scenario before you encounter it accidentally. - Stretch goal: Access the RioLog panel in VS Code (Ctrl+Shift+P → WPILib: Start RioLog) while connected to the robot. Compare the output to the DS Messages tab. Note the differences in format, timing, and completeness. Add a startup message with the current date and WPILib version (
System.out.println("Robot started: " + edu.wpi.first.wpilibj.RobotBase.class.getPackage().getImplementationVersion())) and verify it appears in both views.