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 'stack_zone_specification.dart'; | |
11 import 'trace.dart'; | |
12 import 'utils.dart'; | |
13 | |
14 /// A function that handles errors in the zone wrapped by [Chain.capture]. | |
15 typedef void ChainHandler(error, Chain chain); | |
16 | |
17 /// A chain of stack traces. | |
18 /// | |
19 /// A stack chain is a collection of one or more stack traces that collectively | |
20 /// represent the path from [main] through nested function calls to a particular | |
21 /// code location, usually where an error was thrown. Multiple stack traces are | |
22 /// necessary when using asynchronous functions, since the program's stack is | |
23 /// reset as each asynchronous callback is run. | |
floitsch
2013/11/21 15:28:08
"as" feels wrong to me.
nweiz
2013/11/22 00:50:52
Changed to "before".
| |
24 /// | |
25 /// Stack chains can be automatically tracked using [Chain.capture]. This sets | |
26 /// up a new [Zone] in which the current stack chain is tracked and can be | |
27 /// accessed using [new Chain.current]. Any errors that would be top-leveled in | |
28 /// the zone can be handled, along with their associated chains, with the | |
29 /// `onError` callback. | |
30 /// | |
31 /// For the most part [Chain.capture] will notice when an error is thrown and | |
32 /// associate the correct stack chain with it; the chain can be accessed using | |
33 /// [new Chain.forTrace]. However, there are some cases where exceptions won't | |
34 /// be automatically detected: any [Future] constructor, | |
35 /// [Completer.completeError], [Stream.addError], and libraries that use these. | |
36 /// For these, all you need to do is wrap the Future or Stream in a call to | |
37 /// [Chain.track] and the errors will be tracked correctly. | |
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]; | |
floitsch
2013/11/21 15:28:08
missing indent.
nweiz
2013/11/22 00:50:52
I like using two spaces here because this is basic
| |
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 /// For the most part an error thrown in the zone will have the correct stack | |
65 /// chain associated with it. However, there are some cases where exceptions | |
66 /// won't be automatically detected: any [Future] constructor, | |
67 /// [Completer.completeError], [Stream.addError], and libraries that use | |
68 /// these. For these, all you need to do is wrap the Future or Stream in a | |
69 /// call to [Chain.track] and the errors will be tracked correctly. | |
70 /// | |
71 /// If [callback] returns a value, it will be returned by [capture] as well. | |
72 /// | |
73 /// Currently, capturing stack chains doesn't work when using dart2js due to | |
74 /// issues [15171] and [15105]. Stack chains reported on dart2js will contain only | |
floitsch
2013/11/21 15:28:08
long line.
nweiz
2013/11/22 00:50:52
Done.
| |
75 /// one trace. | |
76 /// | |
77 /// [15171]: https://code.google.com/p/dart/issues/detail?id=15171 | |
78 /// [15105]: https://code.google.com/p/dart/issues/detail?id=15105 | |
79 static capture(callback(), {ChainHandler onError}) { | |
80 var spec = new StackZoneSpecification(onError); | |
81 return runZoned(callback, zoneSpecification: spec.toSpec(), zoneValues: { | |
82 #stack_trace.stack_zone.spec: spec | |
83 }); | |
84 } | |
85 | |
86 /// Ensures that any errors emitted by [futureOrStream] have the correct stack | |
87 /// chain information associated with them. | |
88 /// | |
89 /// For the most part an error thrown within a [capture] zone will have the | |
90 /// correct stack chain automatically associated with it. However, there are | |
91 /// some cases where exceptions won't be automatically detected: any [Future] | |
92 /// constructor, [Completer.completeError], [Stream.addError], and libraries | |
93 /// that use these. | |
94 /// | |
95 /// This returns a [Future] or [Stream] that will emit the same values and | |
96 /// errors as [futureOrStream]. The only exception is that if [futureOrStream] | |
97 /// emits an error without a stack trace, one will be added in the return | |
98 /// value. | |
99 /// | |
100 /// If this is called outside of a [capture] zone, it just returns | |
101 /// [futureOrStream] as-is. | |
102 /// | |
103 /// As the name suggests, [futureOrStream] may be either a [Future] or a | |
104 /// [Stream]. | |
105 static track(futureOrStream) { | |
106 if (_currentSpec == null) return futureOrStream; | |
107 if (futureOrStream is Future) { | |
108 return _currentSpec.trackFuture(futureOrStream, 1); | |
109 } else { | |
110 return _currentSpec.trackStream(futureOrStream, 1); | |
111 } | |
112 } | |
113 | |
114 /// Returns the current stack chain. | |
115 /// | |
116 /// By default, the first frame of the first trace will be the line where | |
117 /// [Chain.current] is called. If [level] is passed, the first trace will | |
118 /// start that many frames up instead. | |
119 /// | |
120 /// If this is called outside of a [capture] zone, it just returns a | |
121 /// single-trace chain. | |
122 factory Chain.current([int level=0]) { | |
123 if (_currentSpec != null) return _currentSpec.currentChain(level + 1); | |
124 return new Chain([new Trace.current(level + 1)]); | |
125 } | |
126 | |
127 /// Returns the stack chain associated with [trace]. | |
128 /// | |
129 /// The first stack trace in the returned chain will always be [trace] | |
130 /// (converted to a [Trace] if necessary). If there is no chain associated | |
131 /// with [trace] or if this is called outside of a [capture] zone, this just | |
132 /// returns a single-trace chain containing [trace]. | |
133 /// | |
134 /// If [trace] is already a [Chain], it will be returned as-is. | |
135 factory Chain.forTrace(StackTrace trace) { | |
136 if (trace is Chain) return trace; | |
137 if (_currentSpec == null) return new Chain([new Trace.from(trace)]); | |
138 return _currentSpec.chainFor(trace); | |
139 } | |
140 | |
141 /// Parses a string representation of a stack chain. | |
142 /// | |
143 /// Specifically, this parses the output of [Chain.toString]. | |
144 factory Chain.parse(String chain) => | |
145 new Chain(chain.split(_GAP).map((trace) => new Trace.parseFriendly(trace))); | |
146 | |
147 /// Returns a new [Chain] comprised of [traces]. | |
148 Chain(Iterable<Trace> traces) | |
149 : traces = new UnmodifiableListView<Trace>(traces.toList()); | |
150 | |
151 /// Returns a terser version of [this]. | |
152 /// | |
153 /// This calls [Trace.terse] on every trace in [traces], and discards any | |
154 /// trace that contain only internal frames. | |
155 Chain get terse { | |
156 return new Chain(traces.map((trace) => trace.terse).where((trace) { | |
157 // Ignore traces that contain only internal processing. | |
158 return trace.frames.length > 1; | |
159 })); | |
160 } | |
161 | |
162 /// Converts [this] to a [Trace]. | |
163 /// | |
164 /// The trace version of a chain is just the concatenation of all the traces | |
165 /// in the chain. | |
166 Trace toTrace() => new Trace(flatten(traces.map((trace) => trace.frames))); | |
167 | |
168 String toString() => traces.join(_GAP); | |
169 } | |
OLD | NEW |