28 min

Logging and visualisation: the run is gone, the recording is not

A robot failure cannot be reproduced, so the recording is the only evidence that will ever exist. Learn the format deeply and the viewers loosely.

Where you are. You have the honest ROS 2 minimum and a bag file you recorded at the end of the last lesson. This lesson is about what belongs inside that file, and why it is the most valuable artifact your capstone will produce.

Trial fourteen

Forty trials. Thirty-one succeeded. Trial fourteen is the interesting one: the gripper closed while the cube was still moving, nudged it sideways, and then lifted nothing.

You want to know why. Was the frame the policy acted on already stale? Did the command go out on time and the servo arrive late? Did perception report the wrong position, or the right position too late?

Here is what you actually have. A phone video from a bad angle. A terminal buffer of print statements with no timestamps, interleaved from two threads. A success-rate table with a zero in row fourteen.

None of that answers the question. And you cannot re-run trial fourteen, because the cube was never in exactly that position before and never will be again.

The evidence either got written down while it was happening, or it does not exist.

The idea in one paragraph

In software you keep logs because they help; if they fail you re-run the request with a debugger attached. In robotics the run is unrepeatable, so the recording is not a debugging aid, it is the entire evidence base. That changes what a log has to be. It has to carry every stream the robot saw and produced, each message stamped with when it happened rather than when you received it, all on one timeline, in a format that a stranger can open and scrub through months later without your code. The viewers you use to look at it are fashion and they turn over every few months. The container format is the durable skill, and it is the one that makes you immediately useful on your first day at a robotics company.

Why a print statement stops working here

Four reasons, and they compound.

There are many streams. Camera frames, joint states, the policy’s action, the gripper’s actual position, and whatever your planner decided. Interleaving those as text destroys the one thing you need, which is their alignment.

They run at different rates. Cameras at 30 Hz, joint feedback at 200 Hz or more, a language-model planner at something like one call every few seconds. A single ordered text stream flattens three clocks into one and silently reorders them under load.

Most of it is not text. An image is not a line, and neither is a point cloud, a transform tree or a trajectory.

And the failure is usually a gap rather than a value.

camera joint states policy actions 30 Hz 200 Hz 30 Hz gripper closes no new frame in this window the failure is a relationship between lanes, and no print statement records it
Three time-aligned lanes at different rates, with a gripper-close event marked and the window of missing camera frames before it highlighted

Wider than the screen; scroll it sideways.

Look at what the question in trial fourteen actually is. Not “what was the value of gripper_cmd”. It is “how old was the most recent camera frame at the moment the gripper command went out”. That is a horizontal distance between two lanes. No amount of well-formatted text will ever show it to you, and every visualisation tool in this field exists to make that horizontal distance visible.

The two timestamps

This is the mistake that quietly ruins a dataset, and it is worth being pedantic about once.

sensor exposure when it happened driver buffers, then publishes transport queues and retries your recorder when you saw it tcapture treceive stamp only the right-hand end and this interval never existed
A sensor exposure passing through a driver and the network to your recorder, with capture time and receive time marked and the interval between them bracketed

Wider than the screen; scroll it sideways.

Every message has at least two meaningful times: the moment the measurement was taken, and the moment your process got hold of it. Between them sits driver buffering, format conversion, a network hop and a scheduler. On a good day that is a few milliseconds. On a loaded machine with three cameras it is tens.

The related trap is clock domains. A camera with its own oscillator, a robot controller with another, and your workstation with a third will drift apart over an hour. Either synchronise them, or record the offsets so a later reader can correct for them.

What a recording has to be

Five properties. Any format that has them will do; the point is to check rather than assume.

PropertyWhy it mattersWhat breaks without it
One timeline, many channelsAlignment is the whole questionYou are back to interleaved text
Per-message timestamps from the sourcePhysical time, not your timeLatency and staleness become unmeasurable
Self-describing schemasA stranger opens it without your codeThe file is readable only from inside your repository
An index for random seekYou jump to trial fourteen at 3:47Every question costs a full re-read of 40 GB
Append-safe writingA crash mid-run still leaves a usable fileThe interesting run is the one that crashes

