diff --git a/lib/src/io.dart b/lib/src/io.dart
index 39a854d846bd0e9181c552b00fa1ecff227a9a5a..c7e3b04625a1a91b023e39bcd87561f5a8509b27 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -104,14 +104,16 @@ Future<String> readTextFile(file) {
 /**
  * Creates [file] (which can either be a [String] or a [File]), and writes
  * [contents] to it. Completes when the file is written and closed.
+ *
+ * If [dontLogContents] is true, the contents of the file will never be logged.
  */
-Future<File> writeTextFile(file, String contents) {
+Future<File> writeTextFile(file, String contents, {dontLogContents: false}) {
   var path = _getPath(file);
   file = new File(path);
 
   // Sanity check: don't spew a huge file.
   log.io("Writing ${contents.length} characters to text file $path.");
-  if (contents.length < 1024 * 1024) {
+  if (!dontLogContents && contents.length < 1024 * 1024) {
     log.fine("Contents:\n$contents");
   }
 
@@ -517,7 +519,8 @@ class PubHttpClient extends http.BaseClient {
 
   Future<http.StreamedResponse> send(http.BaseRequest request) {
     log.io("Sending HTTP request $request.");
-    // TODO(rnystrom): Log request body when it's available and plaintext.
+    // TODO(rnystrom): Log request body when it's available and plaintext, but
+    // not when it contains OAuth2 credentials.
 
     // TODO(nweiz): remove this when issue 4061 is fixed.
     var stackTrace;
diff --git a/lib/src/oauth2.dart b/lib/src/oauth2.dart
index 78cd4c2766cf2d2fb0a577c0fc43e25c96a9a3c6..ec3d745f2d2a29af175592cc10d985102edfb6c6 100644
--- a/lib/src/oauth2.dart
+++ b/lib/src/oauth2.dart
@@ -139,7 +139,7 @@ Future _saveCredentials(SystemCache cache, Credentials credentials) {
   _credentials = credentials;
   var path = _credentialsFile(cache);
   return ensureDir(dirname(path)).chain((_) =>
-      writeTextFile(path, credentials.toJson()));
+      writeTextFile(path, credentials.toJson(), dontLogContents: true));
 }
 
 /// The path to the file in which the user's OAuth2 credentials are stored.