Updated on 2026-08-14

This commit is contained in:
Tangem 2023-01-10 11:42:49 +03:00
parent 7794446857
commit 5d448b9500
87 changed files with 223 additions and 121 deletions

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.tangem.core.analytics" />

View file

@ -0,0 +1,92 @@
package com.tangem.core.analytics
import com.tangem.core.analytics.api.AnalyticsEventFilter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.AnalyticsFilterHolder
import com.tangem.core.analytics.api.AnalyticsHandler
import com.tangem.core.analytics.api.AnalyticsHandlerHolder
import com.tangem.core.analytics.api.ParamsInterceptor
import com.tangem.core.analytics.api.ParamsInterceptorHolder
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
import java.util.concurrent.Executors
/**
[REDACTED_AUTHOR]
*/
interface GlobalAnalyticsEventHandler : AnalyticsEventHandler,
AnalyticsHandlerHolder,
AnalyticsFilterHolder,
ParamsInterceptorHolder
object Analytics : GlobalAnalyticsEventHandler {
private val analyticsScope: CoroutineScope by lazy { createScope() }
private val handlers = mutableMapOf<String, AnalyticsHandler>()
private val paramsInterceptors = mutableMapOf<String, ParamsInterceptor>()
private val analyticsFilters = mutableSetOf<AnalyticsEventFilter>()
private val analyticsHandlers: List<AnalyticsHandler>
get() = handlers.values.toList()
override fun addHandler(name: String, handler: AnalyticsHandler) {
handlers[name] = handler
}
override fun removeHandler(name: String): AnalyticsHandler? {
return handlers.remove(name)
}
override fun addFilter(filter: AnalyticsEventFilter) {
analyticsFilters.add(filter)
}
override fun removeFilter(filter: AnalyticsEventFilter): Boolean {
return analyticsFilters.remove(filter)
}
override fun addParamsInterceptor(interceptor: ParamsInterceptor) {
paramsInterceptors[interceptor.id()] = interceptor
}
override fun removeParamsInterceptor(interceptor: ParamsInterceptor): ParamsInterceptor? {
return paramsInterceptors.remove(interceptor.id())
}
override fun send(event: AnalyticsEvent) {
analyticsScope.launch {
event.params = applyParamsInterceptors(event)
val eventFilter = analyticsFilters.firstOrNull { it.canBeAppliedTo(event) }
when {
eventFilter == null -> analyticsHandlers.forEach { handler -> handler.send(event) }
eventFilter.canBeSent(event) -> {
analyticsHandlers
.filter { handler -> eventFilter.canBeConsumedByHandler(handler, event) }
.forEach { handler -> handler.send(event) }
}
}
}
}
private fun applyParamsInterceptors(event: AnalyticsEvent): MutableMap<String, String> {
val interceptedParams = event.params.toMutableMap()
paramsInterceptors.values
.filter { it.canBeAppliedTo(event) }
.forEach { it.intercept(interceptedParams) }
return interceptedParams
}
private fun createScope(): CoroutineScope {
val name = "Analytics"
val dispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher()
val exHandler = FeatureCoroutineExceptionHandler.create(name)
return CoroutineScope(SupervisorJob() + dispatcher + CoroutineName(name) + exHandler)
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.core.analytics
/**
[REDACTED_AUTHOR]
*/
open class AnalyticsEvent(
val category: String,
val event: String,
var params: Map<String, String> = mapOf(),
val error: Throwable? = null,
var filterData: Any? = null,
)

View file

@ -0,0 +1,18 @@
package com.tangem.analytics.api
/**
[REDACTED_AUTHOR]
*/
interface EventLogger {
fun logEvent(
event: String,
params: Map<String, String> = emptyMap(),
)
}
interface ErrorEventLogger {
fun logErrorEvent(
error: Throwable,
params: Map<String, String> = emptyMap(),
)
}

View file

@ -0,0 +1,30 @@
package com.tangem.core.analytics.api
import com.tangem.core.analytics.AnalyticsEvent
/**
[REDACTED_AUTHOR]
* The filter is used to separate events to send and determine which handler it can be processed with.
*/
interface AnalyticsEventFilter {
/**
* Recognizes the event and checks if the given filter can be applied to the event.
*/
fun canBeAppliedTo(event: AnalyticsEvent): Boolean
/**
* An internal filter check that, on external or internal conditions, recognizes the possibility of
* sending an event.
*/
fun canBeSent(event: AnalyticsEvent): Boolean
/**
* Performs a check to see if the event can be dispatched by a specific handler
*/
fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean
}
interface AnalyticsFilterHolder {
fun addFilter(filter: AnalyticsEventFilter)
fun removeFilter(filter: AnalyticsEventFilter): Boolean
}

View file

@ -0,0 +1,34 @@
package com.tangem.core.analytics.api
import com.tangem.core.analytics.AnalyticsEvent
/**
[REDACTED_AUTHOR]
*/
interface AnalyticsEventHandler {
fun send(event: AnalyticsEvent)
}
interface AnalyticsHandler : AnalyticsEventHandler {
fun id(): String
fun send(event: String, params: Map<String, String> = emptyMap())
override fun send(event: AnalyticsEvent) {
send(prepareEventString(event), event.params)
}
fun prepareEventString(event: AnalyticsEvent): String = "[${event.category}] ${event.event}"
}
interface ErrorEventHandler {
fun send(
error: Throwable,
params: Map<String, String> = emptyMap(),
)
}
interface AnalyticsHandlerHolder {
fun addHandler(name: String, handler: AnalyticsHandler)
fun removeHandler(name: String): AnalyticsHandler?
}

View file

@ -0,0 +1,17 @@
package com.tangem.core.analytics.api
import com.tangem.core.analytics.AnalyticsEvent
/**
[REDACTED_AUTHOR]
*/
interface ParamsInterceptor {
fun id(): String
fun canBeAppliedTo(event: AnalyticsEvent): Boolean
fun intercept(params: MutableMap<String, String>)
}
interface ParamsInterceptorHolder {
fun addParamsInterceptor(interceptor: ParamsInterceptor)
fun removeParamsInterceptor(interceptor: ParamsInterceptor): ParamsInterceptor?
}

View file

@ -0,0 +1,20 @@
package com.tangem.core.analytics.di
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class AnalyticsModule {
@Singleton
@Provides
fun provideAnalyticsHandler(): AnalyticsEventHandler {
return Analytics //todo replace after refactoring calling Analytics in whole project
}
}