diff --git a/CHANGELOG.md b/CHANGELOG.md
index f91d8e00a..eb5743a57 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,9 @@
 - [changed] Dropped support for App Engine's Java 7 runtime. Developers
   are advised to use the Admin SDK with Java 8 when deploying to App
   Engine.
+- [changed] Removed the deprecated `FirebaseDatabase.setLogLevel()` API
+  and the related logging utilities. Developers should use SLF4J to
+  configure logging directly.
 
 # v5.9.0
 
diff --git a/src/main/java/com/google/firebase/database/FirebaseDatabase.java b/src/main/java/com/google/firebase/database/FirebaseDatabase.java
index 6a196f913..2ddb55d58 100644
--- a/src/main/java/com/google/firebase/database/FirebaseDatabase.java
+++ b/src/main/java/com/google/firebase/database/FirebaseDatabase.java
@@ -256,24 +256,6 @@ public void goOffline() {
     RepoManager.interrupt(ensureRepo());
   }
 
-  /**
-   * By default, this is set to {@link Logger.Level#INFO INFO}. This includes any internal errors
-   * ({@link Logger.Level#ERROR ERROR}) and any security debug messages ({@link Logger.Level#INFO
-   * INFO}) that the client receives. Set to {@link Logger.Level#DEBUG DEBUG} to turn on the
-   * diagnostic logging, and {@link Logger.Level#NONE NONE} to disable all logging.
-   *
-   * @param logLevel The desired minimum log level
-   * @deprecated This method will be removed in a future release. Use SLF4J-based logging instead.
-   *     For example, add the slf4j-simple.jar to the classpath to log to STDERR. See
-   *     SLF4J user manual for more details.
-   */
-  public synchronized void setLogLevel(Logger.Level logLevel) {
-    synchronized (lock) {
-      assertUnfrozen("setLogLevel");
-      this.config.setLogLevel(logLevel);
-    }
-  }
-
   /**
    * The Firebase Database client will cache synchronized data and keep track of all writes you've
    * initiated while your application is running. It seamlessly handles intermittent network
diff --git a/src/main/java/com/google/firebase/database/connection/Connection.java b/src/main/java/com/google/firebase/database/connection/Connection.java
index 4059e46e8..83e377304 100644
--- a/src/main/java/com/google/firebase/database/connection/Connection.java
+++ b/src/main/java/com/google/firebase/database/connection/Connection.java
@@ -17,10 +17,11 @@
 package com.google.firebase.database.connection;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.firebase.database.logging.LogWrapper;
 
 import java.util.HashMap;
 import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 class Connection implements WebsocketConnection.Delegate {
 
@@ -39,11 +40,14 @@ class Connection implements WebsocketConnection.Delegate {
   private static final String SERVER_HELLO_TIMESTAMP = "ts";
   private static final String SERVER_HELLO_HOST = "h";
   private static final String SERVER_HELLO_SESSION_ID = "s";
+
+  private static final Logger logger = LoggerFactory.getLogger(Connection.class);
+
   private static long connectionIds = 0;
 
-  private final LogWrapper logger;
   private final HostInfo hostInfo;
   private final Delegate delegate;
+  private final String label;
 
   private WebsocketConnection conn;
   private State state;
@@ -54,36 +58,31 @@ class Connection implements WebsocketConnection.Delegate {
       String cachedHost,
       Delegate delegate,
       String optLastSessionId) {
-    this(context, hostInfo, delegate,
+    this(hostInfo, delegate,
         new DefaultWebsocketConnectionFactory(context, hostInfo, cachedHost, optLastSessionId));
   }
 
   @VisibleForTesting
   Connection(
-      ConnectionContext context,
       HostInfo hostInfo,
       Delegate delegate,
       WebsocketConnectionFactory connFactory) {
     long connId = connectionIds++;
     this.hostInfo = hostInfo;
     this.delegate = delegate;
-    this.logger = new LogWrapper(context.getLogger(), Connection.class, "conn_" + connId);
+    this.label = "[conn_" + connId + "]";
     this.state = State.REALTIME_CONNECTING;
     this.conn = connFactory.newConnection(this);
   }
 
   public void open() {
-    if (logger.logsDebug()) {
-      logger.debug("Opening a connection");
-    }
+    logger.debug("{} Opening a connection", label);
     conn.open();
   }
 
   public void close(DisconnectReason reason) {
     if (state != State.REALTIME_DISCONNECTED) {
-      if (logger.logsDebug()) {
-        logger.debug("closing realtime connection");
-      }
+      logger.debug("{} Closing realtime connection", label);
       state = State.REALTIME_DISCONNECTED;
 
       if (conn != null) {
@@ -123,21 +122,14 @@ public void onMessage(Map message) {
           Map data = (Map) message.get(SERVER_ENVELOPE_DATA);
           onControlMessage(data);
         } else {
-          if (logger.logsDebug()) {
-            logger.debug("Ignoring unknown server message type: " + messageType);
-          }
+          logger.debug("{} Ignoring unknown server message type: {}", label, messageType);
         }
       } else {
-        if (logger.logsDebug()) {
-          logger.debug(
-              "Failed to parse server message: missing message type:" + message.toString());
-        }
+        logger.debug("{} Failed to parse server message: missing message type: {}", label, message);
         close();
       }
     } catch (ClassCastException e) {
-      if (logger.logsDebug()) {
-        logger.debug("Failed to parse server message: " + e.toString());
-      }
+      logger.debug("{} Failed to parse server message", label, e);
       close();
     }
   }
@@ -146,30 +138,22 @@ public void onMessage(Map message) {
   public void onDisconnect(boolean wasEverConnected) {
     conn = null;
     if (!wasEverConnected && state == State.REALTIME_CONNECTING) {
-      if (logger.logsDebug()) {
-        logger.debug("Realtime connection failed");
-      }
+      logger.debug("{} Realtime connection failed", label);
     } else {
-      if (logger.logsDebug()) {
-        logger.debug("Realtime connection lost");
-      }
+      logger.debug("{} Realtime connection lost", label);
     }
 
     close();
   }
 
   private void onDataMessage(Map data) {
-    if (logger.logsDebug()) {
-      logger.debug("received data message: " + data.toString());
-    }
+    logger.debug("{} Received data message: {}", label, data);
     // We don't do anything with data messages, just kick them up a level
     delegate.onDataMessage(data);
   }
 
   private void onControlMessage(Map data) {
-    if (logger.logsDebug()) {
-      logger.debug("Got control message: " + data.toString());
-    }
+    logger.debug("{} Got control message: {}", label, data);
     try {
       String messageType = (String) data.get(SERVER_CONTROL_MESSAGE_TYPE);
       if (messageType != null) {
@@ -185,28 +169,20 @@ private void onControlMessage(Map data) {
               (Map) data.get(SERVER_CONTROL_MESSAGE_DATA);
           onHandshake(handshakeData);
         } else {
-          if (logger.logsDebug()) {
-            logger.debug("Ignoring unknown control message: " + messageType);
-          }
+          logger.debug("{} Ignoring unknown control message: {}", label, messageType);
         }
       } else {
-        if (logger.logsDebug()) {
-          logger.debug("Got invalid control message: " + data.toString());
-        }
+        logger.debug("{} Got invalid control message: {}", label, data);
         close();
       }
     } catch (ClassCastException e) {
-      if (logger.logsDebug()) {
-        logger.debug("Failed to parse control message: " + e.toString());
-      }
+      logger.debug("{} Failed to parse control message", label, e);
       close();
     }
   }
 
   private void onConnectionShutdown(String reason) {
-    if (logger.logsDebug()) {
-      logger.debug("Connection shutdown command received. Shutting down...");
-    }
+    logger.debug("{} Connection shutdown command received. Shutting down...", label);
     delegate.onKill(reason);
     close();
   }
@@ -224,21 +200,15 @@ private void onHandshake(Map handshake) {
   }
 
   private void onConnectionReady(long timestamp, String sessionId) {
-    if (logger.logsDebug()) {
-      logger.debug("realtime connection established");
-    }
+    logger.debug("{} Realtime connection established", label);
     state = State.REALTIME_CONNECTED;
     delegate.onReady(timestamp, sessionId);
   }
 
   private void onReset(String host) {
-    if (logger.logsDebug()) {
-      logger.debug(
-          "Got a reset; killing connection to "
-              + this.hostInfo.getHost()
-              + "; Updating internalHost to "
-              + host);
-    }
+    logger.debug(
+        "{} Got a reset; killing connection to {}; Updating internalHost to {}",
+            label, hostInfo.getHost(), host);
     delegate.onCacheHost(host);
 
     // Explicitly close the connection with SERVER_RESET so calling code knows to reconnect
@@ -248,12 +218,12 @@ private void onReset(String host) {
 
   private void sendData(Map data, boolean isSensitive) {
     if (state != State.REALTIME_CONNECTED) {
-      logger.debug("Tried to send on an unconnected connection");
+      logger.debug("{} Tried to send on an unconnected connection", label);
     } else {
       if (isSensitive) {
-        logger.debug("Sending data (contents hidden)");
+        logger.debug("{} Sending data (contents hidden)", label);
       } else {
-        logger.debug("Sending data: %s", data);
+        logger.debug("{} Sending data: {}", label, data);
       }
       conn.send(data);
     }
diff --git a/src/main/java/com/google/firebase/database/connection/ConnectionContext.java b/src/main/java/com/google/firebase/database/connection/ConnectionContext.java
index 03b4b45f4..574bc1df9 100644
--- a/src/main/java/com/google/firebase/database/connection/ConnectionContext.java
+++ b/src/main/java/com/google/firebase/database/connection/ConnectionContext.java
@@ -16,8 +16,6 @@
 
 package com.google.firebase.database.connection;
 
-import com.google.firebase.database.logging.Logger;
-
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ThreadFactory;
 
@@ -25,21 +23,18 @@ public class ConnectionContext {
 
   private final ScheduledExecutorService executorService;
   private final ConnectionAuthTokenProvider authTokenProvider;
-  private final Logger logger;
   private final boolean persistenceEnabled;
   private final String clientSdkVersion;
   private final String userAgent;
   private final ThreadFactory threadFactory;
 
   public ConnectionContext(
-      Logger logger,
       ConnectionAuthTokenProvider authTokenProvider,
       ScheduledExecutorService executorService,
       boolean persistenceEnabled,
       String clientSdkVersion,
       String userAgent,
       ThreadFactory threadFactory) {
-    this.logger = logger;
     this.authTokenProvider = authTokenProvider;
     this.executorService = executorService;
     this.persistenceEnabled = persistenceEnabled;
@@ -48,10 +43,6 @@ public ConnectionContext(
     this.threadFactory = threadFactory;
   }
 
-  public Logger getLogger() {
-    return this.logger;
-  }
-
   public ConnectionAuthTokenProvider getAuthTokenProvider() {
     return this.authTokenProvider;
   }
diff --git a/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java b/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java
index 5f2f71303..fc79b4cfe 100644
--- a/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java
+++ b/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java
@@ -19,7 +19,6 @@
 import static com.google.firebase.database.connection.ConnectionUtils.hardAssert;
 
 import com.google.firebase.database.connection.util.RetryHelper;
-import com.google.firebase.database.logging.LogWrapper;
 import com.google.firebase.database.util.GAuthToken;
 
 import java.util.ArrayList;
@@ -33,6 +32,8 @@
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class PersistentConnectionImpl implements Connection.Delegate, PersistentConnection {
 
@@ -91,6 +92,9 @@ public class PersistentConnectionImpl implements Connection.Delegate, Persistent
   private static final String SERVER_KILL_INTERRUPT_REASON = "server_kill";
   private static final String IDLE_INTERRUPT_REASON = "connection_idle";
   private static final String TOKEN_REFRESH_INTERRUPT_REASON = "token_refresh";
+
+  private static final Logger logger = LoggerFactory.getLogger(PersistentConnection.class);
+
   private static long connectionIds = 0;
 
   private final Delegate delegate;
@@ -99,8 +103,8 @@ public class PersistentConnectionImpl implements Connection.Delegate, Persistent
   private final ConnectionFactory connFactory;
   private final ConnectionAuthTokenProvider authTokenProvider;
   private final ScheduledExecutorService executorService;
-  private final LogWrapper logger;
   private final RetryHelper retryHelper;
+  private final String label;
 
   private String cachedHost;
   private HashSet interruptReasons = new HashSet<>();
@@ -143,7 +147,7 @@ public PersistentConnectionImpl(ConnectionContext context, HostInfo info, Delega
     this.outstandingPuts = new HashMap<>();
     this.onDisconnectRequestQueue = new ArrayList<>();
     this.retryHelper =
-        new RetryHelper.Builder(this.executorService, context.getLogger(), RetryHelper.class)
+        new RetryHelper.Builder(this.executorService, RetryHelper.class)
             .withMinDelayAfterFailure(1000)
             .withRetryExponent(1.3)
             .withMaxDelay(30 * 1000)
@@ -151,7 +155,7 @@ public PersistentConnectionImpl(ConnectionContext context, HostInfo info, Delega
             .build();
 
     long connId = connectionIds++;
-    this.logger = new LogWrapper(context.getLogger(), PersistentConnection.class, "pc_" + connId);
+    this.label = "[pc_" + connId + "]";
     this.lastSessionId = null;
     doIdleCheck();
   }
@@ -159,9 +163,7 @@ public PersistentConnectionImpl(ConnectionContext context, HostInfo info, Delega
   // Connection.Delegate methods
   @Override
   public void onReady(long timestamp, String sessionId) {
-    if (logger.logsDebug()) {
-      logger.debug("onReady");
-    }
+    logger.debug("{} onReady", label);
     lastConnectionEstablishedTime = System.currentTimeMillis();
     handleTimestamp(timestamp);
 
@@ -188,16 +190,12 @@ public void listen(
       Long tag,
       RequestResultCallback listener) {
     ListenQuerySpec query = new ListenQuerySpec(path, queryParams);
-    if (logger.logsDebug()) {
-      logger.debug("Listening on " + query);
-    }
+    logger.debug("{} Listening on {}", label, query);
     // TODO: Fix this somehow?
     //hardAssert(query.isDefault() || !query.loadsAllData(), "listen() called for non-default but "
     //          + "complete query");
     hardAssert(!listens.containsKey(query), "listen() called twice for same QuerySpec.");
-    if (logger.logsDebug()) {
-      logger.debug("Adding listen query: " + query);
-    }
+    logger.debug("{} Adding listen query: {}", label, query);
     OutstandingListen outstandingListen =
         new OutstandingListen(listener, query, tag, currentHashFn);
     listens.put(query, outstandingListen);
@@ -277,23 +275,19 @@ public void onDataMessage(Map message) {
       Map body = (Map) message.get(SERVER_ASYNC_PAYLOAD);
       onDataPush(action, body);
     } else {
-      if (logger.logsDebug()) {
-        logger.debug("Ignoring unknown message: " + message);
-      }
+      logger.debug("{} Ignoring unknown message: {}", label, message);
     }
   }
 
   @Override
   public void onDisconnect(Connection.DisconnectReason reason) {
-    if (logger.logsDebug()) {
-      logger.debug("Got on disconnect due to " + reason.name());
-    }
+    logger.debug("{} Got on disconnect due to {}", label, reason.name());
     this.connectionState = ConnectionState.Disconnected;
     this.realtime = null;
     this.hasOnDisconnects = false;
     requestCBHash.clear();
     if (inactivityTimer != null) {
-      logger.debug("cancelling idle time checker");
+      logger.debug("{} Cancelling idle time checker", label);
       inactivityTimer.cancel(false);
       inactivityTimer = null;
     }
@@ -319,21 +313,16 @@ public void onDisconnect(Connection.DisconnectReason reason) {
 
   @Override
   public void onKill(String reason) {
-    if (logger.logsDebug()) {
-      logger.debug(
-          "Firebase Database connection was forcefully killed by the server. Will not attempt "
-              + "reconnect. Reason: "
-              + reason);
-    }
+    logger.debug(
+        "{} Firebase Database connection was forcefully killed by the server. Will not attempt "
+            + "reconnect. Reason: {}", label, reason);
     interrupt(SERVER_KILL_INTERRUPT_REASON);
   }
 
   @Override
   public void unlisten(List path, Map queryParams) {
     ListenQuerySpec query = new ListenQuerySpec(path, queryParams);
-    if (logger.logsDebug()) {
-      logger.debug("unlistening on " + query);
-    }
+    logger.debug("{} Unlistening on {}", label, query);
 
     // TODO: fix this by understanding query params?
     //Utilities.hardAssert(query.isDefault() || !query.loadsAllData(),
@@ -395,9 +384,7 @@ public void onDisconnectCancel(List path, RequestResultCallback onComple
 
   @Override
   public void interrupt(String reason) {
-    if (logger.logsDebug()) {
-      logger.debug("Connection interrupted for: " + reason);
-    }
+    logger.debug("{} Connection interrupted for: {}", label, reason);
     interruptReasons.add(reason);
 
     if (realtime != null) {
@@ -414,10 +401,7 @@ public void interrupt(String reason) {
 
   @Override
   public void resume(String reason) {
-    if (logger.logsDebug()) {
-      logger.debug("Connection no longer interrupted for: " + reason);
-    }
-
+    logger.debug("{} Connection no longer interrupted for: {}", label, reason);
     interruptReasons.remove(reason);
 
     if (shouldReconnect() && connectionState == ConnectionState.Disconnected) {
@@ -444,7 +428,7 @@ public void refreshAuthToken() {
     // we close the connection to make sure any writes/listens are queued until the connection
     // is reauthed with the current token after reconnecting. Note that this will trigger
     // onDisconnects which isn't ideal.
-    logger.debug("Auth token refresh requested");
+    logger.debug("{} Auth token refresh requested", label);
 
     // By using interrupt instead of closing the connection we make sure there are no race
     // conditions with other fetch token attempts (interrupt/resume is expected to handle those
@@ -455,7 +439,7 @@ public void refreshAuthToken() {
 
   @Override
   public void refreshAuthToken(String token) {
-    logger.debug("Auth token refreshed.");
+    logger.debug("{} Auth token refreshed.", label);
     this.authToken = token;
     if (connected()) {
       if (token != null) {
@@ -473,13 +457,13 @@ private void tryScheduleReconnect() {
           "Not in disconnected state: %s",
           this.connectionState);
       final boolean forceRefresh = this.forceAuthTokenRefresh;
-      logger.debug("Scheduling connection attempt");
+      logger.debug("{} Scheduling connection attempt", label);
       this.forceAuthTokenRefresh = false;
       retryHelper.retry(
           new Runnable() {
             @Override
             public void run() {
-              logger.debug("Trying to fetch auth token");
+              logger.debug("{} Trying to fetch auth token", label);
               hardAssert(
                   connectionState == ConnectionState.Disconnected,
                   "Not in disconnected state: %s",
@@ -496,7 +480,7 @@ public void onSuccess(String token) {
                         // Someone could have interrupted us while fetching the token,
                         // marking the connection as Disconnected
                         if (connectionState == ConnectionState.GettingToken) {
-                          logger.debug("Successfully fetched token, opening connection");
+                          logger.debug("{} Successfully fetched token, opening connection", label);
                           openNetworkConnection(token);
                         } else {
                           hardAssert(
@@ -504,13 +488,13 @@ public void onSuccess(String token) {
                               "Expected connection state disconnected, but was %s",
                               connectionState);
                           logger.debug(
-                              "Not opening connection after token refresh, "
-                                  + "because connection was set to disconnected");
+                              "{} Not opening connection after token refresh, because connection "
+                                  + "was set to disconnected", label);
                         }
                       } else {
                         logger.debug(
-                            "Ignoring getToken result, because this was not the "
-                                + "latest attempt.");
+                            "{} Ignoring getToken result, because this was not the "
+                                + "latest attempt.", label);
                       }
                     }
 
@@ -518,12 +502,12 @@ public void onSuccess(String token) {
                     public void onError(String error) {
                       if (thisGetTokenAttempt == currentGetTokenAttempt) {
                         connectionState = ConnectionState.Disconnected;
-                        logger.debug("Error fetching token: " + error);
+                        logger.debug("{} Error fetching token: {}", label, error);
                         tryScheduleReconnect();
                       } else {
                         logger.debug(
-                            "Ignoring getToken error, because this was not the "
-                                + "latest attempt.");
+                            "{} Ignoring getToken error, because this was not the "
+                                + "latest attempt.", label);
                       }
                     }
                   });
@@ -609,14 +593,10 @@ private void sendUnlisten(OutstandingListen listen) {
   }
 
   private OutstandingListen removeListen(ListenQuerySpec query) {
-    if (logger.logsDebug()) {
-      logger.debug("removing query " + query);
-    }
+    logger.debug("{} removing query {}", label, query);
     if (!listens.containsKey(query)) {
-      if (logger.logsDebug()) {
-        logger.debug(
-            "Trying to remove listener for QuerySpec " + query + " but no listener exists.");
-      }
+      logger.debug(
+          "{} Trying to remove listener for QuerySpec {} but no listener exists.", label, query);
       return null;
     } else {
       OutstandingListen oldListen = listens.get(query);
@@ -627,9 +607,7 @@ private OutstandingListen removeListen(ListenQuerySpec query) {
   }
 
   private Collection removeListens(List path) {
-    if (logger.logsDebug()) {
-      logger.debug("removing all listens at path " + path);
-    }
+    logger.debug("{} Removing all listens at path {}", label, path);
     List removedListens = new ArrayList<>();
     for (Map.Entry entry : listens.entrySet()) {
       ListenQuerySpec query = entry.getKey();
@@ -649,9 +627,7 @@ private Collection removeListens(List path) {
   }
 
   private void onDataPush(String action, Map body) {
-    if (logger.logsDebug()) {
-      logger.debug("handleServerMessage: " + action + " " + body);
-    }
+    logger.debug("{} handleServerMessage: {} {}", label, action, body);
     if (action.equals(SERVER_ASYNC_DATA_UPDATE) || action.equals(SERVER_ASYNC_DATA_MERGE)) {
       boolean isMerge = action.equals(SERVER_ASYNC_DATA_MERGE);
 
@@ -660,9 +636,7 @@ private void onDataPush(String action, Map body) {
       Long tagNumber = ConnectionUtils.longFromObject(body.get(SERVER_DATA_TAG));
       // ignore empty merges
       if (isMerge && (payloadData instanceof Map) && ((Map) payloadData).size() == 0) {
-        if (logger.logsDebug()) {
-          logger.debug("ignoring empty merge for path " + pathString);
-        }
+        logger.debug("{} Ignoring empty merge for path {}", label, pathString);
       } else {
         List path = ConnectionUtils.stringToPath(pathString);
         delegate.onDataUpdate(path, payloadData, isMerge, tagNumber);
@@ -684,9 +658,7 @@ private void onDataPush(String action, Map body) {
         rangeMerges.add(new RangeMerge(start, end, update));
       }
       if (rangeMerges.isEmpty()) {
-        if (logger.logsDebug()) {
-          logger.debug("Ignoring empty range merge for path " + pathString);
-        }
+        logger.debug("{} Ignoring empty range merge for path {}", label, pathString);
       } else {
         this.delegate.onRangeMergeUpdate(path, rangeMerges, tag);
       }
@@ -701,9 +673,7 @@ private void onDataPush(String action, Map body) {
     } else if (action.equals(SERVER_ASYNC_SECURITY_DEBUG)) {
       onSecurityDebugPacket(body);
     } else {
-      if (logger.logsDebug()) {
-        logger.debug("Unrecognized action from server: " + action);
-      }
+      logger.debug("{} Unrecognized action from server: {}", label, action);
     }
   }
 
@@ -723,7 +693,7 @@ private void onAuthRevoked(String errorCode, String errorMessage) {
     // This might be for an earlier token than we just recently sent. But since we need to close
     // the connection anyways, we can set it to null here and we will refresh the token later
     // on reconnect.
-    logger.debug("Auth token revoked: " + errorCode + " (" + errorMessage + ")");
+    logger.debug("{} Auth token revoked: {} ({})", label, errorCode, errorMessage);
     this.authToken = null;
     this.forceAuthTokenRefresh = true;
     this.delegate.onAuthStatus(false);
@@ -733,7 +703,7 @@ private void onAuthRevoked(String errorCode, String errorMessage) {
 
   private void onSecurityDebugPacket(Map message) {
     // TODO: implement on iOS too
-    logger.info((String) message.get("msg"));
+    logger.info("{} {}", label, message.get("msg"));
   }
 
   private void upgradeAuth() {
@@ -766,7 +736,7 @@ public void onResponse(Map response) {
               forceAuthTokenRefresh = true;
               delegate.onAuthStatus(false);
               String reason = (String) response.get(SERVER_RESPONSE_DATA);
-              logger.debug("Authentication failed: " + status + " (" + reason + ")");
+              logger.debug("{} Authentication failed: {} ({})", label, status, reason);
               realtime.close();
 
               if (status.equals("invalid_token") || status.equals("permission_denied")) {
@@ -778,11 +748,11 @@ public void onResponse(Map response) {
                   // Set a long reconnect delay because recovery is unlikely.
                   retryHelper.setMaxDelay();
                   logger.warn(
-                      "Provided authentication credentials are invalid. This "
+                      "{} Provided authentication credentials are invalid. This "
                           + "usually indicates your FirebaseApp instance was not initialized "
                           + "correctly. Make sure your database URL is correct and that your "
                           + "service account is for the correct project and is authorized to "
-                          + "access it.");
+                          + "access it.", label);
                 }
               }
             }
@@ -814,9 +784,7 @@ private void sendUnauth() {
   }
 
   private void restoreAuth() {
-    if (logger.logsDebug()) {
-      logger.debug("calling restore state");
-    }
+    logger.debug("{} Calling restore state", label);
 
     hardAssert(
         this.connectionState == ConnectionState.Connecting,
@@ -824,15 +792,11 @@ private void restoreAuth() {
         this.connectionState);
 
     if (authToken == null) {
-      if (logger.logsDebug()) {
-        logger.debug("Not restoring auth because token is null.");
-      }
+      logger.debug("{} Not restoring auth because token is null.", label);
       this.connectionState = ConnectionState.Connected;
       restoreState();
     } else {
-      if (logger.logsDebug()) {
-        logger.debug("Restoring auth.");
-      }
+      logger.debug("{} Restoring auth.", label);
       this.connectionState = ConnectionState.Authenticating;
       sendAuthAndRestoreState();
     }
@@ -845,19 +809,13 @@ private void restoreState() {
         this.connectionState);
 
     // Restore listens
-    if (logger.logsDebug()) {
-      logger.debug("Restoring outstanding listens");
-    }
+    logger.debug("{} Restoring outstanding listens", label);
     for (OutstandingListen listen : listens.values()) {
-      if (logger.logsDebug()) {
-        logger.debug("Restoring listen " + listen.getQuery());
-      }
+      logger.debug("{} Restoring listen {}", label, listen.getQuery());
       sendListen(listen);
     }
 
-    if (logger.logsDebug()) {
-      logger.debug("Restoring writes.");
-    }
+    logger.debug("{} Restoring writes.", label);
     // Restore puts
     ArrayList outstanding = new ArrayList<>(outstandingPuts.keySet());
     // Make sure puts are restored in order
@@ -878,9 +836,7 @@ private void restoreState() {
   }
 
   private void handleTimestamp(long timestamp) {
-    if (logger.logsDebug()) {
-      logger.debug("handling timestamp");
-    }
+    logger.debug("{} Handling timestamp", label);
     long timestampDelta = timestamp - System.currentTimeMillis();
     Map updates = new HashMap<>();
     updates.put(Constants.DOT_INFO_SERVERTIME_OFFSET, timestampDelta);
@@ -930,9 +886,7 @@ assert canSendWrites()
         new ConnectionRequestCallback() {
           @Override
           public void onResponse(Map response) {
-            if (logger.logsDebug()) {
-              logger.debug(action + " response: " + response);
-            }
+            logger.debug("{} {} response: {}", label, action, response);
 
             OutstandingPut currentPut = outstandingPuts.get(putId);
             if (currentPut == put) {
@@ -948,10 +902,8 @@ public void onResponse(Map response) {
                 }
               }
             } else {
-              if (logger.logsDebug()) {
-                logger.debug(
-                    "Ignoring on complete for put " + putId + " because it was removed already.");
-              }
+              logger.debug("{} Ignoring on complete for put {} because it was removed already.",
+                  label, putId);
             }
             doIdleCheck();
           }
@@ -1032,17 +984,13 @@ public void onResponse(Map response) {
               String status = (String) response.get(REQUEST_STATUS);
               if (!status.equals("ok")) {
                 String errorMessage = (String) response.get(SERVER_DATA_UPDATE_BODY);
-                if (logger.logsDebug()) {
-                  logger.debug(
-                      "Failed to send stats: " + status + " (message: " + errorMessage + ")");
-                }
+                logger.debug(
+                    "{} Failed to send stats: {} (message: {})", label, stats, errorMessage);
               }
             }
           });
     } else {
-      if (logger.logsDebug()) {
-        logger.debug("Not sending stats because stats are empty");
-      }
+      logger.debug("{} Not sending stats because stats are empty", label);
     }
   }
 
@@ -1051,11 +999,9 @@ private void warnOnListenerWarnings(List warnings, ListenQuerySpec query
     if (warnings.contains("no_index")) {
       String indexSpec = "\".indexOn\": \"" + query.queryParams.get("i") + '\"';
       logger.warn(
-          "Using an unspecified index. Consider adding '"
-              + indexSpec
-              + "' at "
-              + ConnectionUtils.pathToString(query.path)
-              + " to your security and Firebase Database rules for better performance");
+          "{} Using an unspecified index. Consider adding '{}' at {} to your security and "
+              + "Firebase Database rules for better performance",
+          label, indexSpec, ConnectionUtils.pathToString(query.path));
     }
   }
 
@@ -1064,9 +1010,7 @@ private void sendConnectStats() {
     assert !this.context.isPersistenceEnabled()
         : "Stats for persistence on JVM missing (persistence not yet supported)";
     stats.put("sdk.admin_java." + context.getClientSdkVersion().replace('.', '-'), 1);
-    if (logger.logsDebug()) {
-      logger.debug("Sending first connection stats");
-    }
+    logger.debug("{} Sending first connection stats", label);
     sendStats(stats);
   }
 
diff --git a/src/main/java/com/google/firebase/database/connection/WebsocketConnection.java b/src/main/java/com/google/firebase/database/connection/WebsocketConnection.java
index d58a1af80..44335b038 100644
--- a/src/main/java/com/google/firebase/database/connection/WebsocketConnection.java
+++ b/src/main/java/com/google/firebase/database/connection/WebsocketConnection.java
@@ -20,7 +20,6 @@
 import static com.google.common.base.Preconditions.checkState;
 
 import com.google.common.collect.ImmutableList;
-import com.google.firebase.database.logging.LogWrapper;
 import com.google.firebase.database.util.JsonMapper;
 
 import java.io.EOFException;
@@ -33,6 +32,8 @@
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Represents a WebSocket connection to the Firebase Realtime Database. This abstraction acts as
@@ -47,11 +48,12 @@ class WebsocketConnection {
   private static final long CONNECT_TIMEOUT_MS = 30 * 1000; // 30 seconds
   private static final int MAX_FRAME_SIZE = 16384;
   private static final AtomicLong CONN_ID = new AtomicLong(0);
+  private static final Logger logger = LoggerFactory.getLogger(WebsocketConnection.class);
 
   private final ScheduledExecutorService executorService;
-  private final LogWrapper logger;
   private final WSClient conn;
   private final Delegate delegate;
+  private final String label;
 
   private StringList buffer;
   private boolean everConnected = false;
@@ -75,8 +77,7 @@ class WebsocketConnection {
       WSClientFactory clientFactory) {
     this.executorService = connectionContext.getExecutorService();
     this.delegate = delegate;
-    this.logger = new LogWrapper(connectionContext.getLogger(), WebsocketConnection.class,
-        "ws_" + CONN_ID.getAndIncrement());
+    this.label = "[ws_" + CONN_ID.getAndIncrement() + "]";
     this.conn = clientFactory.newClient(new WSClientHandlerImpl());
   }
 
@@ -99,9 +100,7 @@ void start() {
   }
 
   void close() {
-    if (logger.logsDebug()) {
-      logger.debug("websocket is being closed");
-    }
+    logger.debug("{} Websocket is being closed", label);
     isClosed = true;
     conn.close();
 
@@ -130,7 +129,7 @@ void send(Map message) {
         conn.send(seg);
       }
     } catch (IOException e) {
-      logger.error("Failed to serialize message: " + message.toString(), e);
+      logger.error("{} Failed to serialize message: {}", label, message, e);
       closeAndNotify();
     }
   }
@@ -150,9 +149,7 @@ private List splitIntoFrames(String src, int maxFrameSize) {
   }
 
   private void handleNewFrameCount(int numFrames) {
-    if (logger.logsDebug()) {
-      logger.debug("HandleNewFrameCount: " + numFrames);
-    }
+    logger.debug("{} Handle new frame count: {}", label, numFrames);
     buffer = new StringList(numFrames);
   }
 
@@ -165,15 +162,13 @@ private void appendFrame(String message) {
     String combined = buffer.combine();
     try {
       Map decoded = JsonMapper.parseJson(combined);
-      if (logger.logsDebug()) {
-        logger.debug("handleIncomingFrame complete frame: " + decoded);
-      }
+      logger.debug("{} Parsed complete frame: {}", label, decoded);
       delegate.onMessage(decoded);
     } catch (IOException e) {
-      logger.error("Error parsing frame: " + combined, e);
+      logger.error("{} Error parsing frame: {}", label, combined, e);
       closeAndNotify();
     } catch (ClassCastException e) {
-      logger.error("Error parsing frame (cast error): " + combined, e);
+      logger.error("{} Error parsing frame (cast error): {}", label, combined, e);
       closeAndNotify();
     }
   }
@@ -218,13 +213,10 @@ private void resetKeepAlive() {
     }
     if (keepAlive != null) {
       keepAlive.cancel(false);
-      if (logger.logsDebug()) {
-        logger.debug("Reset keepAlive. Remaining: " + keepAlive.getDelay(TimeUnit.MILLISECONDS));
-      }
+      logger.debug("{} Reset keepAlive. Remaining: {}", label,
+          keepAlive.getDelay(TimeUnit.MILLISECONDS));
     } else {
-      if (logger.logsDebug()) {
-        logger.debug("Reset keepAlive");
-      }
+      logger.debug("{} Reset keepAlive", label);
     }
     keepAlive = executorService.schedule(nop(), KEEP_ALIVE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
   }
@@ -253,18 +245,14 @@ private void closeAndNotify() {
 
   private void onClosed() {
     if (!isClosed) {
-      if (logger.logsDebug()) {
-        logger.debug("closing itself");
-      }
+      logger.debug("{} Closing itself", label);
       closeAndNotify();
     }
   }
 
   private void closeIfNeverConnected() {
     if (!everConnected && !isClosed) {
-      if (logger.logsDebug()) {
-        logger.debug("timed out on connect");
-      }
+      logger.debug("{} Timed out on connect", label);
       closeAndNotify();
     }
   }
@@ -278,9 +266,7 @@ private class WSClientHandlerImpl implements WSClientEventHandler {
 
     @Override
     public void onOpen() {
-      if (logger.logsDebug()) {
-        logger.debug("websocket opened");
-      }
+      logger.debug("{} Websocket opened", label);
       executorService.execute(new Runnable() {
         @Override
         public void run() {
@@ -293,9 +279,7 @@ public void run() {
 
     @Override
     public void onMessage(final String message) {
-      if (logger.logsDebug()) {
-        logger.debug("ws message: " + message);
-      }
+      logger.debug("{} WS message: {}", label, message);
       executorService.execute(new Runnable() {
         @Override
         public void run() {
@@ -306,9 +290,7 @@ public void run() {
 
     @Override
     public void onClose() {
-      if (logger.logsDebug()) {
-        logger.debug("closed");
-      }
+      logger.debug("{} Closed", label);
       if (!isClosed) {
         // If the connection tear down was initiated by the higher-layer, isClosed will already
         // be true. Nothing more to do in that case.
@@ -325,9 +307,9 @@ public void run() {
     @Override
     public void onError(final Throwable e) {
       if (e.getCause() != null && e.getCause() instanceof EOFException) {
-        logger.error("WebSocket reached EOF", e);
+        logger.error("{} WebSocket reached EOF", label, e);
       } else {
-        logger.error("WebSocket error", e);
+        logger.error("{} WebSocket error", label, e);
       }
       executorService.execute(
           new Runnable() {
diff --git a/src/main/java/com/google/firebase/database/connection/util/RetryHelper.java b/src/main/java/com/google/firebase/database/connection/util/RetryHelper.java
index 2563a30ca..f8f6bb77a 100644
--- a/src/main/java/com/google/firebase/database/connection/util/RetryHelper.java
+++ b/src/main/java/com/google/firebase/database/connection/util/RetryHelper.java
@@ -16,18 +16,18 @@
 
 package com.google.firebase.database.connection.util;
 
-import com.google.firebase.database.logging.LogWrapper;
-import com.google.firebase.database.logging.Logger;
-
 import java.util.Random;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class RetryHelper {
 
+  private static final Logger logger = LoggerFactory.getLogger(RetryHelper.class);
+
   private final ScheduledExecutorService executorService;
-  private final LogWrapper logger;
   /** The minimum delay for a retry in ms. */
   private final long minRetryDelayAfterFailure;
   /** The maximum retry delay in ms. */
