Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,29 @@ package com.onesignal.debug
/**
* Access to debug the SDK in the event additional information is required to diagnose any
* SDK-related issues.
*
* WARNING: This should not be used in a production setting.
*/
interface IDebugManager {
/**
* The log level the OneSignal SDK should be writing to the Android log. Defaults to [LogLevel.WARN].
* WARNING: This should not be set higher than LogLevel.WARN in a production setting.
*/
var logLevel: LogLevel

/**
* The log level the OneSignal SDK should be showing as a modal. Defaults to [LogLevel.NONE].
* WARNING: This should not be used in a production setting.
*/
var alertLevel: LogLevel

/**
* Add a listener to receive all logging messages the SDK produces.
* Useful to capture and send logs to your server.
* NOTE: All log messages are always passed, logLevel has no effect on this.
*/
fun addLogListener(listener: ILogListener)

/**
* Removes a listener added by addLogListener
*/
fun removeLogListener(listener: ILogListener)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.onesignal.debug

fun interface ILogListener {
fun onLogEvent(event: OneSignalLogEvent)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.onesignal.debug

data class OneSignalLogEvent(
val level: LogLevel,
val entry: String,
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.onesignal.debug.internal

import com.onesignal.debug.IDebugManager
import com.onesignal.debug.ILogListener
import com.onesignal.debug.LogLevel
import com.onesignal.debug.internal.logging.Logging

Expand All @@ -21,4 +22,12 @@ internal class DebugManager() : IDebugManager {
logLevel = LogLevel.WARN
alertLevel = LogLevel.NONE
}

override fun addLogListener(listener: ILogListener) {
Logging.addListener(listener)
}

override fun removeLogListener(listener: ILogListener) {
Logging.removeListener(listener)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,28 @@ package com.onesignal.debug.internal.logging
import android.app.AlertDialog
import com.onesignal.common.threading.suspendifyOnMain
import com.onesignal.core.internal.application.IApplicationService
import com.onesignal.debug.ILogListener
import com.onesignal.debug.LogLevel
import com.onesignal.debug.OneSignalLogEvent
import java.io.PrintWriter
import java.io.StringWriter
import java.util.concurrent.CopyOnWriteArraySet

object Logging {
private const val TAG = "OneSignal"

var applicationService: IApplicationService? = null

private val logListeners = CopyOnWriteArraySet<ILogListener>()

@JvmStatic
var logLevel = LogLevel.WARN

@JvmStatic
var visualLogLevel = LogLevel.NONE

@JvmStatic
fun atLogLevel(level: LogLevel): Boolean {
return level.compareTo(visualLogLevel) < 1 || level.compareTo(logLevel) < 1
}
fun atLogLevel(level: LogLevel): Boolean = level.compareTo(visualLogLevel) < 1 || level.compareTo(logLevel) < 1

@JvmStatic
fun verbose(
Expand Down Expand Up @@ -86,41 +89,82 @@ object Logging {
throwable: Throwable?,
) {
val fullMessage = "[${Thread.currentThread().name}] $message"
if (level.compareTo(logLevel) < 1) {
when (level) {
LogLevel.VERBOSE -> android.util.Log.v(TAG, fullMessage, throwable)
LogLevel.DEBUG -> android.util.Log.d(TAG, fullMessage, throwable)
LogLevel.INFO -> android.util.Log.i(TAG, fullMessage, throwable)
LogLevel.WARN -> android.util.Log.w(TAG, fullMessage, throwable)
LogLevel.ERROR, LogLevel.FATAL -> android.util.Log.e(TAG, message, throwable)
else -> {}
}

logToLogcat(level, fullMessage, throwable)
showVisualLogging(level, fullMessage, throwable)
callLogListeners(level, fullMessage, throwable)
}

private fun logToLogcat(
level: LogLevel,
message: String,
throwable: Throwable?,
) {
if (level.compareTo(logLevel) >= 1) return
when (level) {
LogLevel.VERBOSE -> android.util.Log.v(TAG, message, throwable)
LogLevel.DEBUG -> android.util.Log.d(TAG, message, throwable)
LogLevel.INFO -> android.util.Log.i(TAG, message, throwable)
LogLevel.WARN -> android.util.Log.w(TAG, message, throwable)
LogLevel.ERROR, LogLevel.FATAL -> android.util.Log.e(TAG, message, throwable)
else -> {}
}
}

if (level.compareTo(visualLogLevel) < 1 && applicationService?.current != null) {
try {
var fullMessage: String? = "$message\n".trimIndent()
if (throwable != null) {
fullMessage += throwable.message
val sw = StringWriter()
val pw = PrintWriter(sw)
throwable.printStackTrace(pw)
fullMessage += sw.toString()
}
val finalFullMessage = fullMessage

suspendifyOnMain {
val currentActivity = applicationService?.current
if (currentActivity != null) {
AlertDialog.Builder(currentActivity)
.setTitle(level.toString())
.setMessage(finalFullMessage)
.show()
}
private fun showVisualLogging(
level: LogLevel,
message: String,
throwable: Throwable?,
) {
if (level.compareTo(visualLogLevel) >= 1) return

try {
var fullMessage: String? = "$message\n".trimIndent()
if (throwable != null) {
fullMessage += throwable.message
val sw = StringWriter()
val pw = PrintWriter(sw)
throwable.printStackTrace(pw)
fullMessage += sw.toString()
}
val finalFullMessage = fullMessage

suspendifyOnMain {
val currentActivity = applicationService?.current
if (currentActivity != null) {
AlertDialog
.Builder(currentActivity)
.setTitle(level.toString())
.setMessage(finalFullMessage)
.show()
}
} catch (t: Throwable) {
android.util.Log.e(TAG, "Error showing logging message.", t)
}
} catch (t: Throwable) {
android.util.Log.e(TAG, "Error showing logging message.", t)
}
}

private fun callLogListeners(
level: LogLevel,
message: String,
throwable: Throwable?,
) {
if (logListeners.isEmpty()) return

var logEntry = message
if (throwable != null) {
logEntry += "\n" + android.util.Log.getStackTraceString(throwable)
}
for (listener in logListeners) {
listener.onLogEvent(OneSignalLogEvent(level, logEntry))
}
}

fun addListener(listener: ILogListener) {
logListeners.add(listener)
}

fun removeListener(listener: ILogListener) {
logListeners.remove(listener)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.onesignal.debug.internal

import com.onesignal.debug.ILogListener
import com.onesignal.debug.LogLevel
import com.onesignal.debug.OneSignalLogEvent
import com.onesignal.debug.internal.logging.Logging
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldEndWith

class TestLogLister : ILogListener {
val calls = ArrayList<String>()

override fun onLogEvent(event: OneSignalLogEvent) {
calls += event.entry
}
}

infix fun <T : Collection<String>> T.shouldHaveEachItemEndWith(expected: Array<String>): T {
this.forEachIndexed { index, it -> it shouldEndWith expected[index] }
return this
}

class LoggingTests : FunSpec({
beforeAny {
Logging.logLevel = LogLevel.NONE
}

test("addListener") {
// Given
val listener = TestLogLister()
Logging.addListener(listener)

// When
Logging.debug("test")

// Then
listener.calls shouldHaveEachItemEndWith arrayOf("test")
}

test("addListener twice") {
// Given
val listener = TestLogLister()
Logging.addListener(listener)
Logging.addListener(listener)

// When
Logging.debug("test")

// Then
listener.calls shouldHaveEachItemEndWith arrayOf("test")
}

test("removeListener") {
// Given
val listener = TestLogLister()
Logging.addListener(listener)
Logging.removeListener(listener)

// When
Logging.debug("test")

// Then
listener.calls shouldBe arrayOf<String>()
}

test("removeListener twice") {
// Given
val listener = TestLogLister()
Logging.addListener(listener)
Logging.removeListener(listener)
Logging.removeListener(listener)

// When
Logging.debug("test")

// Then
listener.calls shouldBe arrayOf<String>()
}

test("addListener nested") {
// Given
val nestedListener = TestLogLister()
Logging.addListener { Logging.addListener(nestedListener) }

// When
Logging.debug("test")
Logging.debug("test2")
Logging.debug("test3")

// Then
nestedListener.calls shouldHaveEachItemEndWith arrayOf("test2", "test3")
}

test("removeListener nested") {
// Given
val calls = ArrayList<String>()
var listener: ILogListener? = null
listener =
ILogListener {
calls += it.entry
Logging.removeListener(listener!!)
}
Logging.addListener(listener!!)

// When
Logging.debug("test")
Logging.debug("test2")

// Then
calls shouldHaveEachItemEndWith arrayOf("test")
}
})
Loading