refactor(logging): Reduce log noise by lowering severity of common errors (#4591)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-02-18 15:16:11 -06:00 committed by GitHub
parent 7ffbbd6113
commit f012e3818d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 91 additions and 56 deletions

View file

@ -24,6 +24,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import co.touchlab.kermit.Severity
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import com.geeksville.mesh.repository.network.NetworkRepository
import com.geeksville.mesh.repository.network.NetworkRepository.Companion.toAddressString
@ -51,8 +52,6 @@ import org.meshtastic.core.strings.meshtastic
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import javax.inject.Inject
// ... (DeviceListEntry sealed class remains the same) ...
@HiltViewModel
@Suppress("LongParameterList", "TooManyFunctions")
class BTScanModel
@ -209,11 +208,20 @@ constructor(
changeDeviceAddress(entry.fullAddress)
} catch (ex: SecurityException) {
Logger.w(ex) { "Bonding failed for ${entry.peripheral.address.anonymize} Permissions not granted" }
serviceRepository.setErrorMessage("Bonding failed: ${ex.message} Permissions not granted")
serviceRepository.setErrorMessage(
text = "Bonding failed: ${ex.message} Permissions not granted",
severity = Severity.Warn,
)
} catch (ex: Exception) {
// Bonding is often flaky and can fail for many reasons (timeout, user cancel, etc)
Logger.w(ex) { "Bonding failed for ${entry.peripheral.address.anonymize}" }
serviceRepository.setErrorMessage("Bonding failed: ${ex.message}")
val message = ex.message ?: ""
if (message.contains("Received bond state changed 11")) {
// This is a known issue where bonding is still in progress, ignore as error
Logger.d { "Bonding still in progress for ${entry.peripheral.address.anonymize}" }
} else {
Logger.w(ex) { "Bonding failed for ${entry.peripheral.address.anonymize}" }
serviceRepository.setErrorMessage(text = "Bonding failed: ${ex.message}", severity = Severity.Warn)
}
}
}
}

View file

@ -70,10 +70,12 @@ constructor(
fun disconnect() {
Logger.i { "MQTT Disconnected" }
mqttClient?.apply {
ignoreException { disconnect() }
close(true)
mqttClient = null
if (isConnected) {
ignoreException { disconnect() }
}
ignoreException { close(true) }
}
mqttClient = null
}
val proxyMessageFlow: Flow<MqttClientProxyMessage> = callbackFlow {
@ -166,7 +168,11 @@ constructor(
val token = mqttClient?.publish(topic, data, DEFAULT_QOS, retained)
Logger.i { "MQTT Publish messageId: ${token?.messageId}" }
} catch (ex: Exception) {
Logger.e { "MQTT Publish error: ${ex.message}" }
if (ex.message?.contains("Client is disconnected") == true) {
Logger.w { "MQTT Publish skipped: Client is disconnected" }
} else {
Logger.e(ex) { "MQTT Publish error: ${ex.message}" }
}
}
}
}

View file

