Add more docs about stack chains to the stack trace package. BUG= R=kathyw@google.com Review URL: https://codereview.chromium.org//201183006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/stack_trace@34199 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/stack_trace/README.md b/pkgs/stack_trace/README.md index 6a8112a..492a52a 100644 --- a/pkgs/stack_trace/README.md +++ b/pkgs/stack_trace/README.md
@@ -55,3 +55,163 @@ pkg/stack_trace/lib/stack_trace.dart 24:28 format test.dart 21:29 main.<fn> dart:async Timer.Timer.<fn> + +## Stack Chains + +This library also provides the ability to capture "stack chains" with the +`Chain` class. When writing asynchronous code, a single stack trace isn't very +useful, since the call stack is unwound every time something async happens. A +stack chain tracks stack traces through asynchronous calls, so that you can see +the full path from `main` down to the error. + +To use stack chains, just wrap the code that you want to track in +`Chain.capture`. This will create a new [Zone][] in which stack traces are +recorded and woven into chains every time an asynchronous call occurs. Zones are +sticky, too, so any asynchronous operations started in the `Chain.capture` +callback will have their chains tracked, as will asynchronous operations they +start and so on. + +Here's an example of some code that doesn't capture its stack chains: + +```dart +import 'dart:async'; + +void main() { + scheduleAsync(); +} + +void scheduleAsync() { + return new Future.delayed(new Duration(seconds: 1)) + .then((_) => runAsync()); +} + +void runAsync() { + throw 'oh no!'; +} +``` + +If we run this, it prints the following: + + Uncaught Error: oh no! + Stack Trace: + #0 runAsync (file:///usr/local/google-old/home/goog/dart/dart/test.dart:13:3) + #1 scheduleAsync.<anonymous closure> (file:///usr/local/google-old/home/goog/dart/dart/test.dart:9:28) + #2 _rootRunUnary (dart:async/zone.dart:717) + #3 _RootZone.runUnary (dart:async/zone.dart:854) + #4 _Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:488) + #5 _Future._propagateToListeners (dart:async/future_impl.dart:571) + #6 _Future._complete (dart:async/future_impl.dart:317) + #7 _SyncCompleter.complete (dart:async/future_impl.dart:44) + #8 Future.Future.delayed.<anonymous closure> (dart:async/future.dart:219) + #9 _createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:11) + #10 _handleTimeout (dart:io/timer_impl.dart:292) + #11 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:115) + +Notice how there's no mention of `main` in that stack trace. All we know is that +the error was in `runAsync`; we don't know why `runAsync` was called. + +Now let's look at the same code with stack chains captured: + +```dart +import 'dart:async'; + +import 'package:stack_trace/stack_trace.dart'; + +void main() { + Chain.capture(() { + scheduleAsync(); + }); +} + +void scheduleAsync() { + new Future.delayed(new Duration(seconds: 1)) + .then((_) => runAsync()); +} + +void runAsync() { + throw 'oh no!'; +} +``` + +Now if we run it, it prints this: + + Uncaught Error: oh no! + Stack Trace: + test.dart 17:3 runAsync + test.dart 13:28 scheduleAsync.<fn> + package:stack_trace/src/stack_zone_specification.dart 129:26 registerUnaryCallback.<fn>.<fn> + package:stack_trace/src/stack_zone_specification.dart 174:15 StackZoneSpecification._run + package:stack_trace/src/stack_zone_specification.dart 177:7 StackZoneSpecification._run + package:stack_trace/src/stack_zone_specification.dart 175:7 StackZoneSpecification._run + package:stack_trace/src/stack_zone_specification.dart 129:18 registerUnaryCallback.<fn> + dart:async/zone.dart 717 _rootRunUnary + dart:async/zone.dart 449 _ZoneDelegate.runUnary + dart:async/zone.dart 654 _CustomizedZone.runUnary + dart:async/future_impl.dart 488 _Future._propagateToListeners.handleValueCallback + dart:async/future_impl.dart 571 _Future._propagateToListeners + dart:async/future_impl.dart 317 _Future._complete + dart:async/future_impl.dart 44 _SyncCompleter.complete + dart:async/future.dart 219 Future.Future.delayed.<fn> + package:stack_trace/src/stack_zone_specification.dart 174:15 StackZoneSpecification._run + package:stack_trace/src/stack_zone_specification.dart 119:52 registerCallback.<fn> + dart:async/zone.dart 706 _rootRun + dart:async/zone.dart 440 _ZoneDelegate.run + dart:async/zone.dart 650 _CustomizedZone.run + dart:async/zone.dart 561 _BaseZone.runGuarded + dart:async/zone.dart 586 _BaseZone.bindCallback.<fn> + package:stack_trace/src/stack_zone_specification.dart 174:15 StackZoneSpecification._run + package:stack_trace/src/stack_zone_specification.dart 119:52 registerCallback.<fn> + dart:async/zone.dart 710 _rootRun + dart:async/zone.dart 440 _ZoneDelegate.run + dart:async/zone.dart 650 _CustomizedZone.run + dart:async/zone.dart 561 _BaseZone.runGuarded + dart:async/zone.dart 586 _BaseZone.bindCallback.<fn> + dart:async-patch/timer_patch.dart 11 _createTimer.<fn> + dart:io/timer_impl.dart 292 _handleTimeout + dart:isolate-patch/isolate_patch.dart 115 _RawReceivePortImpl._handleMessage + ===== asynchronous gap =========================== + dart:async/zone.dart 476 _ZoneDelegate.registerUnaryCallback + dart:async/zone.dart 666 _CustomizedZone.registerUnaryCallback + dart:async/future_impl.dart 164 _Future._Future._then + dart:async/future_impl.dart 187 _Future.then + test.dart 13:12 scheduleAsync + test.dart 7:18 main.<fn> + dart:async/zone.dart 710 _rootRun + dart:async/zone.dart 440 _ZoneDelegate.run + dart:async/zone.dart 650 _CustomizedZone.run + dart:async/zone.dart 944 runZoned + package:stack_trace/src/chain.dart 93:20 Chain.capture + test.dart 6:16 main + dart:isolate-patch/isolate_patch.dart 216 _startIsolate.isolateStartHandler + dart:isolate-patch/isolate_patch.dart 115 _RawReceivePortImpl._handleMessage + +That's a lot of text! If you look closely, though, you can see that `main` is +listed in the first trace in the chain. + +Thankfully, you can call `Chain.terse` just like `Trace.terse` to get rid of all +the frames you don't care about. The terse version of the stack chain above is +this: + + test.dart 17:3 runAsync + test.dart 13:28 scheduleAsync.<fn> + dart:isolate _RawReceivePortImpl._handleMessage + ===== asynchronous gap =========================== + dart:async _Future.then + test.dart 13:12 scheduleAsync + test.dart 7:18 main.<fn> + package:stack_trace/src/chain.dart 93:20 Chain.capture + test.dart 6:16 main + dart:isolate _RawReceivePortImpl._handleMessage + +That's a lot easier to understand! + +### `Chain.track` + +For the most part `Chain.capture` will notice when an error is thrown and +associate the correct stack chain with it. However, there are some cases where +exceptions won't be automatically detected: any `Future` constructor, +`Completer.completeError`, `Stream.addError`, and libraries that use these such +as `dart:io` and `dart:async`. For these, all you need to do is wrap the Future +or Stream in a call to `Chain.track` and the errors will be tracked correctly. + +[Zone]: https://api.dartlang.org/apidocs/channels/stable/#dart-async.Zone
diff --git a/pkgs/stack_trace/lib/src/chain.dart b/pkgs/stack_trace/lib/src/chain.dart index 2500e5e..a097bba 100644 --- a/pkgs/stack_trace/lib/src/chain.dart +++ b/pkgs/stack_trace/lib/src/chain.dart
@@ -27,7 +27,14 @@ /// up a new [Zone] in which the current stack chain is tracked and can be /// accessed using [new Chain.current]. Any errors that would be top-leveled in /// the zone can be handled, along with their associated chains, with the -/// `onError` callback. +/// `onError` callback. For example: +/// +/// Chain.capture(() { +/// // ... +/// }, onError: (error, stackChain) { +/// print("Caught error $error\n" +/// "$stackChain"); +/// }); /// /// For the most part [Chain.capture] will notice when an error is thrown and /// associate the correct stack chain with it; the chain can be accessed using