Use SLF4J for logging.

This commit is contained in:
Felix Ableitner 2016-05-04 07:03:02 +02:00
parent 8bafd62e35
commit 2cc4928a99
17 changed files with 82 additions and 148 deletions

View File

@ -17,6 +17,7 @@ dependencies {
compile 'com.mobsandgeeks:adapter-kit:0.5.3'
compile 'com.google.zxing:android-integration:3.2.1'
compile 'com.google.zxing:core:3.2.1'
compile 'org.slf4j:slf4j-android:1.7.21'
compile project(path: ':core')
androidTestCompile 'com.android.support:multidex-instrumentation:1.0.1',
{ exclude module: 'multidex' }

View File

@ -1,14 +1,12 @@
package com.nutomic.ensichat
import android.support.multidex.MultiDexApplication
import com.nutomic.ensichat.core.interfaces.Log
import com.nutomic.ensichat.util.{Logging, PRNGFixes}
import com.nutomic.ensichat.util.PRNGFixes
class App extends MultiDexApplication {
override def onCreate(): Unit = {
super.onCreate()
Log.setLogInstance(new Logging())
PRNGFixes.apply()
}

View File

@ -1,13 +0,0 @@
package com.nutomic.ensichat.util
import android.util
import com.nutomic.ensichat.core.interfaces.Log
class Logging extends Log {
def v(tag: String, message: String, tr: Throwable = null) = util.Log.v(tag, message, tr)
def d(tag: String, message: String, tr: Throwable = null) = util.Log.d(tag, message, tr)
def i(tag: String, message: String, tr: Throwable = null) = util.Log.i(tag, message, tr)
def w(tag: String, message: String, tr: Throwable = null) = util.Log.w(tag, message, tr)
def e(tag: String, message: String, tr: Throwable = null) = util.Log.e(tag, message, tr)
}

View File

@ -4,6 +4,7 @@ dependencies {
compile 'org.scala-lang:scala-library:2.11.7'
compile 'com.h2database:h2:1.4.191'
compile 'com.typesafe.slick:slick_2.11:3.1.1'
compile 'com.typesafe.scala-logging:scala-logging_2.11:3.4.0'
testCompile 'junit:junit:4.12'
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="slick" level="INFO" />
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

View File

@ -7,6 +7,7 @@ import com.nutomic.ensichat.core.header.ContentHeader
import com.nutomic.ensichat.core.interfaces._
import com.nutomic.ensichat.core.internet.InternetInterface
import com.nutomic.ensichat.core.util.{Database, FutureHelper}
import com.typesafe.scalalogging.Logger
import scala.concurrent.ExecutionContext.Implicits.global
@ -20,7 +21,7 @@ final class ConnectionHandler(settings: SettingsInterface, database: Database,
callbacks: CallbackInterface, crypto: Crypto,
maxInternetConnections: Int) {
private val Tag = "ConnectionHandler"
private val logger = Logger(this.getClass)
private var transmissionInterfaces = Set[TransmissionInterface]()
@ -45,8 +46,8 @@ final class ConnectionHandler(settings: SettingsInterface, database: Database,
additionalInterfaces.foreach(transmissionInterfaces += _)
FutureHelper {
crypto.generateLocalKeys()
Log.i(Tag, "Service started, address is " + crypto.localAddress)
Log.i(Tag, "Local user is " + settings.get(SettingsInterface.KeyUserName, "none") +
logger.info("Service started, address is " + crypto.localAddress)
logger.info("Local user is " + settings.get(SettingsInterface.KeyUserName, "none") +
" with status '" + settings.get(SettingsInterface.KeyUserStatus, "") + "'")
transmissionInterfaces += new InternetInterface(this, crypto, settings, maxInternetConnections)
transmissionInterfaces.foreach(_.create())
@ -83,11 +84,11 @@ final class ConnectionHandler(settings: SettingsInterface, database: Database,
*/
def onMessageReceived(msg: Message): Unit = {
if (router.isMessageSeen(msg)) {
Log.v(Tag, "Ignoring message from " + msg.header.origin + " that we already received")
logger.trace("Ignoring message from " + msg.header.origin + " that we already received")
} else if (msg.header.target == crypto.localAddress) {
crypto.verifyAndDecrypt(msg) match {
case Some(m) => onNewMessage(m)
case None => Log.i(Tag, "Ignoring message with invalid signature from " + msg.header.origin)
case None => logger.info("Ignoring message with invalid signature from " + msg.header.origin)
}
} else {
router.forwardMessage(msg)
@ -127,34 +128,34 @@ final class ConnectionHandler(settings: SettingsInterface, database: Database,
val maxConnections = settings.get(SettingsInterface.KeyMaxConnections,
SettingsInterface.DefaultMaxConnections.toString).toInt
if (connections().size == maxConnections) {
Log.i(Tag, "Maximum number of connections reached")
logger.info("Maximum number of connections reached")
return false
}
val info = msg.body.asInstanceOf[ConnectionInfo]
val sender = crypto.calculateAddress(info.key)
if (sender == Address.Broadcast || sender == Address.Null) {
Log.i(Tag, "Ignoring ConnectionInfo message with invalid sender " + sender)
logger.info("Ignoring ConnectionInfo message with invalid sender " + sender)
return false
}
if (crypto.havePublicKey(sender) && !crypto.verify(msg, Option(crypto.getPublicKey(sender)))) {
Log.i(Tag, "Ignoring ConnectionInfo message with invalid signature")
logger.info("Ignoring ConnectionInfo message with invalid signature")
return false
}
synchronized {
if (!crypto.havePublicKey(sender)) {
crypto.addPublicKey(sender, info.key)
Log.i(Tag, "Added public key for new device " + sender.toString)
logger.info("Added public key for new device " + sender.toString)
}
}
// Log with username if we know it.
if (allKnownUsers().map(_.address).contains(sender))
Log.i(Tag, "Node " + getUser(sender).name + " (" + sender + ") connected")
logger.info("Node " + getUser(sender).name + " (" + sender + ") connected")
else
Log.i(Tag, "Node " + sender + " connected")
logger.info("Node " + sender + " connected")
sendTo(sender, new UserInfo(settings.get(SettingsInterface.KeyUserName, ""),
settings.get(SettingsInterface.KeyUserStatus, "")))

View File

@ -9,7 +9,8 @@ import javax.crypto.{Cipher, CipherOutputStream, KeyGenerator, SecretKey}
import com.nutomic.ensichat.core.Crypto._
import com.nutomic.ensichat.core.body._
import com.nutomic.ensichat.core.header.ContentHeader
import com.nutomic.ensichat.core.interfaces.{Log, SettingsInterface}
import com.nutomic.ensichat.core.interfaces.SettingsInterface
import com.typesafe.scalalogging.Logger
object Crypto {
@ -76,7 +77,7 @@ object Crypto {
*/
class Crypto(settings: SettingsInterface, keyFolder: File) {
private val Tag = "Crypto"
private val logger = Logger(this.getClass)
/**
* Generates a new key pair using [[keyFolder]] with [[PublicKeySize]] bits and stores the
@ -104,7 +105,7 @@ class Crypto(settings: SettingsInterface, keyFolder: File) {
saveKey(PrivateKeyAlias, keyPair.getPrivate)
saveKey(PublicKeyAlias, keyPair.getPublic)
Log.i(Tag, "Generated cryptographic keys, address is " + address)
logger.info("Generated cryptographic keys, address is " + address)
}
/**
@ -184,7 +185,7 @@ class Crypto(settings: SettingsInterface, keyFolder: File) {
fos = Option(new FileOutputStream(path))
fos.foreach(_.write(key.getEncoded))
} catch {
case e: IOException => Log.w(Tag, "Failed to save key for alias " + alias, e)
case e: IOException => logger.warn("Failed to save key for alias " + alias, e)
} finally {
fos.foreach(_.close())
}
@ -212,7 +213,7 @@ class Crypto(settings: SettingsInterface, keyFolder: File) {
data = new Array[Byte](path.length().asInstanceOf[Int])
fis.foreach(_.read(data))
} catch {
case e: IOException => Log.e(Tag, "Failed to load key for alias " + alias, e)
case e: IOException => logger.error("Failed to load key for alias " + alias, e)
} finally {
fis.foreach(_.close())
}
@ -240,7 +241,7 @@ class Crypto(settings: SettingsInterface, keyFolder: File) {
None
} catch {
case e: InvalidKeyException =>
Log.w(Tag, "Failed to verify or decrypt message", e)
logger.warn("Failed to verify or decrypt message", e)
None
}
}

View File

@ -1,25 +0,0 @@
package com.nutomic.ensichat.core.interfaces
object Log {
def setLogInstance(log: Log) = instance = Option(log)
private var instance: Option[Log] = None
def v(tag: String, message: String, tr: Throwable = null) = instance.foreach(_.v(tag, message, tr))
def d(tag: String, message: String, tr: Throwable = null) = instance.foreach(_.d(tag, message, tr))
def i(tag: String, message: String, tr: Throwable = null) = instance.foreach(_.i(tag, message, tr))
def w(tag: String, message: String, tr: Throwable = null) = instance.foreach(_.w(tag, message, tr))
def e(tag: String, message: String, tr: Throwable = null) = instance.foreach(_.e(tag, message, tr))
}
trait Log {
def v(tag: String, message: String, tr: Throwable = null)
def d(tag: String, message: String, tr: Throwable = null)
def i(tag: String, message: String, tr: Throwable = null)
def w(tag: String, message: String, tr: Throwable = null)
def e(tag: String, message: String, tr: Throwable = null)
}

View File

@ -6,8 +6,8 @@ import java.net.{InetAddress, Socket}
import com.nutomic.ensichat.core.Message.ReadMessageException
import com.nutomic.ensichat.core.body.ConnectionInfo
import com.nutomic.ensichat.core.header.MessageHeader
import com.nutomic.ensichat.core.interfaces.Log
import com.nutomic.ensichat.core.{Address, Crypto, Message}
import com.typesafe.scalalogging.Logger
/**
* Encapsulates an active connection to another node.
@ -15,14 +15,14 @@ import com.nutomic.ensichat.core.{Address, Crypto, Message}
class InternetConnectionThread(socket: Socket, crypto: Crypto, onDisconnected: (InternetConnectionThread) => Unit,
onReceive: (Message, InternetConnectionThread) => Unit) extends Thread {
private val Tag = "InternetConnectionThread"
private val logger = Logger(this.getClass)
private val inStream: InputStream =
try {
socket.getInputStream
} catch {
case e: IOException =>
Log.e(Tag, "Failed to open stream", e)
logger.error("Failed to open stream", e)
close()
null
}
@ -32,7 +32,7 @@ class InternetConnectionThread(socket: Socket, crypto: Crypto, onDisconnected: (
socket.getOutputStream
} catch {
case e: IOException =>
Log.e(Tag, "Failed to open stream", e)
logger.error("Failed to open stream", e)
close()
null
}
@ -42,7 +42,7 @@ class InternetConnectionThread(socket: Socket, crypto: Crypto, onDisconnected: (
}
override def run(): Unit = {
Log.i(Tag, "Connection opened to " + socket.getInetAddress)
logger.info("Connection opened to " + socket.getInetAddress)
send(crypto.sign(new Message(new MessageHeader(ConnectionInfo.Type,
Address.Null, Address.Null, 0), new ConnectionInfo(crypto.getLocalPublicKey))))
@ -51,13 +51,13 @@ class InternetConnectionThread(socket: Socket, crypto: Crypto, onDisconnected: (
socket.setKeepAlive(true)
while (socket.isConnected) {
val msg = Message.read(inStream)
Log.v(Tag, "Received " + msg)
logger.trace("Received " + msg)
onReceive(msg, this)
}
} catch {
case e @ (_: ReadMessageException | _: IOException) =>
Log.w(Tag, "Failed to read incoming message", e)
logger.warn("Failed to read incoming message", e)
close()
return
}
@ -68,7 +68,7 @@ class InternetConnectionThread(socket: Socket, crypto: Crypto, onDisconnected: (
try {
outStream.write(msg.write)
} catch {
case e: IOException => Log.e(Tag, "Failed to write message", e)
case e: IOException => logger.error("Failed to write message", e)
}
}
@ -76,9 +76,9 @@ class InternetConnectionThread(socket: Socket, crypto: Crypto, onDisconnected: (
try {
socket.close()
} catch {
case e: IOException => Log.w(Tag, "Failed to close socket", e)
case e: IOException => logger.warn("Failed to close socket", e)
}
Log.d(Tag, "Connection to " + socket.getInetAddress + " closed")
logger.debug("Connection to " + socket.getInetAddress + " closed")
onDisconnected(this)
}

View File

@ -1,14 +1,15 @@
package com.nutomic.ensichat.core.internet
import java.io.IOException
import java.net.{InetAddress, Socket}
import com.nutomic.ensichat.core.body.ConnectionInfo
import com.nutomic.ensichat.core.interfaces.{Log, SettingsInterface, TransmissionInterface}
import com.nutomic.ensichat.core.interfaces.{SettingsInterface, TransmissionInterface}
import com.nutomic.ensichat.core.util.FutureHelper
import com.nutomic.ensichat.core.{Address, ConnectionHandler, Crypto, Message}
import com.typesafe.scalalogging.Logger
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.Random
object InternetInterface {
@ -26,7 +27,7 @@ class InternetInterface(connectionHandler: ConnectionHandler, crypto: Crypto,
settings: SettingsInterface, maxConnections: Int)
extends TransmissionInterface {
private val Tag = "InternetInterface"
private val logger = Logger(this.getClass)
private lazy val serverThread =
new InternetServerThread(crypto, onConnected, onDisconnected, onReceiveMessage)
@ -83,15 +84,14 @@ class InternetInterface(connectionHandler: ConnectionHandler, crypto: Crypto,
* Opens connection to the specified IP address in client mode.
*/
private def openConnection(address: String, port: Int): Unit = {
Log.i(Tag, s"Attempting connection to $address:$port")
try {
logger.info(s"Attempting connection to $address:$port")
Future {
val socket = new Socket(InetAddress.getByName(address), port)
val ct = new InternetConnectionThread(socket, crypto, onDisconnected, onReceiveMessage)
connections += ct
ct.start()
} catch {
case e: IOException =>
Log.w(Tag, "Failed to open connection to " + address + ":" + port, e)
}.onFailure { case e =>
logger.warn("Failed to open connection to " + address + ":" + port, e)
}
}
@ -101,7 +101,7 @@ class InternetInterface(connectionHandler: ConnectionHandler, crypto: Crypto,
private def onDisconnected(connectionThread: InternetConnectionThread): Unit = {
addressDeviceMap.find(_._2 == connectionThread).foreach { ad =>
Log.d(Tag, "Connection closed to " + ad._1)
logger.trace("Connection closed to " + ad._1)
connections -= connectionThread
addressDeviceMap -= ad._1
connectionHandler.onConnectionClosed()
@ -112,7 +112,7 @@ class InternetInterface(connectionHandler: ConnectionHandler, crypto: Crypto,
case info: ConnectionInfo =>
val address = crypto.calculateAddress(info.key)
if (address == crypto.localAddress) {
Log.i(Tag, "Address " + address + " is me, not connecting to myself")
logger.info("Address " + address + " is me, not connecting to myself")
thread.close()
return
}
@ -141,7 +141,7 @@ class InternetInterface(connectionHandler: ConnectionHandler, crypto: Crypto,
def connectionChanged(): Unit = {
FutureHelper {
Log.i(Tag, "Network has changed. Closing all connections and connecting to bootstrap nodes again")
logger.info("Network has changed. Closing all connections and connecting to bootstrap nodes again")
connections.foreach(_.close())
openAllConnections(maxConnections)
}

View File

@ -3,19 +3,19 @@ package com.nutomic.ensichat.core.internet
import java.io.IOException
import java.net.ServerSocket
import com.nutomic.ensichat.core.interfaces.Log
import com.nutomic.ensichat.core.{Crypto, Message}
import com.typesafe.scalalogging.Logger
class InternetServerThread(crypto: Crypto, onConnected: (InternetConnectionThread) => Unit,
onDisconnected: (InternetConnectionThread) => Unit, onReceive: (Message, InternetConnectionThread) => Unit) extends Thread {
private val Tag = "InternetServerThread"
private val logger = Logger(this.getClass)
private lazy val socket: Option[ServerSocket] = try {
Option(new ServerSocket(InternetInterface.ServerPort))
} catch {
case e: IOException =>
Log.w(Tag, "Failed to create server socket", e)
logger.warn("Failed to create server socket", e)
None
}
@ -27,7 +27,7 @@ class InternetServerThread(crypto: Crypto, onConnected: (InternetConnectionThrea
connection.start()
}
} catch {
case e: IOException => Log.w(Tag, "Failed to accept connection", e)
case e: IOException => logger.warn("Failed to accept connection", e)
}
}
@ -35,7 +35,7 @@ class InternetServerThread(crypto: Crypto, onConnected: (InternetConnectionThrea
try {
socket.get.close()
} catch {
case e: IOException => Log.w(Tag, "Failed to close socket", e)
case e: IOException => logger.warn("Failed to close socket", e)
}
}

View File

@ -5,10 +5,10 @@ import java.util.Date
import com.nutomic.ensichat.core.body.Text
import com.nutomic.ensichat.core.header.ContentHeader
import com.nutomic.ensichat.core.interfaces.{Log, CallbackInterface}
import com.nutomic.ensichat.core.interfaces.CallbackInterface
import com.nutomic.ensichat.core.{Address, Message, User}
import com.typesafe.scalalogging.Logger
import slick.driver.H2Driver.api._
import slick.jdbc.meta.MTable
import scala.concurrent.Await
import scala.concurrent.duration.Duration
@ -21,7 +21,7 @@ import scala.concurrent.duration.Duration
*/
class Database(path: File, callbackInterface: CallbackInterface) {
private val Tag = "Database"
private val logger = Logger(this.getClass)
private class Messages(tag: Tag) extends Table[Message](tag, "MESSAGES") {
def id = primaryKey("id", (origin, messageId))
@ -63,7 +63,7 @@ class Database(path: File, callbackInterface: CallbackInterface) {
// H2 appends a .mv.db suffix to the path which we can't change, so we have to check that file.
val dbFile = new File(path.getAbsolutePath + ".mv.db")
if (!dbFile.exists()) {
Log.i(Tag, "Database does not exist, creating tables")
logger.info("Database does not exist, creating tables")
Await.result(db.run((messages.schema ++ contacts.schema).create), Duration.Inf)
}
}

View File

@ -1,6 +1,6 @@
package com.nutomic.ensichat.core.util
import com.nutomic.ensichat.core.interfaces.Log
import com.typesafe.scalalogging.Logger
import scala.concurrent.{ExecutionContext, Future}
@ -11,7 +11,7 @@ import scala.concurrent.{ExecutionContext, Future}
*/
object FutureHelper {
private val Tag = "FutureHelper"
private val logger = Logger(this.getClass)
def apply[A](action: => A)(implicit executor: ExecutionContext): Future[A] = {
val f = Future(action)
@ -20,7 +20,7 @@ object FutureHelper {
// HACK: Android does not close app when crash occurs in background thread, and there's no
// cross-platform way to execute on the foreground thread.
// We use this to make sure exceptions are not hidden in the logs.
Log.e(Tag, "Exception in Future", e)
logger.error("Exception in Future", e)
//System.exit(-1)
}
f

View File

@ -5,6 +5,7 @@ dependencies {
compile 'org.scala-lang:scala-library:2.11.7'
compile project(path: ':core')
compile 'com.github.scopt:scopt_2.10:3.3.0'
compile 'ch.qos.logback:logback-classic:1.1.7'
}
mainClassName = 'com.nutomic.ensichat.server.Main'

View File

@ -1,42 +0,0 @@
package com.nutomic.ensichat.server
import java.io.{PrintWriter, StringWriter}
import java.text.DateFormat
import java.util.{Date, Locale}
import com.nutomic.ensichat.core.interfaces.Log
import scala.collection.mutable
class Logging extends Log {
private val logs = new mutable.Queue[String]()
def dequeue(): Seq[String] = logs.dequeueAll((String) => true)
private def enqueue(tag: String, message: String, tr: Option[Throwable]): Unit = {
val df = DateFormat.getTimeInstance(DateFormat.DEFAULT, Locale.UK)
val throwableString = tr.map { tr =>
val sw = new StringWriter()
tr.printStackTrace(new PrintWriter(sw))
"\n" + sw.toString
}
logs.enqueue(df.format(new Date()) + " " + tag + ": " + message + throwableString.getOrElse(""))
}
def v(tag: String, message: String, tr: Throwable = null): Unit =
enqueue("V/" + tag, message, Option(tr))
def d(tag: String, message: String, tr: Throwable = null): Unit =
enqueue("D/" + tag, message, Option(tr))
def i(tag: String, message: String, tr: Throwable = null): Unit =
enqueue("I/" + tag, message, Option(tr))
def w(tag: String, message: String, tr: Throwable = null): Unit =
enqueue("W/" + tag, message, Option(tr))
def e(tag: String, message: String, tr: Throwable = null): Unit =
enqueue("E/" + tag, message, Option(tr))
}

View File

@ -5,24 +5,21 @@ import java.nio.file.Paths
import java.util.concurrent.TimeUnit
import com.nutomic.ensichat.core.body.Text
import com.nutomic.ensichat.core.interfaces.SettingsInterface._
import com.nutomic.ensichat.core.interfaces.{CallbackInterface, Log, SettingsInterface}
import com.nutomic.ensichat.core.interfaces.{CallbackInterface, SettingsInterface}
import com.nutomic.ensichat.core.util.Database
import com.nutomic.ensichat.core.{ConnectionHandler, Crypto, Message}
import com.typesafe.scalalogging.Logger
import scopt.OptionParser
object Main extends App with CallbackInterface {
private val Tag = "Main"
private val logger = Logger(this.getClass)
private val ConfigFolder = Paths.get("").toFile.getAbsoluteFile
private val ConfigFile = new File(ConfigFolder, "config.properties")
private val DatabaseFile = new File(ConfigFolder, "database")
private val KeyFolder = new File(ConfigFolder, "keys")
private val LogInterval = TimeUnit.SECONDS.toMillis(1)
private lazy val logInstance = new Logging()
private lazy val settings = new Settings(ConfigFile)
private lazy val crypto = new Crypto(settings, KeyFolder)
private lazy val database = new Database(DatabaseFile, this)
@ -38,7 +35,6 @@ object Main extends App with CallbackInterface {
private def init(): Unit = {
ConfigFolder.mkdirs()
KeyFolder.mkdirs()
Log.setLogInstance(logInstance)
sys.addShutdownHook(connectionHandler.stop())
val parser = new OptionParser[Config]("ensichat") {
@ -64,8 +60,7 @@ object Main extends App with CallbackInterface {
// Keep alive and print logs
while (true) {
Thread.sleep(LogInterval)
logInstance.dequeue().foreach(System.out.println)
Thread.sleep(TimeUnit.SECONDS.toMillis(1))
}
}
@ -78,9 +73,9 @@ object Main extends App with CallbackInterface {
val address = msg.header.origin
val name = connectionHandler.getUser(address).name
connectionHandler.sendTo(address, new Text("Hello " + name))
Log.i(Tag, "Received text: " + text.text)
logger.info("Received text: " + text.text)
case _ =>
Log.i(Tag, "Received msg: " + msg.body)
logger.info("Received msg: " + msg.body)
}
}

View File

@ -3,13 +3,14 @@ package com.nutomic.ensichat.server
import java.io._
import java.util.Properties
import com.nutomic.ensichat.core.interfaces.{Log, SettingsInterface}
import com.nutomic.ensichat.core.interfaces.SettingsInterface
import com.typesafe.scalalogging.Logger
import scala.collection.JavaConverters._
class Settings(file: File) extends SettingsInterface {
private val Tag = "Settings"
private val logger = Logger(this.getClass)
if (!file.exists()) {
file.createNewFile()
@ -23,7 +24,7 @@ class Settings(file: File) extends SettingsInterface {
p.load(fis)
fis.close()
} catch {
case e: IOException => Log.w(Tag, "Failed to load settings from " + file, e)
case e: IOException => logger.warn("Failed to load settings from " + file, e)
}
p
}
@ -35,7 +36,7 @@ class Settings(file: File) extends SettingsInterface {
props.store(fos, "")
fos.close()
} catch {
case e: IOException => Log.w(Tag, "Failed to write preference for key " + key, e)
case e: IOException => logger.warn("Failed to write preference for key " + key, e)
}
}