@ -96,7 +96,8 @@ constructor(
0
}
thrown?.let { e ->
Logger.e(e) { "[$address] Serial error after ${uptime}ms: ${e.message}" }
// USB errors are common when unplugging; log as warning to avoid Crashlytics noise
Logger.w(e) { "[$address] Serial error after ${uptime}ms: ${e.message}" }
}
Logger.w {
"[$address] Serial device disconnected - " +

View file

@ -84,7 +84,8 @@ constructor(
try {
stream.write(p)
} catch (ex: IOException) {
Logger.e(ex) { "[$address] TCP write error: ${ex.message}" }
// TCP write errors are common when the connection is lost; log as warning to avoid Crashlytics noise
Logger.w(ex) { "[$address] TCP write error: ${ex.message}" }
onDeviceDisconnect(false)
}
}
@ -95,7 +96,8 @@ constructor(
try {
stream.flush()
} catch (ex: IOException) {
Logger.e(ex) { "[$address] TCP flush error: ${ex.message}" }
// TCP flush errors are common when the connection is lost; log as warning to avoid Crashlytics noise
Logger.w(ex) { "[$address] TCP flush error: ${ex.message}" }
onDeviceDisconnect(false)
}
}

View file

@ -42,29 +42,30 @@ internal class SerialConnectionImpl(
@Suppress("TooGenericExceptionCaught")
override fun sendBytes(bytes: ByteArray) {
ioRef.get()?.let {
Logger.d { "writing ${bytes.size} byte(s }" }
Logger.d { "writing ${bytes.size} byte(s)" }
try {
it.writeAsync(bytes)
} catch (e: BufferOverflowException) {
Logger.e(e) { "Buffer overflow while writing to serial port" }
Logger.w(e) { "Buffer overflow while writing to serial port" }
} catch (e: Exception) {
Logger.e(e) { "Failed to write to serial port" }
// USB disconnections often cause IOExceptions here; log as warning to avoid Crashlytics noise
Logger.w(e) { "Failed to write to serial port (likely disconnected)" }
}
}
}
override fun close(waitForStopped: Boolean) {
ignoreException {
if (closed.compareAndSet(false, true)) {
ioRef.get()?.stop()
if (closed.compareAndSet(false, true)) {
ignoreException(silent = true) { ioRef.get()?.stop() }
ignoreException(silent = true) {
port.close() // This will cause the reader thread to exit
}
}
// Allow a short amount of time for the manager to quit (so the port can be cleanly closed)
if (waitForStopped) {
Logger.d { "Waiting for USB manager to stop..." }
closedLatch.await(1.seconds)
}
// Allow a short amount of time for the manager to quit (so the port can be cleanly closed)
if (waitForStopped) {
Logger.d { "Waiting for USB manager to stop..." }
ignoreException(silent = true) { closedLatch.await(1.seconds) }
}
}
@ -99,11 +100,9 @@ internal class SerialConnectionImpl(
override fun onRunError(e: Exception?) {
closed.set(true)
ignoreException {
port.dtr = false
port.rts = false
port.close()
}
// Connection is already failing, don't try to set DTR/RTS as it will just throw more
// IOExceptions
ignoreException(silent = true) { port.close() }
closedLatch.countDown()
listener.onDisconnected(e)
}

View file

@ -18,6 +18,7 @@ package com.geeksville.mesh.service
import android.util.Log
import co.touchlab.kermit.Logger
import co.touchlab.kermit.Severity
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.repository.radio.InterfaceId
@ -459,7 +460,7 @@ constructor(
val payload = packet.decoded?.payload ?: return
val r = Routing.ADAPTER.decodeOrNull(payload, Logger) ?: return
if (r.error_reason == Routing.Error.DUTY_CYCLE_LIMIT) {
serviceRepository.setErrorMessage(getString(Res.string.error_duty_cycle))
serviceRepository.setErrorMessage(getString(Res.string.error_duty_cycle), Severity.Warn)
}
handleAckNak(
packet.decoded?.request_id ?: 0,

View file

@ -17,6 +17,7 @@
package com.geeksville.mesh.service
import co.touchlab.kermit.Logger
import co.touchlab.kermit.Severity
import com.geeksville.mesh.repository.network.MQTTRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -49,7 +50,12 @@ constructor(
mqttMessageFlow =
mqttRepository.proxyMessageFlow
.onEach { message -> packetHandler.sendToRadio(ToRadio(mqttClientProxyMessage = message)) }
.catch { throwable -> serviceRepository.setErrorMessage("MqttClientProxy failed: $throwable") }
.catch { throwable ->
serviceRepository.setErrorMessage(
text = "MqttClientProxy failed: $throwable",
severity = Severity.Warn,
)
}
.launchIn(scope)
}
}

View file

@ -196,6 +196,10 @@ constructor(
throw RadioNotConnectedException()
}
sendToRadio(ToRadio(packet = packet))
} catch (ex: RadioNotConnectedException) {
// Expected when radio is not connected, log as warning to avoid Crashlytics noise
Logger.w(ex) { "sendToRadio skipped: Not connected to radio" }
deferred.complete(false)
} catch (ex: Exception) {
Logger.e(ex) { "sendToRadio error: ${ex.message}" }
deferred.complete(false)

View file

@ -63,7 +63,7 @@ import kotlin.time.Duration.Companion.seconds
private const val RSSI_DELAY = 10
private const val RSSI_TIMEOUT = 5
@Suppress("LongMethod", "LoopWithTooManyJumpStatements")
@Suppress("LongMethod", "LoopWithTooManyJumpStatements", "TooGenericExceptionCaught")
@Composable
fun CurrentlyConnectedInfo(
node: Node,
@ -80,13 +80,17 @@ fun CurrentlyConnectedInfo(
rssi = withTimeout(RSSI_TIMEOUT.seconds) { bleDevice.peripheral.readRssi() }
delay(RSSI_DELAY.seconds)
} catch (e: PeripheralNotConnectedException) {
Logger.e(e) { "Failed to read RSSI ${e.message}" }
Logger.w(e) { "Failed to read RSSI ${e.message}" }
break
} catch (e: OperationFailedException) {
Logger.e(e) { "Failed to read RSSI ${e.message}" }
// RSSI reading failures are common when disconnecting; log as warning to avoid Crashlytics noise
Logger.w(e) { "Failed to read RSSI ${e.message}" }
break
} catch (e: SecurityException) {
Logger.e(e) { "Failed to read RSSI ${e.message}" }
Logger.w(e) { "Failed to read RSSI ${e.message}" }
break
} catch (e: Exception) {
Logger.w(e) { "Unexpected error reading RSSI: ${e.message}" }
break
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.util
import android.os.RemoteException
@ -57,7 +56,7 @@ fun ignoreException(silent: Boolean = false, inner: () -> Unit) {
inner()
} catch (ex: Throwable) {
// DO NOT THROW users expect we have fully handled/discarded the exception
if (!silent) Logger.e(ex) { "ignoring exception" }
if (!silent) Logger.w(ex) { "ignoring exception" }
}
}