MCAP is the container to learn. It has all five properties, it came out of the robotics tooling world rather than a research lab, and it has become the default storage format for the standard ROS 2 recorder, which makes it the closest thing this field has to a neutral interchange. Check what your own distribution writes by default, since that transition happened in a specific release and older tutorials will show you the previous sqlite-backed format.

Learn one dataset schema too, on the learning side, because a recording and a training set are different objects with different consumers. The LeRobot layout and HDF5 dominate, and a Google-originated episodic format is also in circulation. Pick one, be genuinely fluent, and be able to convert to and from your recordings.

Three viewers, three jobs

robot and sim same schema, both one recording self-describing, indexed seekable by time RViz2 live, and what the interview assumes Foxglove shared and remote review Rerun notebooks, datasets, agents the container is the durable skill the viewers change every few months
One recording read by three different viewers, each doing a different job

Wider than the screen; scroll it sideways.

They have split. Choose by the situation rather than by preference.

ToolUse it whenWatch out for
RViz2You are inside ROS 2, want live 3D of frames, meshes and sensor data, and the interview assumes itPainful on macOS; this is the one that wants a Linux machine
FoxgloveSomeone else has to look at it: shared review, remote robots, fleets, a link you sendNot fully open source since 2024; some practitioners left over it. The free tier is genuinely generous
RerunYou are in Python or a notebook, working on datasets, policies or training loopsShips every few weeks, so check the release notes before relying on any specific feature

The recommendation for this course: MCAP as the interchange, the Python-native tool for dataset and policy work, the shared platform when someone else has to look, and RViz2 in the ROS lesson because that is what a robotics interview will put in front of you.

What to log, and what to drop

Volume is a real constraint and the arithmetic is worth doing once. Three cameras at 640 by 480, three bytes a pixel, 30 frames a second, uncompressed, is about 83 megabytes a second. That is roughly 300 gigabytes an hour, and it will fill a laptop during a single afternoon of evaluation.

Encode the same frames as JPEG at, say, 30 kilobytes each and the three cameras cost about 2.7 megabytes a second, near 10 gigabytes an hour. A thirty-fold reduction, and for diagnosing trial fourteen it loses nothing that matters.

Meanwhile the numeric streams are nearly free. Six joints with position, velocity and effort at 200 Hz, at a couple of hundred bytes a message, is about 40 kilobytes a second: roughly 150 megabytes an hour, against 10 gigabytes for the compressed video beside it. The pixels cost everything; the numbers cost almost nothing.

Give each record two timestamps and enough identity to join it to the others afterwards. The shape, not the library:

record = {
    "channel":    "policy/action",   # which stream
    "t_capture":  1754716203.115,    # when the input it acted on was taken
    "t_publish":  1754716203.121,    # when this record was produced
    "episode":    14,                # which trial
    "step":       213,               # which control cycle
    "payload":    {"joints": [...], "gripper": 0.0},
}

Two more disciplines pay off later. Log the same channel names in simulation and on hardware, so the same viewer layout, the same analysis notebook and the same eval harness work against both; the sim-versus-real comparison then becomes a diff rather than a project. And commit the viewer layout file next to the recording, so opening it is one click for someone who has never seen your system. A recording a stranger can scrub, with the panels already arranged, is a portfolio object in its own right.

Check yourself

1. Why is a robot log a fundamentally different object from an application log?

Because the run cannot be repeated. In software a log is a convenience; if it fails you re-run the request under a debugger with the same inputs. A robot trial happens once, against a physical arrangement that will never recur exactly, so whatever was not written down while it happened does not exist and never will. The recording is not a debugging aid, it is the complete evidence base, which is why it must be exhaustive and timestamped at the source rather than selective and convenient.

2. You stamp every message when your logger receives it. Name three things that are now wrong.

