Bug 725525 - Part 1: simplify and remove excess logging in Sync.
authorRichard Newman <rnewman@mozilla.com>
Wed, 15 Feb 2012 22:05:52 -0800
changeset 89864 7282e6d27e2c348303851d81337fab8ec5d11ae7
parent 89863 6ed5b46b7afe1d1d010cee0a17a72d7b9de3b213
child 89865 99c336b7d8708d5fc926a76152010966d93be17e
push idunknown
push userunknown
push dateunknown
bugs725525
milestone13.0a1
Bug 725525 - Part 1: simplify and remove excess logging in Sync.
mobile/android/base/sync/DelayedWorkTracker.java
mobile/android/base/sync/Logger.java
mobile/android/base/sync/SynchronizerConfiguration.java
mobile/android/base/sync/Utils.java
mobile/android/base/sync/jpake/JPakeClient.java
mobile/android/base/sync/repositories/RepositorySession.java
mobile/android/base/sync/repositories/Server11RepositorySession.java
mobile/android/base/sync/repositories/StoreTrackingRepositorySession.java
mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java
mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java
mobile/android/base/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java
mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java
mobile/android/base/sync/repositories/android/RepoUtils.java
mobile/android/base/sync/repositories/domain/BookmarkRecord.java
mobile/android/base/sync/repositories/domain/HistoryRecord.java
mobile/android/base/sync/setup/SyncAuthenticatorService.java
mobile/android/base/sync/stage/FetchInfoCollectionsStage.java
mobile/android/base/sync/synchronizer/ConcurrentRecordConsumer.java
mobile/android/base/sync/synchronizer/RecordsChannel.java
mobile/android/base/sync/synchronizer/Synchronizer.java
mobile/android/sync/java-sources.mn
--- a/mobile/android/base/sync/DelayedWorkTracker.java
+++ b/mobile/android/base/sync/DelayedWorkTracker.java
@@ -32,39 +32,37 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.sync;
 
