diff --git a/README.md b/README.md index c48a1b845..694904891 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,23 @@ val peripheral = scope.peripheral(advertisement) { _I/O data is only shown in logs when logging `level` is set to `Data`._ +When logging, the identity of the peripheral is prefixed on log messages to differentiate messages when multiple +peripherals are logging. The identifier (for the purposes of logging) can be set via the `identifier` property: + +```kotlin +val peripheral = scope.peripheral(advertisement) { + logging { + identifier = "Example" + } +} +``` + +The default (when not specified, or set to `null`) is to use the platform specific peripheral identifier: + +- Android: Hardware (MAC) address (e.g. "00:11:22:AA:BB:CC") +- Apple: The UUID associated with the peer +- JavaScript: A `DOMString` that uniquely identifies a device + #### Service Discovery All platforms support an `onServicesDiscovered` action (that is executed after service discovery but before observations diff --git a/core/api/core.api b/core/api/core.api index 351cbdb92..1462f7f6f 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -399,10 +399,12 @@ public final class com/juul/kable/logs/Logging { public final fun getData ()Lcom/juul/kable/logs/Logging$DataProcessor; public final fun getEngine ()Lcom/juul/kable/logs/LogEngine; public final fun getFormat ()Lcom/juul/kable/logs/Logging$Format; + public final fun getIdentifier ()Ljava/lang/String; public final fun getLevel ()Lcom/juul/kable/logs/Logging$Level; public final fun setData (Lcom/juul/kable/logs/Logging$DataProcessor;)V public final fun setEngine (Lcom/juul/kable/logs/LogEngine;)V public final fun setFormat (Lcom/juul/kable/logs/Logging$Format;)V + public final fun setIdentifier (Ljava/lang/String;)V public final fun setLevel (Lcom/juul/kable/logs/Logging$Level;)V } diff --git a/core/src/androidMain/kotlin/Observers.kt b/core/src/androidMain/kotlin/Observers.kt index fee13998e..fd663bbe2 100644 --- a/core/src/androidMain/kotlin/Observers.kt +++ b/core/src/androidMain/kotlin/Observers.kt @@ -53,7 +53,7 @@ internal class Observers( logging: Logging, ) { - private val logger = Logger(logging, tag = "Kable/Observers") + private val logger = Logger(logging, tag = "Kable/Observers", peripheral.bluetoothDevice.address) val characteristicChanges = MutableSharedFlow() private val observations = Observations() diff --git a/core/src/androidMain/kotlin/Peripheral.kt b/core/src/androidMain/kotlin/Peripheral.kt index bf6d07a3c..f39909617 100644 --- a/core/src/androidMain/kotlin/Peripheral.kt +++ b/core/src/androidMain/kotlin/Peripheral.kt @@ -125,14 +125,14 @@ public enum class Priority { Low, Balanced, High } public class AndroidPeripheral internal constructor( parentCoroutineContext: CoroutineContext, - private val bluetoothDevice: BluetoothDevice, + internal val bluetoothDevice: BluetoothDevice, private val transport: Transport, private val phy: Phy, private val onServicesDiscovered: ServicesDiscoveredAction, private val logging: Logging, ) : Peripheral { - private val logger = Logger(logging, tag = "Kable/Peripheral", prefix = "$bluetoothDevice ") + private val logger = Logger(logging, tag = "Kable/Peripheral", identifier = bluetoothDevice.address) private val _state = MutableStateFlow(State.Disconnected()) public override val state: Flow = _state.asStateFlow() diff --git a/core/src/androidMain/kotlin/Scanner.kt b/core/src/androidMain/kotlin/Scanner.kt index 930cb6c50..d08087390 100644 --- a/core/src/androidMain/kotlin/Scanner.kt +++ b/core/src/androidMain/kotlin/Scanner.kt @@ -26,7 +26,7 @@ public class AndroidScanner internal constructor( logging: Logging, ) : Scanner { - private val logger = Logger(logging, tag = "Kable/Scanner") + private val logger = Logger(logging, tag = "Kable/Scanner", identifier = null) private val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() ?: error("Bluetooth not supported") diff --git a/core/src/androidMain/kotlin/gatt/Callback.kt b/core/src/androidMain/kotlin/gatt/Callback.kt index ade503acb..3aa1bb213 100644 --- a/core/src/androidMain/kotlin/gatt/Callback.kt +++ b/core/src/androidMain/kotlin/gatt/Callback.kt @@ -58,7 +58,7 @@ internal class Callback( macAddress: String, ) : BluetoothGattCallback() { - private val logger = Logger(logging, tag = "Kable/Callback", prefix = "$macAddress ") + private val logger = Logger(logging, tag = "Kable/Callback", identifier = macAddress) private var disconnectedAction: DisconnectedAction? = null fun invokeOnDisconnected(action: DisconnectedAction) { diff --git a/core/src/appleMain/kotlin/Peripheral.kt b/core/src/appleMain/kotlin/Peripheral.kt index cb832f7ff..7ea1ce0cd 100644 --- a/core/src/appleMain/kotlin/Peripheral.kt +++ b/core/src/appleMain/kotlin/Peripheral.kt @@ -97,7 +97,7 @@ public class ApplePeripheral internal constructor( private val centralManager: CentralManager = CentralManager.Default - private val logger = Logger(logging, prefix = "${cbPeripheral.identifier} ") + private val logger = Logger(logging, identifier = cbPeripheral.identifier.UUIDString) private val _state = MutableStateFlow(State.Disconnected()) override val state: Flow = _state.asStateFlow() @@ -151,7 +151,9 @@ public class ApplePeripheral internal constructor( }.launchIn(scope) try { - val delegate = PeripheralDelegate(logging).freeze() // todo: Create in `connectPeripheral`. + // todo: Create in `connectPeripheral`. + val delegate = PeripheralDelegate(logging, cbPeripheral.identifier.UUIDString).freeze() + val connection = centralManager.connectPeripheral(cbPeripheral, delegate).also { _connection.value = it } diff --git a/core/src/appleMain/kotlin/PeripheralDelegate.kt b/core/src/appleMain/kotlin/PeripheralDelegate.kt index 23c0d247b..0c5e5e682 100644 --- a/core/src/appleMain/kotlin/PeripheralDelegate.kt +++ b/core/src/appleMain/kotlin/PeripheralDelegate.kt @@ -30,7 +30,10 @@ import platform.darwin.NSObject import kotlin.native.concurrent.freeze // https://developer.apple.com/documentation/corebluetooth/cbperipheraldelegate -internal class PeripheralDelegate(logging: Logging) : NSObject(), CBPeripheralDelegateProtocol { +internal class PeripheralDelegate( + logging: Logging, + identifier: String +) : NSObject(), CBPeripheralDelegateProtocol { sealed class Response { @@ -100,7 +103,7 @@ internal class PeripheralDelegate(logging: Logging) : NSObject(), CBPeripheralDe private val _characteristicChanges = MutableSharedFlow(extraBufferCapacity = 64) val characteristicChanges = _characteristicChanges.asSharedFlow() - private val logger = Logger(logging) + private val logger = Logger(logging, tag = "Kable/Delegate", identifier = identifier) /* Discovering Services */ diff --git a/core/src/commonMain/kotlin/logs/LogMessage.kt b/core/src/commonMain/kotlin/logs/LogMessage.kt index a22761b00..1a2f92d46 100644 --- a/core/src/commonMain/kotlin/logs/LogMessage.kt +++ b/core/src/commonMain/kotlin/logs/LogMessage.kt @@ -30,8 +30,12 @@ internal class LogMessage { detail(key, value.toString()) } - fun build(logging: Logging, prefix: String?): String = buildString { - if (prefix != null) append(prefix) + fun build(logging: Logging, platformIdentifier: String?): String = buildString { + val prefix = logging.identifier ?: platformIdentifier + if (!prefix.isNullOrEmpty()) { + append(prefix) + append(' ') + } append(message) when (logging.format) { diff --git a/core/src/commonMain/kotlin/logs/Logger.kt b/core/src/commonMain/kotlin/logs/Logger.kt index bf095d1f1..fe89a6512 100644 --- a/core/src/commonMain/kotlin/logs/Logger.kt +++ b/core/src/commonMain/kotlin/logs/Logger.kt @@ -6,14 +6,14 @@ import com.juul.kable.logs.Logging.Level.Events internal class Logger( private val logging: Logging, private val tag: String = "Kable", - private val prefix: String? = null, + private val identifier: String?, ) { inline fun verbose(throwable: Throwable? = null, crossinline init: LogMessage.() -> Unit) { if (logging.level == Events || logging.level == Data) { val message = LogMessage() message.init() - logging.engine.verbose(throwable, tag, message.build(logging, prefix)) + logging.engine.verbose(throwable, tag, message.build(logging, identifier)) } } @@ -21,7 +21,7 @@ internal class Logger( if (logging.level == Events || logging.level == Data) { val message = LogMessage() message.init() - logging.engine.debug(throwable, tag, message.build(logging, prefix)) + logging.engine.debug(throwable, tag, message.build(logging, identifier)) } } @@ -29,25 +29,25 @@ internal class Logger( if (logging.level == Events || logging.level == Data) { val message = LogMessage() message.init() - logging.engine.info(throwable, tag, message.build(logging, prefix)) + logging.engine.info(throwable, tag, message.build(logging, identifier)) } } inline fun warn(throwable: Throwable? = null, crossinline init: LogMessage.() -> Unit) { val message = LogMessage() message.init() - logging.engine.warn(throwable, tag, message.build(logging, prefix)) + logging.engine.warn(throwable, tag, message.build(logging, identifier)) } inline fun error(throwable: Throwable? = null, crossinline init: LogMessage.() -> Unit) { val message = LogMessage() message.init() - logging.engine.error(throwable, tag, message.build(logging, prefix)) + logging.engine.error(throwable, tag, message.build(logging, identifier)) } inline fun assert(throwable: Throwable? = null, crossinline init: LogMessage.() -> Unit) { val message = LogMessage() message.init() - logging.engine.assert(throwable, tag, message.build(logging, prefix)) + logging.engine.assert(throwable, tag, message.build(logging, identifier)) } } diff --git a/core/src/commonMain/kotlin/logs/Logging.kt b/core/src/commonMain/kotlin/logs/Logging.kt index b19fca53a..8462ef59f 100644 --- a/core/src/commonMain/kotlin/logs/Logging.kt +++ b/core/src/commonMain/kotlin/logs/Logging.kt @@ -44,6 +44,15 @@ public class Logging { public fun process(data: ByteArray): String } + /** + * Identifier to use in log messages. When `null`, defaults to the platform's peripheral identifier: + * + * - Android: Hardware (MAC) address (e.g. "00:11:22:AA:BB:CC") + * - Apple: The UUID associated with the peer + * - JavaScript: A `DOMString` that uniquely identifies a device + */ + public var identifier: String? = null + public var engine: LogEngine = SystemLogEngine public var level: Level = Level.Warnings public var format: Format = Format.Multiline diff --git a/core/src/jsMain/kotlin/Peripheral.kt b/core/src/jsMain/kotlin/Peripheral.kt index 80e7a8e29..f8ff6b83d 100644 --- a/core/src/jsMain/kotlin/Peripheral.kt +++ b/core/src/jsMain/kotlin/Peripheral.kt @@ -68,7 +68,7 @@ public class JsPeripheral internal constructor( private val scope = CoroutineScope(parentCoroutineContext + job) - private val logger = Logger(logging, prefix = "${bluetoothDevice.id} ") + private val logger = Logger(logging, identifier = bluetoothDevice.id) private val ioLock = Mutex()