Skip to content

Commit 08ae018

Browse files
authored
android: send Android logs to logz (#515)
TSLog sends log messages to Android's logcat and Tailscale's logger Libtailscale wrapper is a Kotlin wrapper that allows us to get around the problems with mocking a native library Fixes tailscale/corp#23191 Signed-off-by: kari-ts <[email protected]>
1 parent f26a828 commit 08ae018

File tree

21 files changed

+246
-120
lines changed

21 files changed

+246
-120
lines changed

android/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ dependencies {
160160

161161
// Unit Tests
162162
testImplementation 'junit:junit:4.13.2'
163+
testImplementation 'org.mockito:mockito-core:5.4.0'
164+
testImplementation 'org.mockito:mockito-inline:5.2.0'
165+
testImplementation 'org.mockito.kotlin:mockito-kotlin:5.4.0'
163166

164167
debugImplementation("androidx.compose.ui:ui-tooling")
165168
implementation("androidx.compose.ui:ui-tooling-preview")

android/src/main/java/com/tailscale/ipn/App.kt

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import com.tailscale.ipn.ui.notifier.HealthNotifier
3131
import com.tailscale.ipn.ui.notifier.Notifier
3232
import com.tailscale.ipn.ui.viewModel.VpnViewModel
3333
import com.tailscale.ipn.ui.viewModel.VpnViewModelFactory
34+
import com.tailscale.ipn.util.TSLog
3435
import kotlinx.coroutines.CoroutineScope
3536
import kotlinx.coroutines.Dispatchers
3637
import kotlinx.coroutines.SupervisorJob
@@ -162,7 +163,7 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
162163
result.fold(
163164
onSuccess = { onSuccess?.invoke() },
164165
onFailure = { error ->
165-
Log.d("TAG", "Set want running: failed to update preferences: ${error.message}")
166+
TSLog.d("TAG", "Set want running: failed to update preferences: ${error.message}")
166167
})
167168
}
168169
Client(applicationScope)
@@ -203,7 +204,7 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
203204
private fun updateConnStatus(ableToStartVPN: Boolean) {
204205
setAbleToStartVPN(ableToStartVPN)
205206
QuickToggleService.updateTile()
206-
Log.d("App", "Set Tile Ready: $ableToStartVPN")
207+
TSLog.d("App", "Set Tile Ready: $ableToStartVPN")
207208
}
208209

209210
override fun getModelName(): String {
@@ -266,14 +267,14 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
266267
downloads.mkdirs()
267268
}
268269
} catch (e: Exception) {
269-
Log.e(TAG, "Failed to create downloads folder: $e")
270+
TSLog.e(TAG, "Failed to create downloads folder: $e")
270271
downloads = File(this.filesDir, "Taildrop")
271272
try {
272273
if (!downloads.exists()) {
273274
downloads.mkdirs()
274275
}
275276
} catch (e: Exception) {
276-
Log.e(TAG, "Failed to create Taildrop folder: $e")
277+
TSLog.e(TAG, "Failed to create Taildrop folder: $e")
277278
downloads = File("")
278279
}
279280
}
@@ -308,7 +309,7 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
308309
val list = setting.value as? List<*>
309310
return Json.encodeToString(list)
310311
} catch (e: Exception) {
311-
Log.d("MDM", "$key value cannot be serialized to JSON. Throwing NoSuchKeyException.")
312+
TSLog.d("MDM", "$key value cannot be serialized to JSON. Throwing NoSuchKeyException.")
312313
throw MDMSettings.NoSuchKeyException()
313314
}
314315
}
@@ -373,13 +374,13 @@ open class UninitializedApp : Application() {
373374
try {
374375
startForegroundService(intent)
375376
} catch (foregroundServiceStartException: IllegalStateException) {
376-
Log.e(
377+
TSLog.e(
377378
TAG,
378379
"startVPN hit ForegroundServiceStartNotAllowedException in startForegroundService(): $foregroundServiceStartException")
379380
} catch (securityException: SecurityException) {
380-
Log.e(TAG, "startVPN hit SecurityException in startForegroundService(): $securityException")
381+
TSLog.e(TAG, "startVPN hit SecurityException in startForegroundService(): $securityException")
381382
} catch (e: Exception) {
382-
Log.e(TAG, "startVPN hit exception in startForegroundService(): $e")
383+
TSLog.e(TAG, "startVPN hit exception in startForegroundService(): $e")
383384
}
384385
}
385386

@@ -388,9 +389,9 @@ open class UninitializedApp : Application() {
388389
try {
389390
startService(intent)
390391
} catch (illegalStateException: IllegalStateException) {
391-
Log.e(TAG, "stopVPN hit IllegalStateException in startService(): $illegalStateException")
392+
TSLog.e(TAG, "stopVPN hit IllegalStateException in startService(): $illegalStateException")
392393
} catch (e: Exception) {
393-
Log.e(TAG, "stopVPN hit exception in startService(): $e")
394+
TSLog.e(TAG, "stopVPN hit exception in startService(): $e")
394395
}
395396
}
396397

@@ -465,7 +466,7 @@ open class UninitializedApp : Application() {
465466

466467
fun addUserDisallowedPackageName(packageName: String) {
467468
if (packageName.isEmpty()) {
468-
Log.e(TAG, "addUserDisallowedPackageName called with empty packageName")
469+
TSLog.e(TAG, "addUserDisallowedPackageName called with empty packageName")
469470
return
470471
}
471472

@@ -480,7 +481,7 @@ open class UninitializedApp : Application() {
480481

481482
fun removeUserDisallowedPackageName(packageName: String) {
482483
if (packageName.isEmpty()) {
483-
Log.e(TAG, "removeUserDisallowedPackageName called with empty packageName")
484+
TSLog.e(TAG, "removeUserDisallowedPackageName called with empty packageName")
484485
return
485486
}
486487

@@ -498,7 +499,7 @@ open class UninitializedApp : Application() {
498499
val mdmDisallowed =
499500
MDMSettings.excludedPackages.flow.value.value?.split(",")?.map { it.trim() } ?: emptyList()
500501
if (mdmDisallowed.isNotEmpty()) {
501-
Log.d(TAG, "Excluded application packages were set via MDM: $mdmDisallowed")
502+
TSLog.d(TAG, "Excluded application packages were set via MDM: $mdmDisallowed")
502503
return builtInDisallowedPackageNames + mdmDisallowed
503504
}
504505
val userDisallowed =

android/src/main/java/com/tailscale/ipn/IPNService.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import android.content.pm.PackageManager
88
import android.net.VpnService
99
import android.os.Build
1010
import android.system.OsConstants
11-
import android.util.Log
1211
import com.tailscale.ipn.mdm.MDMSettings
1312
import com.tailscale.ipn.ui.model.Ipn
1413
import com.tailscale.ipn.ui.notifier.Notifier
14+
import com.tailscale.ipn.util.TSLog
1515
import libtailscale.Libtailscale
1616
import java.util.UUID
1717

@@ -97,7 +97,7 @@ open class IPNService : VpnService(), libtailscale.IPNService {
9797
UninitializedApp.STATUS_NOTIFICATION_ID,
9898
UninitializedApp.get().buildStatusNotification(true))
9999
} catch (e: Exception) {
100-
Log.e(TAG, "Failed to start foreground service: $e")
100+
TSLog.e(TAG, "Failed to start foreground service: $e")
101101
}
102102
}
103103

@@ -113,7 +113,7 @@ open class IPNService : VpnService(), libtailscale.IPNService {
113113
try {
114114
b.addDisallowedApplication(name)
115115
} catch (e: PackageManager.NameNotFoundException) {
116-
Log.d(TAG, "Failed to add disallowed application: $e")
116+
TSLog.d(TAG, "Failed to add disallowed application: $e")
117117
}
118118
}
119119

@@ -135,15 +135,15 @@ open class IPNService : VpnService(), libtailscale.IPNService {
135135
// Tailscale,
136136
// then only allow those apps.
137137
for (packageName in includedPackages) {
138-
Log.d(TAG, "Including app: $packageName")
138+
TSLog.d(TAG, "Including app: $packageName")
139139
b.addAllowedApplication(packageName)
140140
}
141141
} else {
142142
// Otherwise, prevent certain apps from getting their traffic + DNS routed via Tailscale:
143143
// - any app that the user manually disallowed in the GUI
144144
// - any app that we disallowed via hard-coding
145145
for (disallowedPackageName in UninitializedApp.get().disallowedPackageNames()) {
146-
Log.d(TAG, "Disallowing app: $disallowedPackageName")
146+
TSLog.d(TAG, "Disallowing app: $disallowedPackageName")
147147
disallowApp(b, disallowedPackageName)
148148
}
149149
}

android/src/main/java/com/tailscale/ipn/MainActivity.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import android.net.ConnectivityManager
1616
import android.net.NetworkCapabilities
1717
import android.os.Bundle
1818
import android.provider.Settings
19-
import android.util.Log
2019
import androidx.activity.ComponentActivity
2120
import androidx.activity.compose.setContent
2221
import androidx.activity.result.ActivityResultLauncher
@@ -78,6 +77,7 @@ import com.tailscale.ipn.ui.viewModel.MainViewModelFactory
7877
import com.tailscale.ipn.ui.viewModel.PingViewModel
7978
import com.tailscale.ipn.ui.viewModel.SettingsNav
8079
import com.tailscale.ipn.ui.viewModel.VpnViewModel
80+
import com.tailscale.ipn.util.TSLog
8181
import kotlinx.coroutines.Dispatchers
8282
import kotlinx.coroutines.cancel
8383
import kotlinx.coroutines.flow.MutableStateFlow
@@ -128,15 +128,15 @@ class MainActivity : ComponentActivity() {
128128
vpnPermissionLauncher =
129129
registerForActivityResult(VpnPermissionContract()) { granted ->
130130
if (granted) {
131-
Log.d("VpnPermission", "VPN permission granted")
131+
TSLog.d("VpnPermission", "VPN permission granted")
132132
vpnViewModel.setVpnPrepared(true)
133133
App.get().startVPN()
134134
} else {
135135
if (isAnotherVpnActive(this)) {
136-
Log.d("VpnPermission", "Another VPN is likely active")
136+
TSLog.d("VpnPermission", "Another VPN is likely active")
137137
showOtherVPNConflictDialog()
138138
} else {
139-
Log.d("VpnPermission", "Permission was denied by the user")
139+
TSLog.d("VpnPermission", "Permission was denied by the user")
140140
vpnViewModel.setVpnPrepared(false)
141141
}
142142
}
@@ -357,7 +357,7 @@ class MainActivity : ComponentActivity() {
357357
}
358358
}
359359
} catch (e: Exception) {
360-
Log.e(TAG, "Login: failed to start MainActivity: $e")
360+
TSLog.e(TAG, "Login: failed to start MainActivity: $e")
361361
}
362362
}
363363

@@ -371,7 +371,7 @@ class MainActivity : ComponentActivity() {
371371
val fallbackIntent = Intent(Intent.ACTION_VIEW, url)
372372
startActivity(fallbackIntent)
373373
} catch (e: Exception) {
374-
Log.e(TAG, "Login: failed to open browser: $e")
374+
TSLog.e(TAG, "Login: failed to open browser: $e")
375375
}
376376
}
377377
}

android/src/main/java/com/tailscale/ipn/NetworkChangeCallback.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import android.net.Network
88
import android.net.NetworkCapabilities
99
import android.net.NetworkRequest
1010
import android.util.Log
11+
import com.tailscale.ipn.util.TSLog
1112
import libtailscale.Libtailscale
1213
import java.util.concurrent.locks.ReentrantLock
1314
import kotlin.concurrent.withLock
@@ -47,7 +48,7 @@ object NetworkChangeCallback {
4748
override fun onAvailable(network: Network) {
4849
super.onAvailable(network)
4950

50-
Log.d(TAG, "onAvailable: network ${network}")
51+
TSLog.d(TAG, "onAvailable: network ${network}")
5152
lock.withLock {
5253
activeNetworks[network] = NetworkInfo(NetworkCapabilities(), LinkProperties())
5354
}
@@ -69,7 +70,7 @@ object NetworkChangeCallback {
6970
override fun onLost(network: Network) {
7071
super.onLost(network)
7172

72-
Log.d(TAG, "onLost: network ${network}")
73+
TSLog.d(TAG, "onLost: network ${network}")
7374
lock.withLock {
7475
activeNetworks.remove(network)
7576
maybeUpdateDNSConfig("onLost", dns)
@@ -137,7 +138,7 @@ object NetworkChangeCallback {
137138
private fun maybeUpdateDNSConfig(why: String, dns: DnsConfig) {
138139
val defaultNetwork = pickDefaultNetwork()
139140
if (defaultNetwork == null) {
140-
Log.d(TAG, "${why}: no default network available; not updating DNS config")
141+
TSLog.d(TAG, "${why}: no default network available; not updating DNS config")
141142
return
142143
}
143144
val info = activeNetworks[defaultNetwork]
@@ -158,7 +159,7 @@ object NetworkChangeCallback {
158159
sb.append(searchDomains)
159160
}
160161
if (dns.updateDNSFromNetwork(sb.toString())) {
161-
Log.d(
162+
TSLog.d(
162163
TAG,
163164
"${why}: updated DNS config for network ${defaultNetwork} (${info.linkProps.interfaceName})")
164165
Libtailscale.onDNSConfigChanged(info.linkProps.interfaceName)

android/src/main/java/com/tailscale/ipn/ShareActivity.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import android.net.Uri
88
import android.os.Build
99
import android.os.Bundle
1010
import android.provider.OpenableColumns
11-
import android.util.Log
1211
import android.webkit.MimeTypeMap
1312
import androidx.activity.ComponentActivity
1413
import androidx.activity.compose.setContent
@@ -20,6 +19,7 @@ import com.tailscale.ipn.ui.theme.AppTheme
2019
import com.tailscale.ipn.ui.util.set
2120
import com.tailscale.ipn.ui.util.universalFit
2221
import com.tailscale.ipn.ui.view.TaildropView
22+
import com.tailscale.ipn.util.TSLog
2323
import kotlinx.coroutines.flow.MutableStateFlow
2424
import kotlinx.coroutines.flow.StateFlow
2525
import kotlin.random.Random
@@ -59,7 +59,7 @@ class ShareActivity : ComponentActivity() {
5959
// Loads the files from the intent.
6060
fun loadFiles() {
6161
if (intent == null) {
62-
Log.e(TAG, "Share failure - No intent found")
62+
TSLog.e(TAG, "Share failure - No intent found")
6363
return
6464
}
6565

@@ -83,7 +83,7 @@ class ShareActivity : ComponentActivity() {
8383
}
8484
}
8585
else -> {
86-
Log.e(TAG, "No extras found in intent - nothing to share")
86+
TSLog.e(TAG, "No extras found in intent - nothing to share")
8787
null
8888
}
8989
}
@@ -117,7 +117,7 @@ class ShareActivity : ComponentActivity() {
117117
} ?: emptyList()
118118

119119
if (pendingFiles.isEmpty()) {
120-
Log.e(TAG, "Share failure - no files extracted from intent")
120+
TSLog.e(TAG, "Share failure - no files extracted from intent")
121121
}
122122

123123
requestedTransfers.set(pendingFiles)

android/src/main/java/com/tailscale/ipn/StartVPNWorker.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import androidx.work.Worker;
1616
import androidx.work.WorkerParameters;
1717

18+
import com.tailscale.ipn.util.TSLog;
19+
1820
/**
1921
* A worker that exists to support IPNReceiver.
2022
*/
@@ -38,7 +40,7 @@ public Result doWork() {
3840
}
3941

4042
// We aren't ready to start the VPN or don't have permission, open the Tailscale app.
41-
android.util.Log.e("StartVPNWorker", "Tailscale isn't ready to start the VPN, notify the user.");
43+
TSLog.e("StartVPNWorker", "Tailscale isn't ready to start the VPN, notify the user.");
4244

4345
// Send notification
4446
NotificationManager notificationManager = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);

android/src/main/java/com/tailscale/ipn/ui/localapi/Client.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package com.tailscale.ipn.ui.localapi
55

66
import android.content.Context
7-
import android.util.Log
87
import com.tailscale.ipn.ui.model.BugReportID
98
import com.tailscale.ipn.ui.model.Errors
109
import com.tailscale.ipn.ui.model.Ipn
@@ -13,6 +12,7 @@ import com.tailscale.ipn.ui.model.IpnState
1312
import com.tailscale.ipn.ui.model.StableNodeID
1413
import com.tailscale.ipn.ui.model.Tailcfg
1514
import com.tailscale.ipn.ui.util.InputStreamAdapter
15+
import com.tailscale.ipn.util.TSLog
1616
import kotlinx.coroutines.CoroutineScope
1717
import kotlinx.coroutines.Dispatchers
1818
import kotlinx.coroutines.launch
@@ -175,7 +175,7 @@ class Client(private val scope: CoroutineScope) {
175175
})
176176
} catch (e: Exception) {
177177
parts.forEach { it.body.close() }
178-
Log.e(TAG, "Error creating file upload body: $e")
178+
TSLog.e(TAG, "Error creating file upload body: $e")
179179
responseHandler(Result.failure(e))
180180
return
181181
}
@@ -307,7 +307,7 @@ class Request<T>(
307307
@OptIn(ExperimentalSerializationApi::class)
308308
fun execute() {
309309
scope.launch(Dispatchers.IO) {
310-
Log.d(TAG, "Executing request:${method}:${fullPath} on app $app")
310+
TSLog.d(TAG, "Executing request:${method}:${fullPath} on app $app")
311311
try {
312312
val resp =
313313
if (parts != null) app.callLocalAPIMultipart(timeoutMillis, method, fullPath, parts)
@@ -350,7 +350,7 @@ class Request<T>(
350350
// The response handler will invoked internally by the request parser
351351
scope.launch { responseHandler(response) }
352352
} catch (e: Exception) {
353-
Log.e(TAG, "Error executing request:${method}:${fullPath}: $e")
353+
TSLog.e(TAG, "Error executing request:${method}:${fullPath}: $e")
354354
scope.launch { responseHandler(Result.failure(e)) }
355355
}
356356
}

0 commit comments

Comments
 (0)