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 <kari@tailscale.com>pull/522/head
parent
f26a828cbd
commit
08ae018468
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
package com.tailscale.ipn.util
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import libtailscale.Libtailscale
|
||||||
|
|
||||||
|
object TSLog {
|
||||||
|
var libtailscaleWrapper = LibtailscaleWrapper()
|
||||||
|
|
||||||
|
fun d(tag: String?, message: String) {
|
||||||
|
Log.d(tag, message)
|
||||||
|
libtailscaleWrapper.sendLog(tag, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun w(tag: String, message: String) {
|
||||||
|
Log.w(tag, message)
|
||||||
|
libtailscaleWrapper.sendLog(tag, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overloaded function without Throwable because Java does not support default parameters
|
||||||
|
@JvmStatic
|
||||||
|
fun e(tag: String?, message: String) {
|
||||||
|
Log.e(tag, message)
|
||||||
|
libtailscaleWrapper.sendLog(tag, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun e(tag: String?, message: String, throwable: Throwable? = null) {
|
||||||
|
if (throwable == null) {
|
||||||
|
Log.e(tag, message)
|
||||||
|
libtailscaleWrapper.sendLog(tag, message)
|
||||||
|
} else {
|
||||||
|
Log.e(tag, message, throwable)
|
||||||
|
libtailscaleWrapper.sendLog(tag, "$message ${throwable?.localizedMessage}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LibtailscaleWrapper {
|
||||||
|
public fun sendLog(tag: String?, message: String) {
|
||||||
|
val logTag = tag ?: ""
|
||||||
|
Libtailscale.sendLog((logTag + ": " + message).toByteArray(Charsets.UTF_8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,60 +1,107 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
|
||||||
package com.tailcale.ipn.ui.util
|
package com.tailcale.ipn.ui.util
|
||||||
|
|
||||||
|
|
||||||
import com.tailscale.ipn.ui.util.TimeUtil
|
import com.tailscale.ipn.ui.util.TimeUtil
|
||||||
|
import com.tailscale.ipn.util.TSLog
|
||||||
|
import com.tailscale.ipn.util.TSLog.LibtailscaleWrapper
|
||||||
|
import org.junit.After
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.mockito.ArgumentMatchers.anyString
|
||||||
|
import org.mockito.Mockito.doNothing
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
|
|
||||||
class TimeUtilTest {
|
class TimeUtilTest {
|
||||||
|
|
||||||
@Test
|
|
||||||
fun durationInvalidMsUnits() {
|
private lateinit var libtailscaleWrapperMock: LibtailscaleWrapper
|
||||||
val input = "5s10ms"
|
private lateinit var originalWrapper: LibtailscaleWrapper
|
||||||
val actual = TimeUtil.duration(input)
|
|
||||||
assertNull("Should return null", actual)
|
|
||||||
}
|
@Before
|
||||||
|
fun setUp() {
|
||||||
@Test
|
libtailscaleWrapperMock = mock(LibtailscaleWrapper::class.java)
|
||||||
fun durationInvalidUsUnits() {
|
doNothing().`when`(libtailscaleWrapperMock).sendLog(anyString(), anyString())
|
||||||
val input = "5s10us"
|
|
||||||
val actual = TimeUtil.duration(input)
|
|
||||||
assertNull("Should return null", actual)
|
// Store the original wrapper so we can reset it later
|
||||||
}
|
originalWrapper = TSLog.libtailscaleWrapper
|
||||||
|
// Inject mock into TSLog
|
||||||
@Test
|
TSLog.libtailscaleWrapper = libtailscaleWrapperMock
|
||||||
fun durationTestHappyPath() {
|
}
|
||||||
val input = arrayOf("1.0y1.0w1.0d1.0h1.0m1.0s", "1s", "1m", "1h", "1d", "1w", "1y")
|
|
||||||
val expectedSeconds =
|
|
||||||
arrayOf((31536000 + 604800 + 86400 + 3600 + 60 + 1), 1, 60, 3600, 86400, 604800, 31536000)
|
@After
|
||||||
val expected = expectedSeconds.map { Duration.ofSeconds(it.toLong()) }
|
fun tearDown() {
|
||||||
val actual = input.map { TimeUtil.duration(it) }
|
// Reset TSLog after each test to avoid side effects
|
||||||
assertEquals("Incorrect conversion", expected, actual)
|
TSLog.libtailscaleWrapper = originalWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testBadDurationString() {
|
@Test
|
||||||
val input = "1..0y1.0w1.0d1.0h1.0m1.0s"
|
fun durationInvalidMsUnits() {
|
||||||
val actual = TimeUtil.duration(input)
|
val input = "5s10ms"
|
||||||
assertNull("Should return null", actual)
|
val actual = TimeUtil.duration(input)
|
||||||
}
|
assertNull("Should return null", actual)
|
||||||
|
}
|
||||||
@Test
|
|
||||||
fun testBadDInputString() {
|
|
||||||
val input = "1.0yy1.0w1.0d1.0h1.0m1.0s"
|
@Test
|
||||||
val actual = TimeUtil.duration(input)
|
fun durationInvalidUsUnits() {
|
||||||
assertNull("Should return null", actual)
|
val input = "5s10us"
|
||||||
}
|
val actual = TimeUtil.duration(input)
|
||||||
|
assertNull("Should return null", actual)
|
||||||
@Test
|
}
|
||||||
fun testIgnoreFractionalSeconds() {
|
|
||||||
val input = "10.9s"
|
|
||||||
val expectedSeconds = 10
|
@Test
|
||||||
val expected = Duration.ofSeconds(expectedSeconds.toLong())
|
fun durationTestHappyPath() {
|
||||||
val actual = TimeUtil.duration(input)
|
val input = arrayOf("1.0y1.0w1.0d1.0h1.0m1.0s", "1s", "1m", "1h", "1d", "1w", "1y")
|
||||||
assertEquals("Should return $expectedSeconds seconds", expected, actual)
|
val expectedSeconds =
|
||||||
}
|
arrayOf((31536000 + 604800 + 86400 + 3600 + 60 + 1), 1, 60, 3600, 86400, 604800, 31536000)
|
||||||
|
val expected = expectedSeconds.map { Duration.ofSeconds(it.toLong()) }
|
||||||
|
val actual = input.map { TimeUtil.duration(it) }
|
||||||
|
assertEquals("Incorrect conversion", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBadDurationString() {
|
||||||
|
val input = "1..0y1.0w1.0d1.0h1.0m1.0s"
|
||||||
|
val actual = TimeUtil.duration(input)
|
||||||
|
assertNull("Should return null", actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBadDInputString() {
|
||||||
|
val libtailscaleWrapperMock = mock(LibtailscaleWrapper::class.java)
|
||||||
|
doNothing().`when`(libtailscaleWrapperMock).sendLog(anyString(), anyString())
|
||||||
|
|
||||||
|
|
||||||
|
val input = "1.0yy1.0w1.0d1.0h1.0m1.0s"
|
||||||
|
val actual = TimeUtil.duration(input)
|
||||||
|
assertNull("Should return null", actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testIgnoreFractionalSeconds() {
|
||||||
|
val input = "10.9s"
|
||||||
|
val expectedSeconds = 10
|
||||||
|
val expected = Duration.ofSeconds(expectedSeconds.toLong())
|
||||||
|
val actual = TimeUtil.duration(input)
|
||||||
|
assertEquals("Should return $expectedSeconds seconds", expected, actual)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue