1)推荐目录结构
bash
lib/
core/
observability/
observability.dart // 统一门面:O.log / O.track / O.perf
logger.dart // Logger接口 + 默认实现
tracker.dart // Tracker接口 + 默认实现
perf.dart // Perf接口 + Trace
route_analytics.dart // RouteObserver + 页面曝光
mixins/
log_mixin.dart
track_mixin.dart
perf_mixin.dart
page_lifecycle_mixin.dart
auto_dispose_mixin.dart
widgets/
track_tap.dart // 点击埋点组件(低侵入)
app.dart
main.dart
pages/
demo_page.dart
2)核心:Observability 统一门面(O)
lib/core/observability/observability.dart
Dart
import 'logger.dart';
import 'tracker.dart';
import 'perf.dart';
/// 统一观测门面:后续接 SDK 只换实现,不动业务代码
class O {
O._();
static Logger logger = ConsoleLogger();
static Tracker tracker = DebugTracker();
static Perf perf = DebugPerf();
static void log(String msg, {Map<String, Object?>? fields}) =>
logger.log(msg, fields: fields);
static void event(String name, {Map<String, Object?>? props}) =>
tracker.event(name, props: props);
static Trace trace(String name, {Map<String, Object?>? tags}) =>
perf.trace(name, tags: tags);
}
3)Logger / Tracker / Perf 接口与默认实现
logger.dart
Dart
import 'package:flutter/foundation.dart';
abstract class Logger {
void log(String msg, {Map<String, Object?>? fields});
void warn(String msg, {Map<String, Object?>? fields});
void error(String msg, {Object? err, StackTrace? st, Map<String, Object?>? fields});
}
class ConsoleLogger implements Logger {
@override
void log(String msg, {Map<String, Object?>? fields}) {
debugPrint('[LOG] $msg ${fields ?? {}}');
}
@override
void warn(String msg, {Map<String, Object?>? fields}) {
debugPrint('[WARN] $msg ${fields ?? {}}');
}
@override
void error(String msg, {Object? err, StackTrace? st, Map<String, Object?>? fields}) {
debugPrint('[ERROR] $msg err=$err fields=${fields ?? {}}');
if (st != null) debugPrint(st.toString());
}
}
tracker.dart
Dart
import 'package:flutter/foundation.dart';
abstract class Tracker {
void event(String name, {Map<String, Object?>? props});
void pageView(String routeName, {Map<String, Object?>? props});
}
class DebugTracker implements Tracker {
@override
void event(String name, {Map<String, Object?>? props}) {
debugPrint('[TRACK] event=$name props=${props ?? {}}');
}
@override
void pageView(String routeName, {Map<String, Object?>? props}) {
debugPrint('[TRACK] page=$routeName props=${props ?? {}}');
}
}
perf.dart
Dart
import 'dart:async';
import 'package:flutter/foundation.dart';
abstract class Perf {
Trace trace(String name, {Map<String, Object?>? tags});
}
abstract class Trace {
void tag(String key, Object? value);
void end({Object? error});
}
/// Debug 版 Trace:用 Stopwatch 统计耗时
class DebugPerf implements Perf {
@override
Trace trace(String name, {Map<String, Object?>? tags}) {
return _DebugTrace(name, tags: tags);
}
}
class _DebugTrace implements Trace {
_DebugTrace(this.name, {Map<String, Object?>? tags})
: _tags = {...?tags},
_sw = Stopwatch()..start();
final String name;
final Stopwatch _sw;
final Map<String, Object?> _tags;
@override
void tag(String key, Object? value) => _tags[key] = value;
@override
void end({Object? error}) {
_sw.stop();
debugPrint('[PERF] $name cost=${_sw.elapsedMilliseconds}ms tags=$_tags error=$error');
}
}
/// 便捷:包一层 async trace
Future<T> traceAsync<T>(
String name,
Future<T> Function(Trace t) body, {
Map<String, Object?>? tags,
}) async {
final t = DebugPerf().trace(name, tags: tags);
try {
final r = await body(t);
t.end();
return r;
} catch (e) {
t.end(error: e);
rethrow;
}
}
4)mixin 切面层(静态 AOP)
4.1 LogMixin
mixins/log_mixin.dart
Dart
import '../observability.dart';
mixin LogMixin {
void log(String msg, {Map<String, Object?>? fields}) => O.log(msg, fields: fields);
}
4.2 TrackMixin(事件埋点)
mixins/track_mixin.dart
Dart
import '../observability.dart';
mixin TrackMixin {
void track(String event, {Map<String, Object?>? props}) => O.event(event, props: props);
}
4.3 PerfMixin(性能 trace)
mixins/perf_mixin.dart
Dart
import '../observability.dart';
import '../perf.dart';
mixin PerfMixin {
Trace trace(String name, {Map<String, Object?>? tags}) => O.trace(name, tags: tags);
Future<T> traceFuture<T>(
String name,
Future<T> Function(Trace t) body, {
Map<String, Object?>? tags,
}) async {
final t = trace(name, tags: tags);
try {
final r = await body(t);
t.end();
return r;
} catch (e) {
t.end(error: e);
rethrow;
}
}
}
4.4 AutoDisposeMixin(资源释放切面)
mixins/auto_dispose_mixin.dart
Dart
import 'package:flutter/widgets.dart';
mixin AutoDisposeMixin<T extends StatefulWidget> on State<T> {
final _disposers = <VoidCallback>[];
void addDisposer(VoidCallback disposer) => _disposers.add(disposer);
@override
void dispose() {
for (final d in _disposers.reversed) {
d();
}
_disposers.clear();
super.dispose();
}
}
4.5 PageLifecycleMixin(页面生命周期切面)
mixins/page_lifecycle_mixin.dart
Dart
import 'package:flutter/widgets.dart';
import '../observability.dart';
/// 你可以把它当成 "Android onCreate/onResume/onPause/onDestroy 的替代"
mixin PageLifecycleMixin<T extends StatefulWidget> on State<T> {
/// 推荐提供一个稳定的页面名(routeName 或业务名)
String get pageName => widget.runtimeType.toString();
@mustCallSuper
void onPageCreate() {}
@mustCallSuper
void onPageDispose() {}
@override
void initState() {
super.initState();
O.log('page_create', fields: {'page': pageName});
onPageCreate();
}
@override
void dispose() {
O.log('page_dispose', fields: {'page': pageName});
onPageDispose();
super.dispose();
}
}
5)RouteObserver:自动页面曝光 PV(最像 Android 的"自动埋点")
route_analytics.dart
Dart
import 'package:flutter/widgets.dart';
import 'observability.dart';
class AnalyticsRouteObserver extends RouteObserver<ModalRoute<dynamic>> {
void _pageView(Route<dynamic>? route) {
final name = route?.settings.name;
if (name == null || name.isEmpty) return;
O.tracker.pageView(name);
O.log('page_view', fields: {'route': name});
}
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPush(route, previousRoute);
_pageView(route);
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPop(route, previousRoute);
_pageView(previousRoute);
}
@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
_pageView(newRoute);
}
}
6)低侵入点击埋点组件 TrackTap(替代到处手写 track)
widgets/track_tap.dart
Dart
import 'package:flutter/material.dart';
import '../observability.dart';
class TrackTap extends StatelessWidget {
const TrackTap({
super.key,
required this.event,
this.props,
required this.child,
this.onTap,
});
final String event;
final Map<String, Object?>? props;
final Widget child;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
O.event(event, props: props);
onTap?.call();
},
child: child,
);
}
}
7)在 App 里接入(MaterialApp)
app.dart
Dart
import 'package:flutter/material.dart';
import 'core/observability/route_analytics.dart';
import 'pages/demo_page.dart';
final routeObserver = AnalyticsRouteObserver();
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorObservers: [routeObserver],
routes: {
'/': (_) => const DemoPage(),
},
initialRoute: '/',
);
}
}
main.dart
Dart
import 'package:flutter/material.dart';
import 'app.dart';
void main() {
runApp(const App());
}
8)页面使用示例(把 mixin 当 AOP 切面拼装)
pages/demo_page.dart
Dart
import 'package:flutter/material.dart';
import '../core/observability/mixins/log_mixin.dart';
import '../core/observability/mixins/track_mixin.dart';
import '../core/observability/mixins/perf_mixin.dart';
import '../core/observability/mixins/page_lifecycle_mixin.dart';
import '../core/observability/mixins/auto_dispose_mixin.dart';
import '../core/observability/widgets/track_tap.dart';
class DemoPage extends StatefulWidget {
const DemoPage({super.key});
@override
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage>
with
LogMixin,
TrackMixin,
PerfMixin,
PageLifecycleMixin<DemoPage>,
AutoDisposeMixin<DemoPage> {
@override
String get pageName => 'DemoPage';
@override
void onPageCreate() {
log('DemoPage init');
}
Future<void> _loadData() async {
await traceFuture('api.load_demo', (t) async {
t.tag('endpoint', '/demo');
await Future<void>.delayed(const Duration(milliseconds: 300));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Observability Demo')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TrackTap(
event: 'click_reload',
props: {'page': pageName},
onTap: () async {
track('reload_start', props: {'page': pageName});
await _loadData();
track('reload_done', props: {'page': pageName});
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Reload (TrackTap + PerfTrace)'),
Icon(Icons.refresh),
],
),
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
// 也可以直接手写埋点(更灵活)
track('click_native_button', props: {'page': pageName});
log('button clicked', fields: {'btn': 'native'});
},
child: const Text('Native Button'),
),
],
),
),
);
}
}