Skip to content

Commit

Permalink
android: send Android logs to logz (#515)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
kari-ts authored Sep 24, 2024
1 parent f26a828 commit 08ae018
Show file tree
Hide file tree
Showing 21 changed files with 246 additions and 120 deletions.
3 changes: 3 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ dependencies {

// Unit Tests
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:5.4.0'
testImplementation 'org.mockito:mockito-inline:5.2.0'
testImplementation 'org.mockito.kotlin:mockito-kotlin:5.4.0'

debugImplementation("androidx.compose.ui:ui-tooling")
implementation("androidx.compose.ui:ui-tooling-preview")
Expand Down
27 changes: 14 additions & 13 deletions android/src/main/java/com/tailscale/ipn/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.tailscale.ipn.ui.notifier.HealthNotifier
import com.tailscale.ipn.ui.notifier.Notifier
import com.tailscale.ipn.ui.viewModel.VpnViewModel
import com.tailscale.ipn.ui.viewModel.VpnViewModelFactory
import com.tailscale.ipn.util.TSLog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
Expand Down Expand Up @@ -162,7 +163,7 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
result.fold(
onSuccess = { onSuccess?.invoke() },
onFailure = { error ->
Log.d("TAG", "Set want running: failed to update preferences: ${error.message}")
TSLog.d("TAG", "Set want running: failed to update preferences: ${error.message}")
})
}
Client(applicationScope)
Expand Down Expand Up @@ -203,7 +204,7 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
private fun updateConnStatus(ableToStartVPN: Boolean) {
setAbleToStartVPN(ableToStartVPN)
QuickToggleService.updateTile()
Log.d("App", "Set Tile Ready: $ableToStartVPN")
TSLog.d("App", "Set Tile Ready: $ableToStartVPN")
}

override fun getModelName(): String {
Expand Down Expand Up @@ -266,14 +267,14 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
downloads.mkdirs()
}
} catch (e: Exception) {
Log.e(TAG, "Failed to create downloads folder: $e")
TSLog.e(TAG, "Failed to create downloads folder: $e")
downloads = File(this.filesDir, "Taildrop")
try {
if (!downloads.exists()) {
downloads.mkdirs()
}
} catch (e: Exception) {
Log.e(TAG, "Failed to create Taildrop folder: $e")
TSLog.e(TAG, "Failed to create Taildrop folder: $e")
downloads = File("")
}
}
Expand Down Expand Up @@ -308,7 +309,7 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
val list = setting.value as? List<*>
return Json.encodeToString(list)
} catch (e: Exception) {
Log.d("MDM", "$key value cannot be serialized to JSON. Throwing NoSuchKeyException.")
TSLog.d("MDM", "$key value cannot be serialized to JSON. Throwing NoSuchKeyException.")
throw MDMSettings.NoSuchKeyException()
}
}
Expand Down Expand Up @@ -373,13 +374,13 @@ open class UninitializedApp : Application() {
try {
startForegroundService(intent)
} catch (foregroundServiceStartException: IllegalStateException) {
Log.e(
TSLog.e(
TAG,
"startVPN hit ForegroundServiceStartNotAllowedException in startForegroundService(): $foregroundServiceStartException")
} catch (securityException: SecurityException) {
Log.e(TAG, "startVPN hit SecurityException in startForegroundService(): $securityException")
TSLog.e(TAG, "startVPN hit SecurityException in startForegroundService(): $securityException")
} catch (e: Exception) {
Log.e(TAG, "startVPN hit exception in startForegroundService(): $e")
TSLog.e(TAG, "startVPN hit exception in startForegroundService(): $e")
}
}

Expand All @@ -388,9 +389,9 @@ open class UninitializedApp : Application() {
try {
startService(intent)
} catch (illegalStateException: IllegalStateException) {
Log.e(TAG, "stopVPN hit IllegalStateException in startService(): $illegalStateException")
TSLog.e(TAG, "stopVPN hit IllegalStateException in startService(): $illegalStateException")
} catch (e: Exception) {
Log.e(TAG, "stopVPN hit exception in startService(): $e")
TSLog.e(TAG, "stopVPN hit exception in startService(): $e")
}
}

Expand Down Expand Up @@ -465,7 +466,7 @@ open class UninitializedApp : Application() {

fun addUserDisallowedPackageName(packageName: String) {
if (packageName.isEmpty()) {
Log.e(TAG, "addUserDisallowedPackageName called with empty packageName")
TSLog.e(TAG, "addUserDisallowedPackageName called with empty packageName")
return
}

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

fun removeUserDisallowedPackageName(packageName: String) {
if (packageName.isEmpty()) {
Log.e(TAG, "removeUserDisallowedPackageName called with empty packageName")
TSLog.e(TAG, "removeUserDisallowedPackageName called with empty packageName")
return
}

Expand All @@ -498,7 +499,7 @@ open class UninitializedApp : Application() {
val mdmDisallowed =
MDMSettings.excludedPackages.flow.value.value?.split(",")?.map { it.trim() } ?: emptyList()
if (mdmDisallowed.isNotEmpty()) {
Log.d(TAG, "Excluded application packages were set via MDM: $mdmDisallowed")
TSLog.d(TAG, "Excluded application packages were set via MDM: $mdmDisallowed")
return builtInDisallowedPackageNames + mdmDisallowed
}
val userDisallowed =
Expand Down
10 changes: 5 additions & 5 deletions android/src/main/java/com/tailscale/ipn/IPNService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import android.content.pm.PackageManager
import android.net.VpnService
import android.os.Build
import android.system.OsConstants
import android.util.Log
import com.tailscale.ipn.mdm.MDMSettings
import com.tailscale.ipn.ui.model.Ipn
import com.tailscale.ipn.ui.notifier.Notifier
import com.tailscale.ipn.util.TSLog
import libtailscale.Libtailscale
import java.util.UUID

Expand Down Expand Up @@ -97,7 +97,7 @@ open class IPNService : VpnService(), libtailscale.IPNService {
UninitializedApp.STATUS_NOTIFICATION_ID,
UninitializedApp.get().buildStatusNotification(true))
} catch (e: Exception) {
Log.e(TAG, "Failed to start foreground service: $e")
TSLog.e(TAG, "Failed to start foreground service: $e")
}
}

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

Expand All @@ -135,15 +135,15 @@ open class IPNService : VpnService(), libtailscale.IPNService {
// Tailscale,
// then only allow those apps.
for (packageName in includedPackages) {
Log.d(TAG, "Including app: $packageName")
TSLog.d(TAG, "Including app: $packageName")
b.addAllowedApplication(packageName)
}
} else {
// Otherwise, prevent certain apps from getting their traffic + DNS routed via Tailscale:
// - any app that the user manually disallowed in the GUI
// - any app that we disallowed via hard-coding
for (disallowedPackageName in UninitializedApp.get().disallowedPackageNames()) {
Log.d(TAG, "Disallowing app: $disallowedPackageName")
TSLog.d(TAG, "Disallowing app: $disallowedPackageName")
disallowApp(b, disallowedPackageName)
}
}
Expand Down
12 changes: 6 additions & 6 deletions android/src/main/java/com/tailscale/ipn/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.ActivityResultLauncher
Expand Down Expand Up @@ -78,6 +77,7 @@ import com.tailscale.ipn.ui.viewModel.MainViewModelFactory
import com.tailscale.ipn.ui.viewModel.PingViewModel
import com.tailscale.ipn.ui.viewModel.SettingsNav
import com.tailscale.ipn.ui.viewModel.VpnViewModel
import com.tailscale.ipn.util.TSLog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -128,15 +128,15 @@ class MainActivity : ComponentActivity() {
vpnPermissionLauncher =
registerForActivityResult(VpnPermissionContract()) { granted ->
if (granted) {
Log.d("VpnPermission", "VPN permission granted")
TSLog.d("VpnPermission", "VPN permission granted")
vpnViewModel.setVpnPrepared(true)
App.get().startVPN()
} else {
if (isAnotherVpnActive(this)) {
Log.d("VpnPermission", "Another VPN is likely active")
TSLog.d("VpnPermission", "Another VPN is likely active")
showOtherVPNConflictDialog()
} else {
Log.d("VpnPermission", "Permission was denied by the user")
TSLog.d("VpnPermission", "Permission was denied by the user")
vpnViewModel.setVpnPrepared(false)
}
}
Expand Down Expand Up @@ -357,7 +357,7 @@ class MainActivity : ComponentActivity() {
}
}
} catch (e: Exception) {
Log.e(TAG, "Login: failed to start MainActivity: $e")
TSLog.e(TAG, "Login: failed to start MainActivity: $e")
}
}

Expand All @@ -371,7 +371,7 @@ class MainActivity : ComponentActivity() {
val fallbackIntent = Intent(Intent.ACTION_VIEW, url)
startActivity(fallbackIntent)
} catch (e: Exception) {
Log.e(TAG, "Login: failed to open browser: $e")
TSLog.e(TAG, "Login: failed to open browser: $e")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.util.Log
import com.tailscale.ipn.util.TSLog
import libtailscale.Libtailscale
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
Expand Down Expand Up @@ -47,7 +48,7 @@ object NetworkChangeCallback {
override fun onAvailable(network: Network) {
super.onAvailable(network)

Log.d(TAG, "onAvailable: network ${network}")
TSLog.d(TAG, "onAvailable: network ${network}")
lock.withLock {
activeNetworks[network] = NetworkInfo(NetworkCapabilities(), LinkProperties())
}
Expand All @@ -69,7 +70,7 @@ object NetworkChangeCallback {
override fun onLost(network: Network) {
super.onLost(network)

Log.d(TAG, "onLost: network ${network}")
TSLog.d(TAG, "onLost: network ${network}")
lock.withLock {
activeNetworks.remove(network)
maybeUpdateDNSConfig("onLost", dns)
Expand Down Expand Up @@ -137,7 +138,7 @@ object NetworkChangeCallback {
private fun maybeUpdateDNSConfig(why: String, dns: DnsConfig) {
val defaultNetwork = pickDefaultNetwork()
if (defaultNetwork == null) {
Log.d(TAG, "${why}: no default network available; not updating DNS config")
TSLog.d(TAG, "${why}: no default network available; not updating DNS config")
return
}
val info = activeNetworks[defaultNetwork]
Expand All @@ -158,7 +159,7 @@ object NetworkChangeCallback {
sb.append(searchDomains)
}
if (dns.updateDNSFromNetwork(sb.toString())) {
Log.d(
TSLog.d(
TAG,
"${why}: updated DNS config for network ${defaultNetwork} (${info.linkProps.interfaceName})")
Libtailscale.onDNSConfigChanged(info.linkProps.interfaceName)
Expand Down
8 changes: 4 additions & 4 deletions android/src/main/java/com/tailscale/ipn/ShareActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.OpenableColumns
import android.util.Log
import android.webkit.MimeTypeMap
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
Expand All @@ -20,6 +19,7 @@ import com.tailscale.ipn.ui.theme.AppTheme
import com.tailscale.ipn.ui.util.set
import com.tailscale.ipn.ui.util.universalFit
import com.tailscale.ipn.ui.view.TaildropView
import com.tailscale.ipn.util.TSLog
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlin.random.Random
Expand Down Expand Up @@ -59,7 +59,7 @@ class ShareActivity : ComponentActivity() {
// Loads the files from the intent.
fun loadFiles() {
if (intent == null) {
Log.e(TAG, "Share failure - No intent found")
TSLog.e(TAG, "Share failure - No intent found")
return
}

Expand All @@ -83,7 +83,7 @@ class ShareActivity : ComponentActivity() {
}
}
else -> {
Log.e(TAG, "No extras found in intent - nothing to share")
TSLog.e(TAG, "No extras found in intent - nothing to share")
null
}
}
Expand Down Expand Up @@ -117,7 +117,7 @@ class ShareActivity : ComponentActivity() {
} ?: emptyList()

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

requestedTransfers.set(pendingFiles)
Expand Down
4 changes: 3 additions & 1 deletion android/src/main/java/com/tailscale/ipn/StartVPNWorker.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import androidx.work.Worker;
import androidx.work.WorkerParameters;

import com.tailscale.ipn.util.TSLog;

/**
* A worker that exists to support IPNReceiver.
*/
Expand All @@ -38,7 +40,7 @@ public Result doWork() {
}

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

// Send notification
NotificationManager notificationManager = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);
Expand Down
8 changes: 4 additions & 4 deletions android/src/main/java/com/tailscale/ipn/ui/localapi/Client.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package com.tailscale.ipn.ui.localapi

import android.content.Context
import android.util.Log
import com.tailscale.ipn.ui.model.BugReportID
import com.tailscale.ipn.ui.model.Errors
import com.tailscale.ipn.ui.model.Ipn
Expand All @@ -13,6 +12,7 @@ import com.tailscale.ipn.ui.model.IpnState
import com.tailscale.ipn.ui.model.StableNodeID
import com.tailscale.ipn.ui.model.Tailcfg
import com.tailscale.ipn.ui.util.InputStreamAdapter
import com.tailscale.ipn.util.TSLog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -175,7 +175,7 @@ class Client(private val scope: CoroutineScope) {
})
} catch (e: Exception) {
parts.forEach { it.body.close() }
Log.e(TAG, "Error creating file upload body: $e")
TSLog.e(TAG, "Error creating file upload body: $e")
responseHandler(Result.failure(e))
return
}
Expand Down Expand Up @@ -307,7 +307,7 @@ class Request<T>(
@OptIn(ExperimentalSerializationApi::class)
fun execute() {
scope.launch(Dispatchers.IO) {
Log.d(TAG, "Executing request:${method}:${fullPath} on app $app")
TSLog.d(TAG, "Executing request:${method}:${fullPath} on app $app")
try {
val resp =
if (parts != null) app.callLocalAPIMultipart(timeoutMillis, method, fullPath, parts)
Expand Down Expand Up @@ -350,7 +350,7 @@ class Request<T>(
// The response handler will invoked internally by the request parser
scope.launch { responseHandler(response) }
} catch (e: Exception) {
Log.e(TAG, "Error executing request:${method}:${fullPath}: $e")
TSLog.e(TAG, "Error executing request:${method}:${fullPath}: $e")
scope.launch { responseHandler(Result.failure(e)) }
}
}
Expand Down
Loading

0 comments on commit 08ae018

Please sign in to comment.