diff --git a/java/src/com/android/inputmethod/research/LogStatement.java b/java/src/com/android/inputmethod/research/LogStatement.java
index 1d83e1a86cb58473eee033fd339a304b0af511a9..059146ae684188db6250fae65e5115d19d11403b 100644
--- a/java/src/com/android/inputmethod/research/LogStatement.java
+++ b/java/src/com/android/inputmethod/research/LogStatement.java
@@ -16,6 +16,18 @@
 
 package com.android.inputmethod.research;
 
+import android.content.SharedPreferences;
+import android.util.JsonWriter;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.inputmethod.CompletionInfo;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.define.ProductionFlag;
+
+import java.io.IOException;
+
 /**
  * A template for typed information stored in the logs.
  *
@@ -24,6 +36,9 @@ package com.android.inputmethod.research;
  * actual values are stored separately.
  */
 class LogStatement {
+    private static final String TAG = LogStatement.class.getSimpleName();
+    private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
+
     // Constants for particular statements
     public static final String TYPE_POINTER_TRACKER_CALL_LISTENER_ON_CODE_INPUT =
             "PointerTrackerCallListenerOnCodeInput";
@@ -36,6 +51,11 @@ class LogStatement {
     public static final String TYPE_MOTION_EVENT = "MotionEvent";
     public static final String KEY_IS_LOGGING_RELATED = "isLoggingRelated";
 
+    // Keys for internal key/value pairs
+    private static final String CURRENT_TIME_KEY = "_ct";
+    private static final String UPTIME_KEY = "_ut";
+    private static final String EVENT_TYPE_KEY = "_ty";
+
     // Name specifying the LogStatement type.
     private final String mType;
 
@@ -142,4 +162,61 @@ class LogStatement {
         }
         return false;
     }
+
+    /**
+     * Write the contents out through jsonWriter.
+     *
+     * Note that this method is not thread safe for the same jsonWriter.  Callers must ensure
+     * thread safety.
+     */
+    public boolean outputToLocked(final JsonWriter jsonWriter, final Long time,
+            final Object... values) {
+        if (DEBUG) {
+            if (mKeys.length != values.length) {
+                Log.d(TAG, "Key and Value list sizes do not match. " + mType);
+            }
+        }
+        try {
+            jsonWriter.beginObject();
+            jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
+            jsonWriter.name(UPTIME_KEY).value(time);
+            jsonWriter.name(EVENT_TYPE_KEY).value(mType);
+            final int length = values.length;
+            for (int i = 0; i < length; i++) {
+                jsonWriter.name(mKeys[i]);
+                final Object value = values[i];
+                if (value instanceof CharSequence) {
+                    jsonWriter.value(value.toString());
+                } else if (value instanceof Number) {
+                    jsonWriter.value((Number) value);
+                } else if (value instanceof Boolean) {
+                    jsonWriter.value((Boolean) value);
+                } else if (value instanceof CompletionInfo[]) {
+                    JsonUtils.writeJson((CompletionInfo[]) value, jsonWriter);
+                } else if (value instanceof SharedPreferences) {
+                    JsonUtils.writeJson((SharedPreferences) value, jsonWriter);
+                } else if (value instanceof Key[]) {
+                    JsonUtils.writeJson((Key[]) value, jsonWriter);
+                } else if (value instanceof SuggestedWords) {
+                    JsonUtils.writeJson((SuggestedWords) value, jsonWriter);
+                } else if (value instanceof MotionEvent) {
+                    JsonUtils.writeJson((MotionEvent) value, jsonWriter);
+                } else if (value == null) {
+                    jsonWriter.nullValue();
+                } else {
+                    if (DEBUG) {
+                        Log.w(TAG, "Unrecognized type to be logged: "
+                                + (value == null ? "<null>" : value.getClass().getName()));
+                    }
+                    jsonWriter.nullValue();
+                }
+            }
+            jsonWriter.endObject();
+        } catch (IOException e) {
+            e.printStackTrace();
+            Log.w(TAG, "Error in JsonWriter; skipping LogStatement");
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index 2e732fc6c84ebf3e3a245f18f137bf5038148e4d..a584a3af6ed738cbf9c1cf6aebd4dad6c17bd8f1 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -17,13 +17,11 @@
 package com.android.inputmethod.research;
 
 import android.content.SharedPreferences;
+import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.JsonWriter;
 import android.util.Log;
-import android.view.MotionEvent;
-import android.view.inputmethod.CompletionInfo;
 
-import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
@@ -153,11 +151,10 @@ import java.util.List;
                     jsonWriter = researchLog.getValidJsonWriterLocked();
                     outputLogUnitStart(jsonWriter, canIncludePrivateData);
                 }
-                outputLogStatementToLocked(jsonWriter, mLogStatementList.get(i), mValuesList.get(i),
-                        mTimeList.get(i));
+                logStatement.outputToLocked(jsonWriter, mTimeList.get(i), mValuesList.get(i));
                 if (DEBUG) {
-                    outputLogStatementToLocked(debugJsonWriter, mLogStatementList.get(i),
-                            mValuesList.get(i), mTimeList.get(i));
+                    logStatement.outputToLocked(debugJsonWriter, mTimeList.get(i),
+                            mValuesList.get(i));
                 }
             }
             if (jsonWriter != null) {
@@ -180,97 +177,34 @@ import java.util.List;
         }
     }
 