First, your latency measurements measure your own logger’s scheduling rather than the robot: driver buffering, conversion, a network hop and the scheduler are all folded into the number. Second, cross-stream alignment is soft by a variable amount, because each stream picks up a different and fluctuating delay, so “how stale was the frame” becomes unanswerable. Third, any policy trained on the result learns an image-to-action correspondence that did not physically happen, which is a data quality defect that will not show up until evaluation. Take the source timestamp from the message header; a driver that does not provide one is a finding to report, not a gap to fill with the current time.

3. Why does the container format matter more for your career than the viewer?

Because viewers turn over every few months and formats do not. The interchange container is what makes a recording readable by a stranger, by a different tool, and by you in a year, and it is what lets a team switch viewers without touching a byte of data. It is also the concrete skill a robotics company needs on day one: ingest, convert, validate, index. Being fluent in one container plus one dataset schema, and able to convert between them, transfers to any employer in the field, whereas viewer expertise expires.

4. Three cameras at 640 by 480 and 30 Hz, uncompressed, is about 83 MB a second. What do you turn down, and what do you refuse to turn down?

Turn down the images: JPEG at roughly 30 kilobytes a frame takes the same three cameras to about 2.7 megabytes a second, near a thirty-fold saving, and it loses nothing needed to diagnose a timing failure. Refuse to turn down the numeric streams: commands, joint states, planner decisions and skill calls with their arguments and results, all at full rate. Six joints of position, velocity and effort at 200 Hz is about 40 kilobytes a second, roughly 150 megabytes an hour against 10 gigabytes for the video beside it, so throttling them saves nothing measurable and destroys exactly the alignment evidence you are recording for.

5. Trial fourteen failed because the gripper closed on a moving cube. Which question is the recording actually answering, and what shape does it have on a timeline?

The question is how old the most recent camera frame was at the moment the gripper command went out, and whether the command itself was delayed. It is a horizontal distance between two lanes on a shared timeline, not a value in any single stream, which is why text logs cannot express it and why every visualisation tool in this field is built around aligned lanes. Answering it needs source timestamps on both streams and an index that lets you jump straight to that moment.

6. Why log identical channel names in simulation and on hardware?

Because it makes the sim-to-real comparison a diff rather than a project. One viewer layout, one analysis notebook and one evaluation harness then work against both, so a discrepancy shows up as two overlaid lanes instead of two incompatible pipelines you have to reconcile first. It also means the tooling you build while iterating in simulation keeps its value the moment you move to the real robot, which is where the expensive time is.

Do this

One evening, and it produces something you will link to later.

1. Pick one existing evaluation run. Any recorded run from the policy or hardware work. If nothing is recorded, do a fresh set of ten trials for this.

2. Instrument four channels, not forty. Camera frames as JPEG, joint states at full rate, the commanded action, and one event channel carrying trial boundaries and outcomes. Give every record a source timestamp, a publish timestamp, an episode number and a step number, in the shape above. Write MCAP.

3. Open the same file in two tools. The Python-native viewer from a notebook, and either the shared platform or RViz2. Arrange a layout that shows the four channels on one timeline, then save the layout file and commit it beside the recording.

4. Answer one question with it. Choose your worst trial and measure, from the file, how stale the most recent frame was when the decisive command went out. Write the number down. If the recording cannot answer that, the instrumentation is wrong, and fixing it now is the whole point of this exercise.

5. Write four lines in notes/07-logging.md. The container you chose and why, the two timestamps you record, what you deliberately do not log, and the one number you extracted. That paragraph is the seed of a post-mortem, which is the artifact that survives an interviewer asking follow-up questions three levels deep.

What you can now do

You can explain why an unrepeatable run makes the recording the entire evidence base, instrument a robot so that a failed trial stays answerable months later, take timestamps from the source rather than from your own scheduler, choose a container by checking five properties instead of copying a tutorial, budget disk by compressing pixels and never compressing numbers, and pick between the three standard viewers by the job in front of you rather than by which one you saw first.

What you can now do

You can instrument a robot so that any failed trial is answerable afterwards, pick a container format on purpose, and choose between the three standard viewers by job rather than by taste.