@@ -49,13 +49,11 @@ public class RetryHelper {
 
   private RetryHelper(
       ScheduledExecutorService executorService,
-      LogWrapper logger,
       long minRetryDelayAfterFailure,
       long maxRetryDelay,
       double retryExponent,
       double jitterFactor) {
     this.executorService = executorService;
-    this.logger = logger;
     this.minRetryDelayAfterFailure = minRetryDelayAfterFailure;
     this.maxRetryDelay = maxRetryDelay;
     this.retryExponent = retryExponent;
@@ -84,7 +82,7 @@ public void retry(final Runnable runnable) {
                   + (jitterFactor * currentRetryDelay * random.nextDouble()));
     }
     this.lastWasSuccess = false;
-    logger.debug("Scheduling retry in %dms", delay);
+    logger.debug("Scheduling retry in {}ms", delay);
     Runnable wrapped =
         new Runnable() {
           @Override
@@ -119,15 +117,13 @@ public void cancel() {
   public static class Builder {
 
     private final ScheduledExecutorService service;
-    private final LogWrapper logger;
     private long minRetryDelayAfterFailure = 1000;
     private double jitterFactor = 0.5;
     private long retryMaxDelay = 30 * 1000;
     private double retryExponent = 1.3;
 
-    public Builder(ScheduledExecutorService service, Logger logger, Class tag) {
+    public Builder(ScheduledExecutorService service, Class tag) {
       this.service = service;
-      this.logger = new LogWrapper(logger, tag);
     }
 
     public Builder withMinDelayAfterFailure(long delay) {
@@ -156,7 +152,6 @@ public Builder withJitterFactor(double random) {
     public RetryHelper build() {
       return new RetryHelper(
           this.service,
-          this.logger,
           this.minRetryDelayAfterFailure,
           this.retryMaxDelay,
           this.retryExponent,
diff --git a/src/main/java/com/google/firebase/database/core/Context.java b/src/main/java/com/google/firebase/database/core/Context.java
index fa9943c3a..da68861cc 100644
--- a/src/main/java/com/google/firebase/database/core/Context.java
+++ b/src/main/java/com/google/firebase/database/core/Context.java
@@ -26,26 +26,20 @@
 import com.google.firebase.database.connection.PersistentConnection;
 import com.google.firebase.database.core.persistence.NoopPersistenceManager;
 import com.google.firebase.database.core.persistence.PersistenceManager;
-import com.google.firebase.database.logging.LogWrapper;
-import com.google.firebase.database.logging.Logger;
 import com.google.firebase.database.utilities.DefaultRunLoop;
 
-import java.util.List;
 import java.util.concurrent.ScheduledExecutorService;
 
 public class Context {
 
   private static final long DEFAULT_CACHE_SIZE = 10 * 1024 * 1024;
 
-  protected Logger logger;
   FirebaseApp firebaseApp;
 
   EventTarget eventTarget;
   AuthTokenProvider authTokenProvider;
   RunLoop runLoop;
   String persistenceKey;
-  List loggedComponents;
-  Logger.Level logLevel = Logger.Level.INFO;
   boolean persistenceEnabled;
   long cacheSize = DEFAULT_CACHE_SIZE;
 
@@ -103,8 +97,6 @@ public void requireStarted() {
   }
 
   private void initServices() {
-    // Do the logger first, so that other components can get a LogWrapper
-    ensureLogger();
     // Cache platform
     getPlatform();
     ensureUserAgent();
@@ -137,21 +129,8 @@ void assertUnfrozen() {
     }
   }
 
-  public LogWrapper getLogger(String component) {
-    return new LogWrapper(logger, component, null);
-  }
-
-  public LogWrapper getLogger(Class component) {
-    return new LogWrapper(logger, component);
-  }
-
-  public LogWrapper getLogger(Class component, String prefix) {
-    return new LogWrapper(logger, component, prefix);
-  }
-
   public ConnectionContext getConnectionContext() {
     return new ConnectionContext(
-        this.logger,
         wrapAuthTokenProvider(this.getAuthTokenProvider()),
         this.getExecutorService(),
         this.isPersistenceEnabled(),
@@ -213,12 +192,6 @@ private ScheduledExecutorService getExecutorService() {
     return ((DefaultRunLoop) loop).getExecutorService();
   }
 
-  private void ensureLogger() {
-    if (logger == null) {
-      logger = getPlatform().newLogger(this, logLevel, loggedComponents);
-    }
-  }
-
   private void ensureRunLoop() {
     if (runLoop == null) {
       runLoop = platform.newRunLoop(this);
diff --git a/src/main/java/com/google/firebase/database/core/DatabaseConfig.java b/src/main/java/com/google/firebase/database/core/DatabaseConfig.java
index 491098d6a..844f59266 100644
--- a/src/main/java/com/google/firebase/database/core/DatabaseConfig.java
+++ b/src/main/java/com/google/firebase/database/core/DatabaseConfig.java
@@ -18,9 +18,6 @@
 
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.database.DatabaseException;
-import com.google.firebase.database.Logger;
-
-import java.util.List;
 
 /**
  * TODO: Since this is no longer public, we should merge it with Context and clean all
@@ -29,21 +26,6 @@
  */
 public class DatabaseConfig extends Context {
 
-  // TODO: Remove this from the public API since we currently can't pass logging
-  // across AIDL interface.
-
-  /**
-   * If you would like to provide a custom log target, pass an object that implements the {@link
-   * com.google.firebase.database.Logger Logger} interface.
-   *
-   * @hide
-   * @param logger The custom logger that will be called with all log messages
-   */
-  public synchronized void setLogger(com.google.firebase.database.logging.Logger logger) {
-    assertUnfrozen();
-    this.logger = logger;
-  }
-
   /**
    * In the default setup, the Firebase Database library will create a thread to handle all
    * callbacks. On Android, it will attempt to use the main  debugComponents) {
-    assertUnfrozen();
-    setLogLevel(Logger.Level.DEBUG);
-    loggedComponents = debugComponents;
-  }
-
   public void setRunLoop(RunLoop runLoop) {
     this.runLoop = runLoop;
   }
diff --git a/src/main/java/com/google/firebase/database/core/JvmPlatform.java b/src/main/java/com/google/firebase/database/core/JvmPlatform.java
index 2502b6b32..2a226c078 100644
--- a/src/main/java/com/google/firebase/database/core/JvmPlatform.java
+++ b/src/main/java/com/google/firebase/database/core/JvmPlatform.java
@@ -24,14 +24,12 @@
 import com.google.firebase.database.connection.PersistentConnection;
 import com.google.firebase.database.connection.PersistentConnectionImpl;
 import com.google.firebase.database.core.persistence.PersistenceManager;
-import com.google.firebase.database.logging.DefaultLogger;
-import com.google.firebase.database.logging.LogWrapper;
-import com.google.firebase.database.logging.Logger;
 import com.google.firebase.database.utilities.DefaultRunLoop;
 
-import java.util.List;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ThreadFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 class JvmPlatform implements Platform {
 
@@ -43,11 +41,6 @@ class JvmPlatform implements Platform {
     this.firebaseApp = firebaseApp;
   }
 
-  @Override
-  public Logger newLogger(Context ctx, Logger.Level level, List components) {
-    return new DefaultLogger(level, components);
-  }
-
   @Override
   public EventTarget newEventTarget(Context ctx) {
     ThreadFactory threadFactory = ImplFirebaseTrampolines.getThreadFactory(firebaseApp);
@@ -56,7 +49,7 @@ public EventTarget newEventTarget(Context ctx) {
 
   @Override
   public RunLoop newRunLoop(final Context context) {
-    final LogWrapper logger = context.getLogger(RunLoop.class);
+    final Logger logger = LoggerFactory.getLogger(RunLoop.class);
     ThreadFactory threadFactory = ImplFirebaseTrampolines.getThreadFactory(firebaseApp);
     return new DefaultRunLoop(threadFactory) {
       @Override
diff --git a/src/main/java/com/google/firebase/database/core/Platform.java b/src/main/java/com/google/firebase/database/core/Platform.java
index 320a851b5..2d2742a62 100644
--- a/src/main/java/com/google/firebase/database/core/Platform.java
+++ b/src/main/java/com/google/firebase/database/core/Platform.java
@@ -20,17 +20,13 @@
 import com.google.firebase.database.connection.HostInfo;
 import com.google.firebase.database.connection.PersistentConnection;
 import com.google.firebase.database.core.persistence.PersistenceManager;
-import com.google.firebase.database.logging.Logger;
 
-import java.util.List;
 import java.util.concurrent.ScheduledExecutorService;
 
 public interface Platform {
 
   String DEVICE = "AdminJava";
 
-  Logger newLogger(Context ctx, Logger.Level level, List components);
-
   EventTarget newEventTarget(Context ctx);
 
   RunLoop newRunLoop(Context ctx);
diff --git a/src/main/java/com/google/firebase/database/core/Repo.java b/src/main/java/com/google/firebase/database/core/Repo.java
index add6ff3e0..98777d04d 100644
--- a/src/main/java/com/google/firebase/database/core/Repo.java
+++ b/src/main/java/com/google/firebase/database/core/Repo.java
@@ -38,7 +38,6 @@
 import com.google.firebase.database.core.view.Event;
 import com.google.firebase.database.core.view.EventRaiser;
 import com.google.firebase.database.core.view.QuerySpec;
-import com.google.firebase.database.logging.LogWrapper;
 import com.google.firebase.database.snapshot.ChildKey;
 import com.google.firebase.database.snapshot.EmptyNode;
 import com.google.firebase.database.snapshot.IndexedNode;
@@ -54,6 +53,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class Repo implements PersistentConnection.Delegate {
 
@@ -67,14 +68,14 @@ public class Repo implements PersistentConnection.Delegate {
 
   private static final String TRANSACTION_TOO_MANY_RETRIES = "maxretries";
   private static final String TRANSACTION_OVERRIDE_BY_SET = "overriddenBySet";
+
+  private static final Logger logger = LoggerFactory.getLogger(Repo.class);
+
   private final RepoInfo repoInfo;
   private final OffsetClock serverClock = new OffsetClock(new DefaultClock(), 0);
   private final PersistentConnection connection;
   private final EventRaiser eventRaiser;
   private final Context ctx;
-  private final LogWrapper operationLogger;
-  private final LogWrapper transactionLogger;
-  private final LogWrapper dataLogger;
   private SnapshotHolder infoData;
   private SparseSnapshotTree onDisconnect;
   private Tree> transactionQueueTree;
@@ -90,11 +91,6 @@ public class Repo implements PersistentConnection.Delegate {
     this.repoInfo = repoInfo;
     this.ctx = ctx;
     this.database = database;
-
-    operationLogger = this.ctx.getLogger(Repo.class);
-    transactionLogger = this.ctx.getLogger(Repo.class.getName() + ".Transaction");
-    dataLogger = this.ctx.getLogger(Repo.class.getName() + ".DataOperation");
-
     this.eventRaiser = new EventRaiser(this.ctx);
 
     HostInfo hostInfo = new HostInfo(repoInfo.host, repoInfo.namespace, repoInfo.secure);
@@ -134,7 +130,7 @@ private void deferredInitialization() {
           new AuthTokenProvider.TokenChangeListener() {
             @Override
             public void onTokenChange(String token) {
-              operationLogger.debug("Auth token changed, triggering auth token refresh");
+              logger.debug("Auth token changed, triggering auth token refresh");
               connection.refreshAuthToken(token);
             }
           });
@@ -150,7 +146,7 @@ public void onTokenChange(String token) {
 
     transactionQueueTree = new Tree<>();
 
-    infoSyncTree = new SyncTree(ctx, new NoopPersistenceManager(),
+    infoSyncTree = new SyncTree(new NoopPersistenceManager(),
         new SyncTree.ListenProvider() {
           @Override
           public void startListening(
@@ -178,7 +174,7 @@ public void run() {
           public void stopListening(QuerySpec query, Tag tag) {}
         });
 
-    serverSyncTree = new SyncTree(ctx, persistenceManager,
+    serverSyncTree = new SyncTree(persistenceManager,
         new SyncTree.ListenProvider() {
           @Override
           public void startListening(
@@ -263,12 +259,8 @@ boolean hasListeners() {
   public void onDataUpdate(
       List pathSegments, Object message, boolean isMerge, Long optTag) {
     Path path = new Path(pathSegments);
-    if (operationLogger.logsDebug()) {
-      operationLogger.debug("onDataUpdate: " + path);
-    }
-    if (dataLogger.logsDebug()) {
-      operationLogger.debug("onDataUpdate: " + path + " " + message);
-    }
+    logger.debug("onDataUpdate: {} {}", path, message);
+
 
     List extends Event> events;
     try {
@@ -306,7 +298,7 @@ public void onDataUpdate(
 
       postEvents(events);
     } catch (DatabaseException e) {
-      operationLogger.error("FIREBASE INTERNAL ERROR", e);
+      logger.error("Firebase internal error", e);
     }
   }
 
@@ -316,12 +308,7 @@ public void onRangeMergeUpdate(
       List merges,
       Long tagNumber) {
     Path path = new Path(pathSegments);
-    if (operationLogger.logsDebug()) {
-      operationLogger.debug("onRangeMergeUpdate: " + path);
-    }
-    if (dataLogger.logsDebug()) {
-      operationLogger.debug("onRangeMergeUpdate: " + path + " " + merges);
-    }
+    logger.debug("onRangeMergeUpdate: {} {}", path, merges);
 
     List parsedMerges = new ArrayList<>(merges.size());
     for (com.google.firebase.database.connection.RangeMerge merge : merges) {
@@ -383,12 +370,7 @@ public void setValue(
       final Path path,
       Node newValueUnresolved,
       final DatabaseReference.CompletionListener onComplete) {
-    if (operationLogger.logsDebug()) {
-      operationLogger.debug("set: " + path);
-    }
-    if (dataLogger.logsDebug()) {
-      dataLogger.debug("set: " + path + " " + newValueUnresolved);
-    }
+    logger.debug("set: {} {}", path, newValueUnresolved);
 
     Map serverValues = ServerValues.generateServerValues(serverClock);
     Node newValue = ServerValues.resolveDeferredValueSnapshot(newValueUnresolved, serverValues);
@@ -421,16 +403,9 @@ public void updateChildren(
       CompoundWrite updates,
       final DatabaseReference.CompletionListener onComplete,
       Map unParsedUpdates) {
-    if (operationLogger.logsDebug()) {
-      operationLogger.debug("update: " + path);
-    }
-    if (dataLogger.logsDebug()) {
-      dataLogger.debug("update: " + path + " " + unParsedUpdates);
-    }
+    logger.debug("update: {} {}", path, unParsedUpdates);
     if (updates.isEmpty()) {
-      if (operationLogger.logsDebug()) {
-        operationLogger.debug("update called with no changes. No-op");
-      }
+      logger.debug("update called with no changes. No-op");
       // dispatch on complete
       callOnComplete(onComplete, null, path);
       return;
@@ -468,9 +443,7 @@ public void onRequestResult(String optErrorCode, String optErrorMessage) {
   }
 
   public void purgeOutstandingWrites() {
-    if (operationLogger.logsDebug()) {
-      operationLogger.debug("Purging writes");
-    }
+    logger.debug("Purging writes");
     List extends Event> events = serverSyncTree.removeAllWrites();
     postEvents(events);
     // Abort any transactions
@@ -619,7 +592,7 @@ private void updateInfo(ChildKey childKey, Object value) {
       List extends Event> events = this.infoSyncTree.applyServerOverwrite(path, node);
       this.postEvents(events);
     } catch (DatabaseException e) {
-      operationLogger.error("Failed to parse info update", e);
+      logger.error("Failed to parse info update", e);
     }
   }
 
@@ -652,21 +625,16 @@ private void warnIfWriteFailed(String writeType, Path path, DatabaseError error)
     if (error != null
         && !(error.getCode() == DatabaseError.DATA_STALE
         || error.getCode() == DatabaseError.WRITE_CANCELED)) {
-      operationLogger.warn(writeType + " at " + path.toString() + " failed: " + error.toString());
+      logger.warn(writeType + " at " + path.toString() + " failed: " + error.toString());
     }
   }
 
   public void startTransaction(Path path, final Transaction.Handler handler, boolean applyLocally) {
-    if (operationLogger.logsDebug()) {
-      operationLogger.debug("transaction: " + path);
-    }
-    if (dataLogger.logsDebug()) {
-      operationLogger.debug("transaction: " + path);
-    }
+    logger.debug("transaction: {}", path);
 
     if (this.ctx.isPersistenceEnabled() && !loggedTransactionPersistenceWarning) {
       loggedTransactionPersistenceWarning = true;
-      transactionLogger.info(
+      logger.info(
           "runTransaction() usage detected while persistence is enabled. Please be aware that "
               + "transactions *will not* be persisted across database restarts.  See "
               + "https://www.firebase.com/docs/android/guide/offline-capabilities.html"
@@ -1142,11 +1110,7 @@ public void visitTree(Tree> tree) {
 
   private Path abortTransactions(Path path, final int reason) {
     Path affectedPath = getAncestorTransactionNode(path).getPath();
-
-    if (transactionLogger.logsDebug()) {
-      operationLogger.debug(
-          "Aborting transactions for path: " + path + ". Affected: " + affectedPath);
-    }
+    logger.debug("Aborting transactions for path: {}. Affected: {}", path, affectedPath);
 
     Tree> transactionNode = transactionQueueTree.subTree(path);
     transactionNode.forEachAncestor(
@@ -1247,7 +1211,7 @@ private void runTransactionOnComplete(Transaction.Handler handler, DatabaseError
     try {
       handler.onComplete(error, committed, snapshot);
     } catch (Exception e) {
-      operationLogger.error("Exception in transaction onComplete callback", e);
+      logger.error("Exception in transaction onComplete callback", e);
     }
   }
 
diff --git a/src/main/java/com/google/firebase/database/core/SyncTree.java b/src/main/java/com/google/firebase/database/core/SyncTree.java
index 17fcad445..f7da11750 100644
--- a/src/main/java/com/google/firebase/database/core/SyncTree.java
+++ b/src/main/java/com/google/firebase/database/core/SyncTree.java
@@ -37,7 +37,6 @@
 import com.google.firebase.database.core.view.Event;
 import com.google.firebase.database.core.view.QuerySpec;
 import com.google.firebase.database.core.view.View;
-import com.google.firebase.database.logging.LogWrapper;
 import com.google.firebase.database.snapshot.ChildKey;
 import com.google.firebase.database.snapshot.CompoundHash;
 import com.google.firebase.database.snapshot.EmptyNode;
@@ -57,6 +56,8 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Callable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * SyncTree is the central class for managing event callback registration, data caching, views
@@ -80,6 +81,9 @@ public class SyncTree {
 
   // Size after which we start including the compound hash
   private static final long SIZE_THRESHOLD_FOR_COMPOUND_HASH = 1024;
+
+  private static final Logger logger = LoggerFactory.getLogger(SyncTree.class);
+
   /**
    * A tree of all pending user writes (user-initiated set()'s, transaction()'s, update()'s, etc.).
    */
@@ -90,14 +94,13 @@ public class SyncTree {
   private final Set keepSyncedQueries;
   private final ListenProvider listenProvider;
   private final PersistenceManager persistenceManager;
-  private final LogWrapper logger;
   /** Tree of SyncPoints. There's a SyncPoint at any location that has 1 or more views. */
   private ImmutableTree syncPointTree;
   /** Static tracker for next query tag. */
   private long nextQueryTag = 1L;
 
   public SyncTree(
-      Context context, PersistenceManager persistenceManager, ListenProvider listenProvider) {
+      PersistenceManager persistenceManager, ListenProvider listenProvider) {
     this.syncPointTree = ImmutableTree.emptyInstance();
     this.pendingWriteTree = new WriteTree();
     this.tagToQueryMap = new HashMap<>();
@@ -105,7 +108,6 @@ public SyncTree(
     this.keepSyncedQueries = new HashSet<>();
     this.listenProvider = listenProvider;
     this.persistenceManager = persistenceManager;
-    this.logger = context.getLogger(SyncTree.class);
   }
 
   public boolean isEmpty() {
@@ -968,7 +970,7 @@ public List extends Event> onListenComplete(DatabaseError error) {
           return SyncTree.this.applyListenComplete(query.getPath());
         }
       } else {
-        logger.warn("Listen at " + view.getQuery().getPath() + " failed: " + error.toString());
+        logger.warn("Listen at {} failed: {}", view.getQuery().getPath(), error);
 
         // If a listen failed, kill all of the listeners here, not just the one that triggered the
         // error. Note that this may need to be scoped to just this listener if we change
diff --git a/src/main/java/com/google/firebase/database/core/persistence/DefaultPersistenceManager.java b/src/main/java/com/google/firebase/database/core/persistence/DefaultPersistenceManager.java
index 1bc546295..40e1b99dc 100644
--- a/src/main/java/com/google/firebase/database/core/persistence/DefaultPersistenceManager.java
+++ b/src/main/java/com/google/firebase/database/core/persistence/DefaultPersistenceManager.java
@@ -17,12 +17,10 @@
 package com.google.firebase.database.core.persistence;
 
 import com.google.firebase.database.core.CompoundWrite;
-import com.google.firebase.database.core.Context;
 import com.google.firebase.database.core.Path;
 import com.google.firebase.database.core.UserWriteRecord;
 import com.google.firebase.database.core.view.CacheNode;
 import com.google.firebase.database.core.view.QuerySpec;
-import com.google.firebase.database.logging.LogWrapper;
 import com.google.firebase.database.snapshot.ChildKey;
 import com.google.firebase.database.snapshot.EmptyNode;
 import com.google.firebase.database.snapshot.IndexedNode;
@@ -34,24 +32,26 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Callable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class DefaultPersistenceManager implements PersistenceManager {
 
+  private static final Logger logger = LoggerFactory.getLogger(PersistenceManager.class);
+
   private final PersistenceStorageEngine storageLayer;
   private final TrackedQueryManager trackedQueryManager;
-  private final LogWrapper logger;
   private final CachePolicy cachePolicy;
   private long serverCacheUpdatesSinceLastPruneCheck = 0;
 
   public DefaultPersistenceManager(
-      Context ctx, PersistenceStorageEngine engine, CachePolicy cachePolicy) {
-    this(ctx, engine, cachePolicy, new DefaultClock());
+      PersistenceStorageEngine engine, CachePolicy cachePolicy) {
+    this(engine, cachePolicy, new DefaultClock());
   }
 
   public DefaultPersistenceManager(
-      Context ctx, PersistenceStorageEngine engine, CachePolicy cachePolicy, Clock clock) {
+      PersistenceStorageEngine engine, CachePolicy cachePolicy, Clock clock) {
     this.storageLayer = engine;
-    this.logger = ctx.getLogger(PersistenceManager.class);
     this.trackedQueryManager = new TrackedQueryManager(storageLayer, logger, clock);
     this.cachePolicy = cachePolicy;
   }
@@ -253,15 +253,11 @@ public  T runInTransaction(Callable callable) {
   private void doPruneCheckAfterServerUpdate() {
     serverCacheUpdatesSinceLastPruneCheck++;
     if (cachePolicy.shouldCheckCacheSize(serverCacheUpdatesSinceLastPruneCheck)) {
-      if (logger.logsDebug()) {
-        logger.debug("Reached prune check threshold.");
-      }
+      logger.debug("Reached prune check threshold.");
       serverCacheUpdatesSinceLastPruneCheck = 0;
       boolean canPrune = true;
       long cacheSize = storageLayer.serverCacheEstimatedSizeInBytes();
-      if (logger.logsDebug()) {
-        logger.debug("Cache size: " + cacheSize);
-      }
+      logger.debug("Cache size: {}", cacheSize);
       while (canPrune
           && cachePolicy.shouldPrune(cacheSize, trackedQueryManager.countOfPrunableQueries())) {
         PruneForest pruneForest = this.trackedQueryManager.pruneOldQueries(cachePolicy);
@@ -271,9 +267,7 @@ private void doPruneCheckAfterServerUpdate() {
           canPrune = false;
         }
         cacheSize = storageLayer.serverCacheEstimatedSizeInBytes();
-        if (logger.logsDebug()) {
-          logger.debug("Cache size after prune: " + cacheSize);
-        }
+        logger.debug("Cache size after prune: {}", cacheSize);
       }
     }
   }
diff --git a/src/main/java/com/google/firebase/database/core/persistence/TrackedQueryManager.java b/src/main/java/com/google/firebase/database/core/persistence/TrackedQueryManager.java
index b4f6b545c..20278b947 100644
--- a/src/main/java/com/google/firebase/database/core/persistence/TrackedQueryManager.java
+++ b/src/main/java/com/google/firebase/database/core/persistence/TrackedQueryManager.java
@@ -23,7 +23,6 @@
 import com.google.firebase.database.core.utilities.Predicate;
 import com.google.firebase.database.core.view.QueryParams;
 import com.google.firebase.database.core.view.QuerySpec;
-import com.google.firebase.database.logging.LogWrapper;
 import com.google.firebase.database.snapshot.ChildKey;
 import com.google.firebase.database.utilities.Clock;
 import com.google.firebase.database.utilities.Utilities;
@@ -36,6 +35,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import org.slf4j.Logger;
 
 public class TrackedQueryManager {
 
@@ -74,7 +74,7 @@ public boolean evaluate(TrackedQuery query) {
       };
   // DB, where we permanently store tracked queries.
   private final PersistenceStorageEngine storageLayer;
-  private final LogWrapper logger;
+  private final Logger logger;
   private final Clock clock;
   // In-memory cache of tracked queries.  Should always be in-sync with the DB.
   private ImmutableTree