Skip to content

Commit

Permalink
feature(logger): added type & duration filters for logs (#312)
Browse files Browse the repository at this point in the history
Co-authored-by: Aastha <aastha@quiph.com>
  • Loading branch information
aasthajn and Aastha committed Apr 11, 2024
1 parent b4dfd8d commit f2fc28c
Show file tree
Hide file tree
Showing 16 changed files with 511 additions and 108 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.pluto.plugins.logger

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import com.pluto.plugins.logger.internal.LogTimeStamp
import com.pluto.plugins.logger.internal.LogType
import com.pluto.plugins.logger.internal.Session
import com.pluto.utilities.selector.SelectorOption

internal class FilterViewModel(application: Application) : AndroidViewModel(application) {
val selectedFiltersList: LiveData<List<LogType>>
get() = _selectedFiltersList
private val _selectedFiltersList = MutableLiveData<List<LogType>>()

val selectedTimeStamp: LiveData<LogTimeStamp>
get() = _selectedTimeStamp
private val _selectedTimeStamp = MutableLiveData<LogTimeStamp>()

val searchTextLogger: LiveData<String>
get() = _searchTextLogger
private val _searchTextLogger = MutableLiveData<String>()

private val preferences = Preferences(application)
private val logTypes = listOf(
LogType("debug"),
LogType("verbose"),
LogType("error"),
LogType("info"),
LogType("event")
)

private val timeStamps = listOf(
LogTimeStamp(1),
LogTimeStamp(5),
LogTimeStamp(10),
LogTimeStamp(Integer.MIN_VALUE, true)
)

val isTriggerSearch: LiveData<Boolean>
get() = _isTriggerSearch
private val _isTriggerSearch = MediatorLiveData<Boolean>()
val isFilterApplied: LiveData<Boolean>
get() = _isFilterApplied
private val _isFilterApplied = MediatorLiveData<Boolean>()

val isFilterVisible: LiveData<Boolean>
get() = _isFilterVisible
private val _isFilterVisible = MediatorLiveData<Boolean>()

init {

_isTriggerSearch.addSource(_selectedFiltersList) { _isTriggerSearch.postValue(true) }
_isTriggerSearch.addSource(_searchTextLogger) { _isTriggerSearch.postValue(true) }
_isTriggerSearch.addSource(_selectedTimeStamp) { _isTriggerSearch.postValue(true) }
_searchTextLogger.postValue(Session.loggerSearchText)
_selectedFiltersList.postValue(preferences.selectedFilterLogType)
_selectedTimeStamp.postValue(preferences.selectedFilterTime)
_isFilterApplied.addSource(_selectedFiltersList) {
if (it.isNotEmpty() || getSelectedTimeStamp().timeStamp != 0) {
_isFilterApplied.postValue(true)
} else {
_isFilterApplied.postValue(false)
}
}
_isFilterApplied.addSource(_selectedTimeStamp) {
if (it.timeStamp != 0 || getSelectedFilters().isNotEmpty()) {
_isFilterApplied.postValue(true)
} else {
_isFilterApplied.postValue(false)
}
}
_isFilterVisible.postValue(false)
}

fun getLogTypes(): List<SelectorOption> {
return logTypes
}

fun getTimeStamps(): List<SelectorOption> {
return timeStamps
}

fun getSelectedFilters(): List<LogType> {
return selectedFiltersList.value ?: emptyList()
}

fun getSelectedTimeStamp(): LogTimeStamp {
return selectedTimeStamp.value ?: LogTimeStamp(0, false)
}

fun updateSearchText(searchText: String) {
_searchTextLogger.postValue(searchText)
Session.loggerSearchText = searchText
}

fun setSelectedFiltersLogType(logTypeList: ArrayList<LogType>) {
_selectedFiltersList.postValue(logTypeList)
preferences.selectedFilterLogType = logTypeList
}
fun setSelectedFilterTimeStamp(logTimeStamp: LogTimeStamp) {
_selectedTimeStamp.postValue(logTimeStamp)
preferences.selectedFilterTime = logTimeStamp
}
fun getSearchText(): String {
return searchTextLogger.value ?: ""
}

fun toggleFilterViewVisibility() {
_isFilterVisible.postValue(_isFilterVisible.value?.not())
}
fun clearFilters() {
preferences.clearFilters()
_selectedFiltersList.postValue(emptyList())
_selectedTimeStamp.postValue(LogTimeStamp(0, false))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class PlutoLoggerPlugin() : Plugin(ID) {
@Deprecated("Use the default constructor PlutoLoggerPlugin() instead.")
constructor(identifier: String) : this()

private val settingsPreferences by lazy { Preferences(application) }

override fun getConfig(): PluginConfiguration = PluginConfiguration(
name = context.getString(R.string.pluto_logger___plugin_name),
icon = R.drawable.pluto_logger___ic_logger_icon,
Expand All @@ -35,6 +37,7 @@ class PlutoLoggerPlugin() : Plugin(ID) {

override fun onPluginDataCleared() {
LogDBHandler.flush()
settingsPreferences.clearFilters()
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.pluto.plugins.logger

import android.content.Context
import com.pluto.plugins.logger.internal.LogTimeStamp
import com.pluto.plugins.logger.internal.LogType
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types

internal class Preferences(context: Context) {

private val settingsPrefs by lazy { context.preferences("_pluto_log_filter_settings") }
private val moshi: Moshi = Moshi.Builder().build()
private val moshiAdapter: JsonAdapter<List<String>> =
moshi.adapter(Types.newParameterizedType(List::class.java, String::class.java))

private var timeStampAdapter: JsonAdapter<LogTimeStamp> =
moshi.adapter(LogTimeStamp::class.java)

internal var selectedFilterLogType: List<LogType>
get() = settingsPrefs.getString(SELECTED_FILTER_LOG_TYPE, null)?.let {
moshiAdapter.fromJson(it)?.map { type -> LogType(type) }
} ?: run { emptyList() }
set(value) = settingsPrefs.edit()
.putString(SELECTED_FILTER_LOG_TYPE, moshiAdapter.toJson(value.map { it.type })).apply()

internal var selectedFilterTime: LogTimeStamp
get() = settingsPrefs.getString(SELECTED_FILTER_TIMESTAMP, null)?.let {
timeStampAdapter.fromJson(it)
} ?: run { LogTimeStamp(0, false) }
set(value) = settingsPrefs.edit()
.putString(SELECTED_FILTER_TIMESTAMP, timeStampAdapter.toJson(value)).apply()

companion object {
private const val SELECTED_FILTER_LOG_TYPE = "selected_filter_logtype"
private const val SELECTED_FILTER_TIMESTAMP = "selected_filter_timestamp"
}

fun clearFilters() {
settingsPrefs.edit().clear().apply()
}
}

private fun Context.preferences(name: String, mode: Int = Context.MODE_PRIVATE) =
getSharedPreferences(name, mode)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.annotation.DrawableRes
import androidx.annotation.Keep
import com.pluto.plugins.logger.R
import com.pluto.utilities.list.ListItem
import com.pluto.utilities.selector.SelectorOption
import com.squareup.moshi.JsonClass

@Keep
Expand All @@ -20,7 +21,11 @@ internal open class Level(
object Warning : Level("warning")
object WTF : Level("wtf")
object Error : Level("error", R.color.pluto___red_05, R.color.pluto___red_80)
object Event : Level(label = "event", iconRes = R.drawable.pluto_logger___ic_analytics, textColor = R.color.pluto___blue)
object Event : Level(
label = "event",
iconRes = R.drawable.pluto_logger___ic_analytics,
textColor = R.color.pluto___blue
)
}

@Keep
Expand All @@ -47,3 +52,30 @@ internal data class StackTrace(
val fileName: String,
val lineNumber: Int,
)

@Keep
@JsonClass(generateAdapter = true)
internal data class LogType(
val type: String
) : SelectorOption() {
override fun displayText(): CharSequence {
return type
}
}

@Keep
@JsonClass(generateAdapter = true)
internal data class LogTimeStamp(
val timeStamp: Int = 0,
val isSessionFilter: Boolean = false
) : SelectorOption() {
override fun displayText(): CharSequence {
if (isSessionFilter) {
return "Current session only"
}
if (timeStamp == 1) {
return "< $timeStamp minute"
}
return "< $timeStamp minutes"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.pluto.plugins.logger.internal.persistence.LogDBHandler
import com.pluto.plugins.logger.internal.persistence.LogEntity
import com.pluto.utilities.extensions.asFormattedDate
import com.pluto.utilities.list.ListItem
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

Expand All @@ -27,13 +28,36 @@ internal class LogsViewModel(application: Application) : AndroidViewModel(applic
get() = _serializedLogs
private val _serializedLogs = MutableLiveData<String>()

fun fetch(search: String = "") {
fun searchAndFilter(
search: String = "",
logType: List<LogType> = emptyList(),
logTimeStamp: LogTimeStamp = LogTimeStamp(0, false)
) {
viewModelScope.launch(Dispatchers.IO) {
if (rawLogs == null) {
rawLogs = LogDBHandler.fetchAll()
}
val currentSessionLogs = (rawLogs ?: arrayListOf()).filter { it.sessionId == Session.id && it.data.isValidSearch(search) }.map { it.data }
val previousSessionLogs = (rawLogs ?: arrayListOf()).filter { it.sessionId != Session.id && it.data.isValidSearch(search) }.map { it.data }
val currentSessionLogs =
(rawLogs ?: arrayListOf())
.asSequence()
.filter { it.sessionId == Session.id }
.filter { pastTimeFilter(it.timestamp, logTimeStamp) }
.filter { logType.map { type -> type.type }.contains(it.data.tag) }
.filter { it.data.isValidSearch(search) }
.map { it.data }
.toList()

val previousSessionLogs = if (!logTimeStamp.isSessionFilter) {
(rawLogs ?: arrayListOf())
.asSequence()
.filter { it.sessionId != Session.id }
.filter { it.data.isValidSearch(search) }
.filter { pastTimeFilter(it.timestamp, logTimeStamp) }
.map { it.data }
.toList()
} else {
emptyList()
}

val list = arrayListOf<ListItem>()
list.addAll(currentSessionLogs)
Expand All @@ -45,6 +69,13 @@ internal class LogsViewModel(application: Application) : AndroidViewModel(applic
}
}

private fun pastTimeFilter(logTime: Long, log: LogTimeStamp): Boolean {
if (log.timeStamp == 0 || log.isSessionFilter) {
return true
}
return logTime >= System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(log.timeStamp.toLong())
}

fun deleteAll() {
viewModelScope.launch(Dispatchers.IO) {
LogDBHandler.flush()
Expand Down Expand Up @@ -93,5 +124,8 @@ internal class LogsViewModel(application: Application) : AndroidViewModel(applic
}
}

private fun LogData.isValidSearch(search: String): Boolean =
search.isEmpty() || tag.contains(search, true) || message.contains(search, true) || stackTrace.fileName.contains(search, true)
private fun LogData.isValidSearch(search: String): Boolean {
return search.isEmpty() ||
message.contains(search, true) ||
stackTrace.fileName.contains(search, true)
}
Loading

0 comments on commit f2fc28c

Please sign in to comment.