Developer #
dart:developer adalah library bawaan Dart yang menyediakan alat untuk debugging dan profiling yang terintegrasi langsung dengan Dart DevTools. Berbeda dari print() yang hanya mengeluarkan teks ke konsol, dart:developer memungkinkan logging terstruktur dengan level dan zona, menandai blok kode untuk profiling timeline, memicu breakpoint secara programatik, menginspeksi objek di debugger, dan bahkan menambahkan command baru ke DevTools melalui service extension. Library ini adalah alat yang digunakan developer Dart profesional untuk memahami performa dan perilaku aplikasi mereka.
Gambaran dart:developer
#
flowchart LR
DD["dart:developer"] --> LOG["log()\nLogging terstruktur\nke DevTools Console"]
DD --> TL["Timeline\nProfiling performa\ndi DevTools Timeline"]
DD --> DBG["debugger()\nTrigger breakpoint\nsecara programatik"]
DD --> INSP["inspect()\nHighlight objek\ndi DevTools Inspector"]
DD --> EXT["registerExtension()\nTambah command\nke DevTools"]
DD --> EVT["postEvent()\nKirim event kustom\nke DevTools"]
DD --> TAG["UserTag\nLabelkan region kode\nuntuk profiler"]log() — Logging Terstruktur
#
log() dari dart:developer lebih powerful dari print() karena pesan muncul di DevTools Console dengan metadata: waktu, zona, level severity, dan bisa di-filter:
import 'dart:developer';
void main() {
// log dasar — pesan dengan nama logger
log('Aplikasi dimulai', name: 'main');
// log dengan level — menggunakan konstanta dari package logging
// Level: ALL=0, FINEST=300, FINER=400, FINE=500, CONFIG=700,
// INFO=800, WARNING=900, SEVERE=1000, SHOUT=1200, OFF=2000
log('Informasi biasa', name: 'auth', level: 800); // INFO
log('Peringatan!', name: 'db', level: 900); // WARNING
log('Error kritis!', name: 'network', level: 1000); // SEVERE
// log dengan error dan stack trace
try {
throw Exception('Koneksi gagal');
} catch (e, stackTrace) {
log(
'Gagal terhubung ke database',
name: 'database',
level: 1000, // SEVERE
error: e,
stackTrace: stackTrace,
);
}
// log dengan zone — menentukan konteks eksekusi
log('Pesan dari zone khusus', name: 'app', zone: Zone.current);
// log dengan sequence number — untuk ordering manual
log('Event pertama', name: 'stream', sequenceNumber: 1);
log('Event kedua', name: 'stream', sequenceNumber: 2);
log('Event ketiga', name: 'stream', sequenceNumber: 3);
// log dengan waktu eksplisit
log(
'Event dengan timestamp kustom',
name: 'audit',
time: DateTime.now(),
);
}
Integrasi dengan Package logging
#
dart:developer’s log() bekerja sangat baik bersama package logging untuk logging yang lebih terstruktur:
import 'dart:developer' as developer;
import 'package:logging/logging.dart';
// Setup sekali di main atau konfigurasi aplikasi
void setupLogging() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
// Forward semua log ke dart:developer
developer.log(
record.message,
name: record.loggerName,
level: record.level.value,
error: record.error,
stackTrace: record.stackTrace,
time: record.time,
);
// Juga print ke konsol untuk development
if (record.level >= Level.WARNING) {
print('[${record.level.name}] ${record.loggerName}: ${record.message}');
if (record.error != null) print(' Error: ${record.error}');
}
});
}
// Penggunaan di seluruh aplikasi
final _log = Logger('ProdukService');
class ProdukService {
Future<List<Produk>> ambilSemua() async {
_log.info('Mengambil semua produk');
try {
final data = await _api.get('/produk');
_log.fine('Berhasil mengambil ${data.length} produk');
return data;
} catch (e, st) {
_log.severe('Gagal mengambil produk', e, st);
rethrow;
}
}
}
Timeline — Profiling Performa
#
Timeline memungkinkan menandai blok kode dengan label yang muncul di DevTools Timeline — membantu mengidentifikasi bagian kode yang lambat:
import 'dart:developer';
Future<void> main() async {
// Cara 1: startSync / finishSync — untuk kode sinkron
Timeline.startSync('Parsing JSON');
final data = parseJson(rawJson);
Timeline.finishSync();
// Cara 2: TimelineTask untuk kode asinkron
final task = TimelineTask();
task.start('HTTP Request', arguments: {'url': '/api/produk'});
try {
final response = await http.get(Uri.parse('/api/produk'));
task.finish(arguments: {'statusCode': response.statusCode});
} catch (e) {
task.finish(arguments: {'error': e.toString()});
}
// Cara 3: timeSync — wrapper praktis untuk fungsi sinkron
final hasil = Timeline.timeSync(
'Sorting 10000 items',
() {
final list = List.generate(10000, (i) => 10000 - i);
list.sort();
return list;
},
arguments: {'ukuran': 10000},
);
// arguments — metadata tambahan yang muncul di DevTools
Timeline.startSync(
'Database Query',
arguments: {
'query': 'SELECT * FROM produk WHERE aktif = true',
'estimatedRows': 500,
},
);
final rows = await database.query('produk');
Timeline.finishSync();
}
Profiling Fungsi Secara Otomatis #
import 'dart:developer';
// Wrapper untuk profiling otomatis
T profileSync<T>(String nama, T Function() fungsi, {Map<String, dynamic>? args}) {
return Timeline.timeSync(nama, fungsi, arguments: args);
}
Future<T> profileAsync<T>(String nama, Future<T> Function() fungsi, {Map<String, dynamic>? args}) async {
final task = TimelineTask();
task.start(nama, arguments: args);
try {
final hasil = await fungsi();
task.finish(arguments: {'sukses': true});
return hasil;
} catch (e) {
task.finish(arguments: {'sukses': false, 'error': e.toString()});
rethrow;
}
}
// Penggunaan
Future<void> jalankanAplikasi() async {
final produk = await profileAsync(
'Muat Produk',
() => service.ambilProduk(),
args: {'source': 'API'},
);
final terfilter = profileSync(
'Filter Produk',
() => produk.where((p) => p.harga > 100000).toList(),
args: {'kriteria': 'harga > 100000'},
);
print('Produk terfilter: ${terfilter.length}');
}
debugger() — Breakpoint Programatik
#
debugger() memicu breakpoint di debugger yang terhubung — sangat berguna untuk berhenti di kondisi tertentu tanpa harus set breakpoint manual di IDE:
import 'dart:developer';
void prosesData(List<dynamic> data) {
for (int i = 0; i < data.length; i++) {
final item = data[i];
// Berhenti di debugger hanya saat kondisi tertentu
// when: false = tidak berhenti (skip breakpoint)
debugger(
when: item == null, // hanya berhenti jika item adalah null
message: 'Item null ditemukan di indeks $i',
);
if (item != null) prosesItem(item);
}
}
// Berguna untuk debugging kondisi yang sulit direproduksi
Future<void> prosesPembayaran(Map<String, dynamic> data) async {
final jumlah = data['jumlah'] as double?;
// Berhenti jika jumlah tidak masuk akal
debugger(
when: jumlah != null && jumlah > 1000000000,
message: 'Jumlah pembayaran sangat besar: $jumlah',
);
await prosesTransaksi(data);
}
debugger() tidak melakukan apa-apa jika tidak ada debugger yang terhubung — aman untuk production code, tapi umumnya lebih baik dihapus setelah selesai debugging.inspect() — Highlight di Inspector
#
inspect() mengirim objek ke DevTools Objects Inspector untuk diperiksa secara mendalam:
import 'dart:developer';
class Pengguna {
final String id;
final String nama;
final String email;
final List<String> peran;
Pengguna({required this.id, required this.nama,
required this.email, required this.peran});
}
void main() {
final pengguna = Pengguna(
id: 'U001',
nama: 'Budi Santoso',
email: '[email protected]',
peran: ['admin', 'editor'],
);
// inspect() menyebabkan objek muncul di DevTools Inspector
// berguna untuk memeriksa state objek yang kompleks
inspect(pengguna);
// Bisa digunakan dengan koleksi juga
final semuaPengguna = [pengguna, pengguna];
inspect(semuaPengguna);
}
postEvent() — Event Kustom ke DevTools
#
postEvent() mengirim event kustom yang bisa ditangkap oleh DevTools atau service extension lain:
import 'dart:developer';
// Kirim event kustom dengan data
void lacakAksi(String aksi, Map<String, dynamic> data) {
postEvent('ActionTracked', {
'aksi': aksi,
'timestamp': DateTime.now().toIso8601String(),
...data,
});
}
// Penggunaan
void main() {
lacakAksi('login', {'userId': 'U001', 'platform': 'mobile'});
lacakAksi('purchase', {'productId': 'P123', 'amount': 150000});
// Event bisa dimonitor via Observatory/DevTools VM service
}
registerExtension() — Tambah Command ke DevTools
#
Service extension memungkinkan menambahkan command kustom yang bisa dipanggil dari DevTools atau alat eksternal via VM Service Protocol:
import 'dart:developer';
import 'dart:convert';
void main() {
// Register service extension
registerExtension('ext.myApp.clearCache', (method, params) async {
// Bersihkan cache
await _cacheService.clear();
return ServiceExtensionResponse.result(
jsonEncode({'status': 'cache cleared', 'timestamp': DateTime.now().toIso8601String()}),
);
});
registerExtension('ext.myApp.getStats', (method, params) async {
final stats = {
'cacheSize': _cacheService.ukuran,
'activeConnections': _connectionPool.aktif,
'requestCount': _metrics.totalRequest,
'errorRate': _metrics.errorRate,
};
return ServiceExtensionResponse.result(jsonEncode(stats));
});
// Extension dengan parameter
registerExtension('ext.myApp.setLogLevel', (method, params) async {
final level = params['level'] ?? 'INFO';
Logger.root.level = Level.LEVELS.firstWhere(
(l) => l.name == level,
orElse: () => Level.INFO,
);
return ServiceExtensionResponse.result(
jsonEncode({'level': Logger.root.level.name}),
);
});
// Panggil via curl atau DevTools:
// curl 'http://localhost:8181/ext.myApp.getStats'
}
UserTag — Label Region Kode untuk Profiler
#
UserTag menandai region kode yang muncul di CPU profiler sebagai label yang bermakna, menggantikan nama stack frame yang sulit dibaca:
import 'dart:developer';
// Buat UserTag — biasanya sekali saat startup
final tagParsing = UserTag('JSON Parsing');
final tagRendering = UserTag('UI Rendering');
final tagNetwork = UserTag('Network I/O');
// Gunakan tag untuk menandai region kode
Future<void> muatHalaman() async {
// Tandai selama fetching data
tagNetwork.makeCurrent();
final jsonData = await fetchDataFromApi();
// Tandai selama parsing
tagParsing.makeCurrent();
final objects = parseJsonToObjects(jsonData);
// Tandai selama rendering
tagRendering.makeCurrent();
renderToScreen(objects);
// Kembalikan ke default
UserTag.defaultTag.makeCurrent();
}
Monitoring Performa dengan Timeline + log #
Kombinasi Timeline dan log memberikan observability yang komprehensif:
import 'dart:developer';
class PerformanceMonitor {
static final Map<String, int> _hitungPanggilan = {};
static final Map<String, Duration> _totalDurasi = {};
static T ukur<T>(String nama, T Function() fungsi) {
_hitungPanggilan[nama] = (_hitungPanggilan[nama] ?? 0) + 1;
final mulai = DateTime.now();
late T hasil;
try {
hasil = Timeline.timeSync(nama, fungsi);
} finally {
final durasi = DateTime.now().difference(mulai);
_totalDurasi[nama] = (_totalDurasi[nama] ?? Duration.zero) + durasi;
// Log jika lambat (> 16ms = jank di 60fps)
if (durasi.inMilliseconds > 16) {
log(
'$nama memakan ${durasi.inMilliseconds}ms (LAMBAT)',
name: 'PerformanceMonitor',
level: 900, // WARNING
);
}
}
return hasil;
}
static void laporan() {
log('=== Laporan Performa ===', name: 'PerformanceMonitor', level: 800);
for (final entry in _totalDurasi.entries) {
final nama = entry.key;
final total = entry.value;
final panggilan = _hitungPanggilan[nama] ?? 1;
final rataRata = total.inMicroseconds ~/ panggilan;
log(
'$nama: ${panggilan}x dipanggil, rata-rata ${rataRata}µs',
name: 'PerformanceMonitor',
level: 800,
);
}
}
}
// Penggunaan
void main() {
final data = PerformanceMonitor.ukur('ParseJSON', () => parseJson(rawData));
final sorted = PerformanceMonitor.ukur('Sort', () => data..sort());
PerformanceMonitor.laporan();
}
Perbedaan print vs log
#
import 'dart:developer';
// print — output ke stdout, tidak ada metadata, tidak terfilter
print('Pesan debug'); // selalu muncul, tidak ada konteks
// log — output ke DevTools Console, dengan metadata
log(
'Pesan debug',
name: 'ComponentName', // nama logger — bisa difilter di DevTools
level: 500, // level — bisa difilter
time: DateTime.now(), // timestamp
);
// Di production:
// print() masih muncul di log device (logcat Android, Console iOS)
// log() hanya visible di DevTools ketika debug session aktif
// → lebih bersih untuk deployment
Anti-Pattern dart:developer
#
Menggunakan print untuk Logging Produksi
#
// ANTI-PATTERN: print untuk semua logging
void prosesOrder(Order order) {
print('Memproses order: ${order.id}'); // ✗ tidak bisa difilter
print('Total: ${order.total}'); // ✗ tidak ada level/nama
print('Error: gagal kirim email'); // ✗ tidak bisa dibedakan dari info
}
// BENAR: log terstruktur dengan nama dan level
final _log = Logger('OrderService');
void prosesOrder(Order order) {
log('Memproses order: ${order.id}', name: 'OrderService', level: 800);
log('Total: ${order.total}', name: 'OrderService', level: 500);
if (gagalKirimEmail) {
log('Gagal kirim email', name: 'OrderService', level: 900); // WARNING
}
}
Meninggalkan debugger() di Production Code
#
// ANTI-PATTERN: debugger() tertinggal di kode produksi
void prosesData(List data) {
debugger(when: data.isEmpty); // ✗ seharusnya dihapus setelah debugging
// Tidak berbahaya jika tidak ada debugger yang terhubung,
// tapi membuat kode kotor dan bisa jadi tidak diinginkan
}
// BENAR: hapus setelah debugging, atau gunakan assert untuk invariant
void prosesData(List data) {
assert(data.isNotEmpty, 'Data tidak boleh kosong'); // ✓ lebih tepat
// debugger dihapus
}
Ringkasan #
log()lebih baik dariprint()untuk logging yang serius — muncul di DevTools Console dengan metadata (nama, level, timestamp, error) yang bisa difilter dan di-search.Timeline.startSync/finishSyncuntuk menandai blok kode yang ingin diprofile — muncul di DevTools Timeline sebagai blok berwarna yang menunjukkan durasi eksekusi.Timeline.timeSyncsebagai wrapper praktis — menangani start/finish otomatis termasuk saat ada exception.TimelineTaskuntuk operasi asinkron — berbeda daristartSync/finishSyncyang tidak bisa span await,TimelineTaskbisa di-startdan di-finishdi waktu yang berbeda.debugger(when: kondisi)untuk conditional breakpoint programatik — lebih fleksibel dari breakpoint IDE karena bisa menggunakan logika Dart yang kompleks.registerExtensionuntuk menambahkan command kustom ke DevTools — berguna untuk cache control, toggle flag, dan ekspor state dari DevTools tanpa restart.UserTagmembuat CPU profile lebih mudah dibaca — menggantikan nama method/class yang tidak bermakna dengan label yang kamu tentukan sendiri.postEventuntuk event kustom yang bisa ditangkap oleh alat monitoring eksternal — berguna untuk A/B testing, analytics, dan tracing.- Integrasi dengan package
loggingmemungkinkan routing log kedart:developersekaligus ke sistem logging lain (file, remote server) dalam satu handler.dart:developertidak berdampak di production jika tidak ada DevTools/debugger yang terhubung — aman digunakan tanpa khawatir overhead di build release (sebagian besar fungsi menjadi no-op).
← Sebelumnya: dart:isolate