library let_log; import 'dart:convert'; import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; part 'log_widget.dart'; part 'net_widget.dart'; enum _Type { log, debug, warn, error } List _printNames = ["😄", "🐛", "❗", "❌", "⬆️", "⬇️"]; List _tabNames = ["[Log]", "[Debug]", "[Warn]", "[Error]"]; final RegExp _tabReg = RegExp(r"\[|\]"); String _getTabName(int index) { return _tabNames[index].replaceAll(_tabReg, ""); } class _Config { /// Whether to display the log in reverse order bool reverse = false; /// Whether or not to print logs in the ide bool printNet = true; /// Whether or not to print net logs in the ide bool printLog = true; /// Maximum number of logs, larger than this number, will be cleaned up, default value 500 int maxLimit = 500; /// Set the names in ide print. void setPrintNames({ String log, String debug, String warn, String error, String request, String response, }) { _printNames = [ log ?? "[Log]", debug ?? "[Debug]", warn ?? "[Warn]", error ?? "[Error]", request ?? "[Req]", response ?? "[Res]", ]; } /// Set the names in the app. void setTabNames({ String log, String debug, String warn, String error, String request, String response, }) { _tabNames = [ log ?? "[Log]", debug ?? "[Debug]", warn ?? "[Warn]", error ?? "[Error]", ]; } } class Logger extends StatelessWidget { @override Widget build(BuildContext context) { return DefaultTabController( length: 2, child: Scaffold( appBar: AppBar( title: const TabBar( tabs: [ Tab(child: Text("Log")), Tab(child: Text("Net")), ], ), elevation: 0, ), body: const TabBarView( children: [ LogWidget(), NetWidget(), ], ), ), ); } static bool enabled = true; static _Config config = _Config(); /// Logging static void log(Object message, [Object detail]) { if (enabled) _Log.add(_Type.log, message, detail); } /// Record debug information static void debug(Object message, [Object detail]) { if (enabled) _Log.add(_Type.debug, message, detail); } /// Record warnning information static void warn(Object message, [Object detail]) { if (enabled) _Log.add(_Type.warn, message, detail); } /// Record error information static void error(Object message, [Object detail]) { if (enabled) _Log.add(_Type.error, message, detail); } /// Start recording time static void time(Object key) { assert(key != null); if (enabled) _Log.time(key); } /// End of record time static void endTime(Object key) { assert(key != null); if (enabled) _Log.endTime(key); } /// Clearance log static void clear() { _Log.clear(); } /// Recording network information static void net(String api, {String type = "接口", int status = 100, Object data}) { assert(api != null); if (enabled) _Net.request(api, type, status, data); } /// End of record network information, with statistics on duration and size. static void endNet(String api, {int status = 200, Object data, Object headers, String type}) { assert(api != null); if (enabled) _Net.response(api, status, data, headers, type); } } class _Log { static final List<_Log> list = []; static final ValueNotifier length = ValueNotifier(0); static final Map _map = {}; final _Type type; final String message; final String detail; final DateTime start; const _Log({this.type, this.message, this.detail, this.start}); String get typeName { return _printNames[type.index]; } String get tabName { return _tabNames[type.index]; } bool contains(String keyword) { if (keyword.isEmpty) return true; return message != null && message.contains(keyword) || detail != null && detail.contains(keyword); } @override String toString() { final StringBuffer sb = StringBuffer(); sb.writeln("Message: $message"); sb.writeln("Time: $start"); if (detail != null && detail.length > 100) { sb.writeln("Detail: "); sb.writeln(detail); } else { sb.writeln("Detail: $detail"); } return sb.toString(); } static void add(_Type type, Object value, Object detail) { final logUtil = _Log( type: type, message: value?.toString(), detail: detail?.toString(), start: DateTime.now(), ); list.add(logUtil); _clearWhenTooMuch(); length.value++; if (Logger.config.printLog) { // debugPrint( // "${logUtil.typeName} ${logUtil.message}${logUtil.detail == null ? '' : '\n${logUtil.detail}'}\n--------------------------------"); log("\n${logUtil.typeName} ${logUtil.message}${logUtil.detail == null ? '' : '\n${logUtil.detail}'}\n--------------------------------"); if (type == _Type.error) { log(StackTrace.current.toString()); } } } static void _clearWhenTooMuch() { if (list.length > Logger.config.maxLimit) { list.removeRange(0, (Logger.config.maxLimit * 0.2).ceil()); } } static void time(Object key) { _map[key] = DateTime.now(); } static void endTime(Object key) { final data = _map[key]; if (data != null) { _map.remove(key); final spend = DateTime.now().difference(data).inMilliseconds; _Log.add(_Type.log, '$key: $spend ms', null); } } static void clear() { list.clear(); _map.clear(); length.value = 0; } } class _Net extends ChangeNotifier { static const all = "All"; static final List<_Net> list = []; static final ValueNotifier length = ValueNotifier(0); static final Map _map = {}; static final List types = [all]; static final ValueNotifier typeLength = ValueNotifier(1); final String api; final String req; final DateTime start; String type; int status = 100; int spend = 0; String res; String headers; bool showDetail = false; int _reqSize = -1; int _resSize = -1; _Net({ this.api, this.type, this.req, this.headers, this.start, this.res, this.spend = 0, this.status = 100, }); int getReqSize() { if (_reqSize > -1) return _reqSize; if (req != null && req.isNotEmpty) { try { return _reqSize = utf8.encode(req).length; } catch (e) { // print(e); } } return 0; } int getResSize() { if (_resSize > -1) return _resSize; if (res != null && res.isNotEmpty) { try { return _resSize = utf8.encode(res).length; } catch (e) { // print(e); } } return 0; } bool contains(String keyword) { if (keyword.isEmpty) return true; return api.contains(keyword) || req != null && req.contains(keyword) || res != null && res.contains(keyword); } @override String toString() { final StringBuffer sb = StringBuffer(); sb.writeln("[$status] $api"); sb.writeln("Start: $start"); sb.writeln("Spend: $spend ms"); sb.writeln("Headers: $headers"); sb.writeln("Request: $req"); sb.writeln("Response: $res"); return sb.toString(); } static void request(String api, String type, int status, Object data) { final net = _Net( api: api, type: type, status: status, req: data?.toString(), start: DateTime.now(), ); list.add(net); _map[api] = net; if (type != null && type != "" && !types.contains(type)) { types.add(type); typeLength.value++; } _clearWhenTooMuch(); length.value++; if (Logger.config.printNet) { log("\n${_printNames[4]} ${type == null ? '' : '$type: '}${net.api}${net.req == null ? '' : '\n${_printNames[0]} 请求数据: ${net.req}'}\n--------------------------------"); } } static void _clearWhenTooMuch() { if (list.length > Logger.config.maxLimit) { list.removeRange(0, (Logger.config.maxLimit * 0.2).ceil()); } } static void response( String api, int status, Object data, Object headers, String type) { _Net net = _map[api]; if (net != null) { _map.remove(net); net.spend = DateTime.now().difference(net.start).inMilliseconds; net.status = status; net.headers = headers?.toString(); net.res = data?.toString(); length.notifyListeners(); } else { net = _Net(api: api, start: DateTime.now(), type: type); net.status = status; net.headers = headers?.toString(); net.res = data?.toString(); list.add(net); _clearWhenTooMuch(); length.value++; } if (Logger.config.printNet) { log("\n${_printNames[5]} ${net.type == null ? '' : '${net.type}: '}${net.api}${net.res == null ? '' : '\n${_printNames[0]} 响应数据: ${net.res}'}\nSpend: ${net.spend} ms\n--------------------------------"); } } static void clear() { list.clear(); _map.clear(); length.value = 0; } }