Skip to content
Open
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
21 changes: 11 additions & 10 deletions core/src/main/kotlin/dev/hotwire/core/bridge/Bridge.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package dev.hotwire.core.bridge
import android.webkit.JavascriptInterface
import android.webkit.WebView
import androidx.annotation.VisibleForTesting
import dev.hotwire.core.logging.logEvent
import dev.hotwire.core.logging.logDebug
import dev.hotwire.core.logging.logVerbose
import kotlinx.serialization.json.JsonElement
import java.lang.ref.WeakReference

Expand All @@ -30,37 +31,37 @@ class Bridge internal constructor(webView: WebView) {
}

internal fun register(component: String) {
logEvent("bridgeWillRegisterComponent", component)
logDebug("bridgeWillRegisterComponent", component)
val javascript = generateJavaScript("register", component.toJsonElement())
evaluate(javascript)
}

internal fun register(components: List<String>) {
logEvent("bridgeWillRegisterComponents", components.joinToString())
logDebug("bridgeWillRegisterComponents", components.joinToString())
val javascript = generateJavaScript("register", components.toJsonElement())
evaluate(javascript)
}

internal fun unregister(component: String) {
logEvent("bridgeWillUnregisterComponent", component)
logDebug("bridgeWillUnregisterComponent", component)
val javascript = generateJavaScript("unregister", component.toJsonElement())
evaluate(javascript)
}

internal fun replyWith(message: Message) {
logEvent("bridgeWillReplyWithMessage", message.toString())
logDebug("bridgeWillReplyWithMessage", message.toString())
val internalMessage = InternalMessage.fromMessage(message)
val javascript = generateJavaScript("replyWith", internalMessage.toJson().toJsonElement())
evaluate(javascript)
}

internal fun load() {
logEvent("bridgeWillLoad")
logDebug("bridgeWillLoad")
evaluate(userScript())
}

internal fun reset() {
logEvent("bridgeDidReset")
logDebug("bridgeDidReset")
componentsAreRegistered = false
}