-    private static final String CURRENT_TIME_KEY = "_ct";
-    private static final String UPTIME_KEY = "_ut";
-    private static final String EVENT_TYPE_KEY = "_ty";
     private static final String WORD_KEY = "_wo";
     private static final String CORRECTION_TYPE_KEY = "_corType";
     private static final String LOG_UNIT_BEGIN_KEY = "logUnitStart";
     private static final String LOG_UNIT_END_KEY = "logUnitEnd";
 
+    final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA =
+            new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */,
+                    false /* isPotentiallyRevealing */, WORD_KEY, CORRECTION_TYPE_KEY);
+    final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA =
+            new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */,
+                    false /* isPotentiallyRevealing */);
     private void outputLogUnitStart(final JsonWriter jsonWriter,
             final boolean canIncludePrivateData) {
-        try {
-            jsonWriter.beginObject();
-            jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
-            if (canIncludePrivateData) {
-                jsonWriter.name(WORD_KEY).value(getWord());
-                jsonWriter.name(CORRECTION_TYPE_KEY).value(getCorrectionType());
-            }
-            jsonWriter.name(EVENT_TYPE_KEY).value(LOG_UNIT_BEGIN_KEY);
-            jsonWriter.endObject();
-        } catch (IOException e) {
-            e.printStackTrace();
-            Log.w(TAG, "Error in JsonWriter; cannot write LogUnitStart");
+        final LogStatement logStatement;
+        if (canIncludePrivateData) {
+            LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA.outputToLocked(jsonWriter,
+                    SystemClock.uptimeMillis(), getWord(), getCorrectionType());
+        } else {
+            LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA.outputToLocked(jsonWriter,
+                    SystemClock.uptimeMillis());
         }
     }
 
+    final LogStatement LOGSTATEMENT_LOG_UNIT_END =
+            new LogStatement(LOG_UNIT_END_KEY, false /* isPotentiallyPrivate */,
+                    false /* isPotentiallyRevealing */);
     private void outputLogUnitStop(final JsonWriter jsonWriter) {
-        try {
-            jsonWriter.beginObject();
-            jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
-            jsonWriter.name(EVENT_TYPE_KEY).value(LOG_UNIT_END_KEY);
-            jsonWriter.endObject();
-        } catch (IOException e) {
-            e.printStackTrace();
-            Log.w(TAG, "Error in JsonWriter; cannot write LogUnitStop");
-        }
-    }
-
-    /**
-     * Write the logStatement and its contents out through jsonWriter.
-     *
-     * Note that this method is not thread safe for the same jsonWriter.  Callers must ensure
-     * thread safety.
-     */
-    private boolean outputLogStatementToLocked(final JsonWriter jsonWriter,
-            final LogStatement logStatement, final Object[] values, final Long time) {
-        if (DEBUG) {
-            if (logStatement.getKeys().length != values.length) {
-                Log.d(TAG, "Key and Value list sizes do not match. " + logStatement.getType());
-            }
-        }
-        try {
-            jsonWriter.beginObject();
-            jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
-            jsonWriter.name(UPTIME_KEY).value(time);
-            jsonWriter.name(EVENT_TYPE_KEY).value(logStatement.getType());
-            final String[] keys = logStatement.getKeys();
-            final int length = values.length;
-            for (int i = 0; i < length; i++) {
-                jsonWriter.name(keys[i]);
-                final Object value = values[i];
-                if (value instanceof CharSequence) {
-                    jsonWriter.value(value.toString());
-                } else if (value instanceof Number) {
-                    jsonWriter.value((Number) value);
-                } else if (value instanceof Boolean) {
-                    jsonWriter.value((Boolean) value);
-                } else if (value instanceof CompletionInfo[]) {
-                    JsonUtils.writeJson((CompletionInfo[]) value, jsonWriter);
-                } else if (value instanceof SharedPreferences) {
-                    JsonUtils.writeJson((SharedPreferences) value, jsonWriter);
-                } else if (value instanceof Key[]) {
-                    JsonUtils.writeJson((Key[]) value, jsonWriter);
-                } else if (value instanceof SuggestedWords) {
-                    JsonUtils.writeJson((SuggestedWords) value, jsonWriter);
-                } else if (value instanceof MotionEvent) {
-                    JsonUtils.writeJson((MotionEvent) value, jsonWriter);
-                } else if (value == null) {
-                    jsonWriter.nullValue();
-                } else {
-                    Log.w(TAG, "Unrecognized type to be logged: "
-                            + (value == null ? "<null>" : value.getClass().getName()));
-                    jsonWriter.nullValue();
-                }
-            }
-            jsonWriter.endObject();
-        } catch (IOException e) {
-            e.printStackTrace();
-            Log.w(TAG, "Error in JsonWriter; skipping LogStatement");
-            return false;
-        }
-        return true;
+        LOGSTATEMENT_LOG_UNIT_END.outputToLocked(jsonWriter, SystemClock.uptimeMillis());
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 8fc62ea7bef34d5c4dda30814e5964d554949874..364ab2da2b48570bd8dab5d1e21d989b9b1a62ec 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -1003,15 +1003,23 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
         }
     }
 
