diff --git a/bramble-api/src/main/java/org/briarproject/bramble/util/TimeUtils.java b/bramble-api/src/main/java/org/briarproject/bramble/util/TimeUtils.java
index 34420a346ccd097c33dfde9ef477d9f8f311257d..06cc6e4b23aa6dc70917c4ec56ebb18e402dd299 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/util/TimeUtils.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/util/TimeUtils.java
@@ -1,5 +1,9 @@
 package org.briarproject.bramble.util;
 
+import java.util.logging.Logger;
+
+import static java.util.logging.Level.FINE;
+
 public class TimeUtils {
 
 	private static final int NANOS_PER_MILLI = 1000 * 1000;
@@ -11,4 +15,17 @@ public class TimeUtils {
 	public static long now() {
 		return System.nanoTime() / NANOS_PER_MILLI;
 	}
+
+	/**
+	 * Logs the duration of a task.
+	 * @param logger the logger to use
+	 * @param task a description of the task
+	 * @param start the start time of the task, as returned by {@link #now()}
+	 */
+	public static void logDuration(Logger logger, String task, long start) {
+		if (logger.isLoggable(FINE)) {
+			long duration = now() - start;
+			logger.fine(task + " took " + duration + " ms");
+		}
+	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
index 40b5132f126978a76bfca6a2545907c396be20e7..5ca18e89b518ace58f878bcced9d0af19f6ad3c7 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
@@ -30,9 +30,9 @@ import java.util.logging.Logger;
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.INFO;
 import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 
 @NotNullByDefault
@@ -136,8 +136,7 @@ class CryptoComponentImpl implements CryptoComponent {
 		byte allZero = 0;
 		for (byte b : secret) allZero |= b;
 		if (allZero == 0) throw new GeneralSecurityException();
-		if (LOG.isLoggable(FINE))
-			LOG.fine("Deriving shared secret took " + (now() - start) + " ms");
+		logDuration(LOG, "Deriving shared secret", start);
 		return secret;
 	}
 
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/ScryptKdf.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/ScryptKdf.java
index f56f0d9dabd81f515e59ac430a995b7af7aa2963..596cf1f7bae5c747f11b2fadb6d4c7e296d3b184 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/ScryptKdf.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/ScryptKdf.java
@@ -9,8 +9,8 @@ import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.INFO;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 
 class ScryptKdf implements PasswordBasedKdf {
@@ -56,10 +56,7 @@ class ScryptKdf implements PasswordBasedKdf {
 		byte[] passwordBytes = StringUtils.toUtf8(password);
 		SecretKey k = new SecretKey(SCrypt.generate(passwordBytes, salt, cost,
 				BLOCK_SIZE, PARALLELIZATION, SecretKey.LENGTH));
-		if (LOG.isLoggable(FINE)) {
-			long duration = now() - start;
-			LOG.fine("Deriving key from password took " + duration + " ms");
-		}
+		logDuration(LOG, "Deriving key from password", start);
 		return k;
 	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/Sec1KeyParser.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Sec1KeyParser.java
index 4e43dad6705c78a05c95d309ba77a0aab10c399b..555510a7fb87c07a2c8bd0698f1639336d1a54cc 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/Sec1KeyParser.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Sec1KeyParser.java
@@ -16,7 +16,7 @@ import java.util.logging.Logger;
 
 import javax.annotation.concurrent.Immutable;
 
-import static java.util.logging.Level.FINE;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 
 /**
@@ -81,8 +81,7 @@ class Sec1KeyParser implements KeyParser {
 		// Construct a public key from the point (x, y) and the params
 		ECPublicKeyParameters k = new ECPublicKeyParameters(pub, params);
 		PublicKey p = new Sec1PublicKey(k);
-		if (LOG.isLoggable(FINE))
-			LOG.fine("Parsing public key took " + (now() - start) + " ms");
+		logDuration(LOG, "Parsing public key", start);
 		return p;
 	}
 
@@ -99,8 +98,7 @@ class Sec1KeyParser implements KeyParser {
 		// Construct a private key from the private value and the params
 		ECPrivateKeyParameters k = new ECPrivateKeyParameters(d, params);
 		PrivateKey p = new Sec1PrivateKey(k, keyBits);
-		if (LOG.isLoggable(FINE))
-			LOG.fine("Parsing private key took " + (now() - start) + " ms");
+		logDuration(LOG, "Parsing private key", start);
 		return p;
 	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
index 0b478e11997f28db58d0efa643fba6722d94dfb2..e85b036987d0490ec14235b62737780f7b4cef83 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
@@ -68,13 +68,13 @@ import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
 import javax.inject.Inject;
 
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
 import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
 import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
 import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
 import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 
 @ThreadSafe
@@ -127,12 +127,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		if (lock.getReadHoldCount() > 0) throw new IllegalStateException();
 		if (lock.getWriteHoldCount() > 0) throw new IllegalStateException();
 		long start = now();
-		if (readOnly) lock.readLock().lock();
-		else lock.writeLock().lock();
-		if (LOG.isLoggable(FINE)) {
-			long duration = now() - start;
-			if (readOnly) LOG.fine("Waited " + duration + " ms for read lock");
-			else LOG.fine("Waited " + duration + " ms for write lock");
+		if (readOnly) {
+			lock.readLock().lock();
+			logDuration(LOG, "Waiting for read lock", start);
+		} else {
+			lock.writeLock().lock();
+			logDuration(LOG, "Waiting for write lock", start);
 		}
 		try {
 			return new Transaction(db.startTransaction(), readOnly);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java
index 8bbddb739fcd6aca0a44f19abf6cd37d8d00e049..cd09f2220f9053bf7aacf8491e863fb39ce80a2b 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java
@@ -44,6 +44,7 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResul
 import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
 import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
 import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 
 @ThreadSafe
@@ -109,18 +110,14 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
 		byte[] privateKey = keyPair.getPrivate().getEncoded();
 		LocalAuthor localAuthor = authorFactory
 				.createLocalAuthor(nickname, publicKey, privateKey);
-		if (LOG.isLoggable(FINE))
-			LOG.fine("Creating local author took " + (now() - start) + " ms");
+		logDuration(LOG, "Creating local author", start);
 		return localAuthor;
 	}
 
 	private void registerLocalAuthor(LocalAuthor author) throws DbException {
 		long start = now();
 		identityManager.registerLocalAuthor(author);
-		if (LOG.isLoggable(FINE)) {
-			LOG.fine("Registering local author took " + (now() - start)
-					+ " ms");
-		}
+		logDuration(LOG, "Registering local author", start);
 	}
 
 	@Override
@@ -134,12 +131,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
 			long start = now();
 
 			boolean reopened = db.open(this);
-			if (LOG.isLoggable(FINE)) {
-				long duration = now() - start;
-				if (reopened)
-					LOG.fine("Reopening database took " + duration + " ms");
-				else LOG.fine("Creating database took " + duration + " ms");
-			}
+			if (reopened) logDuration(LOG, "Reopening database", start);
+			else logDuration(LOG, "Creating database", start);
 
 			if (nickname != null) {
 				registerLocalAuthor(createLocalAuthor(nickname));
@@ -155,9 +148,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
 					start = now();
 					c.createLocalState(txn);
 					if (LOG.isLoggable(FINE)) {
-						LOG.fine("Starting client "
-								+ c.getClass().getSimpleName()
-								+ " took " + (now() - start) + " ms");
+						logDuration(LOG, "Starting client "
+								+ c.getClass().getSimpleName(), start);
 					}
 				}
 				db.commitTransaction(txn);
@@ -168,8 +160,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
 				start = now();
 				s.startService();
 				if (LOG.isLoggable(FINE)) {
-					LOG.fine("Starting service " + s.getClass().getSimpleName()
-							+ " took " + (now() - start) + " ms");
+					logDuration(LOG, "Starting service "
+							+ s.getClass().getSimpleName(), start);
 				}
 			}
 
@@ -216,8 +208,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
 				long start = now();
 				s.stopService();
 				if (LOG.isLoggable(FINE)) {
-					LOG.fine("Stopping service " + s.getClass().getSimpleName()
-							+ " took " + (now() - start) + " ms");
+					logDuration(LOG, "Stopping service "
+							+ s.getClass().getSimpleName(), start);
 				}
 			}
 			for (ExecutorService e : executors) {
@@ -229,8 +221,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
 			}
 			long start = now();
 			db.close();
-			if (LOG.isLoggable(FINE))
-				LOG.fine("Closing database took " + (now() - start) + " ms");
+			logDuration(LOG, "Closing database", start);
 			shutdownLatch.countDown();
 		} catch (DbException | ServiceException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java
index a86949de71b9e1fa8c9719823d977489d586a838..10ffed8508440cf62e2611766ffb3320b445be8c 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java
@@ -52,6 +52,7 @@ import javax.inject.Inject;
 import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 
 @ThreadSafe
@@ -210,8 +211,8 @@ class PluginManagerImpl implements PluginManager, Service {
 				long start = now();
 				plugin.start();
 				if (LOG.isLoggable(FINE)) {
-					LOG.fine("Starting plugin " + plugin.getId()
-							+ " took " + (now() - start) + " ms");
+					logDuration(LOG, "Starting plugin " + plugin.getId(),
+							start);
 				}
 			} catch (PluginException e) {
 				if (LOG.isLoggable(WARNING)) {
@@ -247,8 +248,8 @@ class PluginManagerImpl implements PluginManager, Service {
 				long start = now();
 				plugin.stop();
 				if (LOG.isLoggable(FINE)) {
-					LOG.fine("Stopping plugin " + plugin.getId()
-							+ " took " + (now() - start) + " ms");
+					logDuration(LOG, "Stopping plugin " + plugin.getId(),
+							start);
 				}
 			} catch (InterruptedException e) {
 				LOG.warning("Interrupted while waiting for plugin to stop");
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseControllerImpl.java
index 6bcef86c657a3d7f9ac3b989918409df4c2121a6..624104bb34a77f806b9dce12958a370971debf2b 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseControllerImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseControllerImpl.java
@@ -32,8 +32,8 @@ import java.util.logging.Logger;
 
 import javax.annotation.Nullable;
 
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 import static org.briarproject.briar.util.HtmlUtils.ARTICLE;
 
@@ -113,8 +113,7 @@ abstract class BaseControllerImpl extends DbControllerImpl
 		long start = now();
 		Collection<BlogPostHeader> headers =
 				blogManager.getPostHeaders(groupId);
-		if (LOG.isLoggable(FINE))
-			LOG.fine("Loading headers took " + (now() - start) + " ms");
+		logDuration(LOG, "Loading headers", start);
 		Collection<BlogPostItem> items = new ArrayList<>(headers.size());
 		start = now();
 		for (BlogPostHeader h : headers) {
@@ -122,8 +121,7 @@ abstract class BaseControllerImpl extends DbControllerImpl
 			BlogPostItem item = getItem(h);
 			items.add(item);
 		}
-		if (LOG.isLoggable(FINE))
-			LOG.fine("Loading bodies took " + (now() - start) + " ms");
+		logDuration(LOG, "Loading bodies", start);
 		return items;
 	}
 
@@ -141,8 +139,7 @@ abstract class BaseControllerImpl extends DbControllerImpl
 			try {
 				long start = now();
 				BlogPostItem item = getItem(header);
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Loading body took " + (now() - start) + " ms");
+				logDuration(LOG, "Loading body", start);
 				handler.onResult(item);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING))
@@ -167,8 +164,7 @@ abstract class BaseControllerImpl extends DbControllerImpl
 				long start = now();
 				BlogPostHeader header1 = getPostHeader(g, m);
 				BlogPostItem item = getItem(header1);
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Loading post took " + (now() - start) + " ms");
+				logDuration(LOG, "Loading post", start);
 				handler.onResult(item);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING))
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogControllerImpl.java
index 323dda2e5a44cba85a5a8ef2aee52e73ce45594f..017ab200aa4f11537128fe3a6a642772e9d3c48b 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogControllerImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogControllerImpl.java
@@ -35,8 +35,8 @@ import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 
 @MethodsNotNullByDefault
@@ -161,8 +161,7 @@ class BlogControllerImpl extends BaseControllerImpl
 				boolean ours = a.getId().equals(b.getAuthor().getId());
 				boolean removable = blogManager.canBeRemoved(b);
 				BlogItem blog = new BlogItem(b, ours, removable);
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Loading blog took " + (now() - start) + " ms");
+				logDuration(LOG, "Loading blog", start);
 				handler.onResult(blog);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING))
@@ -180,8 +179,7 @@ class BlogControllerImpl extends BaseControllerImpl
 				long start = now();
 				Blog b = blogManager.getBlog(groupId);
 				blogManager.removeBlog(b);
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Removing blog took " + (now() - start) + " ms");
+				logDuration(LOG, "Removing blog", start);
 				handler.onResult(null);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING))
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedControllerImpl.java
index 08134e5bfafbe26cfb3d8e4f434cfcaa295c9a78..7fbdd61e55a52a525d9e50f6b8c027a4656059ae 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedControllerImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedControllerImpl.java
@@ -26,8 +26,8 @@ import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID;
 
@@ -110,10 +110,7 @@ class FeedControllerImpl extends BaseControllerImpl
 							LOG.log(WARNING, e.toString(), e);
 					}
 				}
-				if (LOG.isLoggable(FINE)) {
-					long duration = now() - start;
-					LOG.fine("Loading all posts took " + duration + " ms");
-				}
+				logDuration(LOG, "Loading all posts", start);
 				handler.onResult(posts);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -130,9 +127,7 @@ class FeedControllerImpl extends BaseControllerImpl
 				long start = now();
 				Author a = identityManager.getLocalAuthor();
 				Blog b = blogManager.getPersonalBlog(a);
-				long duration = now() - start;
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Loading blog took " + duration + " ms");
+				logDuration(LOG, "Loading personal blog", start);
 				handler.onResult(b);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java
index 35999ca56bb935e13c1e1cbe9cfa72aec3097df8..91bde1e8e082a7ede7b2560ab75cd85c401fa6ad 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java
@@ -59,8 +59,8 @@ import javax.inject.Inject;
 
 import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
 import static android.support.v4.view.ViewCompat.getTransitionName;
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 import static org.briarproject.briar.android.contact.ConversationActivity.CONTACT_ID;
 
@@ -209,8 +209,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 						// Continue
 					}
 				}
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Full load took " + (now() - start) + " ms");
+				logDuration(LOG, "Full load", start);
 				displayContacts(revision, contacts);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java
index 70b8d1ccccab751ec72008ace9164386458f49eb..dcc91bd274a047a7b48f1b634b805881b91307d1 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java
@@ -104,9 +104,9 @@ import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptSt
 import static android.support.v4.view.ViewCompat.setTransitionName;
 import static android.support.v7.util.SortedList.INVALID_POSITION;
 import static android.widget.Toast.LENGTH_SHORT;
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
 import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
@@ -298,8 +298,7 @@ public class ConversationActivity extends BriarActivity
 					contactName = contact.getAuthor().getName();
 					contactAuthorId = contact.getAuthor().getId();
 				}
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Loading contact took " + (now() - start) + " ms");
+				logDuration(LOG, "Loading contact", start);
 				loadMessages();
 				displayContactDetails();
 			} catch (NoSuchContactException e) {
@@ -358,10 +357,7 @@ public class ConversationActivity extends BriarActivity
 				invitations.addAll(forumInvitations);
 				invitations.addAll(blogInvitations);
 				invitations.addAll(groupInvitations);
-				if (LOG.isLoggable(FINE)) {
-					long duration = now() - start;
-					LOG.fine("Loading messages took " + duration + " ms");
-				}
+				logDuration(LOG, "Loading messages", start);
 				displayMessages(revision, headers, introductions, invitations);
 			} catch (NoSuchContactException e) {
 				finishOnUiThread();
@@ -442,8 +438,7 @@ public class ConversationActivity extends BriarActivity
 			try {
 				long start = now();
 				String body = messagingManager.getMessageBody(m);
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Loading body took " + (now() - start) + " ms");
+				logDuration(LOG, "Loading body", start);
 				displayMessageBody(m, body);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -692,8 +687,7 @@ public class ConversationActivity extends BriarActivity
 			try {
 				long start = now();
 				messagingManager.addLocalMessage(m);
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Storing message took " + (now() - start) + " ms");
+				logDuration(LOG, "Storing message", start);
 				Message message = m.getMessage();
 				PrivateMessageHeader h = new PrivateMessageHeader(
 						message.getId(), message.getGroupId(),
@@ -818,8 +812,7 @@ public class ConversationActivity extends BriarActivity
 			try {
 				long start = now();
 				messagingManager.setReadFlag(g, m, true);
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Marking read took " + (now() - start) + " ms");
+				logDuration(LOG, "Marking read", start);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/CreateForumActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/CreateForumActivity.java
index 67a25c1dfd431fe267d92a137f61ec70b018d908..65bf9578b3211a37e7eed810e551b23ec7ae56b7 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/forum/CreateForumActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/CreateForumActivity.java
@@ -28,8 +28,8 @@ import javax.inject.Inject;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 import static android.widget.Toast.LENGTH_LONG;
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
 
@@ -125,8 +125,7 @@ public class CreateForumActivity extends BriarActivity {
 			try {
 				long start = now();
 				Forum f = forumManager.addForum(name);
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Storing forum took " + (now() - start) + " ms");
+				logDuration(LOG, "Storing forum", start);
 				displayForum(f);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumListFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumListFragment.java
index dbd91ddec4b03957c80088493fbd7d49203b6a31..8ac34e717fe59c3b5e964b4347e6b530987cfe24 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumListFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumListFragment.java
@@ -44,8 +44,8 @@ import javax.annotation.Nullable;
 import javax.inject.Inject;
 
 import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 import static org.briarproject.briar.api.forum.ForumManager.CLIENT_ID;
 
@@ -169,8 +169,7 @@ public class ForumListFragment extends BaseEventFragment implements
 						// Continue
 					}
 				}
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Full load took " + (now() - start) + " ms");
+				logDuration(LOG, "Full load", start);
 				displayForums(revision, forums);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -196,10 +195,7 @@ public class ForumListFragment extends BaseEventFragment implements
 			try {
 				long start = now();
 				int available = forumSharingManager.getInvitations().size();
-				if (LOG.isLoggable(FINE)) {
-					long duration = now() - start;
-					LOG.fine("Loading available took " + duration + " ms");
-				}
+				logDuration(LOG, "Loading available", start);
 				displayAvailableForums(available);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordControllerImpl.java
index 04e4064200de0fd7e24c80ba821d35e693d4f8df..86833aed28e5af626e42061c9174e4e002f329a8 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordControllerImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordControllerImpl.java
@@ -17,7 +17,7 @@ import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
-import static java.util.logging.Level.FINE;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 
 @NotNullByDefault
@@ -89,8 +89,7 @@ public class PasswordControllerImpl extends ConfigControllerImpl
 	String encryptDatabaseKey(SecretKey key, String password) {
 		long start = now();
 		byte[] encrypted = crypto.encryptWithPassword(key.getBytes(), password);
-		if (LOG.isLoggable(FINE))
-			LOG.fine("Key derivation took " + (now() - start) + " ms");
+		logDuration(LOG, "Key derivation", start);
 		return StringUtils.toHexString(encrypted);
 	}
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/list/GroupListControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/list/GroupListControllerImpl.java
index 9b636ce2f8f69cc407650d71dcd1a7b42563af88..89e74e8388f8181f3ed19b7686ffbee750a2eb6b 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/list/GroupListControllerImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/list/GroupListControllerImpl.java
@@ -36,8 +36,8 @@ import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID;
 
@@ -162,8 +162,7 @@ class GroupListControllerImpl extends DbControllerImpl
 						// Continue
 					}
 				}
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Loading groups took " + (now() - start) + " ms");
+				logDuration(LOG, "Loading groups", start);
 				handler.onResult(items);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -178,8 +177,7 @@ class GroupListControllerImpl extends DbControllerImpl
 			try {
 				long start = now();
 				groupManager.removePrivateGroup(g);
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Removing group took " + (now() - start) + " ms");
+				logDuration(LOG, "Removing group", start);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 				handler.onException(e);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java
index 6c6ff69531e5c0615c77077d3bc80506e239d74d..b2f661289bbe5880bac920e59a32c810abee54e7 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java
@@ -59,12 +59,12 @@ import static android.provider.Settings.EXTRA_CHANNEL_ID;
 import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI;
 import static android.support.v4.view.ViewCompat.LAYOUT_DIRECTION_LTR;
 import static android.widget.Toast.LENGTH_SHORT;
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
 import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
 import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_ALWAYS;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
 import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE;
@@ -249,10 +249,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
 				Settings btSettings = settingsManager.getSettings(BT_NAMESPACE);
 				Settings torSettings =
 						settingsManager.getSettings(TOR_NAMESPACE);
-				if (LOG.isLoggable(FINE)) {
-					long duration = now() - start;
-					LOG.fine("Loading settings took " + duration + " ms");
-				}
+				logDuration(LOG, "Loading settings", start);
 				boolean btSetting =
 						btSettings.getBoolean(PREF_BT_ENABLE, false);
 				int torSetting = torSettings.getInt(PREF_TOR_NETWORK,
@@ -440,10 +437,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
 				s.putInt(PREF_TOR_NETWORK, torSetting);
 				long start = now();
 				settingsManager.mergeSettings(s, TOR_NAMESPACE);
-				if (LOG.isLoggable(FINE)) {
-					long duration = now() - start;
-					LOG.fine("Merging settings took " + duration + " ms");
-				}
+				logDuration(LOG, "Merging settings", start);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			}
@@ -457,10 +451,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
 				s.putBoolean(PREF_BT_ENABLE, btSetting);
 				long start = now();
 				settingsManager.mergeSettings(s, BT_NAMESPACE);
-				if (LOG.isLoggable(FINE)) {
-					long duration = now() - start;
-					LOG.fine("Merging settings took " + duration + " ms");
-				}
+				logDuration(LOG, "Merging settings", start);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			}
@@ -472,10 +463,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
 			try {
 				long start = now();
 				settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
-				if (LOG.isLoggable(FINE)) {
-					long duration = now() - start;
-					LOG.fine("Merging settings took " + duration + " ms");
-				}
+				logDuration(LOG, "Merging settings", start);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/sharing/InvitationControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/sharing/InvitationControllerImpl.java
index e457a60a4a505e174e014e29293ce15c19e88c53..895dbb382ed386f78a9df4edaad7bc77d2d38de5 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/sharing/InvitationControllerImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/sharing/InvitationControllerImpl.java
@@ -24,8 +24,8 @@ import java.util.Collection;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 
 @MethodsNotNullByDefault
@@ -98,10 +98,7 @@ public abstract class InvitationControllerImpl<I extends InvitationItem>
 			try {
 				long start = now();
 				Collection<I> invitations = new ArrayList<>(getInvitations());
-				if (LOG.isLoggable(FINE)) {
-					long duration = now() - start;
-					LOG.fine("Loading invitations took " + duration + " ms");
-				}
+				logDuration(LOG, "Loading invitations", start);
 				handler.onResult(invitations);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java
index deb769c1423cd8f81a1d41e4d87fe9c10e205bbc..e2502a19d84ab296565616760464875f2b4dc63e 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java
@@ -34,9 +34,9 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
-import static java.util.logging.Level.FINE;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.TimeUtils.logDuration;
 import static org.briarproject.bramble.util.TimeUtils.now;
 
 @MethodsNotNullByDefault
@@ -135,8 +135,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 			try {
 				long start = now();
 				G groupItem = loadNamedGroup();
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Loading group took " + (now() - start) + " ms");
+				logDuration(LOG, "Loading group", start);
 				handler.onResult(groupItem);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING))
@@ -158,8 +157,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 				// Load headers
 				long start = now();
 				Collection<H> headers = loadHeaders();
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Loading headers took " + (now() - start) + " ms");
+				logDuration(LOG, "Loading headers", start);
 
 				// Load bodies into cache
 				start = now();
@@ -169,8 +167,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 								loadMessageBody(header));
 					}
 				}
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Loading bodies took " + (now() - start) + " ms");
+				logDuration(LOG, "Loading bodies", start);
 
 				// Build and hand over items
 				handler.onResult(buildItems(headers));
@@ -200,8 +197,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 				for (I i : items) {
 					markRead(i.getId());
 				}
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Marking read took " + (now() - start) + " ms");
+				logDuration(LOG, "Marking read", start);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			}
@@ -218,8 +214,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 				long start = now();
 				H header = addLocalMessage(msg);
 				bodyCache.put(msg.getMessage().getId(), body);
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Storing message took " + (now() - start) + " ms");
+				logDuration(LOG, "Storing message", start);
 				resultHandler.onResult(buildItem(header, body));
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -238,8 +233,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 				long start = now();
 				G groupItem = loadNamedGroup();
 				deleteNamedGroup(groupItem);
-				if (LOG.isLoggable(FINE))
-					LOG.fine("Removing group took " + (now() - start) + " ms");
+				logDuration(LOG, "Removing group", start);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 				handler.onException(e);