Debugging
Techniques and tools for debugging code in any VAPP; the examples use vasak-desktop so
that there is a broad use case.
Frontend debugging (Vue.js / TypeScript)#
Open DevTools:
1
| F12 or Ctrl+Shift+I or Cmd+Option+I
|
Main tabs:
- Console: see logs and errors
- Elements: inspect the DOM
- Network: see requests
- Sources: step-by-step debugger
- Vue DevTools: Vue.js extension
2. Console logging#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| // Normal logs
console.log('Volume:', volume);
// Information
console.info('Operation completed');
// Warnings
console.warn('Unusual values detected');
// Errors
console.error('An error occurred:', error);
// Group related logs
console.group('Audio Settings');
console.log('Volume:', volume);
console.log('Device:', device);
console.groupEnd();
// Data table
console.table([
{ name: 'Device 1', volume: 50 },
{ name: 'Device 2', volume: 75 }
]);
// Timing
console.time('loadAudio');
loadAudio();
console.timeEnd('loadAudio');
|
3. Step-by-step debugger#
In the Sources tab of DevTools:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // Conditional breakpoint
function updateVolume(newVolume) {
// Right-click on the line number
// Choose "Add conditional breakpoint"
// Condition: newVolume < 0 || newVolume > 100
this.volume = newVolume;
}
// Inside DevTools, you can:
// - Step over (F10)
// - Step into (F11)
// - Step out (Shift+F11)
// - See local variables
// - Run commands in the console while stopped
|
An extension for debugging Vue:
1
2
3
4
5
6
| 1. Install: https://devtools.vuejs.org/
2. Open DevTools (F12)
3. Go to the "Vue" tab
4. Inspect components
5. Modify data in real time
6. See emitted events
|
5. Tagged logs#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // Create a custom log function
const log = {
audio: (msg: string, data?: any) => {
console.log(`[AUDIO] ${msg}`, data || '');
},
network: (msg: string, data?: any) => {
console.log(`[NETWORK] ${msg}`, data || '');
},
bluetooth: (msg: string, data?: any) => {
console.log(`[BT] ${msg}`, data || '');
},
};
// Usage
log.audio('Volume changed to:', 50);
log.network('WiFi connected to:', 'MyNetwork');
|
Backend debugging (Rust)#
1. Print debugging#
1
2
3
4
5
6
7
8
9
10
11
| // Simple log
println!("Volume: {}", volume);
// Debug format
println!("Struct: {:?}", device);
// Pretty print
println!("Struct: {:#?}", device);
// With eprint for stderr
eprintln!("Error: {}", error);
|
2. Debug macros#
1
2
3
4
5
6
7
8
| // The dbg! macro
let volume = dbg!(get_volume()); // Prints the value and returns it
// assert!/assert_eq!
assert_eq!(volume, 50, "Volume should be 50");
// debug_assert!
debug_assert!(volume <= 100, "Volume out of range");
|
3. Environment variables for logging#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # Show all logs
RUST_LOG=debug cargo run
# Only a specific module
RUST_LOG=vasak_desktop::audio=debug cargo run
# TRACE level (very verbose)
RUST_LOG=trace cargo run
# Multiple modules
RUST_LOG=vasak_desktop::audio=debug,vasak_desktop::network=info cargo run
# Backtrace on panics
RUST_BACKTRACE=1 cargo run
RUST_BACKTRACE=full cargo run
|
4. Log macros (if you use the log crate)#
1
2
3
4
5
6
| use log::{debug, info, warn, error};
debug!("Debug message: {:?}", data);
info!("Information message");
warn!("Warning message");
error!("Error message");
|
5. The GDB/LLDB debugger#
1
2
3
4
5
6
7
8
9
10
11
| # Run with the debugger (Linux)
lldb target/debug/vasak-desktop
# At the prompt:
# (lldb) b main # Breakpoint in main
# (lldb) r # Run
# (lldb) c # Continue
# (lldb) n # Next line
# (lldb) s # Step into function
# (lldb) p variable_name # Print variable
# (lldb) q # Quit
|
IPC debugging (frontend ↔ backend)#
1. Watching the IPC commands#
Frontend:
1
2
3
4
5
6
7
| // Before calling the command
console.log('Calling:', 'set_volume', { level: 50 });
// In the call
const result = await invoke('set_volume', { level: 50 });
console.log('Result:', result);
|
Backend:
1
2
3
4
5
| #[tauri::command]
pub fn set_volume(level: u32) -> Result<(), String> {
eprintln!("📡 IPC set_volume called with level: {}", level);
// implementation
}
|
2. Tauri debug info#
1
2
3
4
5
6
7
| # See Tauri information
cargo tauri info
# Output:
# Platform: Linux
# Tauri version: 2.8.0
# Node.js version: v20.0.0
|
D-Bus debugging#
1. D-Bus monitor#
1
2
3
4
5
6
7
8
9
| # See all D-Bus messages
dbus-monitor --session
# Filter by interface
dbus-monitor --session \
"interface='org.pulseaudio.Server'"
# Filter signals only
dbus-monitor --session type='signal'
|
2. Calling D-Bus from the CLI#
1
2
3
4
5
6
7
8
9
10
| # Call a method
dbus-send --session --print-reply \
/org/pulseaudio/core1 \
org.PulseAudio.Core1.GetVolume
# See properties
busctl --user get-property \
org.freedesktop.NetworkManager \
/org/freedesktop/NetworkManager \
State
|
3. D-Bus logs in Rust#
1
2
3
4
5
6
7
| // Enable zbus logging
env_logger::Builder::from_default_env()
.format_timestamp_millis()
.init();
// Then run with:
RUST_LOG=zbus=debug cargo run
|
Rendering debugging#
1. Inspecting the DOM#
1
2
3
4
5
6
7
8
9
10
| // In the console
document.querySelector('.panel');
document.querySelectorAll('.btn');
// Modify styles
document.querySelector('.panel').style.background = 'red';
// See registered events
monitorEvents(element, 'click');
unmonitorEvents(element);
|
1
2
3
4
5
6
7
8
| // In the console
performance.mark('start-operation');
// ... do something ...
performance.mark('end-operation');
performance.measure('operation', 'start-operation', 'end-operation');
performance.getEntriesByName('operation');
|
3. The Vue component hierarchy#
With Vue DevTools:
- Open DevTools
- Go to the “Vue” tab
- Expand the component tree
- Click a component to see its props and state
- Modify data in real time
1. Frontend#
1
2
3
4
5
6
7
8
9
10
| // Measure render time
console.time('render');
// ... operation ...
console.timeEnd('render');
// Vue timeline
import { useRenderTracking } from '@vueuse/core';
// In DevTools > Performance tab
// Record the session and analyse the flamegraph
|
2. Backend#
1
2
3
4
5
6
7
8
9
| # Build with debug optimisations
RUSTFLAGS="-C debuginfo=full" cargo build
# See build times
cargo build -Z timings
# Profiler (Linux)
perf record -F 99 ./target/debug/vasak-desktop
perf report
|
Async/await debugging#
1. Promises in the frontend#
1
2
3
4
5
6
7
8
9
10
11
12
13
| // See the promise state
const promise = invoke('get_volume');
console.log(promise); // See in the console what it does
// Using async/await
async function checkVolume() {
try {
const volume = await invoke('get_volume');
console.log('Volume:', volume);
} catch (error) {
console.error('Error:', error);
}
}
|
2. Async in Rust#
1
2
3
4
5
6
7
8
9
10
11
| // With logs
#[tauri::command]
pub async fn get_device() -> Result<Device> {
eprintln!("🔄 Starting get_device");
// Simulate async work
tokio::time::sleep(Duration::from_secs(1)).await;
eprintln!("✓ Finished get_device");
Ok(device)
}
|
Memory leak debugging#
1. Frontend#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // In DevTools > Memory tab
// 1. Take a heap snapshot
// 2. Perform the operation you think leaks
// 3. Take another heap snapshot
// 4. Compare the two
// For manual debugging
let listeners = [];
// ❌ Memory leak - the listeners are never cleaned up
element.addEventListener('click', handler);
listeners.push(handler);
// ✅ Correct - clean up on unmount
onUnmounted(() => {
listeners.forEach(h => element.removeEventListener('click', h));
});
|
2. Backend#
1
2
3
4
5
| # Use valgrind (Linux)
valgrind --leak-check=full ./target/debug/vasak-desktop
# Use perf for memory
perf record -e cache-misses ./target/debug/vasak-desktop
|
Thread debugging#
1. Rust threads#
1
2
3
4
5
6
7
8
9
| use std::thread;
let handle = thread::spawn(|| {
eprintln!("🧵 Thread: {:?}", thread::current().id());
// work
});
eprintln!("🧵 Main: {:?}", thread::current().id());
handle.join().unwrap();
|
2. Tokio tasks#
1
2
3
4
5
6
7
8
9
10
11
12
| #[tokio::main]
async fn main() {
eprintln!("📋 Task started");
let task = tokio::spawn(async {
eprintln!("📋 Task running");
// async work
});
task.await.unwrap();
eprintln!("📋 Task finished");
}
|