+    /**
+     * Publish all the logUnits in the logBuffer, without doing any privacy filtering.
+     */
     /* package for test */ void publishLogBuffer(final LogBuffer logBuffer,
-            final ResearchLog researchLog, final boolean isIncludingPrivateData) {
-        publishLogUnits(logBuffer.getLogUnits(), researchLog, isIncludingPrivateData);
+            final ResearchLog researchLog, final boolean canIncludePrivateData) {
+        publishLogUnits(logBuffer.getLogUnits(), researchLog, canIncludePrivateData);
     }
 
     private static final LogStatement LOGSTATEMENT_LOG_SEGMENT_OPENING =
             new LogStatement("logSegmentStart", false, false, "isIncludingPrivateData");
     private static final LogStatement LOGSTATEMENT_LOG_SEGMENT_CLOSING =
             new LogStatement("logSegmentEnd", false, false);
+    /**
+     * Publish all LogUnits in a list.
+     *
+     * Any privacy checks should be performed before calling this method.
+     */
     /* package for test */ void publishLogUnits(final List<LogUnit> logUnits,
             final ResearchLog researchLog, final boolean canIncludePrivateData) {
         final LogUnit openingLogUnit = new LogUnit();
@@ -1392,7 +1400,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
             final int index, final String suggestion, final boolean isBatchMode) {
         final ResearchLogger researchLogger = getInstance();
         if (!replacedWord.equals(suggestion.toString())) {
-            // The user choose something other than what was already there.
+            // The user chose something other than what was already there.
             researchLogger.setCurrentLogUnitContainsCorrection();
             researchLogger.setCurrentLogUnitCorrectionType(LogUnit.CORRECTIONTYPE_TYPO);
         }