radar_trace 0.3.0
radar_trace: ^0.3.0 copied to clipboard
Pure-Dart tracer framework with monotonic spans, log-linear latency histograms, Zone-based async nesting, and a lossless outlier ring.
example/radar_trace_example.dart
// ignore_for_file: avoid_print
import 'dart:async';
import 'package:radar_trace/radar_trace.dart';
Future<void> main() async {
// An exporter tees every finished span into the host's own pipeline —
// here just stdout — while the recorder keeps aggregating.
final tracer = Tracer(
recorder: TraceRecorder(
exporters: [
(span) => print(
'[span] ${span.name} ${span.durationMicros}µs '
'${span.status.name}',
),
],
// Export path only — the histograms still see every span.
exportFilter: (span) => span.name != 'inner',
// Retain the slowest interactions with their full subtree.
slowTraceCapacity: 4,
),
);
// --- Synchronous span --------------------------------------------------
final length = tracer.trace('parse_words', () {
return 'hello world dart'.split(' ').length;
});
print('Word count: $length');
// --- Async span --------------------------------------------------------
final result = await tracer.traceAsync('simulate_fetch', () async {
await Future<void>.delayed(const Duration(milliseconds: 5));
return 'response_payload';
});
print('Fetched: $result');
// --- Nested spans (Zone propagation) -----------------------------------
tracer.trace('outer', () {
tracer.trace('inner', () {
// inner span automatically becomes a child of outer via Zone values.
});
});
// --- Manual start → SpanHandle → stop/fail ----------------------------
// Attributes can be added at stop time, when the outcome is known.
final handle = tracer.start('manual_op', category: 'demo');
try {
await Future<void>.delayed(const Duration(milliseconds: 2));
handle.stop(attributes: {'source': 'cache'}); // records SpanStatus.ok
} catch (error) {
handle.fail(error); // records SpanStatus.error + error.type
}
// --- Latency histogram direct usage ------------------------------------
final histogram = LatencyHistogram();
for (final micros in [120, 450, 1800, 12000, 45000]) {
histogram.record(micros);
}
print('p50: ${histogram.percentile(0.5)} µs');
print('p99: ${histogram.percentile(0.99)} µs');
print('mean: ${histogram.mean?.toStringAsFixed(1)} µs');
// --- Read aggregated snapshot ------------------------------------------
final snap = tracer.snapshot();
print('\n--- Span summary ---');
for (final entry in snap.stats.entries) {
final key = entry.key;
final h = entry.value.histogram;
print(
'${key.name}'
'${key.category != null ? " [${key.category}]" : ""}: '
'count=${h.count} '
'p50=${h.percentile(0.5)} µs '
'p99=${h.percentile(0.99)} µs',
);
}
if (snap.totalDropCount > 0) {
print('Dropped (maxKeys): ${snap.totalDropCount}');
}
// --- Slowest interactions, as waterfalls --------------------------------
print('\n--- Slowest traces ---');
for (final trace in snap.slowTraces) {
print(trace.describe().trimRight());
}
// --- Merge snapshots from separate recorders ---------------------------
// In a real app the second snapshot arrives from a worker isolate as
// `toJson()` over a SendPort and is rebuilt with TraceSnapshot.fromJson.
final worker = TraceRecorder();
final workerTracer = Tracer(recorder: worker);
workerTracer.trace('parse_words', () => 'a b c'.split(' ').length);
final combined = TraceSnapshot.merge([
snap,
TraceSnapshot.fromJson(worker.snapshot().toJson(includeOutliers: true)),
]);
final parseKey = const TraceKey(name: 'parse_words', category: null);
print('\nMerged parse_words count: ${combined.stats[parseKey]?.count}');
}