Merge pull request #27 from dart-lang/devoncarew_st

add a library method to clean stack traces
diff --git a/changelog.md b/changelog.md
index 0009277..119692a 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## 0.0.4
+
+- Moved `sanitizeStacktrace` into the main library
+
 ## 0.0.3
 
 - Replaced optional positional arguments with named arguments
@@ -9,7 +13,7 @@
 ## 0.0.2
 
 - Fixed a bug in `analytics.sendTiming()`
- 
+
 ## 0.0.1
 
 - Initial version, created by Stagehand
diff --git a/lib/src/usage_impl.dart b/lib/src/usage_impl.dart
index 7a24819..6ee4e43 100644
--- a/lib/src/usage_impl.dart
+++ b/lib/src/usage_impl.dart
@@ -13,9 +13,6 @@
 
 final int _MAX_EXCEPTION_LENGTH = 100;
 
-// Matches file:/, non-ws, /, non-ws, .dart
-final RegExp _pathRegex = new RegExp(r'file:/\S+/(\S+\.dart)');
-
 String postEncode(Map<String, dynamic> map) {
   // &foo=bar
   return map.keys.map((key) {
@@ -25,30 +22,6 @@
 }
 
 /**
- * Santitize a string potentially containing file paths. This will remove all
- * but the last file name in order to remove any PII that may be contained in
- * the full file path. For example, this will shorten:
- *
- *     file:///Users/foobar/tmp/error.dart
- *
- * to
- *
- *     error.dart
- */
-String sanitizeFilePaths(String stackTrace) {
-  Iterable<Match> iter = _pathRegex.allMatches(stackTrace);
-  iter = iter.toList().reversed;
-
-  for (Match match in iter) {
-    String replacement = match.group(1);
-    stackTrace = stackTrace.substring(0, match.start)
-        + replacement + stackTrace.substring(match.end);
-  }
-
-  return stackTrace;
-}
-
-/**
  * A throttling algorithim. This models the throttling after a bucket with
  * water dripping into it at the rate of 1 drop per second. If the bucket has
  * water when an operation is requested, 1 drop of water is removed and the
diff --git a/lib/usage.dart b/lib/usage.dart
index 9ddcb9e..9496095 100644
--- a/lib/usage.dart
+++ b/lib/usage.dart
@@ -26,6 +26,9 @@
 
 import 'dart:async';
 
+// Matches file:/, non-ws, /, non-ws, .dart
+final RegExp _pathRegex = new RegExp(r'file:/\S+/(\S+\.dart)');
+
 /**
  * An interface to a Google Analytics session. [AnalyticsHtml] and [AnalyticsIO]
  * are concrete implementations of this interface. [AnalyticsMock] can be used
@@ -144,3 +147,36 @@
     return new Future.value();
   }
 }
+
+/**
+ * Santitize a stacktrace. This will shorten file paths in order to remove any
+ * PII that may be contained in the full file path. For example, this will
+ * shorten `file:///Users/foobar/tmp/error.dart` to `error.dart`.
+ *
+ * If [shorten] is `true`, this method will also attempt to compress the text
+ * of the stacktrace. GA has a 100 char limit on the text that can be sent for
+ * an exception. This will try and make those first 100 chars contain
+ * information useful to debugging the issue.
+ */
+String sanitizeStacktrace(dynamic st, {bool shorten: true}) {
+  String str = '${st}';
+
+  Iterable<Match> iter = _pathRegex.allMatches(str);
+  iter = iter.toList().reversed;
+
+  for (Match match in iter) {
+    String replacement = match.group(1);
+    str = str.substring(0, match.start)
+        + replacement + str.substring(match.end);
+  }
+
+  if (shorten) {
+    // Shorten the stacktrace up a bit.
+    str = str
+        .replaceAll('(package:', '(')
+        .replaceAll('(dart:', '(')
+        .replaceAll(new RegExp(r'\s+'), ' ');
+  }
+
+  return str;
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 6ab1556..21e61f2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@
 # BSD-style license that can be found in the LICENSE file.
 
 name: usage
-version: 0.0.4-dev
+version: 0.0.4
 description: A Google Analytics wrapper for both command-line and web apps.
 homepage: https://github.com/dart-lang/usage
 author: Dart Team <misc@dartlang.org>
diff --git a/readme.md b/readme.md
index 496ff0b..520c4b9 100644
--- a/readme.md
+++ b/readme.md
@@ -3,7 +3,7 @@
 `usage` is a wrapper around Google Analytics for both command-line apps and web
 apps.
 
-[![Build Status](https://travis-ci.org/dart-lang/usage.svg)](https://travis-ci.org/dart-lang/usage) 
+[![Build Status](https://travis-ci.org/dart-lang/usage.svg)](https://travis-ci.org/dart-lang/usage)
 [![Coverage Status](https://img.shields.io/coveralls/dart-lang/usage.svg)](https://coveralls.io/r/dart-lang/usage?branch=master)
 
 ## For web apps
diff --git a/test/usage_impl_test.dart b/test/usage_impl_test.dart
index 80b8690..6622825 100644
--- a/test/usage_impl_test.dart
+++ b/test/usage_impl_test.dart
@@ -54,21 +54,6 @@
     });
   });
 
-  group('sanitizeFilePaths', () {
-    test('replace file', () {
-      expect(sanitizeFilePaths(
-          '(file:///Users/foo/tmp/error.dart:3:13)'),
-          '(error.dart:3:13)');
-    });
-
-    test('replace files', () {
-      expect(sanitizeFilePaths(
-          'foo (file:///Users/foo/tmp/error.dart:3:13)\n'
-          'bar (file:///Users/foo/tmp/error.dart:3:13)'),
-          'foo (error.dart:3:13)\nbar (error.dart:3:13)');
-    });
-  });
-
   group('postEncode', () {
     test('simple', () {
       Map map = {'foo': 'bar', 'baz': 'qux norf'};
diff --git a/test/usage_test.dart b/test/usage_test.dart
index 5edfa45..e92caf3 100644
--- a/test/usage_test.dart
+++ b/test/usage_test.dart
@@ -19,4 +19,41 @@
       mock.setSessionValue('val', 'ue');
     });
   });
+
+  group('sanitizeStacktrace', () {
+    test('replace file', () {
+      expect(sanitizeStacktrace(
+          '(file:///Users/foo/tmp/error.dart:3:13)',
+          shorten: false),
+          '(error.dart:3:13)');
+    });
+
+    test('replace files', () {
+      expect(sanitizeStacktrace(
+          'foo (file:///Users/foo/tmp/error.dart:3:13)\n'
+          'bar (file:///Users/foo/tmp/error.dart:3:13)',
+          shorten: false),
+          'foo (error.dart:3:13)\nbar (error.dart:3:13)');
+    });
+
+    test('shorten 1', () {
+      expect(sanitizeStacktrace(
+          '(file:///Users/foo/tmp/error.dart:3:13)'),
+          '(error.dart:3:13)');
+    });
+
+    test('shorten 2', () {
+      expect(sanitizeStacktrace(
+          'foo (file:///Users/foo/tmp/error.dart:3:13)\n'
+          'bar (file:///Users/foo/tmp/error.dart:3:13)'),
+          'foo (error.dart:3:13) bar (error.dart:3:13)');
+    });
+
+    test('shorten 3', () {
+      expect(sanitizeStacktrace(
+          'foo (package:foo/foo.dart:3:13)\n'
+          'bar (dart:async/schedule_microtask.dart:41)'),
+          'foo (foo/foo.dart:3:13) bar (async/schedule_microtask.dart:41)');
+    });
+  });
 }