OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library stack_trace.chain; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:collection'; |
| 9 |
| 10 import 'frame.dart'; |
| 11 import 'stack_zone_specification.dart'; |
| 12 import 'trace.dart'; |
| 13 import 'utils.dart'; |
| 14 |
| 15 /// A function that handles errors in the zone wrapped by [Chain.capture]. |
| 16 typedef void ChainHandler(error, Chain chain); |
| 17 |
| 18 /// A chain of stack traces. |
| 19 /// |
| 20 /// A stack chain is a collection of one or more stack traces that collectively |
| 21 /// represent the path from [main] through nested function calls to a particular |
| 22 /// code location, usually where an error was thrown. Multiple stack traces are |
| 23 /// necessary when using asynchronous functions, since the program's stack is |
| 24 /// reset before each asynchronous callback is run. |
| 25 /// |
| 26 /// Stack chains can be automatically tracked using [Chain.capture]. This sets |
| 27 /// up a new [Zone] in which the current stack chain is tracked and can be |
| 28 /// accessed using [new Chain.current]. Any errors that would be top-leveled in |
| 29 /// the zone can be handled, along with their associated chains, with the |
| 30 /// `onError` callback. For example: |
| 31 /// |
| 32 /// Chain.capture(() { |
| 33 /// // ... |
| 34 /// }, onError: (error, stackChain) { |
| 35 /// print("Caught error $error\n" |
| 36 /// "$stackChain"); |
| 37 /// }); |
| 38 class Chain implements StackTrace { |
| 39 /// The line used in the string representation of stack chains to represent |
| 40 /// the gap between traces. |
| 41 static const _GAP = '===== asynchronous gap ===========================\n'; |
| 42 |
| 43 /// The stack traces that make up this chain. |
| 44 /// |
| 45 /// Like the frames in a stack trace, the traces are ordered from most local |
| 46 /// to least local. The first one is the trace where the actual exception was |
| 47 /// raised, the second one is where that callback was scheduled, and so on. |
| 48 final List<Trace> traces; |
| 49 |
| 50 /// The [StackZoneSpecification] for the current zone. |
| 51 static StackZoneSpecification get _currentSpec => |
| 52 Zone.current[#stack_trace.stack_zone.spec]; |
| 53 |
| 54 /// Runs [callback] in a [Zone] in which the current stack chain is tracked |
| 55 /// and automatically associated with (most) errors. |
| 56 /// |
| 57 /// If [onError] is passed, any error in the zone that would otherwise go |
| 58 /// unhandled is passed to it, along with the [Chain] associated with that |
| 59 /// error. Note that if [callback] produces multiple unhandled errors, |
| 60 /// [onError] may be called more than once. If [onError] isn't passed, the |
| 61 /// parent Zone's `unhandledErrorHandler` will be called with the error and |
| 62 /// its chain. |
| 63 /// |
| 64 /// Note that even if [onError] isn't passed, this zone will still be an error |
| 65 /// zone. This means that any errors that would cross the zone boundary are |
| 66 /// considered unhandled. |
| 67 /// |
| 68 /// If [callback] returns a value, it will be returned by [capture] as well. |
| 69 /// |
| 70 /// Currently, capturing stack chains doesn't work when using dart2js due to |
| 71 /// issues [15171] and [15105]. Stack chains reported on dart2js will contain |
| 72 /// only one trace. |
| 73 /// |
| 74 /// [15171]: https://code.google.com/p/dart/issues/detail?id=15171 |
| 75 /// [15105]: https://code.google.com/p/dart/issues/detail?id=15105 |
| 76 static capture(callback(), {ChainHandler onError}) { |
| 77 var spec = new StackZoneSpecification(onError); |
| 78 return runZoned(() { |
| 79 try { |
| 80 return callback(); |
| 81 } catch (error, stackTrace) { |
| 82 // TODO(nweiz): Don't special-case this when issue 19566 is fixed. |
| 83 return Zone.current.handleUncaughtError(error, stackTrace); |
| 84 } |
| 85 }, zoneSpecification: spec.toSpec(), zoneValues: { |
| 86 #stack_trace.stack_zone.spec: spec |
| 87 }); |
| 88 } |
| 89 |
| 90 /// Returns [futureOrStream] unmodified. |
| 91 /// |
| 92 /// Prior to Dart 1.7, this was necessary to ensure that stack traces for |
| 93 /// exceptions reported with [Completer.completeError] and |
| 94 /// [StreamController.addError] were tracked correctly. |
| 95 @Deprecated("Chain.track is not necessary in Dart 1.7+.") |
| 96 static track(futureOrStream) => futureOrStream; |
| 97 |
| 98 /// Returns the current stack chain. |
| 99 /// |
| 100 /// By default, the first frame of the first trace will be the line where |
| 101 /// [Chain.current] is called. If [level] is passed, the first trace will |
| 102 /// start that many frames up instead. |
| 103 /// |
| 104 /// If this is called outside of a [capture] zone, it just returns a |
| 105 /// single-trace chain. |
| 106 factory Chain.current([int level=0]) { |
| 107 if (_currentSpec != null) return _currentSpec.currentChain(level + 1); |
| 108 return new Chain([new Trace.current(level + 1)]); |
| 109 } |
| 110 |
| 111 /// Returns the stack chain associated with [trace]. |
| 112 /// |
| 113 /// The first stack trace in the returned chain will always be [trace] |
| 114 /// (converted to a [Trace] if necessary). If there is no chain associated |
| 115 /// with [trace] or if this is called outside of a [capture] zone, this just |
| 116 /// returns a single-trace chain containing [trace]. |
| 117 /// |
| 118 /// If [trace] is already a [Chain], it will be returned as-is. |
| 119 factory Chain.forTrace(StackTrace trace) { |
| 120 if (trace is Chain) return trace; |
| 121 if (_currentSpec == null) return new Chain([new Trace.from(trace)]); |
| 122 return _currentSpec.chainFor(trace); |
| 123 } |
| 124 |
| 125 /// Parses a string representation of a stack chain. |
| 126 /// |
| 127 /// Specifically, this parses the output of [Chain.toString]. |
| 128 factory Chain.parse(String chain) => |
| 129 new Chain(chain.split(_GAP).map((trace) => new Trace.parseFriendly(trace))); |
| 130 |
| 131 /// Returns a new [Chain] comprised of [traces]. |
| 132 Chain(Iterable<Trace> traces) |
| 133 : traces = new UnmodifiableListView<Trace>(traces.toList()); |
| 134 |
| 135 /// Returns a terser version of [this]. |
| 136 /// |
| 137 /// This calls [Trace.terse] on every trace in [traces], and discards any |
| 138 /// trace that contain only internal frames. |
| 139 Chain get terse { |
| 140 var terseTraces = traces.map((trace) => trace.terse); |
| 141 var nonEmptyTraces = terseTraces.where((trace) { |
| 142 // Ignore traces that contain only internal processing. |
| 143 return trace.frames.length > 1; |
| 144 }); |
| 145 |
| 146 // If all the traces contain only internal processing, preserve the last |
| 147 // (top-most) one so that the chain isn't empty. |
| 148 if (nonEmptyTraces.isEmpty && terseTraces.isNotEmpty) { |
| 149 return new Chain([terseTraces.last]); |
| 150 } |
| 151 |
| 152 return new Chain(nonEmptyTraces); |
| 153 } |
| 154 |
| 155 /// Returns a new [Chain] based on [this] where multiple stack frames matching |
| 156 /// [predicate] are folded together. |
| 157 /// |
| 158 /// This means that whenever there are multiple frames in a row that match |
| 159 /// [predicate], only the last one is kept. In addition, traces that are |
| 160 /// composed entirely of frames matching [predicate] are omitted. |
| 161 /// |
| 162 /// This is useful for limiting the amount of library code that appears in a |
| 163 /// stack trace by only showing user code and code that's called by user code. |
| 164 Chain foldFrames(bool predicate(Frame frame)) { |
| 165 var foldedTraces = traces.map((trace) => trace.foldFrames(predicate)); |
| 166 var nonEmptyTraces = foldedTraces.where((trace) { |
| 167 // Ignore traces that contain only folded frames. These traces will be |
| 168 // folded into a single frame each. |
| 169 return trace.frames.length > 1; |
| 170 }); |
| 171 |
| 172 // If all the traces contain only internal processing, preserve the last |
| 173 // (top-most) one so that the chain isn't empty. |
| 174 if (nonEmptyTraces.isEmpty && foldedTraces.isNotEmpty) { |
| 175 return new Chain([foldedTraces.last]); |
| 176 } |
| 177 |
| 178 return new Chain(nonEmptyTraces); |
| 179 } |
| 180 |
| 181 /// Converts [this] to a [Trace]. |
| 182 /// |
| 183 /// The trace version of a chain is just the concatenation of all the traces |
| 184 /// in the chain. |
| 185 Trace toTrace() => new Trace(flatten(traces.map((trace) => trace.frames))); |
| 186 |
| 187 String toString() => traces.join(_GAP); |
| 188 } |
OLD | NEW |