-import android.util.Log;
-
 /**
  * A little class to allow us to maintain a count of extant
  * things (in our case, callbacks that need to fire), and
  * some work that we want done when that count hits 0.
  *
  * @author rnewman
  *
  */
 public class DelayedWorkTracker {
   private static final String LOG_TAG = "DelayedWorkTracker";
   protected Runnable workItem = null;
   protected int outstandingCount = 0;
 
   public int incrementOutstanding() {
-    Log.d(LOG_TAG, "Incrementing outstanding.");
+    Logger.trace(LOG_TAG, "Incrementing outstanding.");
     synchronized(this) {
       return ++outstandingCount;
     }
   }
   public int decrementOutstanding() {
-    Log.d(LOG_TAG, "Decrementing outstanding.");
+    Logger.trace(LOG_TAG, "Decrementing outstanding.");
     Runnable job = null;
     int count;
     synchronized(this) {
       if ((count = --outstandingCount) == 0 &&
           workItem != null) {
         job = workItem;
         workItem = null;
       } else {
@@ -76,27 +74,27 @@ public class DelayedWorkTracker {
     return getOutstandingOperations();
   }
   public int getOutstandingOperations() {
     synchronized(this) {
       return outstandingCount;
     }
   }
   public void delayWorkItem(Runnable item) {
-    Log.d(LOG_TAG, "delayWorkItem.");
+    Logger.trace(LOG_TAG, "delayWorkItem.");
     boolean runnableNow = false;
     synchronized(this) {
-      Log.d(LOG_TAG, "outstandingCount: " + outstandingCount);
+      Logger.trace(LOG_TAG, "outstandingCount: " + outstandingCount);
       if (outstandingCount == 0) {
         runnableNow = true;
       } else {
         if (workItem != null) {
           throw new IllegalStateException("Work item already set!");
         }
         workItem = item;
       }
     }
     if (runnableNow) {
-      Log.d(LOG_TAG, "Running item now.");
+      Logger.trace(LOG_TAG, "Running item now.");
       item.run();
     }
   }
 }
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sync/Logger.java
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.sync;
+
+import android.util.Log;
+
+public class Logger {
+
+  // For extra debugging.
+  public static boolean LOG_PERSONAL_INFORMATION = false;
+
+  // If true, log to System.out as well as using Android's Log.* calls.
+  public static boolean LOG_TO_STDOUT = false;
+
+  public static void logToStdout(String... s) {
+    if (LOG_TO_STDOUT) {
+      for (String string : s) {
+        System.out.print(string);
+      }
+      System.out.println("");
+    }
+  }
+
+  public static void error(String logTag, String message) {
+    Logger.error(logTag, message, null);
+  }
+
+  public static void error(String logTag, String message, Throwable error) {
+    logToStdout(logTag, " :: ERROR: ", message);
+    if (!Log.isLoggable(logTag, Log.ERROR)) {
+      return;
+    }
+    Log.e(logTag, message, error);
+  }
+
+  public static void warn(String logTag, String message) {
+    Logger.warn(logTag, message, null);
+  }
+
+  public static void warn(String logTag, String message, Throwable error) {
+    logToStdout(logTag, " :: WARN: ", message);
+    if (!Log.isLoggable(logTag, Log.WARN)) {
+      return;
+    }
+    Log.w(logTag, message, error);
+  }
+
+  public static void info(String logTag, String message) {
+    logToStdout(logTag, " :: INFO: ", message);
+    if (!Log.isLoggable(logTag, Log.INFO)) {
+      return;
+    }
+    Log.i(logTag, message);
+  }
+
+  public static void debug(String logTag, String message) {
+    Logger.debug(logTag, message, null);
+  }
+
+  public static void debug(String logTag, String message, Throwable error) {
+    logToStdout(logTag, " :: DEBUG: ", message);
+    if (!Log.isLoggable(logTag, Log.DEBUG)) {
+      return;
+    }
+    Log.d(logTag, message, error);
+  }
+
+  public static void trace(String logTag, String message) {
+    logToStdout(logTag, " :: TRACE: ", message);
+    if (!Log.isLoggable(logTag, Log.VERBOSE)) {
+      return;
+    }
+    Log.v(logTag, message);
+  }
+
+  public static void pii(String logTag, String message) {
+    if (LOG_PERSONAL_INFORMATION) {
+      Logger.debug(logTag, "$$PII$$: " + message);
+    }
+  }
+}
--- a/mobile/android/base/sync/SynchronizerConfiguration.java
+++ b/mobile/android/base/sync/SynchronizerConfiguration.java
@@ -42,17 +42,17 @@ import java.io.IOException;
 import org.json.simple.parser.ParseException;
 import org.mozilla.gecko.sync.SyncConfiguration.ConfigurationBranch;
 import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
 
 import android.content.SharedPreferences.Editor;
 import android.util.Log;
 
 public class SynchronizerConfiguration {
-  private static final String LOG_TAG = "SynchronizerConfiguration";
+  private static final String LOG_TAG = "SynczrConfiguration";
 
   public String syncID;
   public RepositorySessionBundle remoteBundle;
   public RepositorySessionBundle localBundle;
 
   public SynchronizerConfiguration(ConfigurationBranch config) throws NonObjectJSONException, IOException, ParseException {
     this.load(config);
   }
--- a/mobile/android/base/sync/Utils.java
+++ b/mobile/android/base/sync/Utils.java
@@ -37,76 +37,35 @@
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.sync;
 
 import java.io.UnsupportedEncodingException;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
 import java.util.HashMap;
-import java.security.SecureRandom;
 
 import org.mozilla.apache.commons.codec.binary.Base32;
 import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.sync.crypto.Cryptographer;
 
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.util.Log;
 
 public class Utils {
 
   private static final String LOG_TAG = "Utils";
 
   private static SecureRandom sharedSecureRandom = new SecureRandom();
 
   // See <http://developer.android.com/reference/android/content/Context.html#getSharedPreferences%28java.lang.String,%20int%29>
   public static final int SHARED_PREFERENCES_MODE = 0;
 
-
-  // We don't really have a trace logger, so use this to toggle
-  // some debug logging.
-  // This is awful. I'm so sorry.
-  public static boolean ENABLE_TRACE_LOGGING = true;
-
-  // If true, log to System.out as well as using Android's Log.* calls.
-  public static boolean LOG_TO_STDOUT = false;
-  public static void logToStdout(String... s) {
-    if (LOG_TO_STDOUT) {
-      for (String string : s) {
-        System.out.print(string);
-      }
-      System.out.println("");
-    }
-  }
-
-  public static void error(String logTag, String message) {
-    logToStdout(logTag, " :: ERROR: ", message);
-    Log.i(logTag, message);
-  }
-
-  public static void info(String logTag, String message) {
-    logToStdout(logTag, " :: INFO: ", message);
-    Log.i(logTag, message);
-  }
-
-  public static void debug(String logTag, String message) {
-    logToStdout(logTag, " :: DEBUG: ", message);
-    Log.d(logTag, message);
-  }
-
-  public static void trace(String logTag, String message) {
-    if (!ENABLE_TRACE_LOGGING) {
-      return;
-    }
-    logToStdout(logTag, " :: TRACE: ", message);
-    Log.d(logTag, message);
-  }
-
   public static String generateGuid() {
     byte[] encodedBytes = Base64.encodeBase64(generateRandomBytes(9), false);
     return new String(encodedBytes).replace("+", "-").replace("/", "_");
   }
 
   /*
    * Helper to generate secure random bytes.
    *
@@ -137,103 +96,99 @@ public class Utils {
   }
 
   /*
    * Helper to convert Byte Array to a Hex String
    * Input: byte[] array
    * Output: Hex string
    */
   public static String byte2hex(byte[] b) {
-  
-      // String Buffer can be used instead
-      String hs = "";
-      String stmp = "";
-  
-      for (int n = 0; n < b.length; n++) {
-          stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
-  
-          if (stmp.length() == 1) {
-              hs = hs + "0" + stmp;
-          } else {
-              hs = hs + stmp;
-          }
-  
-          if (n < b.length - 1) {
-              hs = hs + "";
-          }
+    // StringBuffer should be used instead.
+    String hs = "";
+    String stmp;
+
+    for (int n = 0; n < b.length; n++) {
+      stmp = java.lang.Integer.toHexString(b[n] & 0XFF);
+
+      if (stmp.length() == 1) {
+        hs = hs + "0" + stmp;
+      } else {
+        hs = hs + stmp;
       }
-  
-      return hs;
+
+      if (n < b.length - 1) {
+        hs = hs + "";
+      }
+    }
+
+    return hs;
   }
 
   /*
    * Helper for array concatenation.
    * Input: At least two byte[]
    * Output: A concatenated version of them
    */
   public static byte[] concatAll(byte[] first, byte[]... rest) {
-      int totalLength = first.length;
-      for (byte[] array : rest) {
-          totalLength += array.length;
-      }
-  
-      byte[] result = new byte[totalLength];
-      int offset = first.length;
+    int totalLength = first.length;
+    for (byte[] array : rest) {
+      totalLength += array.length;
+    }
+
+    byte[] result = new byte[totalLength];
+    int offset = first.length;
 
-      System.arraycopy(first, 0, result, 0, offset);
-  
-      for (byte[] array : rest) {
-          System.arraycopy(array, 0, result, offset, array.length);
-          offset += array.length;
-      }
-      return result;
+    System.arraycopy(first, 0, result, 0, offset);
+
+    for (byte[] array : rest) {
+      System.arraycopy(array, 0, result, offset, array.length);
+      offset += array.length;
+    }
+    return result;
   }
 
   /**
    * Utility for Base64 decoding. Should ensure that the correct
    * Apache Commons version is used.
    *
    * @param base64
    *        An input string. Will be decoded as UTF-8.
    * @return
    *        A byte array of decoded values.
    * @throws UnsupportedEncodingException
    *         Should not occur.
    */
   public static byte[] decodeBase64(String base64) throws UnsupportedEncodingException {
-      return Base64.decodeBase64(base64.getBytes("UTF-8"));
+    return Base64.decodeBase64(base64.getBytes("UTF-8"));
   }
 
   /*
    * Decode a friendly base32 string.
    */
   public static byte[] decodeFriendlyBase32(String base32) {
-      Base32 converter = new Base32();
-      return converter.decode(base32.replace('8', 'l').replace('9', 'o')
-              .toUpperCase());
+    Base32 converter = new Base32();
+    final String translated = base32.replace('8', 'l').replace('9', 'o');
+    return converter.decode(translated.toUpperCase());
   }
 
   /*
    * Helper to convert Hex String to Byte Array
    * Input: Hex string
    * Output: byte[] version of hex string
    */
-  public static byte[] hex2Byte(String str)
-  {
-      if (str.length() % 2 == 1) {
-          str = "0" + str;
-      }
-  
-      byte[] bytes = new byte[str.length() / 2];
-      for (int i = 0; i < bytes.length; i++)
-      {
-          bytes[i] = (byte) Integer
-              .parseInt(str.substring(2 * i, 2 * i + 2), 16);
-      }
-      return bytes;
+  public static byte[] hex2Byte(String str) {
+    if (str.length() % 2 == 1) {
+      str = "0" + str;
+    }
+
+    byte[] bytes = new byte[str.length() / 2];
+    for (int i = 0; i < bytes.length; i++) {
+      bytes[i] = (byte) Integer.parseInt(str.substring(2 * i, 2 * i + 2), 16);
+    }
+    return bytes;
   }
 
   public static String millisecondsToDecimalSecondsString(long ms) {
     return new BigDecimal(ms).movePointLeft(3).toString();
   }
 
   // This lives until Bug 708956 lands, and we don't have to do it any more.
   public static long decimalSecondsToMilliseconds(String decimal) {
@@ -259,17 +214,17 @@ public class Utils {
 
   public static String getPrefsPath(String username, String serverURL)
     throws NoSuchAlgorithmException, UnsupportedEncodingException {
     return "sync.prefs." + Cryptographer.sha1Base32(serverURL + ":" + username);
   }
 
   public static SharedPreferences getSharedPreferences(Context context, String username, String serverURL) throws NoSuchAlgorithmException, UnsupportedEncodingException {
     String prefsPath = getPrefsPath(username, serverURL);
-    Log.d(LOG_TAG, "Shared preferences: " + prefsPath);
+    Logger.debug(LOG_TAG, "Shared preferences: " + prefsPath);
     return context.getSharedPreferences(prefsPath, SHARED_PREFERENCES_MODE);
   }
 
   /**
    * Populate null slots in the provided array from keys in the provided Map.
    * Set values in the map to be the new indices.
    *
    * @param dest
--- a/mobile/android/base/sync/jpake/JPakeClient.java
+++ b/mobile/android/base/sync/jpake/JPakeClient.java
@@ -48,16 +48,17 @@ import java.util.Random;
 import java.util.Timer;
 import java.util.TimerTask;
 
 import org.json.simple.JSONObject;
 import org.json.simple.parser.JSONParser;
 import org.json.simple.parser.ParseException;
 import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
+import org.mozilla.gecko.sync.Logger;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.ThreadPool;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.CryptoInfo;
 import org.mozilla.gecko.sync.crypto.Cryptographer;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.crypto.NoKeyBundleException;
@@ -231,38 +232,44 @@ public class JPakeClient implements JPak
   }
 
   /* Main functionality Steps */
 
   /*
    * (Receiver Only) Request channel for J-PAKE from server.
    */
   private void getChannel() {
-    Log.d(LOG_TAG, "Getting channel.");
-    if (finished)
+    Logger.debug(LOG_TAG, "Getting channel.");
+    if (finished) {
+      Logger.debug(LOG_TAG, "Finished; returning.");
       return;
+    }
 
-    JPakeRequest channelRequest = null;
     try {
-      channelRequest = new JPakeRequest(jpakeServer + "new_channel",
-          makeRequestResourceDelegate());
+      final String uri = jpakeServer + "new_channel";
+      Logger.debug(LOG_TAG, "Fetching " + uri);
+      JPakeRequest channelRequest = new JPakeRequest(uri, makeRequestResourceDelegate());
+      channelRequest.get();
     } catch (URISyntaxException e) {
       Log.e(LOG_TAG, "URISyntaxException", e);
       abort(Constants.JPAKE_ERROR_CHANNEL);
       return;
+    } catch (Exception e) {
+      Log.e(LOG_TAG, "Unexpected exception in getChannel().", e);
+      abort(Constants.JPAKE_ERROR_CHANNEL);
+      return;
     }
-    channelRequest.get();
   }
 
   /*
    * Helper for sending a PUT request to server, with data taken from shared
    * jOutgoing JSONObject.
    */
   private void putStep() {
-    Log.d(LOG_TAG, "Uploading message.");
+    Logger.debug(LOG_TAG, "Uploading message.");
     runOnThread(new Runnable() {
       @Override
       public void run() {
         JPakeRequest putRequest = null;
         try {
           putRequest = new JPakeRequest(channelUrl,
               makeRequestResourceDelegate());
         } catch (URISyntaxException e) {
@@ -270,26 +277,26 @@ public class JPakeClient implements JPak
           abort(Constants.JPAKE_ERROR_CHANNEL);
           return;
         }
         try {
           putRequest.put(jsonEntity(jOutgoing.object));
         } catch (UnsupportedEncodingException e) {
           e.printStackTrace();
         }
-        Log.d(LOG_TAG, "outgoing: " + jOutgoing.toJSONString());
+        Logger.debug(LOG_TAG, "outgoing: " + jOutgoing.toJSONString());
       }
     });
   }
 
   /*
    * Step One of J-PAKE protocol.
    */
   private void computeStepOne() throws NoSuchAlgorithmException, UnsupportedEncodingException {
-    Log.d(LOG_TAG, "Computing round 1.");
+    Logger.debug(LOG_TAG, "Computing round 1.");
 
     JPakeCrypto.round1(jParty, numGen);
 
     // Set outgoing message.
     ExtendedJSONObject jOne = new ExtendedJSONObject();
     jOne.put(Constants.ZKP_KEY_GX1,
         BigIntegerHelper.toEvenLengthHex(jParty.gx1));
     jOne.put(Constants.ZKP_KEY_GX2,
@@ -302,32 +309,32 @@ public class JPakeClient implements JPak
 
     jOne.put(Constants.ZKP_KEY_ZKP_X1, jZkp1);
     jOne.put(Constants.ZKP_KEY_ZKP_X2, jZkp2);
 
     jOutgoing = new ExtendedJSONObject();
     jOutgoing.put(Constants.JSON_KEY_TYPE, mySignerId + "1");
     jOutgoing.put(Constants.JSON_KEY_PAYLOAD, jOne);
     jOutgoing.put(Constants.JSON_KEY_VERSION, KEYEXCHANGE_VERSION);
-    Log.d(LOG_TAG, "Sending: " + jOutgoing.toJSONString());
+    Logger.debug(LOG_TAG, "Sending: " + jOutgoing.toJSONString());
 
     // Store context to determine next step after PUT request.
     stateContext = pairWithPin ? State.SNDR_STEP_ONE : State.RCVR_STEP_ONE;
     state = State.PUT;
     putStep();
   }
 
   /*
    * Step Two of J-PAKE protocol.
    *
    * Verifies message computed by other party in their Step One. Creates Step
    * Two message to be sent.
    */
   private void computeStepTwo() throws NonObjectJSONException {
-    Log.d(LOG_TAG, "Computing round 2.");
+    Logger.debug(LOG_TAG, "Computing round 2.");
 
     // Check incoming message sender.
     if (!jIncoming.get(Constants.JSON_KEY_TYPE).equals(theirSignerId + "1")) {
       Log.e(LOG_TAG, "Invalid round 1 message: " + jIncoming.toJSONString());
       abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
       return;
     }
 
@@ -418,17 +425,17 @@ public class JPakeClient implements JPak
 
   /*
    * Final Step of J-PAKE protocol.
    *
    * Verifies message computed by other party in Step Two. Creates or fetches
    * encrypted message for verification of successful key exchange.
    */
   private void computeFinal() throws NonObjectJSONException {
-    Log.d(LOG_TAG, "Computing final round.");
+    Logger.debug(LOG_TAG, "Computing final round.");
     // Check incoming message type.
     if (!jIncoming.get(Constants.JSON_KEY_TYPE).equals(theirSignerId + "2")) {
       Log.e(LOG_TAG, "Invalid round 2 message: " + jIncoming.toJSONString());
       abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
       return;
     }
 
     // Check incoming message fields.
@@ -473,17 +480,17 @@ public class JPakeClient implements JPak
       return;
     } catch (UnsupportedEncodingException e) {
       Log.e(LOG_TAG, "UnsupportedEncodingException", e);
       abort(Constants.JPAKE_ERROR_INTERNAL);
       return;
     }
 
     if (pairWithPin) { // Wait for other device to send verification of keys.
-      Log.d(LOG_TAG, "get: verifyPairing");
+      Logger.debug(LOG_TAG, "get: verifyPairing");
       this.state = State.VERIFY_PAIRING;
       scheduleGetRequest(jpakePollInterval);
     } else { // Prepare and send verification of keys.
       try {
         jOutgoing = computeKeyVerification(myKeyBundle);
       } catch (UnsupportedEncodingException e) {
         Log.e(LOG_TAG, "Failed to encrypt key verification value.", e);
         abort(Constants.JPAKE_ERROR_INTERNAL);
@@ -502,17 +509,17 @@ public class JPakeClient implements JPak
 
   /*
    * (Receiver Only) Helper method to compute verification message from JPake
    * key bundle, to be sent to other device for verification.
    */
   public ExtendedJSONObject computeKeyVerification(KeyBundle keyBundle)
       throws UnsupportedEncodingException, CryptoException
   {
-    Log.d(LOG_TAG, "Encrypting key verification value.");
+    Logger.debug(LOG_TAG, "Encrypting key verification value.");
     // KeyBundle not null
     ExtendedJSONObject jPayload = encryptPayload(JPAKE_VERIFY_VALUE, keyBundle);
     ExtendedJSONObject result = new ExtendedJSONObject();
     result.put(Constants.JSON_KEY_TYPE, mySignerId + "3");
     result.put(Constants.JSON_KEY_VERSION, KEYEXCHANGE_VERSION);
     result.put(Constants.JSON_KEY_PAYLOAD, jPayload.object);
     return result;
   }
@@ -576,17 +583,17 @@ public class JPakeClient implements JPak
    * (Sender Only)
    *
    * Encrypt payload and package into jOutgoing for sending with a PUT request.
    *
    * @param keyBundle Encryption keys derived during J-PAKE.
    * @param payload   Credentials data to be encrypted.
    */
   private void encryptData(KeyBundle keyBundle, String payload) {
-    Log.d(LOG_TAG, "Encrypting data.");
+    Logger.debug(LOG_TAG, "Encrypting data.");
     ExtendedJSONObject jPayload = null;
     try {
       jPayload = encryptPayload(payload, keyBundle);
     } catch (UnsupportedEncodingException e) {
       Log.e(LOG_TAG, "Failed to encrypt data.", e);
       abort(Constants.JPAKE_ERROR_INTERNAL);
       return;
     } catch (CryptoException e) {
@@ -603,17 +610,17 @@ public class JPakeClient implements JPak
   /*
    * (Receiver Only)
    *
    * Decrypt jIncoming message from other device and extract credentials to be stored.
    *
    * @param keyBundle
    */
   private void decryptData(KeyBundle keyBundle) {
-    Log.d(LOG_TAG, "Verifying their key");
+    Logger.debug(LOG_TAG, "Verifying their key");
     if (!(theirSignerId + "3").equals((String) jIncoming
         .get(Constants.JSON_KEY_TYPE))) {
       try {
         Log.e(LOG_TAG, "Invalid round 3 data: " + jsonEntity(jIncoming.object));
       } catch (UnsupportedEncodingException e) {
         e.printStackTrace();
       }
       abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
@@ -624,17 +631,17 @@ public class JPakeClient implements JPak
     ExtendedJSONObject iPayload = null;
     try {
       iPayload = jIncoming.getObject(Constants.JSON_KEY_PAYLOAD);
     } catch (NonObjectJSONException e1) {
       Log.e(LOG_TAG, "Invalid round 3 data.", e1);
       abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
       return;
     }
-    Log.d(LOG_TAG, "Decrypting data.");
+    Logger.debug(LOG_TAG, "Decrypting data.");
     String cleartext = null;
     try {
       cleartext = new String(decryptPayload(iPayload, keyBundle), "UTF-8");
     } catch (UnsupportedEncodingException e1) {
       Log.e(LOG_TAG, "Failed to decrypt data.", e1);
       abort(Constants.JPAKE_ERROR_INTERNAL);
       return;
     } catch (CryptoException e1) {
@@ -654,17 +661,17 @@ public class JPakeClient implements JPak
   }
 
   /*
    * Complete Sync setup and return credentials to controller to be stored, if receiver.
    *
    * @param jCreds Credentials to be stored by controller. May be null if device is sender.
    */
   private void complete(JSONObject jCreds) {
-    Log.d(LOG_TAG, "Exchange complete.");
+    Logger.debug(LOG_TAG, "Exchange complete.");
     finished = true;
     ssActivity.onComplete(jCreds);
   }
 
   // JPakeRequestDelegate methods.
   @Override
   public void onRequestFailure(HttpResponse res) {
     JPakeResponse response = new JPakeResponse(res);
@@ -678,33 +685,33 @@ public class JPakeClient implements JPak
     case SNDR_STEP_ZERO:
     case SNDR_STEP_ONE:
     case SNDR_STEP_TWO:
     case RCVR_STEP_ONE:
     case RCVR_STEP_TWO:
       int statusCode = response.getStatusCode();
       switch (statusCode) {
       case 304:
-        Log.d(LOG_TAG, "Channel hasn't been updated yet. Will try again later");
+        Logger.debug(LOG_TAG, "Channel hasn't been updated yet. Will try again later");
         if (pollTries >= jpakeMaxTries) {
           Log.e(LOG_TAG, "Tried for " + pollTries + " times, maxTries " + jpakeMaxTries + ", aborting");
           abort(Constants.JPAKE_ERROR_TIMEOUT);
           return;
         }
         pollTries += 1;
         if (!finished) {
           scheduleGetRequest(jpakePollInterval);
         }
         return;
       case 404:
         Log.e(LOG_TAG, "No data found in channel.");
         abort(Constants.JPAKE_ERROR_NODATA);
         break;
       case 412: // "Precondition failed"
-        Log.d(LOG_TAG, "Message already replaced on server by other party.");
+        Logger.debug(LOG_TAG, "Message already replaced on server by other party.");
         onRequestSuccess(res);
         break;
       default:
         Log.e(LOG_TAG, "Could not retrieve data. Server responded with HTTP " + statusCode);
         abort(Constants.JPAKE_ERROR_SERVER);
         return;
       }
       pollTries = 0;
@@ -749,17 +756,17 @@ public class JPakeClient implements JPak
         abort(Constants.JPAKE_ERROR_CHANNEL);
       }
       String channel = body instanceof String ? (String) body : null;
       if (channel == null) { // should be string
         abort(Constants.JPAKE_ERROR_CHANNEL);
         return;
       }
       channelUrl = jpakeServer + channel;
-      Log.d(LOG_TAG, "using channel " + channel);
+      Logger.debug(LOG_TAG, "using channel " + channel);
 
       ssActivity.displayPin(secret + channel);
 
       // Set up next step.
       this.state = State.RCVR_STEP_ONE;
       try {
         computeStepOne();
       } catch (NoSuchAlgorithmException e) {
@@ -806,17 +813,17 @@ public class JPakeClient implements JPak
         e.printStackTrace();
       } catch (ParseException e) {
         abort(Constants.JPAKE_ERROR_INVALID);
         return;
       } catch (NonObjectJSONException e) {
         abort(Constants.JPAKE_ERROR_INVALID);
         return;
       }
-      Log.d(LOG_TAG, "incoming message: " + jIncoming.toJSONString());
+      Logger.debug(LOG_TAG, "incoming message: " + jIncoming.toJSONString());
 
       if (this.state == State.SNDR_STEP_ZERO) {
         try {
           computeStepOne();
         } catch (NoSuchAlgorithmException e) {
           Log.e(LOG_TAG, "NoSuchAlgorithmException", e);
           abort(Constants.JPAKE_ERROR_INTERNAL);
           return;
@@ -1035,39 +1042,39 @@ public class JPakeClient implements JPak
    * abort wasn't due to a network or server error. The controller's 'onAbort()'
    * method is notified in all cases.
    *
    * @param error
    *          [can be null] Error constant indicating the reason for the abort.
    *          Defaults to user abort
    */
   public void abort(String error) {
-    Log.d(LOG_TAG, "aborting...");
+    Logger.debug(LOG_TAG, "aborting...");
     finished = true;
 
     if (error == null) {
       error = Constants.JPAKE_ERROR_USERABORT;
     }
-    Log.d(LOG_TAG, error);
+    Logger.debug(LOG_TAG, error);
 
     if (Constants.JPAKE_ERROR_CHANNEL.equals(error)
         || Constants.JPAKE_ERROR_NETWORK.equals(error)
         || Constants.JPAKE_ERROR_NODATA.equals(error)) {
       // No report.
     } else {
       reportFailure(error);
     }
     ssActivity.displayAbort(error);
   }
 
   /*
    * Make a /report post to to server
    */
   private void reportFailure(String error) {
-    Log.d(LOG_TAG, "reporting error to server");
+    Logger.debug(LOG_TAG, "reporting error to server");
     this.error = error;
     runOnThread(new Runnable() {
       @Override
       public void run() {
         JPakeRequest report;
         try {
           report = new JPakeRequest(jpakeServer + "report",
               makeRequestResourceDelegate());
--- a/mobile/android/base/sync/repositories/RepositorySession.java
+++ b/mobile/android/base/sync/repositories/RepositorySession.java
@@ -36,17 +36,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.sync.repositories;
 
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
-import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.sync.Logger;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
@@ -72,21 +72,21 @@ public abstract class RepositorySession 
     ACTIVE,
     ABORTED,
     DONE
   }
 
   private static final String LOG_TAG = "RepositorySession";
 
   private static void error(String message) {
-    Utils.error(LOG_TAG, message);
+    Logger.error(LOG_TAG, message);
   }
 
   protected static void trace(String message) {
-    Utils.trace(LOG_TAG, message);
+    Logger.trace(LOG_TAG, message);
   }
 
   protected SessionStatus status = SessionStatus.UNSTARTED;
   protected Repository repository;
   protected RepositorySessionStoreDelegate delegate;
 
   /**
    * A queue of Runnables which call out into delegates.
--- a/mobile/android/base/sync/repositories/Server11RepositorySession.java
+++ b/mobile/android/base/sync/repositories/Server11RepositorySession.java
@@ -77,17 +77,17 @@ public class Server11RepositorySession e
       recordsStart    = "[\n".getBytes("UTF-8");
       recordSeparator = ",\n".getBytes("UTF-8");
       recordsEnd      = "\n]\n".getBytes("UTF-8");
     } catch (UnsupportedEncodingException e) {
       // These won't fail.
     }
   }
 
-  public static final String LOG_TAG = "Server11RepositorySession";
+  public static final String LOG_TAG = "Server11Session";
 
   private static final int UPLOAD_BYTE_THRESHOLD = 1024 * 1024;    // 1MB.
   private static final int UPLOAD_ITEM_THRESHOLD = 50;
   private static final int PER_RECORD_OVERHEAD   = 2;              // Comma, newline.
   // {}, newlines, but we get to skip one record overhead.
   private static final int PER_BATCH_OVERHEAD    = 5 - PER_RECORD_OVERHEAD;
 
   /**
--- a/mobile/android/base/sync/repositories/StoreTrackingRepositorySession.java
+++ b/mobile/android/base/sync/repositories/StoreTrackingRepositorySession.java
@@ -6,17 +6,17 @@ package org.mozilla.gecko.sync.repositor
 
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 import android.util.Log;
 
 public abstract class StoreTrackingRepositorySession extends RepositorySession {
-  private static final String LOG_TAG = "StoreTrackingRepositorySession";
+  private static final String LOG_TAG = "StoreTrackSession";
   protected StoreTracker storeTracker;
 
   protected static StoreTracker createStoreTracker() {
     return new HashSetStoreTracker();
   }
 
   public StoreTrackingRepositorySession(Repository repository) {
     super(repository);
@@ -54,9 +54,9 @@ public abstract class StoreTrackingRepos
     super.abort(delegate);
   }
 
   @Override
   public void finish(RepositorySessionFinishDelegate delegate) {
     this.storeTracker = null;
     super.finish(delegate);
   }
-}
\ No newline at end of file
+}
--- a/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java
+++ b/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java
@@ -48,17 +48,17 @@ import org.mozilla.gecko.sync.repositori
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.util.Log;
 
 public class AndroidBrowserBookmarksDataAccessor extends AndroidBrowserRepositoryDataAccessor {
 
-  private static final String LOG_TAG = "AndroidBrowserBookmarksDataAccessor";
+  private static final String LOG_TAG = "BookmarksDataAccessor";
 
   /*
    * Fragments of SQL to make our lives easier.
    */
   private static final String BOOKMARK_IS_FOLDER = BrowserContract.Bookmarks.IS_FOLDER + " = 1";
   private static final String GUID_NOT_TAGS_OR_PLACES = BrowserContract.SyncColumns.GUID + " NOT IN ('" +
                      BrowserContract.Bookmarks.TAGS_FOLDER_GUID + "', '" +
                      BrowserContract.Bookmarks.PLACES_FOLDER_GUID + "')";
--- a/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java
+++ b/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java
@@ -36,16 +36,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.sync.repositories.android;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 
 import org.json.simple.JSONArray;
+import org.mozilla.gecko.sync.Logger;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.BookmarkNeedsReparentingException;
 import org.mozilla.gecko.sync.repositories.NoGuidForIdException;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
 import org.mozilla.gecko.sync.repositories.ParentNotFoundException;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
@@ -117,17 +118,17 @@ public class AndroidBrowserBookmarksRepo
     String parentName = "";
     Cursor name = dataAccessor.fetch(new String[] { parentGUID });
     try {
       name.moveToFirst();
       if (!name.isAfterLast()) {
         parentName = RepoUtils.getStringFromCursor(name, BrowserContract.Bookmarks.TITLE);
       }
       else {
-        Log.e(LOG_TAG, "Couldn't find record with guid '" + parentGUID + "' when looking for parent name.");
+        Logger.error(LOG_TAG, "Couldn't find record with guid '" + parentGUID + "' when looking for parent name.");
         throw new ParentNotFoundException(null);
       }
     } finally {
       name.close();
     }
     return parentName;
   }
 
@@ -152,159 +153,160 @@ public class AndroidBrowserBookmarksRepo
 
       // Get children into array in correct order.
       while (!children.isAfterLast()) {
         String childGuid = getGUID(children);
         trace("  Child GUID: " + childGuid);
         int childPosition = (int) RepoUtils.getLongFromCursor(children, BrowserContract.Bookmarks.POSITION);
         trace("  Child position: " + childPosition);
         if (childPosition >= count) {
-          Log.w(LOG_TAG, "Child position " + childPosition + " greater than expected children " + count);
+          Logger.warn(LOG_TAG, "Child position " + childPosition + " greater than expected children " + count);
           broken.put(childGuid, 0L);
         } else {
           String existing = kids[childPosition];
           if (existing != null) {
-            Log.w(LOG_TAG, "Child position " + childPosition + " already occupied! (" +
-                childGuid + ", " + existing + ")");
+            Logger.warn(LOG_TAG, "Child position " + childPosition + " already occupied! (" +
+                                 childGuid + ", " + existing + ")");
             broken.put(childGuid, 0L);
           } else {
             kids[childPosition] = childGuid;
           }
         }
         children.moveToNext();
       }
 
       try {
         Utils.fillArraySpaces(kids, broken);
       } catch (Exception e) {
-        Log.e(LOG_TAG, "Unable to reposition children to yield a valid sequence. Data loss may result.", e);
+        Logger.error(LOG_TAG, "Unable to reposition children to yield a valid sequence. Data loss may result.", e);
       }
       // TODO: now use 'broken' to edit the records on disk.
 
       // Collect into a more friendly data structure.
       for (int i = 0; i < count; ++i) {
         String kid = kids[i];
         if (forbiddenGUID(kid)) {
           continue;
         }
         childArray.add(kid);
       }
-      if (Utils.ENABLE_TRACE_LOGGING) {
-        Log.d(LOG_TAG, "Output child array: " + childArray.toJSONString());
+      if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+        // Don't JSON-encode unless we're logging.
+        Logger.trace(LOG_TAG, "Output child array: " + childArray.toJSONString());
       }
     } finally {
       children.close();
     }
     return childArray;
   }
 
   @Override
   protected Record recordFromMirrorCursor(Cursor cur) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
     String recordGUID = getGUID(cur);
-    Log.d(LOG_TAG, "Record from mirror cursor: " + recordGUID);
+    Logger.trace(LOG_TAG, "Record from mirror cursor: " + recordGUID);
 
     if (forbiddenGUID(recordGUID)) {
-      Log.d(LOG_TAG, "Ignoring " + recordGUID + " record in recordFromMirrorCursor.");
+      Logger.debug(LOG_TAG, "Ignoring " + recordGUID + " record in recordFromMirrorCursor.");
       return null;
     }
 
     long androidParentID     = getParentID(cur);
     String androidParentGUID = getGUIDForID(androidParentID);
 
     if (androidParentGUID == null) {
-      Log.d(LOG_TAG, "No parent GUID for record " + recordGUID + " with parent " + androidParentID);
+      Logger.debug(LOG_TAG, "No parent GUID for record " + recordGUID + " with parent " + androidParentID);
       // If the parent has been stored and somehow has a null GUID, throw an error.
       if (idToGuid.containsKey(androidParentID)) {
-        Log.e(LOG_TAG, "Have the parent android ID for the record but the parent's GUID wasn't found.");
+        Logger.error(LOG_TAG, "Have the parent android ID for the record but the parent's GUID wasn't found.");
         throw new NoGuidForIdException(null);
       }
     }
 
     // If record is a folder, build out the children array.
     JSONArray childArray = getChildArrayForCursor(cur, recordGUID);
     String parentName = getParentName(androidParentGUID);
     return RepoUtils.bookmarkFromMirrorCursor(cur, androidParentGUID, parentName, childArray);
   }
 
   protected JSONArray getChildArrayForCursor(Cursor cur, String recordGUID) throws NullCursorException {
     JSONArray childArray = null;
     boolean isFolder = rowIsFolder(cur);
-    Log.d(LOG_TAG, "Record " + recordGUID + " is a " + (isFolder ? "folder." : "bookmark."));
+    Logger.debug(LOG_TAG, "Record " + recordGUID + " is a " + (isFolder ? "folder." : "bookmark."));
     if (isFolder) {
       long androidID = guidToID.get(recordGUID);
       childArray = getChildren(androidID);
     }
     if (childArray != null) {
-      Log.d(LOG_TAG, "Fetched " + childArray.size() + " children for " + recordGUID);
+      Logger.debug(LOG_TAG, "Fetched " + childArray.size() + " children for " + recordGUID);
     }
     return childArray;
   }
 
   @Override
   protected boolean checkRecordType(Record record) {
     BookmarkRecord bmk = (BookmarkRecord) record;
     if (bmk.type.equalsIgnoreCase(AndroidBrowserBookmarksDataAccessor.TYPE_BOOKMARK) ||
         bmk.type.equalsIgnoreCase(AndroidBrowserBookmarksDataAccessor.TYPE_FOLDER)) {
       return true;
     }
-    Log.i(LOG_TAG, "Ignoring record with guid: " + record.guid + " and type: " + ((BookmarkRecord)record).type);
+    Logger.info(LOG_TAG, "Ignoring record with guid: " + record.guid + " and type: " + ((BookmarkRecord)record).type);
     return false;
   }
   
   @Override
   public void begin(RepositorySessionBeginDelegate delegate) {
     // Check for the existence of special folders
     // and insert them if they don't exist.
     Cursor cur;
     try {
-      Log.d(LOG_TAG, "Check and build special GUIDs.");
+      Logger.debug(LOG_TAG, "Check and build special GUIDs.");
       dataAccessor.checkAndBuildSpecialGuids();
       cur = dataAccessor.getGuidsIDsForFolders();
-      Log.d(LOG_TAG, "Got GUIDs for folders.");
+      Logger.debug(LOG_TAG, "Got GUIDs for folders.");
     } catch (android.database.sqlite.SQLiteConstraintException e) {
-      Log.e(LOG_TAG, "Got sqlite constraint exception working with Fennec bookmark DB.", e);
+      Logger.error(LOG_TAG, "Got sqlite constraint exception working with Fennec bookmark DB.", e);
       delegate.onBeginFailed(e);
       return;
     } catch (NullCursorException e) {
       delegate.onBeginFailed(e);
       return;
     } catch (Exception e) {
       delegate.onBeginFailed(e);
       return;
     }
     
     // To deal with parent mapping of bookmarks we have to do some
     // hairy stuff. Here's the setup for it.
 
-    Log.d(LOG_TAG, "Preparing folder ID mappings.");
+    Logger.debug(LOG_TAG, "Preparing folder ID mappings.");
     idToGuid.put(0L, "places");       // Fake our root.
     try {
       cur.moveToFirst();
       while (!cur.isAfterLast()) {
         String guid = getGUID(cur);
         long id = RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks._ID);
         guidToID.put(guid, id);
         idToGuid.put(id, guid);
-        Log.d(LOG_TAG, "GUID " + guid + " maps to " + id);
+        Logger.debug(LOG_TAG, "GUID " + guid + " maps to " + id);
         cur.moveToNext();
       }
     } finally {
       cur.close();
     }
-    Log.d(LOG_TAG, "Done with initial setup of bookmarks session.");
+    Logger.debug(LOG_TAG, "Done with initial setup of bookmarks session.");
     super.begin(delegate);
   }
 
   @Override
   public void finish(RepositorySessionFinishDelegate delegate) {
     // Override finish to do this check; make sure all records
     // needing re-parenting have been re-parented.
     if (needsReparenting != 0) {
-      Log.e(LOG_TAG, "Finish called but " + needsReparenting +
-            " bookmark(s) have been placed in unsorted bookmarks and not been reparented.");
+      Logger.error(LOG_TAG, "Finish called but " + needsReparenting +
+                            " bookmark(s) have been placed in unsorted bookmarks and not been reparented.");
 
       // TODO: handling of failed reparenting.
       // E.g., delegate.onFinishFailed(new BookmarkNeedsReparentingException(null));
     }
     super.finish(delegate);
   };
 
   @Override
@@ -332,26 +334,38 @@ public class AndroidBrowserBookmarksRepo
       } else {
         children = new ArrayList<String>();
       }
       children.add(bmk.guid);
       needsReparenting++;
       missingParentToChildren.put(bmk.parentID, children);
     }
 
-    if (bmk.isFolder()) {
-      Log.d(LOG_TAG, "Inserting folder " + bmk.guid + ", " + bmk.title +
-                     " with parent " + bmk.androidParentID +
-                     " (" + bmk.parentID + ", " + bmk.parentName +
-                     ", " + bmk.pos + ")");
+    if (Logger.LOG_PERSONAL_INFORMATION) {
+      if (bmk.isFolder()) {
+        Logger.pii(LOG_TAG, "Inserting folder " + bmk.guid + ", " + bmk.title +
+                            " with parent " + bmk.androidParentID +
+                            " (" + bmk.parentID + ", " + bmk.parentName +
+                            ", " + bmk.pos + ")");
+      } else {
+        Logger.pii(LOG_TAG, "Inserting bookmark " + bmk.guid + ", " + bmk.title + ", " +
+                            bmk.bookmarkURI + " with parent " + bmk.androidParentID +
+                            " (" + bmk.parentID + ", " + bmk.parentName +
+                            ", " + bmk.pos + ")");
+      }
     } else {
-      Log.d(LOG_TAG, "Inserting bookmark " + bmk.guid + ", " + bmk.title + ", " +
-                     bmk.bookmarkURI + " with parent " + bmk.androidParentID +
-                     " (" + bmk.parentID + ", " + bmk.parentName +
-                     ", " + bmk.pos + ")");
+      if (bmk.isFolder()) {
+        Logger.debug(LOG_TAG, "Inserting folder " + bmk.guid +  ", parent " +
+                              bmk.androidParentID +
+                              " (" + bmk.parentID + ", " + bmk.pos + ")");
+      } else {
+        Logger.debug(LOG_TAG, "Inserting bookmark " + bmk.guid + " with parent " +
+                              bmk.androidParentID +
+                              " (" + bmk.parentID + ", " + ", " + bmk.pos + ")");
+      }
     }
     return bmk;
   }
 
   @Override
   @SuppressWarnings("unchecked")
   protected void updateBookkeeping(Record record) throws NoGuidForIdException,
                                                          NullCursorException,
--- a/mobile/android/base/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java
+++ b/mobile/android/base/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java
@@ -46,17 +46,17 @@ import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.util.Log;
 
 public abstract class AndroidBrowserRepositoryDataAccessor {
 
   private static final String[] GUID_COLUMNS = new String[] { BrowserContract.SyncColumns.GUID };
   protected Context context;
-  protected String LOG_TAG = "AndroidBrowserRepositoryDataAccessor";
+  protected static String LOG_TAG = "BrowserDataAccessor";
   private final RepoUtils.QueryHelper queryHelper;
 
   public AndroidBrowserRepositoryDataAccessor(Context context) {
     this.context = context;
     this.queryHelper = new RepoUtils.QueryHelper(context, getUri(), LOG_TAG);
   }
 
   protected abstract String[] getAllColumns();
--- a/mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java
+++ b/mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java
@@ -36,17 +36,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.sync.repositories.android;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 
-import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.sync.Logger;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.InvalidRequestException;
 import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
 import org.mozilla.gecko.sync.repositories.MultipleRecordsForGuidException;
 import org.mozilla.gecko.sync.repositories.NoGuidForIdException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
 import org.mozilla.gecko.sync.repositories.ParentNotFoundException;
@@ -57,17 +57,16 @@ import org.mozilla.gecko.sync.repositori
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 import android.database.Cursor;
 import android.net.Uri;
-import android.util.Log;
 
 /**
  * You'll notice that all delegate calls *either*:
  *
  * - request a deferred delegate with the appropriate work queue, then
  *   make the appropriate call, or
  * - create a Runnable which makes the appropriate call, and pushes it
  *   directly into the appropriate work queue.
@@ -84,17 +83,17 @@ import android.util.Log;
  * ensures that store() and storeDone() consequences occur before-after.
  *
  * @author rnewman
  *
  */
 public abstract class AndroidBrowserRepositorySession extends StoreTrackingRepositorySession {
 
   protected AndroidBrowserRepositoryDataAccessor dbHelper;
-  public static final String LOG_TAG = "AndroidBrowserRepositorySession";
+  public static final String LOG_TAG = "BrowserRepoSession";
   private HashMap<String, String> recordToGuid;
 
   public AndroidBrowserRepositorySession(Repository repository) {
     super(repository);
   }
 
   /**
    * Override this.
@@ -137,37 +136,37 @@ public abstract class AndroidBrowserRepo
     }
 
     try {
       // We do this check here even though it results in one extra call to the DB
       // because if we didn't, we have to do a check on every other call since there
       // is no way of knowing which call would be hit first.
       checkDatabase();
     } catch (ProfileDatabaseException e) {
-      Log.e(LOG_TAG, "ProfileDatabaseException from begin. Fennec must be launched once until this error is fixed");
+      Logger.error(LOG_TAG, "ProfileDatabaseException from begin. Fennec must be launched once until this error is fixed");
       deferredDelegate.onBeginFailed(e);
       return;
     } catch (NullCursorException e) {
       deferredDelegate.onBeginFailed(e);
       return;
     } catch (Exception e) {
       deferredDelegate.onBeginFailed(e);
       return;
     }
     storeTracker = createStoreTracker();
     deferredDelegate.onBeginSucceeded(this);
   }
 
   protected abstract String buildRecordString(Record record);
 
   protected void checkDatabase() throws ProfileDatabaseException, NullCursorException {
-    Utils.info(LOG_TAG, "BEGIN: checking database.");
+    Logger.info(LOG_TAG, "BEGIN: checking database.");
     try {
       dbHelper.fetch(new String[] { "none" }).close();
-      Utils.info(LOG_TAG, "END: checking database.");
+      Logger.info(LOG_TAG, "END: checking database.");
     } catch (NullPointerException e) {
       throw new ProfileDatabaseException(e);
     }
   }
 
   @Override
   public void guidsSince(long timestamp, RepositorySessionGuidsSinceDelegate delegate) {
     GuidsSinceRunnable command = new GuidsSinceRunnable(timestamp, delegate);
@@ -210,17 +209,17 @@ public abstract class AndroidBrowserRepo
           return;
         }
         guids = new ArrayList<String>();
         while (!cur.isAfterLast()) {
           guids.add(RepoUtils.getStringFromCursor(cur, "guid"));
           cur.moveToNext();
         }
       } finally {
-        Log.d(LOG_TAG, "Closing cursor after guidsSince.");
+        Logger.debug(LOG_TAG, "Closing cursor after guidsSince.");
         cur.close();
       }
 
       String guidsArray[] = new String[guids.size()];
       guids.toArray(guidsArray);
       delegate.onGuidsSinceSucceeded(guidsArray);
     }
   }
@@ -235,46 +234,46 @@ public abstract class AndroidBrowserRepo
   abstract class FetchingRunnable implements Runnable {
     protected RepositorySessionFetchRecordsDelegate delegate;
 
     public FetchingRunnable(RepositorySessionFetchRecordsDelegate delegate) {
       this.delegate = delegate;
     }
 
     protected void fetchFromCursor(Cursor cursor, RecordFilter filter, long end) {
-      Log.d(LOG_TAG, "Fetch from cursor:");
+      Logger.debug(LOG_TAG, "Fetch from cursor:");
       try {
         try {
           if (!cursor.moveToFirst()) {
             delegate.onFetchCompleted(end);
             return;
           }
           while (!cursor.isAfterLast()) {
-            Log.d(LOG_TAG, "... one more record.");
             Record r = recordFromMirrorCursor(cursor);
             if (r != null) {
               if (filter == null || !filter.excludeRecord(r)) {
+                Logger.trace(LOG_TAG, "Processing record " + r.guid);
                 delegate.onFetchedRecord(transformRecord(r));
               } else {
-                Log.d(LOG_TAG, "Filter says to skip record.");
+                Logger.debug(LOG_TAG, "Skipping filtered record " + r.guid);
               }
             }
             cursor.moveToNext();
           }
           delegate.onFetchCompleted(end);
         } catch (NoGuidForIdException e) {
-          Log.w(LOG_TAG, "No GUID for ID.", e);
+          Logger.warn(LOG_TAG, "No GUID for ID.", e);
           delegate.onFetchFailed(e, null);
         } catch (Exception e) {
-          Log.w(LOG_TAG, "Exception in fetchFromCursor.", e);
+          Logger.warn(LOG_TAG, "Exception in fetchFromCursor.", e);
           delegate.onFetchFailed(e, null);
           return;
         }
       } finally {
-        Log.d(LOG_TAG, "Closing cursor after fetch.");
+        Logger.trace(LOG_TAG, "Closing cursor after fetch.");
         cursor.close();
       }
     }
   }
 
   class FetchRunnable extends FetchingRunnable {
     private String[] guids;
     private long     end;
@@ -293,17 +292,17 @@ public abstract class AndroidBrowserRepo
     @Override
     public void run() {
       if (!isActive()) {
         delegate.onFetchFailed(new InactiveSessionException(null), null);
         return;
       }
 
       if (guids == null || guids.length < 1) {
-        Log.e(LOG_TAG, "No guids sent to fetch");
+        Logger.error(LOG_TAG, "No guids sent to fetch");
         delegate.onFetchFailed(new InvalidRequestException(null), null);
         return;
       }
 
       try {
         Cursor cursor = dbHelper.fetch(guids);
         this.fetchFromCursor(cursor, filter, end);
       } catch (NullCursorException e) {
@@ -314,17 +313,17 @@ public abstract class AndroidBrowserRepo
 
   @Override
   public void fetchSince(long timestamp,
                          RepositorySessionFetchRecordsDelegate delegate) {
     if (this.storeTracker == null) {
       throw new IllegalStateException("Store tracker not yet initialized!");
     }
 
-    Log.i(LOG_TAG, "Running fetchSince(" + timestamp + ").");
+    Logger.info(LOG_TAG, "Running fetchSince(" + timestamp + ").");
     FetchSinceRunnable command = new FetchSinceRunnable(timestamp, now(), this.storeTracker.getFilter(), delegate);
     delegateQueue.execute(command);
   }
 
   class FetchSinceRunnable extends FetchingRunnable {
     private long since;
     private long end;
     private RecordFilter filter;
@@ -362,17 +361,17 @@ public abstract class AndroidBrowserRepo
   }
 
   @Override
   public void store(final Record record) throws NoStoreDelegateException {
     if (delegate == null) {
       throw new NoStoreDelegateException();
     }
     if (record == null) {
-      Log.e(LOG_TAG, "Record sent to store was null");
+      Logger.error(LOG_TAG, "Record sent to store was null");
       throw new IllegalArgumentException("Null record passed to AndroidBrowserRepositorySession.store().");
     }
 
     // Store Runnables *must* complete synchronously. It's OK, they
     // run on a background thread.
     Runnable command = new Runnable() {
 
       @Override
@@ -383,17 +382,17 @@ public abstract class AndroidBrowserRepo
         }
 
         // Check that the record is a valid type.
         // Fennec only supports bookmarks and folders. All other types of records,
         // including livemarks and queries, are simply ignored.
         // See Bug 708149. This might be resolved by Fennec changing its database
         // schema, or by Sync storing non-applied records in its own private database.
         if (!checkRecordType(record)) {
-          Log.d(LOG_TAG, "Ignoring record " + record.guid + " due to unknown record type.");
+          Logger.debug(LOG_TAG, "Ignoring record " + record.guid + " due to unknown record type.");
 
           // Don't throw: we don't want to abort the entire sync when we get a livemark!
           // delegate.onRecordStoreFailed(new InvalidBookmarkTypeException(null));
           return;
         }
 
 
         // TODO: lift these into the session.
@@ -440,17 +439,17 @@ public abstract class AndroidBrowserRepo
             if (record.lastModified > existingRecord.lastModified) {
               trace("Remote is newer, and deleted. Deleting local.");
               storeRecordDeletion(record);
               return;
             }
 
             trace("Remote is older, local is not deleted. Ignoring.");
             if (!locallyModified) {
-              Log.w(LOG_TAG, "Inconsistency: old remote record is deleted, but local record not modified!");
+              Logger.warn(LOG_TAG, "Inconsistency: old remote record is deleted, but local record not modified!");
               // Ensure that this is tracked for upload.
             }
             return;
           }
           // End deletion logic.
 
           // Now we're processing a non-deleted incoming record.
           if (existingRecord == null) {
@@ -470,45 +469,45 @@ public abstract class AndroidBrowserRepo
           // We found a local dupe.
           trace("Incoming record " + record.guid + " dupes to local record " + existingRecord.guid);
 
           // Populate more expensive fields prior to reconciling.
           existingRecord = transformRecord(existingRecord);
           Record toStore = reconcileRecords(record, existingRecord, lastRemoteRetrieval, lastLocalRetrieval);
 
           if (toStore == null) {
-            Log.d(LOG_TAG, "Reconciling returned null. Not inserting a record.");
+            Logger.debug(LOG_TAG, "Reconciling returned null. Not inserting a record.");
             return;
           }
 
           // TODO: pass in timestamps?
-          Log.d(LOG_TAG, "Replacing " + existingRecord.guid + " with record " + toStore.guid);
+          Logger.debug(LOG_TAG, "Replacing " + existingRecord.guid + " with record " + toStore.guid);
           Record replaced = replace(toStore, existingRecord);
 
           // Note that we don't track records here; deciding that is the job
           // of reconcileRecords.
-          Log.d(LOG_TAG, "Calling delegate callback with guid " + replaced.guid +
-                         "(" + replaced.androidID + ")");
+          Logger.debug(LOG_TAG, "Calling delegate callback with guid " + replaced.guid +
+                                "(" + replaced.androidID + ")");
           delegate.onRecordStoreSucceeded(replaced);
           return;
 
         } catch (MultipleRecordsForGuidException e) {
-          Log.e(LOG_TAG, "Multiple records returned for given guid: " + record.guid);
+          Logger.error(LOG_TAG, "Multiple records returned for given guid: " + record.guid);
           delegate.onRecordStoreFailed(e);
           return;
         } catch (NoGuidForIdException e) {
-          Log.e(LOG_TAG, "Store failed for " + record.guid, e);
+          Logger.error(LOG_TAG, "Store failed for " + record.guid, e);
           delegate.onRecordStoreFailed(e);
           return;
         } catch (NullCursorException e) {
-          Log.e(LOG_TAG, "Store failed for " + record.guid, e);
+          Logger.error(LOG_TAG, "Store failed for " + record.guid, e);
           delegate.onRecordStoreFailed(e);
           return;
         } catch (Exception e) {
-          Log.e(LOG_TAG, "Store failed for " + record.guid, e);
+          Logger.error(LOG_TAG, "Store failed for " + record.guid, e);
           delegate.onRecordStoreFailed(e);
           return;
         }
       }
     };
     storeWorkQueue.execute(command);
   }
 
@@ -518,31 +517,31 @@ public abstract class AndroidBrowserRepo
     dbHelper.delete(record);      // TODO: mm?
     delegate.onRecordStoreSucceeded(record);
   }
 
   protected Record insert(Record record) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
     Record toStore = prepareRecord(record);
     Uri recordURI = dbHelper.insert(toStore);
     long id = RepoUtils.getAndroidIdFromUri(recordURI);
-    Log.d(LOG_TAG, "Inserted as " + id);
+    Logger.debug(LOG_TAG, "Inserted as " + id);
 
     toStore.androidID = id;
     updateBookkeeping(toStore);
-    Log.d(LOG_TAG, "insert() returning record " + toStore.guid);
+    Logger.debug(LOG_TAG, "insert() returning record " + toStore.guid);
     return toStore;
   }
 
   protected Record replace(Record newRecord, Record existingRecord) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
     Record toStore = prepareRecord(newRecord);
 
     // newRecord should already have suitable androidID and guid.
     dbHelper.update(existingRecord.guid, toStore);
     updateBookkeeping(toStore);
-    Log.d(LOG_TAG, "replace() returning record " + toStore.guid);
+    Logger.debug(LOG_TAG, "replace() returning record " + toStore.guid);
     return toStore;
   }
 
   protected Record recordForGUID(String guid) throws
                                              NoGuidForIdException,
                                              NullCursorException,
                                              ParentNotFoundException,
                                              MultipleRecordsForGuidException {
@@ -578,54 +577,54 @@ public abstract class AndroidBrowserRepo
    * @throws MultipleRecordsForGuidException
    * @throws NoGuidForIdException
    * @throws NullCursorException
    * @throws ParentNotFoundException
    */
   protected Record findExistingRecord(Record record) throws MultipleRecordsForGuidException,
     NoGuidForIdException, NullCursorException, ParentNotFoundException {
 
-    Log.d(LOG_TAG, "Finding existing record for incoming record with GUID " + record.guid);
+    Logger.debug(LOG_TAG, "Finding existing record for incoming record with GUID " + record.guid);
     String recordString = buildRecordString(record);
-    Log.d(LOG_TAG, "Searching with record string " + recordString);
+    Logger.debug(LOG_TAG, "Searching with record string " + recordString);
     String guid = getRecordToGuidMap().get(recordString);
     if (guid != null) {
-      Log.d(LOG_TAG, "Found one. Returning computed record.");
+      Logger.debug(LOG_TAG, "Found one. Returning computed record.");
       return recordForGUID(guid);
     }
-    Log.d(LOG_TAG, "findExistingRecord failed to find one for " + record.guid);
+    Logger.debug(LOG_TAG, "findExistingRecord failed to find one for " + record.guid);
     return null;
   }
 
   public HashMap<String, String> getRecordToGuidMap() throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
     if (recordToGuid == null) {
       createRecordToGuidMap();
     }
     return recordToGuid;
   }
 
   private void createRecordToGuidMap() throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
-    Utils.info(LOG_TAG, "BEGIN: creating record -> GUID map.");
+    Logger.info(LOG_TAG, "BEGIN: creating record -> GUID map.");
     recordToGuid = new HashMap<String, String>();
     Cursor cur = dbHelper.fetchAll();
     try {
       if (!cur.moveToFirst()) {
         return;
       }
       while (!cur.isAfterLast()) {
         Record record = recordFromMirrorCursor(cur);
         if (record != null) {
           recordToGuid.put(buildRecordString(record), record.guid);
         }
         cur.moveToNext();
       }
     } finally {
       cur.close();
     }
-    Utils.info(LOG_TAG, "END: creating record -> GUID map.");
+    Logger.info(LOG_TAG, "END: creating record -> GUID map.");
   }
 
   public void putRecordToGuidMap(String recordString, String guid) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
     if (recordToGuid == null) {
       createRecordToGuidMap();
     }
     recordToGuid.put(recordString, guid);
   }
--- a/mobile/android/base/sync/repositories/android/RepoUtils.java
+++ b/mobile/android/base/sync/repositories/android/RepoUtils.java
@@ -41,25 +41,25 @@ package org.mozilla.gecko.sync.repositor
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.json.simple.JSONArray;
 import org.json.simple.parser.JSONParser;
 import org.json.simple.parser.ParseException;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.sync.Logger;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
 import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
 import org.mozilla.gecko.sync.repositories.domain.PasswordRecord;
 
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
-import android.util.Log;
 
 public class RepoUtils {
 
   private static final String LOG_TAG = "DBUtils";
 
   /**
    * An array of known-special GUIDs.
    */
@@ -172,17 +172,17 @@ public class RepoUtils {
       long queryEnd   = android.os.SystemClock.uptimeMillis();
       RepoUtils.queryTimeLogger(logLabel, queryStart, queryEnd);
       return c;
     }
 
     public Cursor safeQuery(String label, String[] projection, String selection, String[] selectionArgs, String sortOrder) throws NullCursorException {
       Cursor c = this.query(label, projection, selection, selectionArgs, sortOrder);
       if (c == null) {
-        Log.e(tag, "Got null cursor exception in " + tag + ((label == null) ? "" : label));
+        Logger.error(tag, "Got null cursor exception in " + tag + ((label == null) ? "" : label));
         throw new NullCursorException(null);
       }
       return c;
     }
   }
 
   public static String getStringFromCursor(Cursor cur, String colId) {
     // TODO: getColumnIndexOrThrow?
@@ -201,49 +201,49 @@ public class RepoUtils {
   public static JSONArray getJSONArrayFromCursor(Cursor cur, String colId) {
     String jsonArrayAsString = getStringFromCursor(cur, colId);
     if (jsonArrayAsString == null) {
       return new JSONArray();
     }
     try {
       return (JSONArray) new JSONParser().parse(getStringFromCursor(cur, colId));
     } catch (ParseException e) {
-      Log.e(LOG_TAG, "JSON parsing error for " + colId, e);
+      Logger.error(LOG_TAG, "JSON parsing error for " + colId, e);
       return null;
     }
   }
 
   // Returns android id from the URI that we get after inserting a
   // bookmark into the local Android store.
   public static long getAndroidIdFromUri(Uri uri) {
     String path = uri.getPath();
     int lastSlash = path.lastIndexOf('/');
     return Long.parseLong(path.substring(lastSlash + 1));
   }
 
   public static BookmarkRecord computeParentFields(BookmarkRecord rec, String suggestedParentID, String suggestedParentName) {
     final String guid = rec.guid;
     if (guid == null) {
       // Oh dear.
-      Log.e(LOG_TAG, "No guid in computeParentFields!");
+      Logger.error(LOG_TAG, "No guid in computeParentFields!");
       return null;
     }
 
     String realParent = SPECIAL_GUID_PARENTS.get(guid);
     if (realParent == null) {
       // No magic parent. Use whatever the caller suggests.
       realParent = suggestedParentID;
     } else {
-      Log.d(LOG_TAG, "Ignoring suggested parent ID " + suggestedParentID +
-                       " for " + guid + "; using " + realParent);
+      Logger.debug(LOG_TAG, "Ignoring suggested parent ID " + suggestedParentID +
+                           " for " + guid + "; using " + realParent);
     }
 
     if (realParent == null) {
       // Oh dear.
-      Log.e(LOG_TAG, "No parent for record " + guid);
+      Logger.error(LOG_TAG, "No parent for record " + guid);
       return null;
     }
 
     // Always set the parent name for special folders back to default.
     String parentName = SPECIAL_GUIDS_MAP.get(realParent);
     if (parentName == null) {
       parentName = suggestedParentName;
     }
@@ -278,28 +278,34 @@ public class RepoUtils {
     // Need to restore the parentId since it isn't stored in content provider.
     // We also take this opportunity to fix up parents for special folders,
     // allowing us to map between the hierarchies used by Fennec and Places.
     return logBookmark(computeParentFields(rec, parentId, parentName));
   }
 
   private static BookmarkRecord logBookmark(BookmarkRecord rec) {
     try {
-      Log.d(LOG_TAG, "Returning bookmark record " + rec.guid + " (" + rec.androidID +
-          ", " + rec.parentName + ":" + rec.parentID + ")");
-      Log.d(LOG_TAG, "> Title:            " + rec.title);
-      Log.d(LOG_TAG, "> Type:             " + rec.type);
-      Log.d(LOG_TAG, "> URI:              " + rec.bookmarkURI);
-      Log.d(LOG_TAG, "> Android position: " + rec.androidPosition);
-      Log.d(LOG_TAG, "> Position:         " + rec.pos);
-      if (rec.isFolder()) {
-        Log.d(LOG_TAG, "FOLDER: Children are " + (rec.children == null ? "null" : rec.children.toJSONString()));
+      Logger.debug(LOG_TAG, "Returning bookmark record " + rec.guid + " (" + rec.androidID +
+                           ", parent " + rec.parentID + ")");
+      if (Logger.LOG_PERSONAL_INFORMATION) {
+        Logger.pii(LOG_TAG, "> Parent name:      " + rec.parentName);
+        Logger.pii(LOG_TAG, "> Title:            " + rec.title);
+        Logger.pii(LOG_TAG, "> Type:             " + rec.type);
+        Logger.pii(LOG_TAG, "> URI:              " + rec.bookmarkURI);
+        Logger.pii(LOG_TAG, "> Android position: " + rec.androidPosition);
+        Logger.pii(LOG_TAG, "> Position:         " + rec.pos);
+        if (rec.isFolder()) {
+          Logger.pii(LOG_TAG, "FOLDER: Children are " +
+                             (rec.children == null ?
+                                 "null" :
+                                 rec.children.toJSONString()));
+        }
       }
     } catch (Exception e) {
-      Log.d(LOG_TAG, "Exception logging bookmark record " + rec, e);
+      Logger.debug(LOG_TAG, "Exception logging bookmark record " + rec, e);
     }
     return rec;
   }
 
   //Create a HistoryRecord object from a cursor on a row with a Moz History record in it
   public static HistoryRecord historyFromMirrorCursor(Cursor cur) {
 
     String guid = getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
@@ -314,23 +320,25 @@ public class RepoUtils {
     rec.fennecDateVisited = getLongFromCursor(cur, BrowserContract.History.DATE_LAST_VISITED);
     rec.fennecVisitCount = getLongFromCursor(cur, BrowserContract.History.VISITS);
 
     return logHistory(rec);
   }
 
   private static HistoryRecord logHistory(HistoryRecord rec) {
     try {
-      Log.d(LOG_TAG, "Returning history record " + rec.guid + " (" + rec.androidID + ")");
-      Log.d(LOG_TAG, "> Title:            " + rec.title);
-      Log.d(LOG_TAG, "> URI:              " + rec.histURI);
-      Log.d(LOG_TAG, "> Visited:          " + rec.fennecDateVisited);
-      Log.d(LOG_TAG, "> Visits:           " + rec.fennecVisitCount);
+      Logger.debug(LOG_TAG, "Returning history record " + rec.guid + " (" + rec.androidID + ")");
+      Logger.debug(LOG_TAG, "> Visited:          " + rec.fennecDateVisited);
+      Logger.debug(LOG_TAG, "> Visits:           " + rec.fennecVisitCount);
+      if (Logger.LOG_PERSONAL_INFORMATION) {
+        Logger.pii(LOG_TAG, "> Title:            " + rec.title);
+        Logger.pii(LOG_TAG, "> URI:              " + rec.histURI);
+      }
     } catch (Exception e) {
-      Log.d(LOG_TAG, "Exception logging bookmark record " + rec, e);
+      Logger.debug(LOG_TAG, "Exception logging bookmark record " + rec, e);
     }
     return rec;
   }
 
   public static PasswordRecord passwordFromMirrorCursor(Cursor cur) {
     
     String guid = getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
     String collection = "passwords";
@@ -351,17 +359,17 @@ public class RepoUtils {
     rec.timeLastUsed = getLongFromCursor(cur, BrowserContract.Passwords.TIME_LAST_USED);
     rec.timesUsed = getLongFromCursor(cur, BrowserContract.Passwords.TIMES_USED);
     
     return rec;
   }
   
   public static void queryTimeLogger(String methodCallingQuery, long queryStart, long queryEnd) {
     long elapsedTime = queryEnd - queryStart;
-    Log.i(LOG_TAG, "Query timer: " + methodCallingQuery + " took " + elapsedTime + "ms.");
+    Logger.debug(LOG_TAG, "Query timer: " + methodCallingQuery + " took " + elapsedTime + "ms.");
   }
 
   public static boolean stringsEqual(String a, String b) {
     // Check for nulls
     if (a == b) return true;
     if (a == null && b != null) return false;
     if (a != null && b == null) return false;
     
--- a/mobile/android/base/sync/repositories/domain/BookmarkRecord.java
+++ b/mobile/android/base/sync/repositories/domain/BookmarkRecord.java
@@ -36,16 +36,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.sync.repositories.domain;
 
 import org.json.simple.JSONArray;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
+import org.mozilla.gecko.sync.Logger;
 import org.mozilla.gecko.sync.NonArrayJSONException;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.android.RepoUtils;
 
 import android.util.Log;
 
 /**
  * Covers the fields used by all bookmark objects.
@@ -219,17 +220,17 @@ public class BookmarkRecord extends Reco
     }
     if (isFolder()) {
       rec.payload.put("children", this.children);
     }
     return rec;
   }
 
   private void trace(String s) {
-    Utils.trace(LOG_TAG, s);
+    Logger.trace(LOG_TAG, s);
   }
 
   @Override
   public boolean equalPayloads(Object o) {
     trace("Calling BookmarkRecord.equalPayloads.");
     if (o == null || !(o instanceof BookmarkRecord)) {
       return false;
     }
--- a/mobile/android/base/sync/repositories/domain/HistoryRecord.java
+++ b/mobile/android/base/sync/repositories/domain/HistoryRecord.java
@@ -38,16 +38,17 @@
 package org.mozilla.gecko.sync.repositories.domain;
 
 import java.util.HashMap;
 
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
+import org.mozilla.gecko.sync.Logger;
 import org.mozilla.gecko.sync.NonArrayJSONException;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.android.RepoUtils;
 
 import android.util.Log;
 
 /**
  * Visits are in microsecond precision.
@@ -186,20 +187,21 @@ public class HistoryRecord extends Recor
       return false;
     }
     HistoryRecord h = (HistoryRecord) other;
     return this.fennecDateVisited == h.fennecDateVisited &&
            this.fennecVisitCount  == h.fennecVisitCount;
   }
 
   private boolean checkVisitsEquals(HistoryRecord other) {
-    Log.d(LOG_TAG, "Checking visits.");
-    if (Utils.ENABLE_TRACE_LOGGING) {
-      Log.d(LOG_TAG, ">> Mine:   " + ((this.visits == null) ? "null" : this.visits.toJSONString()));
-      Log.d(LOG_TAG, ">> Theirs: " + ((other.visits == null) ? "null" : other.visits.toJSONString()));
+    Logger.debug(LOG_TAG, "Checking visits.");
+    if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+      // Don't JSON-encode unless we're logging.
+      Logger.trace(LOG_TAG, ">> Mine:   " + ((this.visits == null) ? "null" : this.visits.toJSONString()));
+      Logger.trace(LOG_TAG, ">> Theirs: " + ((other.visits == null) ? "null" : other.visits.toJSONString()));
     }
 
     // Handle nulls.
     if (this.visits == other.visits) {
       return true;
     }
 
     // Now they can't both be null.
--- a/mobile/android/base/sync/setup/SyncAuthenticatorService.java
+++ b/mobile/android/base/sync/setup/SyncAuthenticatorService.java
@@ -52,17 +52,17 @@ import android.accounts.NetworkErrorExce
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.util.Log;
 
 public class SyncAuthenticatorService extends Service {
-  private static final String LOG_TAG = "SyncAuthenticatorService";
+  private static final String LOG_TAG = "SyncAuthService";
   private SyncAccountAuthenticator sAccountAuthenticator = null;
 
   @Override
   public void onCreate() {
     Log.d(LOG_TAG, "onCreate");
     sAccountAuthenticator = getAuthenticator();
   }
 
--- a/mobile/android/base/sync/stage/FetchInfoCollectionsStage.java
+++ b/mobile/android/base/sync/stage/FetchInfoCollectionsStage.java
@@ -42,17 +42,17 @@ import java.net.URISyntaxException;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.InfoCollections;
 import org.mozilla.gecko.sync.delegates.InfoCollectionsDelegate;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
 
 import android.util.Log;
 
 public class FetchInfoCollectionsStage implements GlobalSyncStage {
-  private static final String LOG_TAG = "FetchInfoCollectionsStage";
+  private static final String LOG_TAG = "FetchInfoCollStage";
 
   public class StageInfoCollectionsDelegate implements InfoCollectionsDelegate {
 
     private GlobalSession session;
     public StageInfoCollectionsDelegate(GlobalSession session) {
       this.session = session;
     }
 
--- a/mobile/android/base/sync/synchronizer/ConcurrentRecordConsumer.java
+++ b/mobile/android/base/sync/synchronizer/ConcurrentRecordConsumer.java
@@ -32,53 +32,53 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.sync.synchronizer;
 
-import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.sync.Logger;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 import android.util.Log;
 
 /**
  * Consume records from a queue inside a RecordsChannel, as fast as we can.
  * TODO: rewrite this in terms of an ExecutorService and a CompletionService.
  * See Bug 713483.
  *
  * @author rnewman
  *
  */
 class ConcurrentRecordConsumer extends RecordConsumer {
-  private static final String LOG_TAG = "ConcurrentRecordConsumer";
+  private static final String LOG_TAG = "CRecordConsumer";
 
   /**
    * When this is true and all records have been processed, the consumer
    * will notify its delegate.
    */
   protected boolean allRecordsQueued = false;
   private long counter = 0;
 
   public ConcurrentRecordConsumer(RecordsConsumerDelegate delegate) {
     this.delegate = delegate;
   }
 
   private static void info(String message) {
-    Utils.info(LOG_TAG, message);
+    Logger.info(LOG_TAG, message);
   }
 
   private static void debug(String message) {
-    Utils.debug(LOG_TAG, message);
+    Logger.debug(LOG_TAG, message);
   }
 
   private static void trace(String message) {
-    Utils.trace(LOG_TAG, message);
+    Logger.trace(LOG_TAG, message);
   }
 
   private Object monitor = new Object();
   @Override
   public void doNotify() {
     synchronized (monitor) {
       monitor.notify();
     }
@@ -99,17 +99,17 @@ class ConcurrentRecordConsumer extends R
       this.stopImmediately = true;
       monitor.notify();
     }
   }
 
   private Object countMonitor = new Object();
   @Override
   public void stored() {
-    debug("Record stored. Notifying.");
+    trace("Record stored. Notifying.");
     synchronized (countMonitor) {
       counter++;
     }
   }
 
   private void consumerIsDone() {
     info("Consumer is done. Processed " + counter + ((counter == 1) ? " record." : " records."));
     delegate.consumerIsDone(!allRecordsQueued);
--- a/mobile/android/base/sync/synchronizer/RecordsChannel.java
+++ b/mobile/android/base/sync/synchronizer/RecordsChannel.java
@@ -35,18 +35,18 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.sync.synchronizer;
 
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutorService;
 
+import org.mozilla.gecko.sync.Logger;
 import org.mozilla.gecko.sync.ThreadPool;
-import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.delegates.DeferredRepositorySessionBeginDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.DeferredRepositorySessionStoreDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
 import org.mozilla.gecko.sync.repositories.domain.Record;
@@ -130,40 +130,16 @@ class RecordsChannel implements
   public ConcurrentLinkedQueue<Record> getQueue() {
     return toProcess;
   }
 
   protected boolean isReady() {
     return source.isActive() && sink.isActive();
   }
 
-
-  private static void info(String message) {
-    Utils.logToStdout(LOG_TAG, "::INFO: ", message);
-    Log.i(LOG_TAG, message);
-  }
-
-  private static void trace(String message) {
-    if (!Utils.ENABLE_TRACE_LOGGING) {
-      return;
-    }
-    Utils.logToStdout(LOG_TAG, "::TRACE: ", message);
-    Log.d(LOG_TAG, message);
-  }
-
-  private static void error(String message, Exception e) {
-    Utils.logToStdout(LOG_TAG, "::ERROR: ", message);
-    Log.e(LOG_TAG, message, e);
-  }
-
-  private static void warn(String message, Exception e) {
-    Utils.logToStdout(LOG_TAG, "::WARN: ", message);
-    Log.w(LOG_TAG, message, e);
-  }
-
   /**
    * Attempt to abort an outstanding fetch. Finish both sessions.
    */
   public void abort() {
     if (source.isActive()) {
       source.abort();
     }
     if (sink.isActive()) {
@@ -189,34 +165,34 @@ class RecordsChannel implements
     waitingForQueueDone = true;
     source.fetchSince(timestamp, this);
   }
 
   /**
    * Begin both sessions, invoking flow() when done.
    */
   public void beginAndFlow() {
-    info("Beginning source.");
+    Logger.info(LOG_TAG, "Beginning source.");
     source.begin(this);
   }
 
   @Override
   public void store(Record record) {
     try {
       sink.store(record);
     } catch (NoStoreDelegateException e) {
-      error("Got NoStoreDelegateException in RecordsChannel.store(). This should not occur. Aborting.", e);
+      Logger.error(LOG_TAG, "Got NoStoreDelegateException in RecordsChannel.store(). This should not occur. Aborting.", e);
       delegate.onFlowStoreFailed(this, e);
       this.abort();
     }
   }
 
   @Override
   public void onFetchFailed(Exception ex, Record record) {
-    warn("onFetchFailed. Calling for immediate stop.", ex);
+    Logger.warn(LOG_TAG, "onFetchFailed. Calling for immediate stop.", ex);
     this.consumer.halt();
   }
 
   @Override
   public void onFetchedRecord(Record record) {
     this.toProcess.add(record);
     this.consumer.doNotify();
   }
@@ -227,18 +203,18 @@ class RecordsChannel implements
       this.toProcess.add(record);
     }
     this.consumer.doNotify();
     this.onFetchCompleted(end);
   }
 
   @Override
   public void onFetchCompleted(long end) {
-    info("onFetchCompleted. Stopping consumer once stores are done.");
-    info("Fetch timestamp is " + end);
+    Logger.info(LOG_TAG, "onFetchCompleted. Stopping consumer once stores are done.");
+    Logger.info(LOG_TAG, "Fetch timestamp is " + end);
     this.end = end;
     this.consumer.queueFilled();
   }
 
   @Override
   public void onRecordStoreFailed(Exception ex) {
     this.consumer.stored();
     delegate.onFlowStoreFailed(this, ex);
@@ -248,43 +224,43 @@ class RecordsChannel implements
   @Override
   public void onRecordStoreSucceeded(Record record) {
     this.consumer.stored();
   }
 
 
   @Override
   public void consumerIsDone(boolean allRecordsQueued) {
-    trace("Consumer is done. Are we waiting for it? " + waitingForQueueDone);
+    Logger.trace(LOG_TAG, "Consumer is done. Are we waiting for it? " + waitingForQueueDone);
     if (waitingForQueueDone) {
       waitingForQueueDone = false;
       this.sink.storeDone();                 // Now we'll be waiting for onStoreCompleted.
     }
   }
 
   @Override
   public void onStoreCompleted() {
-    info("onStoreCompleted. Notifying delegate of onFlowCompleted. End is " + end);
+    Logger.info(LOG_TAG, "onStoreCompleted. Notifying delegate of onFlowCompleted. End is " + end);
     // TODO: synchronize on consumer callback?
     delegate.onFlowCompleted(this, end);
   }
 
   @Override
   public void onBeginFailed(Exception ex) {
     delegate.onFlowBeginFailed(this, ex);
   }
 
   @Override
   public void onBeginSucceeded(RepositorySession session) {
     if (session == source) {
-      info("Source session began. Beginning sink session.");
+      Logger.info(LOG_TAG, "Source session began. Beginning sink session.");
       sink.begin(this);
     }
     if (session == sink) {
-      info("Sink session began. Beginning flow.");
+      Logger.info(LOG_TAG, "Sink session began. Beginning flow.");
       this.flow();
       return;
     }
 
     // TODO: error!
   }
 
   @Override
--- a/mobile/android/base/sync/synchronizer/Synchronizer.java
+++ b/mobile/android/base/sync/synchronizer/Synchronizer.java
@@ -58,17 +58,17 @@ public class Synchronizer {
    * Wrap a SynchronizerDelegate in a SynchronizerSessionDelegate.
    * Also handle communication of bundled data.
    *
    * @author rnewman
    */
   public class SynchronizerDelegateSessionDelegate implements
       SynchronizerSessionDelegate {
 
-    private static final String LOG_TAG = "SynchronizerDelegateSessionDelegate";
+    private static final String LOG_TAG = "SyncDelSDelegate";
     private SynchronizerDelegate synchronizerDelegate;
     private SynchronizerSession  session;
 
     public SynchronizerDelegateSessionDelegate(SynchronizerDelegate delegate) {
       this.synchronizerDelegate = delegate;
     }
 
     @Override
--- a/mobile/android/sync/java-sources.mn
+++ b/mobile/android/sync/java-sources.mn
@@ -1,1 +1,1 @@
-sync/AlreadySyncingException.java sync/CollectionKeys.java sync/CredentialsSource.java sync/crypto/CryptoException.java sync/crypto/Cryptographer.java sync/crypto/CryptoInfo.java sync/crypto/HKDF.java sync/crypto/HMACVerificationException.java sync/crypto/KeyBundle.java sync/crypto/MissingCryptoInputException.java sync/crypto/NoKeyBundleException.java sync/cryptographer/CryptoStatusBundle.java sync/cryptographer/SyncCryptographer.java sync/CryptoRecord.java sync/DelayedWorkTracker.java sync/delegates/FreshStartDelegate.java sync/delegates/GlobalSessionCallback.java sync/delegates/InfoCollectionsDelegate.java sync/delegates/KeyUploadDelegate.java sync/delegates/MetaGlobalDelegate.java sync/delegates/WipeServerDelegate.java sync/ExtendedJSONObject.java sync/GlobalSession.java sync/HTTPFailureException.java sync/InfoCollections.java sync/jpake/BigIntegerHelper.java sync/jpake/Gx3OrGx4IsZeroOrOneException.java sync/jpake/IncorrectZkpException.java sync/jpake/JPakeClient.java sync/jpake/JPakeCrypto.java sync/jpake/JPakeNoActivePairingException.java sync/jpake/JPakeNumGenerator.java sync/jpake/JPakeNumGeneratorRandom.java sync/jpake/JPakeParty.java sync/jpake/JPakeRequest.java sync/jpake/JPakeRequestDelegate.java sync/jpake/JPakeResponse.java sync/jpake/Zkp.java sync/MetaGlobal.java sync/MetaGlobalException.java sync/MetaGlobalMissingEnginesException.java sync/MetaGlobalNotSetException.java sync/middleware/Crypto5MiddlewareRepository.java sync/middleware/Crypto5MiddlewareRepositorySession.java sync/middleware/MiddlewareRepository.java sync/net/BaseResource.java sync/net/CompletedEntity.java sync/net/HandleProgressException.java sync/net/Resource.java sync/net/ResourceDelegate.java sync/net/SyncResourceDelegate.java sync/net/SyncResponse.java sync/net/SyncStorageCollectionRequest.java sync/net/SyncStorageCollectionRequestDelegate.java sync/net/SyncStorageRecordRequest.java sync/net/SyncStorageRequest.java sync/net/SyncStorageRequestDelegate.java sync/net/SyncStorageRequestIncrementalDelegate.java sync/net/SyncStorageResponse.java sync/net/TLSSocketFactory.java sync/net/WBOCollectionRequestDelegate.java sync/NoCollectionKeysSetException.java sync/NonArrayJSONException.java sync/NonObjectJSONException.java sync/PrefsSource.java sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java sync/repositories/android/AndroidBrowserBookmarksRepository.java sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java sync/repositories/android/AndroidBrowserHistoryDataAccessor.java sync/repositories/android/AndroidBrowserHistoryDataExtender.java sync/repositories/android/AndroidBrowserHistoryRepository.java sync/repositories/android/AndroidBrowserHistoryRepositorySession.java sync/repositories/android/AndroidBrowserPasswordsDataAccessor.java sync/repositories/android/AndroidBrowserPasswordsRepository.java sync/repositories/android/AndroidBrowserPasswordsRepositorySession.java sync/repositories/android/AndroidBrowserRepository.java sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java sync/repositories/android/AndroidBrowserRepositorySession.java sync/repositories/android/BrowserContract.java sync/repositories/android/PasswordColumns.java sync/repositories/android/RepoUtils.java sync/repositories/BookmarkNeedsReparentingException.java sync/repositories/BookmarksRepository.java sync/repositories/ConstrainedServer11Repository.java sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionBeginDelegate.java sync/repositories/delegates/RepositorySessionCleanDelegate.java sync/repositories/delegates/RepositorySessionCreationDelegate.java sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/RepositorySessionFinishDelegate.java sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java sync/repositories/delegates/RepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionWipeDelegate.java sync/repositories/domain/BookmarkRecord.java sync/repositories/domain/BookmarkRecordFactory.java sync/repositories/domain/HistoryRecord.java sync/repositories/domain/HistoryRecordFactory.java sync/repositories/domain/PasswordRecord.java sync/repositories/domain/Record.java sync/repositories/HashSetStoreTracker.java sync/repositories/HistoryRepository.java sync/repositories/IdentityRecordFactory.java sync/repositories/InactiveSessionException.java sync/repositories/InvalidBookmarkTypeException.java sync/repositories/InvalidRequestException.java sync/repositories/InvalidSessionTransitionException.java sync/repositories/MultipleRecordsForGuidException.java sync/repositories/NoGuidForIdException.java sync/repositories/NoStoreDelegateException.java sync/repositories/NullCursorException.java sync/repositories/ParentNotFoundException.java sync/repositories/ProfileDatabaseException.java sync/repositories/RecordFactory.java sync/repositories/RecordFilter.java sync/repositories/Repository.java sync/repositories/RepositorySession.java sync/repositories/RepositorySessionBundle.java sync/repositories/Server11Repository.java sync/repositories/Server11RepositorySession.java sync/repositories/StoreTracker.java sync/repositories/StoreTrackingRepositorySession.java sync/setup/activities/AccountActivity.java sync/setup/activities/SetupFailureActivity.java sync/setup/activities/SetupSuccessActivity.java sync/setup/activities/SetupSyncActivity.java sync/setup/Constants.java sync/setup/SyncAuthenticatorService.java sync/stage/AndroidBrowserBookmarksServerSyncStage.java sync/stage/AndroidBrowserHistoryServerSyncStage.java sync/stage/CheckPreconditionsStage.java sync/stage/CompletedStage.java sync/stage/EnsureClusterURLStage.java sync/stage/EnsureKeysStage.java sync/stage/FetchInfoCollectionsStage.java sync/stage/FetchMetaGlobalStage.java sync/stage/GlobalSyncStage.java sync/stage/NoSuchStageException.java sync/stage/NoSyncIDException.java sync/stage/ServerSyncStage.java sync/StubActivity.java sync/syncadapter/SyncAdapter.java sync/syncadapter/SyncService.java sync/SyncConfiguration.java sync/SyncConfigurationException.java sync/SyncException.java sync/synchronizer/ConcurrentRecordConsumer.java sync/synchronizer/RecordConsumer.java sync/synchronizer/RecordsChannel.java sync/synchronizer/RecordsChannelDelegate.java sync/synchronizer/RecordsConsumerDelegate.java sync/synchronizer/SerialRecordConsumer.java sync/synchronizer/SessionNotBegunException.java sync/synchronizer/Synchronizer.java sync/synchronizer/SynchronizerDelegate.java sync/synchronizer/SynchronizerSession.java sync/synchronizer/SynchronizerSessionDelegate.java sync/synchronizer/UnbundleError.java sync/synchronizer/UnexpectedSessionException.java sync/SynchronizerConfiguration.java sync/SynchronizerConfigurations.java sync/ThreadPool.java sync/UnexpectedJSONException.java sync/UnknownSynchronizerConfigurationVersionException.java sync/Utils.java
+sync/AlreadySyncingException.java sync/CollectionKeys.java sync/CredentialsSource.java sync/crypto/CryptoException.java sync/crypto/Cryptographer.java sync/crypto/CryptoInfo.java sync/crypto/HKDF.java sync/crypto/HMACVerificationException.java sync/crypto/KeyBundle.java sync/crypto/MissingCryptoInputException.java sync/crypto/NoKeyBundleException.java sync/cryptographer/CryptoStatusBundle.java sync/cryptographer/SyncCryptographer.java sync/CryptoRecord.java sync/DelayedWorkTracker.java sync/delegates/FreshStartDelegate.java sync/delegates/GlobalSessionCallback.java sync/delegates/InfoCollectionsDelegate.java sync/delegates/KeyUploadDelegate.java sync/delegates/MetaGlobalDelegate.java sync/delegates/WipeServerDelegate.java sync/ExtendedJSONObject.java sync/GlobalSession.java sync/HTTPFailureException.java sync/InfoCollections.java sync/jpake/BigIntegerHelper.java sync/jpake/Gx3OrGx4IsZeroOrOneException.java sync/jpake/IncorrectZkpException.java sync/jpake/JPakeClient.java sync/jpake/JPakeCrypto.java sync/jpake/JPakeNoActivePairingException.java sync/jpake/JPakeNumGenerator.java sync/jpake/JPakeNumGeneratorRandom.java sync/jpake/JPakeParty.java sync/jpake/JPakeRequest.java sync/jpake/JPakeRequestDelegate.java sync/jpake/JPakeResponse.java sync/jpake/Zkp.java sync/Logger.java sync/MetaGlobal.java sync/MetaGlobalException.java sync/MetaGlobalMissingEnginesException.java sync/MetaGlobalNotSetException.java sync/middleware/Crypto5MiddlewareRepository.java sync/middleware/Crypto5MiddlewareRepositorySession.java sync/middleware/MiddlewareRepository.java sync/net/BaseResource.java sync/net/CompletedEntity.java sync/net/HandleProgressException.java sync/net/Resource.java sync/net/ResourceDelegate.java sync/net/SyncResourceDelegate.java sync/net/SyncResponse.java sync/net/SyncStorageCollectionRequest.java sync/net/SyncStorageCollectionRequestDelegate.java sync/net/SyncStorageRecordRequest.java sync/net/SyncStorageRequest.java sync/net/SyncStorageRequestDelegate.java sync/net/SyncStorageRequestIncrementalDelegate.java sync/net/SyncStorageResponse.java sync/net/TLSSocketFactory.java sync/net/WBOCollectionRequestDelegate.java sync/NoCollectionKeysSetException.java sync/NonArrayJSONException.java sync/NonObjectJSONException.java sync/PrefsSource.java sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java sync/repositories/android/AndroidBrowserBookmarksRepository.java sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java sync/repositories/android/AndroidBrowserHistoryDataAccessor.java sync/repositories/android/AndroidBrowserHistoryDataExtender.java sync/repositories/android/AndroidBrowserHistoryRepository.java sync/repositories/android/AndroidBrowserHistoryRepositorySession.java sync/repositories/android/AndroidBrowserPasswordsDataAccessor.java sync/repositories/android/AndroidBrowserPasswordsRepository.java sync/repositories/android/AndroidBrowserPasswordsRepositorySession.java sync/repositories/android/AndroidBrowserRepository.java sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java sync/repositories/android/AndroidBrowserRepositorySession.java sync/repositories/android/BrowserContract.java sync/repositories/android/PasswordColumns.java sync/repositories/android/RepoUtils.java sync/repositories/BookmarkNeedsReparentingException.java sync/repositories/BookmarksRepository.java sync/repositories/ConstrainedServer11Repository.java sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionBeginDelegate.java sync/repositories/delegates/RepositorySessionCleanDelegate.java sync/repositories/delegates/RepositorySessionCreationDelegate.java sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/RepositorySessionFinishDelegate.java sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java sync/repositories/delegates/RepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionWipeDelegate.java sync/repositories/domain/BookmarkRecord.java sync/repositories/domain/BookmarkRecordFactory.java sync/repositories/domain/HistoryRecord.java sync/repositories/domain/HistoryRecordFactory.java sync/repositories/domain/PasswordRecord.java sync/repositories/domain/Record.java sync/repositories/HashSetStoreTracker.java sync/repositories/HistoryRepository.java sync/repositories/IdentityRecordFactory.java sync/repositories/InactiveSessionException.java sync/repositories/InvalidBookmarkTypeException.java sync/repositories/InvalidRequestException.java sync/repositories/InvalidSessionTransitionException.java sync/repositories/MultipleRecordsForGuidException.java sync/repositories/NoGuidForIdException.java sync/repositories/NoStoreDelegateException.java sync/repositories/NullCursorException.java sync/repositories/ParentNotFoundException.java sync/repositories/ProfileDatabaseException.java sync/repositories/RecordFactory.java sync/repositories/RecordFilter.java sync/repositories/Repository.java sync/repositories/RepositorySession.java sync/repositories/RepositorySessionBundle.java sync/repositories/Server11Repository.java sync/repositories/Server11RepositorySession.java sync/repositories/StoreTracker.java sync/repositories/StoreTrackingRepositorySession.java sync/setup/activities/AccountActivity.java sync/setup/activities/SetupFailureActivity.java sync/setup/activities/SetupSuccessActivity.java sync/setup/activities/SetupSyncActivity.java sync/setup/Constants.java sync/setup/SyncAuthenticatorService.java sync/stage/AndroidBrowserBookmarksServerSyncStage.java sync/stage/AndroidBrowserHistoryServerSyncStage.java sync/stage/CheckPreconditionsStage.java sync/stage/CompletedStage.java sync/stage/EnsureClusterURLStage.java sync/stage/EnsureKeysStage.java sync/stage/FetchInfoCollectionsStage.java sync/stage/FetchMetaGlobalStage.java sync/stage/GlobalSyncStage.java sync/stage/NoSuchStageException.java sync/stage/NoSyncIDException.java sync/stage/ServerSyncStage.java sync/StubActivity.java sync/syncadapter/SyncAdapter.java sync/syncadapter/SyncService.java sync/SyncConfiguration.java sync/SyncConfigurationException.java sync/SyncException.java sync/synchronizer/ConcurrentRecordConsumer.java sync/synchronizer/RecordConsumer.java sync/synchronizer/RecordsChannel.java sync/synchronizer/RecordsChannelDelegate.java sync/synchronizer/RecordsConsumerDelegate.java sync/synchronizer/SerialRecordConsumer.java sync/synchronizer/SessionNotBegunException.java sync/synchronizer/Synchronizer.java sync/synchronizer/SynchronizerDelegate.java sync/synchronizer/SynchronizerSession.java sync/synchronizer/SynchronizerSessionDelegate.java sync/synchronizer/UnbundleError.java sync/synchronizer/UnexpectedSessionException.java sync/SynchronizerConfiguration.java sync/SynchronizerConfigurations.java sync/ThreadPool.java sync/UnexpectedJSONException.java sync/UnknownSynchronizerConfigurationVersionException.java sync/Utils.java