Expand All @@ -70,15 +71,15 @@ class Bridge internal constructor(webView: WebView) {

@JavascriptInterface
fun bridgeDidInitialize() {
logEvent("bridgeDidInitialize", "success")
logDebug("bridgeDidInitialize", "success")
runOnUiThread {
delegate?.bridgeDidInitialize()
}
}

@JavascriptInterface
fun bridgeDidUpdateSupportedComponents() {
logEvent("bridgeDidUpdateSupportedComponents", "success")
logDebug("bridgeDidUpdateSupportedComponents", "success")
componentsAreRegistered = true
}

Expand All @@ -99,7 +100,7 @@ class Bridge internal constructor(webView: WebView) {
}

internal fun evaluate(javascript: String) {
logEvent("evaluatingJavascript", javascript)
logVerbose("evaluatingJavascript", javascript)
webView?.evaluateJavascript(javascript) {}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package dev.hotwire.core.bridge

import dev.hotwire.core.config.Hotwire
import dev.hotwire.core.logging.logError
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

abstract class BridgeComponentJsonConverter {
Expand Down
10 changes: 5 additions & 5 deletions core/src/main/kotlin/dev/hotwire/core/bridge/BridgeDelegate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package dev.hotwire.core.bridge
import android.webkit.WebView
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import dev.hotwire.core.logging.logEvent
import dev.hotwire.core.logging.logDebug
import dev.hotwire.core.logging.logWarning

@Suppress("unused")
Expand Down Expand Up @@ -63,7 +63,7 @@ class BridgeDelegate<D : BridgeDestination>(

internal fun bridgeDidReceiveMessage(message: Message): Boolean {
return if (destinationIsActive && resolvedLocation == message.metadata?.url) {
logEvent("bridgeDidReceiveMessage", message.toString())
logDebug("bridgeDidReceiveMessage", message.toString())
getOrCreateComponent(message.component)?.didReceive(message)
true
} else {
Expand All @@ -79,20 +79,20 @@ class BridgeDelegate<D : BridgeDestination>(
// Lifecycle events

override fun onStart(owner: LifecycleOwner) {
logEvent("bridgeDestinationDidStart", resolvedLocation)
logDebug("bridgeDestinationDidStart", resolvedLocation)
destinationIsActive = true
activeComponents.forEach { it.didStart() }
}

override fun onStop(owner: LifecycleOwner) {
activeComponents.forEach { it.didStop() }
destinationIsActive = false
logEvent("bridgeDestinationDidStop", resolvedLocation)
logDebug("bridgeDestinationDidStop", resolvedLocation)
}

override fun onDestroy(owner: LifecycleOwner) {
destinationIsActive = false
logEvent("bridgeDestinationDidDestroy", resolvedLocation)
logDebug("bridgeDestinationDidDestroy", resolvedLocation)
}

// Retrieve component(s) by type
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package dev.hotwire.core.bridge

import dev.hotwire.core.logging.logError
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromJsonElement
Expand Down
15 changes: 6 additions & 9 deletions core/src/main/kotlin/dev/hotwire/core/config/HotwireConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import android.webkit.WebView
import dev.hotwire.core.bridge.BridgeComponent
import dev.hotwire.core.bridge.BridgeComponentFactory
import dev.hotwire.core.bridge.BridgeComponentJsonConverter
import dev.hotwire.core.logging.DefaultHotwireLogger
import dev.hotwire.core.logging.HotwireLogger
import dev.hotwire.core.turbo.config.PathConfiguration
import dev.hotwire.core.turbo.http.HotwireHttpClient
import dev.hotwire.core.turbo.offline.OfflineRequestHandler
import dev.hotwire.core.turbo.webview.HotwireWebView

Expand All @@ -32,16 +33,12 @@ class HotwireConfig internal constructor() {
var offlineRequestHandler: OfflineRequestHandler? = null

/**
* Enables/disables debug logging. This should be disabled in production environments.
* Disabled by default.
* Set a custom logger to handle debug messages.
*
* Important: You should not enable debug logging in production release builds.
* The default logger is [DefaultHotwireLogger] which prints logs to Logcat.
* If you'd like to change this behavior, provide your own implementation of [HotwireLogger].
*/
var debugLoggingEnabled = false
set(value) {
field = value
HotwireHttpClient.reset()
}
var logger: HotwireLogger = DefaultHotwireLogger

/**
* Enables/disables debugging of web contents loaded into WebViews.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import android.webkit.ValueCallback
import android.webkit.WebChromeClient.FileChooserParams
import androidx.activity.result.ActivityResult
import dev.hotwire.core.R
import dev.hotwire.core.logging.logError
import dev.hotwire.core.turbo.session.Session
import dev.hotwire.core.files.util.HOTWIRE_REQUEST_CODE_FILES
import dev.hotwire.core.files.util.HotwireFileProvider
import dev.hotwire.core.logging.logError
import dev.hotwire.core.turbo.session.Session
import dev.hotwire.core.turbo.util.dispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
Expand Down
58 changes: 30 additions & 28 deletions core/src/main/kotlin/dev/hotwire/core/logging/CoreLog.kt
Original file line number Diff line number Diff line change
@@ -1,45 +1,47 @@
package dev.hotwire.core.logging

import android.util.Log
import dev.hotwire.core.config.Hotwire
import okhttp3.logging.HttpLoggingInterceptor

internal object CoreLog {
private const val DEFAULT_TAG = "Hotwire-Core"

private val debugEnabled get() = Hotwire.config.debugLoggingEnabled

internal fun d(msg: String) = log(Log.DEBUG, msg)

internal fun w(msg: String) = log(Log.WARN, msg)

internal fun e(msg: String) = log(Log.ERROR, msg)
private const val DEFAULT_TAG = "Hotwire-Core"
private const val PAD_END_LENGTH = 35

private fun log(logLevel: Int, msg: String) {
when (logLevel) {
Log.DEBUG -> if (debugEnabled) Log.d(DEFAULT_TAG, msg)
Log.WARN -> Log.w(DEFAULT_TAG, msg)
Log.ERROR -> Log.e(DEFAULT_TAG, msg)
}
internal class HotwireHttpLogger : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Hotwire.config.logger.d(DEFAULT_TAG) { message }
}
}

private const val PAD_END_LENGTH = 35
internal fun logVerbose(event: String, details: String = "") {
Hotwire.config.logger.v(DEFAULT_TAG) {
"${"$event ".padEnd(PAD_END_LENGTH, '.')} [$details]"
}
}

internal fun logEvent(event: String, details: String = "") {
CoreLog.d("$event ".padEnd(PAD_END_LENGTH, '.') + " [$details]")
internal fun logDebug(event: String, details: String = "") {
Hotwire.config.logger.d(DEFAULT_TAG) {
"${"$event ".padEnd(PAD_END_LENGTH, '.')} [$details]"
}
}

internal fun logEvent(event: String, attributes: List<Pair<String, Any>>) {
val description = attributes.joinToString(prefix = "[", postfix = "]", separator = ", ") {
"${it.first}: ${it.second}"
internal fun logDebug(event: String, attributes: List<Pair<String, Any>>) {
Hotwire.config.logger.d(DEFAULT_TAG) {
val description = attributes.joinToString(prefix = "[", postfix = "]", separator = ", ") {
"${it.first}: ${it.second}"
}

"${"$event ".padEnd(PAD_END_LENGTH, '.')} $description"
}
CoreLog.d("$event ".padEnd(PAD_END_LENGTH, '.') + " $description")
}

internal fun logWarning(event: String, details: String) {
CoreLog.w("$event ".padEnd(PAD_END_LENGTH, '.') + " [$details]")
Hotwire.config.logger.w(DEFAULT_TAG) {
"${"$event ".padEnd(PAD_END_LENGTH, '.')} [$details]"
}
}

internal fun logError(event: String, error: Exception) {
CoreLog.e("$event: ${error.stackTraceToString()}")
}
internal fun logError(event: String, throwable: Throwable) {
Hotwire.config.logger.e(DEFAULT_TAG, throwable) {
"$event: ${throwable.stackTraceToString()}"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package dev.hotwire.core.logging

import android.util.Log

internal object DefaultHotwireLogger : HotwireLogger {
override var logLevel = HotwireLogLevel.NONE

override fun v(tag: String, msg: () -> String) {
if (logLevel.priority <= HotwireLogLevel.VERBOSE.priority) {
Log.v(tag, msg())
}
}

override fun d(tag: String, msg: () -> String) {
if (logLevel.priority <= HotwireLogLevel.DEBUG.priority) {
Log.d(tag, msg())
}
}

override fun i(tag: String, msg: () -> String) {
if (logLevel.priority <= HotwireLogLevel.INFO.priority) {
Log.i(tag, msg())
}
}

override fun w(tag: String, msg: () -> String) {
if (logLevel.priority <= HotwireLogLevel.WARN.priority) {
Log.w(tag, msg())
}
}

override fun e(tag: String, throwable: Throwable?, msg: () -> String) {
if (logLevel.priority <= HotwireLogLevel.ERROR.priority) {
Log.e(tag, msg(), throwable)
}
}
}
10 changes: 10 additions & 0 deletions core/src/main/kotlin/dev/hotwire/core/logging/HotwireLogLevel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dev.hotwire.core.logging

enum class HotwireLogLevel(val priority: Int) {
VERBOSE(0),
DEBUG(1),
INFO(2),
WARN(3),
ERROR(4),
NONE(5)
}
10 changes: 10 additions & 0 deletions core/src/main/kotlin/dev/hotwire/core/logging/HotwireLogger.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dev.hotwire.core.logging

interface HotwireLogger {
var logLevel: HotwireLogLevel
fun v(tag: String, msg: () -> String)
fun d(tag: String, msg: () -> String)
fun i(tag: String, msg: () -> String)
fun w(tag: String, msg: () -> String)
fun e(tag: String, throwable: Throwable?, msg: () -> String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package dev.hotwire.core.turbo.config
import android.content.Context
import com.google.gson.reflect.TypeToken
import dev.hotwire.core.logging.logError
import dev.hotwire.core.logging.logEvent
import dev.hotwire.core.logging.logDebug
import dev.hotwire.core.turbo.util.dispatcherProvider
import dev.hotwire.core.turbo.util.toObject
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -42,7 +42,7 @@ internal class PathConfigurationLoader(val context: Context) : CoroutineScope {
launch {
repository.getRemoteConfiguration(url, options)?.let { json ->
load(json)?.let {
logEvent("remotePathConfigurationLoaded", url)
logDebug("remotePathConfigurationLoaded", url)
onCompletion(it)
cacheConfigurationForUrl(url, it)
}
Expand All @@ -56,7 +56,7 @@ internal class PathConfigurationLoader(val context: Context) : CoroutineScope {
) {
val json = repository.getBundledConfiguration(context, filePath)
load(json)?.let {
logEvent("bundledPathConfigurationLoaded", filePath)
logDebug("bundledPathConfigurationLoaded", filePath)
onCompletion(it)
}
}
Expand All @@ -67,7 +67,7 @@ internal class PathConfigurationLoader(val context: Context) : CoroutineScope {
) {
repository.getCachedConfigurationForUrl(context, url)?.let { json ->
load(json)?.let {
logEvent("cachedPathConfigurationLoaded", url)
logDebug("cachedPathConfigurationLoaded", url)
onCompletion(it)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package dev.hotwire.core.turbo.http

import android.content.Context
import dev.hotwire.core.config.Hotwire
import dev.hotwire.core.logging.HotwireHttpLogger
import dev.hotwire.core.logging.logError
import okhttp3.Cache
import okhttp3.OkHttpClient
Expand All @@ -16,7 +16,7 @@ import java.util.concurrent.TimeUnit
object HotwireHttpClient {
private var cache: Cache? = null
private var httpCacheSize = 100L * 1024L * 1024L // 100 MBs
private val loggingInterceptor = HttpLoggingInterceptor().apply {
private val loggingInterceptor = HttpLoggingInterceptor(logger = HotwireHttpLogger()).apply {
level = HttpLoggingInterceptor.Level.BASIC
}

Expand Down Expand Up @@ -55,15 +55,12 @@ object HotwireHttpClient {
.connectTimeout(10L, TimeUnit.SECONDS)
.readTimeout(30L, TimeUnit.SECONDS)
.writeTimeout(30L, TimeUnit.SECONDS)
.addInterceptor(loggingInterceptor)

cache?.let {
builder.cache(it)
}

if (Hotwire.config.debugLoggingEnabled) {
builder.addInterceptor(loggingInterceptor)
}

return builder.build()
}
}
Loading