Add option to automatically record stack trace in logging (fixes #2)

R=jmesserly@google.com

Review URL: https://codereview.chromium.org//1132533003
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d072302..b0b759e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.11.1
+
+* Add support for automatically logging the stack trace on error messages. Note
+  this can be expensive, so it is off by default.
+
 ## 0.11.0
 
 * Revert change in `0.10.0`. `stackTrace` must be an instance of `StackTrace`.
diff --git a/lib/logging.dart b/lib/logging.dart
index 7654885..f108ed3 100644
--- a/lib/logging.dart
+++ b/lib/logging.dart
@@ -16,6 +16,12 @@
 bool hierarchicalLoggingEnabled = false;
 
 /**
+ * Automatically record stack traces for any message of this level or above.
+ * Because this is expensive, this is off by default.
+ */
+Level recordStackTraceAtLevel = Level.OFF;
+
+/**
  * Level for the root-logger. This will be the level of all loggers if
  * [hierarchicalLoggingEnabled] is false.
  */
@@ -147,11 +153,15 @@
   void log(Level logLevel, message,
       [Object error, StackTrace stackTrace, Zone zone]) {
     if (isLoggable(logLevel)) {
-      // If message is a Function, evaluate it.
       if (message is Function) message = message();
-      // If message is still not a String, call toString().
       if (message is! String) message = message.toString();
-      // Only record the current zone if it was not given.
+      if (stackTrace == null && logLevel >= recordStackTraceAtLevel) {
+        try {
+          throw "autogenerated stack trace for $logLevel $message";
+        } catch (e, t) {
+          stackTrace = t;
+        }
+      }
       if (zone == null) zone = Zone.current;
 
       var record =
diff --git a/pubspec.yaml b/pubspec.yaml
index 2b7d130..1236b30 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: logging
-version: 0.11.0
+version: 0.11.1
 author: Dart Team <misc@dartlang.org>
 description: >
   Provides APIs for debugging and error logging. This library introduces
diff --git a/test/logging_test.dart b/test/logging_test.dart
index 94696d8..e27ee2f 100644
--- a/test/logging_test.dart
+++ b/test/logging_test.dart
@@ -520,4 +520,54 @@
           equals(['INFO: 5', 'INFO: false', 'INFO: [1, 2, 3]', 'INFO: 10',]));
     });
   });
+
+  group('recordStackTraceAtLevel', () {
+    var root = Logger.root;
+    tearDown(() {
+      recordStackTraceAtLevel = Level.OFF;
+      root.clearListeners();
+    });
+
+    test('no stack trace by default', () {
+      var records = new List<LogRecord>();
+      root.onRecord.listen(records.add);
+      root.severe('hello');
+      root.warning('hello');
+      root.info('hello');
+      expect(records, hasLength(3));
+      expect(records[0].stackTrace, isNull);
+      expect(records[1].stackTrace, isNull);
+      expect(records[2].stackTrace, isNull);
+    });
+
+    test('trace recorded only on requested levels', () {
+      var records = new List<LogRecord>();
+      recordStackTraceAtLevel = Level.WARNING;
+      root.onRecord.listen(records.add);
+      root.severe('hello');
+      root.warning('hello');
+      root.info('hello');
+      expect(records, hasLength(3));
+      expect(records[0].stackTrace, isNotNull);
+      expect(records[1].stackTrace, isNotNull);
+      expect(records[2].stackTrace, isNull);
+    });
+
+    test('provided trace is used if given', () {
+      var trace;
+      try {
+        throw 'trace';
+      } catch(e, t) {
+        trace = t;
+      }
+      var records = new List<LogRecord>();
+      recordStackTraceAtLevel = Level.WARNING;
+      root.onRecord.listen(records.add);
+      root.severe('hello');
+      root.warning('hello', 'a', trace);
+      expect(records, hasLength(2));
+      expect(records[0].stackTrace, isNot(equals(trace)));
+      expect(records[1].stackTrace, trace);
+    });
+  });
 }