Skip to content

Testing & Debugging

Logging strategy on a vehicle

What to log, at what level, and where it goes — balancing a defect you may only ever see once against flash memory that has to last fifteen years.

Intermediate6 minLogging · Debugging · Privacy

Logging on a vehicle sits between two hard constraints. A defect may occur once, in a customer's car, in a country you will never visit — so you want detail. And flash memory has a write budget that must last fifteen years — so you cannot have much of it.

Where logs actually go#

The buffers
adb logcat -b main      # app logs
adb logcat -b system    # framework
adb logcat -b crash     # crashes only
adb logcat -b events    # structured system events
adb logcat -b kernel    # dmesg
adb logcat -b all
 
adb logcat -g           # how big is each buffer?
adb logcat -G 64M       # make them bigger before a long reproduction

Levels, and what they should actually mean#

LevelUse forIn production
VERBOSETracing individual valuesCompiled out
DEBUGDevelopment detailOff
INFOState changes worth knowingOn
WARNSomething unexpected, recoveredOn
ERRORSomething failedOn
ASSERT/wtfShould be impossibleOn

What to log#

State transitions. "Charging started", "user switched to 11", "VHAL connected". These are the skeleton of any incident reconstruction.

Decisions and their reason. Not "request rejected" but "request rejected: level 9 exceeds max 5". The reason is the whole value.

Boundary crossings. What you sent to another process or another ECU, and what came back — with a sequence number. This is what settles cross-team disputes.

Failures, with context. The error, the inputs, and what you did about it.

What not to log#

Redacting rather than removing
// Bad
Log.i(TAG, "navigating to ${destination.address}")
 
// Better — useful for debugging, not a location record
Log.i(TAG, "navigating: ${destination.hashCode()} (${route.distanceKm} km)")

Making verbose logging free#

Compiled out in production
private const val DEBUG = false   // set from BuildConfig
 
fun onPropertyChanged(id: Int, value: Any) {
    // Constant condition — the compiler removes the whole block.
    if (DEBUG) Log.v(TAG, "property $id -> $value")
}
Or runtime-gated for field diagnostics
if (Log.isLoggable(TAG, Log.VERBOSE)) {
    Log.v(TAG, "expensive ${buildDiagnosticString()}")
}
Turning it on for one tag, on a real vehicle
adb shell setprop log.tag.VegaComfort VERBOSE

Native logging#

From C++
#define LOG_TAG "VegaVHAL"
#include <android-base/logging.h>
 
LOG(INFO) << "connected, " << config_count << " properties";
LOG(WARNING) << "property " << prop_id << " unavailable";
PLOG(ERROR) << "cannot open " << path;   // appends the errno description

PLOG is worth knowing — it adds the system error automatically, which turns "cannot open /dev/can0" into "cannot open /dev/can0: Permission denied" and often identifies the problem on its own.

Structured events, for things that are counted#

For events you will want to aggregate across a fleet, a log line is the wrong shape. The event log is structured and machine-readable.

Persistent logging, carefully#

Sometimes you genuinely need logs to survive a reboot — an intermittent fault that reboots the head unit.

Available, and to be used sparingly
adb shell setprop persist.logd.logpersistd logcatd     # on
adb shell setprop persist.logd.logpersistd ""          # off
adb shell ls -la /data/misc/logd/

A strategy that holds up#

  • Default to logcat. It is a memory ring buffer and costs no flash.
  • INFO and above are the incident report. Keep them meaningful.
  • Compile out verbose logging, or guard it with isLoggable.
  • Never log personal data. Redact or hash.
  • Log decisions with reasons, and boundary crossings with sequence numbers.
  • Structured events for anything you will count across a fleet.
  • Persistent logging is a targeted tool, not a default.
  • Raise the buffer size before a long reproduction, not after.

Next#

Compliance, and the suites that decide whether any of this can ship.

References & further reading

Code links target the main branch on cs.android.com. AOSP moves — if a path 404s, search the symbol instead.