diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index 8e7e2984db8564b066fbe84cec6094b37838f6e3..30d0173a710e8da3e70ba9d62809ce8f3da4d60e 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -56,6 +56,10 @@ let package = Package(
       url: "https://github.com/pointfreeco/swift-custom-dump.git",
       .upToNextMajor(from: "0.5.2")
     ),
+    .package(
+      url: "https://github.com/apple/swift-log.git",
+      .upToNextMajor(from: "1.4.4")
+    ),
     .package(
       url: "https://github.com/kean/Pulse.git",
       .upToNextMajor(from: "2.1.2")
@@ -65,7 +69,7 @@ let package = Package(
     .target(
       name: "AppCore",
       dependencies: [
-        .product(name: "Pulse", package: "Pulse"),
+        .product(name: "Logging", package: "swift-log"),
         .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
         .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
         .product(name: "XXDatabase", package: "client-ios-db"),
@@ -104,6 +108,8 @@ let package = Package(
         .target(name: "WelcomeFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
         .product(name: "ComposablePresentation", package: "swift-composable-presentation"),
+        .product(name: "Logging", package: "swift-log"),
+        .product(name: "PulseLogHandler", package: "Pulse"),
         .product(name: "PulseUI", package: "Pulse"),
         .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
         .product(name: "XXModels", package: "client-ios-db"),
diff --git a/Examples/xx-messenger/Sources/AppCore/Logger/Logger.swift b/Examples/xx-messenger/Sources/AppCore/Logger/Logger.swift
index 6766d98d4f6ef506112e5cee8d6efa060ec9b214..128d31124ce1e51ab89c023c3cac258c2d6a9182 100644
--- a/Examples/xx-messenger/Sources/AppCore/Logger/Logger.swift
+++ b/Examples/xx-messenger/Sources/AppCore/Logger/Logger.swift
@@ -1,5 +1,5 @@
 import Foundation
-import Pulse
+import Logging
 import XCTestDynamicOverlay
 
 public struct Logger {
@@ -21,14 +21,12 @@ public struct Logger {
 
 extension Logger {
   public static func live() -> Logger {
-    Logger { msg, file, function, line in
+    let logger = Logging.Logger(label: "xx.network.XXMessengerExample")
+    return Logger { msg, file, function, line in
       switch msg {
       case .error(let error):
-        LoggerStore.shared.storeMessage(
-          label: "xx-messenger",
-          level: .error,
-          message: error.localizedDescription,
-          metadata: [:],
+        logger.error(
+          .init(stringLiteral: error.localizedDescription),
           file: file,
           function: function,
           line: line
diff --git a/Examples/xx-messenger/Sources/AppFeature/App.swift b/Examples/xx-messenger/Sources/AppFeature/App.swift
index d6b0b722c4d610d823b5313aae797e3f72dc10a3..7c3f0e82b5b00ee3aa4ff601b50556dbc8a148d2 100644
--- a/Examples/xx-messenger/Sources/AppFeature/App.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/App.swift
@@ -1,8 +1,14 @@
 import ComposableArchitecture
+import Logging
+import PulseLogHandler
 import SwiftUI
 
 @main
 struct App: SwiftUI.App {
+  init() {
+    LoggingSystem.bootstrap(PersistentLogHandler.init)
+  }
+
   var body: some Scene {
     WindowGroup {
       AppView(store: Store(
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index fef073d1e4844f71b974de12c5c37ee7c988ba00..c4ff9b27f027a0eef0d02eaeab6e8e7db519025c 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -34,6 +34,11 @@ extension AppEnvironment {
     let mainQueue = DispatchQueue.main.eraseToAnyScheduler()
     let bgQueue = DispatchQueue.global(qos: .background).eraseToAnyScheduler()
 
+    defer {
+      _ = try! messenger.setLogLevel(.debug)
+      messenger.startLogging()
+    }
+
     let contactEnvironment = ContactEnvironment(
       messenger: messenger,
       db: dbManager.getDB,
diff --git a/Package.resolved b/Package.resolved
index 225eb0fdb5e6eb944de29fa3bc030fed309735c0..0e663ab05e42e13ad7bd42275e604f72a8041347 100644
--- a/Package.resolved
+++ b/Package.resolved
@@ -18,6 +18,15 @@
         "version" : "0.5.2"
       }
     },
+    {
+      "identity" : "swift-log",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/apple/swift-log.git",
+      "state" : {
+        "revision" : "6fe203dc33195667ce1759bf0182975e4653ba1c",
+        "version" : "1.4.4"
+      }
+    },
     {
       "identity" : "xctest-dynamic-overlay",
       "kind" : "remoteSourceControl",
diff --git a/Package.swift b/Package.swift
index 9136971583d840fd35a2993ec5be731e8ac774c4..cd25ca17a71b46a3f2dfe1d01374d552fb429475 100644
--- a/Package.swift
+++ b/Package.swift
@@ -31,6 +31,10 @@ let package = Package(
       url: "https://github.com/kishikawakatsumi/KeychainAccess.git",
       .upToNextMajor(from: "4.2.2")
     ),
+    .package(
+      url: "https://github.com/apple/swift-log.git",
+      .upToNextMajor(from: "1.4.4")
+    ),
   ],
   targets: [
     .target(
@@ -55,6 +59,7 @@ let package = Package(
       dependencies: [
         .target(name: "XXClient"),
         .product(name: "KeychainAccess", package: "KeychainAccess"),
+        .product(name: "Logging", package: "swift-log"),
         .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
       ],
       swiftSettings: swiftSettings
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerSetLogLevel.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerSetLogLevel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..21e39bd78035438955587c816602956313389fa4
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerSetLogLevel.swift
@@ -0,0 +1,22 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerSetLogLevel {
+  public var run: (LogLevel) throws -> Bool
+
+  public func callAsFunction(_ logLevel: LogLevel) throws -> Bool {
+    try run(logLevel)
+  }
+}
+
+extension MessengerSetLogLevel {
+  public static func live(_ env: MessengerEnvironment) -> MessengerSetLogLevel {
+    MessengerSetLogLevel(run: env.setLogLevel.run)
+  }
+}
+
+extension MessengerSetLogLevel {
+  public static let unimplemented = MessengerSetLogLevel(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerStartLogging.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerStartLogging.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d073ee41cd1c954b41395d2c0de4760d94ed4b5a
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerStartLogging.swift
@@ -0,0 +1,29 @@
+import Foundation
+import Logging
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerStartLogging {
+  public var run: () -> Void
+
+  public func callAsFunction() -> Void {
+    run()
+  }
+}
+
+extension MessengerStartLogging {
+  public static func live(_ env: MessengerEnvironment) -> MessengerStartLogging {
+    return MessengerStartLogging {
+      env.registerLogWriter(.init { messageString in
+        let message = LogMessage.parse(messageString)
+        env.log(message)
+      })
+    }
+  }
+}
+
+extension MessengerStartLogging {
+  public static let unimplemented = MessengerStartLogging(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift
index 692d81fd9bdb7ee8cb540b9a4f1a844e7541966d..22f110303674337006b77b2f1de436a09d0392b1 100644
--- a/Sources/XXMessengerClient/Messenger/Messenger.swift
+++ b/Sources/XXMessengerClient/Messenger/Messenger.swift
@@ -38,6 +38,8 @@ public struct Messenger {
   public var resumeBackup: MessengerResumeBackup
   public var backupParams: MessengerBackupParams
   public var stopBackup: MessengerStopBackup
+  public var setLogLevel: MessengerSetLogLevel
+  public var startLogging: MessengerStartLogging
 }
 
 extension Messenger {
@@ -79,7 +81,9 @@ extension Messenger {
       startBackup: .live(env),
       resumeBackup: .live(env),
       backupParams: .live(env),
-      stopBackup: .live(env)
+      stopBackup: .live(env),
+      setLogLevel: .live(env),
+      startLogging: .live(env)
     )
   }
 }
@@ -122,6 +126,8 @@ extension Messenger {
     startBackup: .unimplemented,
     resumeBackup: .unimplemented,
     backupParams: .unimplemented,
-    stopBackup: .unimplemented
+    stopBackup: .unimplemented,
+    setLogLevel: .unimplemented,
+    startLogging: .unimplemented
   )
 }
diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
index f019df1618b0985f70936fdeb97e4558060a46eb..a98e76f042b5e53a6e8556d3bee32d7ebb946162 100644
--- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
+++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
@@ -1,4 +1,5 @@
 import Foundation
+import Logging
 import XXClient
 import XCTestDynamicOverlay
 
@@ -18,6 +19,7 @@ public struct MessengerEnvironment {
   public var isListeningForMessages: Stored<Bool>
   public var isRegisteredWithUD: IsRegisteredWithUD
   public var loadCMix: LoadCMix
+  public var log: (LogMessage) -> Void
   public var login: Login
   public var lookupUD: LookupUD
   public var messageListeners: ListenersRegistry
@@ -29,8 +31,10 @@ public struct MessengerEnvironment {
   public var newUdManagerFromBackup: NewUdManagerFromBackup
   public var passwordStorage: PasswordStorage
   public var registerForNotifications: RegisterForNotifications
+  public var registerLogWriter: RegisterLogWriter
   public var resumeBackup: ResumeBackup
   public var searchUD: SearchUD
+  public var setLogLevel: SetLogLevel
   public var sleep: (TimeInterval) -> Void
   public var storageDir: String
   public var ud: Stored<UserDiscovery?>
@@ -47,7 +51,9 @@ extension MessengerEnvironment {
     .path
 
   public static func live() -> MessengerEnvironment {
-    MessengerEnvironment(
+    let logger = Logger(label: "xx.network.client")
+
+    return MessengerEnvironment(
       authCallbacks: .live(),
       backup: .inMemory(),
       backupCallbacks: .live(),
@@ -63,6 +69,7 @@ extension MessengerEnvironment {
       isListeningForMessages: .inMemory(false),
       isRegisteredWithUD: .live,
       loadCMix: .live,
+      log: { logger.log(level: $0.level, .init(stringLiteral: $0.text)) },
       login: .live,
       lookupUD: .live,
       messageListeners: .live(),
@@ -74,8 +81,10 @@ extension MessengerEnvironment {
       newUdManagerFromBackup: .live,
       passwordStorage: .keychain,
       registerForNotifications: .live,
+      registerLogWriter: .live,
       resumeBackup: .live,
       searchUD: .live,
+      setLogLevel: .live,
       sleep: { Thread.sleep(forTimeInterval: $0) },
       storageDir: MessengerEnvironment.defaultStorageDir,
       ud: .inMemory(),
@@ -103,6 +112,7 @@ extension MessengerEnvironment {
     isListeningForMessages: .unimplemented(placeholder: false),
     isRegisteredWithUD: .unimplemented,
     loadCMix: .unimplemented,
+    log: XCTUnimplemented("\(Self.self).log"),
     login: .unimplemented,
     lookupUD: .unimplemented,
     messageListeners: .unimplemented,
@@ -114,8 +124,10 @@ extension MessengerEnvironment {
     newUdManagerFromBackup: .unimplemented,
     passwordStorage: .unimplemented,
     registerForNotifications: .unimplemented,
+    registerLogWriter: .unimplemented,
     resumeBackup: .unimplemented,
     searchUD: .unimplemented,
+    setLogLevel: .unimplemented,
     sleep: XCTUnimplemented("\(Self.self).sleep"),
     storageDir: "unimplemented",
     ud: .unimplemented(),
diff --git a/Sources/XXMessengerClient/Utils/LogMessage.swift b/Sources/XXMessengerClient/Utils/LogMessage.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7169d953a60109e654c6d7063cbbe3b1857394cd
--- /dev/null
+++ b/Sources/XXMessengerClient/Utils/LogMessage.swift
@@ -0,0 +1,56 @@
+import Foundation
+import Logging
+
+public struct LogMessage: Equatable {
+  public init(level: Logger.Level, text: String) {
+    self.level = level
+    self.text = text
+  }
+
+  public var level: Logger.Level
+  public var text: String
+}
+
+extension LogMessage {
+  public static func parse(_ string: String) -> LogMessage {
+    let level: Logger.Level
+    let text: String
+    let pattern = #"^([A-Z]+)( \d{4}/\d{2}/\d{2})?( \d{1,2}:\d{2}:\d{2}\.\d+)? (.*)"#
+    let regex = try! NSRegularExpression(
+      pattern: pattern,
+      options: .dotMatchesLineSeparators
+    )
+    let stringRange = NSRange(location: 0, length: string.utf16.count)
+    if let match = regex.firstMatch(in: string, range: stringRange) {
+      var groups: [Int: String] = [:]
+      for rangeIndex in 1..<match.numberOfRanges {
+        let nsRange = match.range(at: rangeIndex)
+        if !NSEqualRanges(nsRange, NSMakeRange(NSNotFound, 0)) {
+          let group = (string as NSString).substring(with: nsRange)
+          groups[rangeIndex] = group
+        }
+      }
+      level = .fromString(groups[1])
+      text = groups[4] ?? string
+    } else {
+      level = .notice
+      text = string
+    }
+    return LogMessage(level: level, text: text)
+  }
+}
+
+private extension Logger.Level {
+  static func fromString(_ string: String?) -> Logger.Level {
+    switch string {
+    case "TRACE": return .trace
+    case "DEBUG": return .debug
+    case "INFO": return .info
+    case "WARN": return .warning
+    case "ERROR": return .error
+    case "CRITICAL": return .critical
+    case "FATAL": return .critical
+    default: return .notice
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSetLogLevelTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSetLogLevelTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d6d44d05465f52cc6c9cb7b0a949397d22560b42
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSetLogLevelTests.swift
@@ -0,0 +1,44 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerSetLogLevelTests: XCTestCase {
+  func testSetLogLevel() throws {
+    var didSetLogLevel: [LogLevel] = []
+    var env: MessengerEnvironment = .unimplemented
+    env.setLogLevel.run = { level in
+      didSetLogLevel.append(level)
+      return true
+    }
+    let setLogLevel: MessengerSetLogLevel = .live(env)
+
+    let result = try setLogLevel(.debug)
+
+    XCTAssertNoDifference(didSetLogLevel, [.debug])
+    XCTAssertNoDifference(result, true)
+  }
+
+  func testSetLogLevelReturnsFalse() throws {
+    var env: MessengerEnvironment = .unimplemented
+    env.setLogLevel.run = { _ in return false }
+    let setLogLevel: MessengerSetLogLevel = .live(env)
+
+    let result = try setLogLevel(.debug)
+
+    XCTAssertNoDifference(result, false)
+  }
+
+  func testSetLogLevelFailure() {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.setLogLevel.run = { _ in throw failure }
+    let setLogLevel: MessengerSetLogLevel = .live(env)
+
+    XCTAssertThrowsError(try setLogLevel(.debug)) { error in
+      XCTAssertNoDifference(error as NSError, failure as NSError)
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartLoggingTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartLoggingTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..b0d0a7c35453348c2f56b4e5e13c973c6fbc639a
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartLoggingTests.swift
@@ -0,0 +1,30 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerStartLoggingTests: XCTestCase {
+  func testStartLogging() {
+    var registeredLogWriters: [LogWriter] = []
+    var logMessages: [LogMessage] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.registerLogWriter.run = { writer in
+      registeredLogWriters.append(writer)
+    }
+    env.log = { message in
+      logMessages.append(message)
+    }
+    let start: MessengerStartLogging = .live(env)
+
+    start()
+
+    XCTAssertNoDifference(registeredLogWriters.count, 1)
+
+    registeredLogWriters.first?.handle("DEBUG Hello, World!")
+
+    XCTAssertNoDifference(logMessages, [
+      .init(level: .debug, text: "Hello, World!"),
+    ])
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Utils/LogMessageTests.swift b/Tests/XXMessengerClientTests/Utils/LogMessageTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..bbea8995439aec672f0ebddf778484cf7889f0ee
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Utils/LogMessageTests.swift
@@ -0,0 +1,71 @@
+import CustomDump
+import XCTest
+@testable import XXMessengerClient
+
+final class LogMessageTests: XCTestCase {
+  func testParsing() {
+    XCTAssertNoDifference(
+      LogMessage.parse("TRACE Tracing..."),
+      LogMessage(level: .trace, text: "Tracing...")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("DEBUG Debugging..."),
+      LogMessage(level: .debug, text: "Debugging...")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("INFO Informing..."),
+      LogMessage(level: .info, text: "Informing...")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("WARN Warning!"),
+      LogMessage(level: .warning, text: "Warning!")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("ERROR Failure!"),
+      LogMessage(level: .error, text: "Failure!")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("CRITICAL Critical failure!"),
+      LogMessage(level: .critical, text: "Critical failure!")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("FATAL Fatal failure!"),
+      LogMessage(level: .critical, text: "Fatal failure!")
+    )
+  }
+
+  func testParsingFallbacks() {
+    XCTAssertNoDifference(
+      LogMessage.parse("1234 Wrongly formatted"),
+      LogMessage(level: .notice, text: "1234 Wrongly formatted")
+    )
+  }
+
+  func testParsingStripsDateTime() {
+    XCTAssertNoDifference(
+      LogMessage.parse("INFO 2022/10/04 Informing..."),
+      LogMessage(level: .info, text: "Informing...")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("INFO 23:36:55.755390 Informing..."),
+      LogMessage(level: .info, text: "Informing...")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("INFO 2022/10/04 23:36:55.755390 Informing..."),
+      LogMessage(level: .info, text: "Informing...")
+    )
+  }
+
+  func testParsingMultilineMessage() {
+    XCTAssertNoDifference(
+      LogMessage.parse("""
+      ERROR 2022/10/04 23:51:15.021658 First line
+      Second line
+      """),
+      LogMessage(level: .error, text: """
+      First line
+      Second line
+      """)
+    )
+  }
+}