Line data Source code
1 : import 'dart:async'; 2 : 3 : import 'package:async/async.dart'; 4 : import 'package:flutter/foundation.dart'; 5 : import 'package:logging/logging.dart'; 6 : import 'package:logging/logging.dart' show LogRecord; 7 : import 'package:quiver/check.dart'; 8 : import 'package:synchronized/synchronized.dart'; 9 : import 'package:timber/src/analytics.dart'; 10 : 11 : import 'crashreport_tree.dart'; 12 : import 'log_tree.dart'; 13 : import 'tree.dart'; 14 : 15 3 : final _forestLogger = Logger('Forest'); 16 : 17 : class Forest extends Tree 18 : with LogTree, CrashReportTree, AnalyticsTree, UserAnalyticsTree { 19 : final _lock = Lock(); 20 : final _memo = AsyncMemoizer(); 21 : final FlutterExceptionHandler _exceptionHandler; 22 : 23 1 : Forest([this._exceptionHandler]); 24 : 25 : StreamSubscription<LogRecord> _subscription; 26 : 27 1 : Future<void> init() { 28 3 : return _memo.runOnce(() { 29 : // This captures errors reported by the Flutter framework. 30 2 : FlutterError.onError = _exceptionHandler ?? onFlutterError; 31 5 : _subscription = Logger.root.onRecord.listen((record) { 32 1 : performLog(record); 33 : }); 34 : }); 35 : } 36 : 37 : // Default Error Handler 38 1 : void onFlutterError(FlutterErrorDetails details) { 39 1 : if (isInDebugMode) { 40 : // In development mode simply print to console. 41 1 : FlutterError.dumpErrorToConsole(details); 42 : } else { 43 : // In production mode report to the application zone to report to 44 : // Forest. 45 1 : performReportFlutterError(details); 46 : } 47 : } 48 : 49 1 : @override 50 : void performLog(LogRecord record) { 51 3 : _logTrees.forEach((tree) { 52 1 : tree.performLog(record); 53 : }); 54 : } 55 : 56 : /// Flutter Error 를 Report 합니다. 57 : /// ex: Crashlytics, Sentry 58 : @override 59 1 : Future<void> performReportError(error, stackTrace) async { 60 3 : _forestLogger.info('Caught error: $error'); 61 : 62 : // Errors thrown in development mode are unlikely to be interesting. You can 63 : // check if you are running in dev mode using an assertion and omit sending 64 : // the report. 65 1 : if (isInDebugMode) { 66 : // 디버그 모드에서는 전체 stacktrace를 출력합니다. 67 2 : _forestLogger.info(stackTrace); 68 2 : _forestLogger.info('In dev mode. Not sending report to Sentry.io.'); 69 : return; 70 : } else { 71 2 : _forestLogger.info('performReport'); 72 : 73 3 : _crashReportTrees.forEach((tree) { 74 1 : tree.performReportError(error, stackTrace); 75 : }); 76 : } 77 : } 78 : 79 : /// Flutter Error 를 Report 합니다. 80 : /// ex: Crashlytics, Sentry 81 1 : @override 82 : void performReportFlutterError(FlutterErrorDetails details) { 83 4 : _forestLogger.info('Caught error: ${details.exception}'); 84 : 85 : // Errors thrown in development mode are unlikely to be interesting. You can 86 : // check if you are running in dev mode using an assertion and omit sending 87 : // the report. 88 1 : if (isInDebugMode) { 89 : // 디버그 모드에서는 전체 stacktrace를 출력합니다. 90 3 : _forestLogger.info(details.stack); 91 2 : _forestLogger.info('In dev mode. Not sending report to Sentry.io.'); 92 : return; 93 : } else { 94 2 : _forestLogger.info('performReport'); 95 : 96 3 : _crashReportTrees.forEach((tree) { 97 1 : tree.performReportFlutterError(details); 98 : }); 99 : } 100 : } 101 : 102 : final _trees = <Tree>[]; 103 : 104 3 : Iterable<LogTree> get _logTrees => _trees.whereType<LogTree>(); 105 : 106 1 : Iterable<CrashReportTree> get _crashReportTrees => 107 2 : _trees.whereType<CrashReportTree>(); 108 : 109 1 : Iterable<AnalyticsTree> get _analyticsTrees => 110 2 : _trees.whereType<AnalyticsTree>(); 111 : 112 1 : Iterable<UserAnalyticsTree> get _userAnalyticsTrees => 113 2 : _trees.whereType<UserAnalyticsTree>(); 114 : 115 : /// Add a new logging tree. 116 1 : void plant(Tree tree) { 117 2 : checkArgument(tree != this); 118 5 : _lock.synchronized(() => _trees.add(tree)); 119 : } 120 : 121 : /// Add a new logging tree. 122 1 : void plantAll(Iterable<Tree> trees) { 123 2 : for (var tree in trees) { 124 1 : checkNotNull(tree); 125 2 : checkArgument(tree != this, message: 'Cannot plant Timber into itself.'); 126 : } 127 5 : _lock.synchronized(() => _trees.addAll(trees)); 128 : } 129 : 130 : /// Remove a planted tree. 131 1 : void uproot(Tree tree) { 132 5 : _lock.synchronized(() => _trees.remove(tree)); 133 : } 134 : 135 : /// Remove all planted tree. 136 1 : void uprootAll() { 137 5 : _lock.synchronized(() => _trees.clear()); 138 : } 139 : 140 : /// Return a copy of all planted [trees][Tree]. 141 1 : Future<List<Tree>> get forest { 142 5 : return _lock.synchronized(() => List.unmodifiable(_trees)); 143 : } 144 : 145 3 : int get treeCount => _trees.length; 146 : 147 : /// Override the level for this particular [Logger] and its children. 148 1 : set level(Level value) { 149 2 : Logger.root.level = value; 150 : } 151 : 152 3 : Level get level => Logger.root.level; 153 : 154 1 : @override 155 : void dispose() { 156 3 : _lock.synchronized(() { 157 3 : _trees.forEach((tree) { 158 1 : tree.dispose(); 159 : }); 160 2 : _trees.clear(); 161 : }); 162 1 : _subscription?.cancel(); 163 1 : _subscription = null; 164 : } 165 : 166 1 : @override 167 : bool isSupportedEventName(String name) { 168 : return true; 169 : } 170 : 171 1 : @override 172 : Set<Type> get supportedTypes { 173 : var set = <Type>{}; 174 3 : _analyticsTrees.forEach((tree) { 175 2 : set.addAll(tree.supportedTypes); 176 : }); 177 : return set; 178 : } 179 : 180 1 : @override 181 : bool isSupportedType(value) { 182 4 : return _analyticsTrees.any((element) => element.isSupportedType(value)); 183 : } 184 : 185 1 : @override 186 : Future<void> performLogEvent( 187 : {@required String name, Map<String, dynamic> parameters}) { 188 6 : _analyticsTrees.where((tree) => isSupportedEventName(name)).forEach((tree) { 189 1 : tree.performLogEvent(name: name, parameters: parameters); 190 : }); 191 : } 192 : 193 : @override 194 1 : Future<void> setUserProperty(String name, value) async { 195 3 : _userAnalyticsTrees.forEach((tree) { 196 1 : tree.setUserProperty(name, value); 197 : }); 198 : } 199 : 200 : /// userId 설정 201 : @override 202 1 : Future<void> setUserId(String id) async { 203 3 : _userAnalyticsTrees.forEach((tree) { 204 1 : tree.setUserId(id); 205 : }); 206 : } 207 : 208 : /// email 설정 209 : @override 210 1 : Future<void> setEmail(String email) async { 211 3 : _userAnalyticsTrees.forEach((tree) { 212 1 : tree.setEmail(email); 213 : }); 214 : } 215 : 216 : /// 전화번호 설정 217 : @override 218 1 : Future<void> setPhone(String phone) async { 219 3 : _userAnalyticsTrees.forEach((tree) { 220 1 : tree.setPhone(phone); 221 : }); 222 : } 223 : 224 : /// uid 설정 225 : @override 226 1 : Future<void> setUid(String uid) async { 227 3 : _userAnalyticsTrees.forEach((tree) { 228 1 : tree.setUid(uid); 229 : }); 230 : } 231 : 232 : @override 233 1 : Future<void> union(Map<String, List<dynamic>> properties) async { 234 3 : _userAnalyticsTrees.forEach((tree) { 235 1 : tree.union(properties); 236 : }); 237 : } 238 : 239 : @override 240 1 : Future<void> increment(Map<String, num> properties) async { 241 3 : _userAnalyticsTrees.forEach((tree) { 242 1 : tree.increment(properties); 243 : }); 244 : } 245 : 246 : @override 247 1 : Future<void> setOnce(Map<String, dynamic> properties) async { 248 3 : _userAnalyticsTrees.forEach((tree) { 249 1 : tree.setOnce(properties); 250 : }); 251 : } 252 : 253 : @override 254 1 : Future<void> setUserName(value) async { 255 3 : _userAnalyticsTrees.forEach((tree) { 256 1 : tree.setUserName(value); 257 : }); 258 : } 259 : 260 : @override 261 1 : Future<void> reset() async { 262 3 : _userAnalyticsTrees.forEach((tree) { 263 1 : tree.reset(); 264 : }); 265 3 : _analyticsTrees.forEach((tree) { 266 1 : tree.reset(); 267 : }); 268 : } 269 : 270 : @override 271 1 : Future<void> flush() async { 272 3 : _userAnalyticsTrees.forEach((tree) { 273 1 : tree.flush(); 274 : }); 275 3 : _analyticsTrees.forEach((tree) { 276 1 : tree.flush(); 277 : }); 278 : } 279 : 280 : @override 281 0 : Future<void> setCurrentScreen( 282 : {String screenName, String screenClassOverride = 'Flutter'}) async { 283 0 : _analyticsTrees.forEach((tree) { 284 0 : tree.setCurrentScreen( 285 : screenName: screenName, screenClassOverride: screenClassOverride); 286 : }); 287 : } 288 : 289 : @override 290 1 : Future<void> timingEvent(String name) async { 291 3 : _analyticsTrees.forEach((tree) { 292 1 : tree.timingEvent(name); 293 : }); 294 : } 295 : }