Skip to content

Commit

Permalink
feat: add EventLogger feature (SDKCF-6879)
Browse files Browse the repository at this point in the history
  • Loading branch information
maureenorea-clores authored Apr 15, 2024
1 parent 3046172 commit 281ee62
Show file tree
Hide file tree
Showing 32 changed files with 2,780 additions and 5 deletions.
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,79 @@ val retrofit = Retrofit.Builder().build("your_baseUrl", okHttpClient gsonConvert
</li>
</ul>

### Event Logger

A remote troubleshooting utility which helps to track or send important events to a remote API server.
This is intended to be used internally by Rakuten's SDKs.

<ul>
<li>

```configure```: Sets the server configuration and other initialization processing. Call this as early as possible in the application lifecycle, such as `onCreate` or other initialization methods. Calling other APIs without calling this API has no effect.

| Parameter | Description | Datatype | Required? |
|-----------| ------------------------ |----------|-----------|
| context | Application context | Context | `Yes` |
| apiUrl | Non-empty server API URL | String | `Yes` |
| apiKey | Non-empty server API Key | String | `Yes` |

```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
// API: configure
EventLogger.configure(this, "my-api-url", "my-api-key")
}

```

</li>

<li>

```send*Event```: Logs an event and sends to server based on criticality.

| Parameter | Description | Datatype | Required? |
|----------|---------|----------|-----------|
| sourceName | Non-empty source of the event, e.g. "sdkutils" | String | `Yes` |
| sourceVersion | Non-empty source' version, e.g. "2.0.0" | String | `Yes` |
| errorCode | Non-empty source' error code or HTTP backend response code e.g. "500" | String | `Yes` |
| errorMessage | Non-empty description of the error. Make it as descriptive as possible, for example, the stacktrace of an exception | String | `Yes` |
| info | Optional parameter to attach other useful key-value pair, such as filename, method, line number, etc. | Map | `No` |

```kotlin

// API: sendCriticalEvent
// Logs a critical event, wherein the unique event will be sent to the server immediately.
EventLogger.sendCriticalEvent(
sourceName = "sdkutils",
sourceVersion = "2.0.0",
errorCode = "XXX",
errorMessage = "java.lang.ArithmeticException: divide by zero
at com.rakuten.test.MainActivityFragment.onViewCreated(MainActivityFragment.kt:58)
at androidx.fragment.app.FragmentManagerImpl.ensureInflatedFragmentView(FragmentManagerImpl.java:1144)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:851)"
info = mapOf("filename" to "MyFile.kt")
)

// API: sendWarningEvent
// Logs a warning event, which will be sent at a later time based on TTL.
EventLogger.sendWarningEvent(
sourceName = "sdkutils",
sourceVersion = "2.0.0",
errorCode = "YYY",
errorMessage = "Incorrect credentials",
info = mapOf("filename" to "MyFile.kt")
)

```

</li>
<li>

## Changelog

### v2.2.0 (In-Progress)
* SDKCF-6859: Added `EventLogger` feature which sends events to a remote API server. It is intended to be used internally by Rakuten's SDKs. See [Event Logger](#event-logger) section for details.

### v2.1.1 (2022-08-04)
* SDKCF-5737: Reverted `Logger` constructor to single parameter.

Expand Down
14 changes: 14 additions & 0 deletions sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ android {
versionCode 1
versionName "1.0"
minSdkVersion 23

buildConfigField("String", "EVENT_LOGGER_API_URL_PRD", "\"${property("EVENT_LOGGER_API_URL_PRD")}\"")
buildConfigField("String", "EVENT_LOGGER_API_URL_STG", "\"${property("EVENT_LOGGER_API_URL_STG")}\"")
buildConfigField("String", "EVENT_LOGGER_API_KEY_PRD", "\"${property("EVENT_LOGGER_API_KEY_PRD")}\"")
buildConfigField("String", "EVENT_LOGGER_API_KEY_STG", "\"${property("EVENT_LOGGER_API_KEY_STG")}\"")
}

sourceSets.each {
Expand Down Expand Up @@ -43,10 +48,18 @@ android {
}

buildTypes {
debug {
buildConfigField("String", "EVENT_LOGGER_API_URL", "\"${property("EVENT_LOGGER_API_URL_STG")}\"")
buildConfigField("String", "EVENT_LOGGER_API_KEY", "\"${property("EVENT_LOGGER_API_KEY_STG")}\"")
}

release {
minifyEnabled true
debuggable false
signingConfig signingConfigs.release

buildConfigField("String", "EVENT_LOGGER_API_URL", "\"${property("EVENT_LOGGER_API_URL_PRD")}\"")
buildConfigField("String", "EVENT_LOGGER_API_KEY", "\"${property("EVENT_LOGGER_API_KEY_PRD")}\"")
}
}
}
Expand All @@ -56,6 +69,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta3'
implementation "com.squareup.okhttp3:okhttp:$CONFIG.versions.okhttp"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
implementation 'com.google.code.gson:gson:2.8.9'
implementation project(':sdk-utils')
}

Expand Down
17 changes: 15 additions & 2 deletions sample/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,27 @@
android:networkSecurityConfig="@xml/network_security_config"
tools:ignore="GoogleAppIndexingWarning">

<activity android:name=".MainActivity"
<activity
android:name=".MainActivity"
android:label="SDK Utils Sample"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

<activity
android:name=".EventLoggerActivity"
android:label="Event Logger"
android:exported="true">
</activity>

<activity
android:name=".EventLoggerCacheActivity"
android:label="Event Logger Cache"
android:exported="true">
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.rakuten.tech.mobile.sdkutils.sample

import android.content.Intent
import android.os.Bundle
import android.widget.RadioButton
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.google.gson.reflect.TypeToken
import com.rakuten.tech.mobile.sdkutils.eventlogger.EventLogger
import com.rakuten.tech.mobile.sdkutils.sample.databinding.ActivityEventLoggerBinding
import kotlin.random.Random

@Suppress(
"UndocumentedPublicClass",
"UndocumentedPublicFunction",
"MagicNumber",
"TooManyFunctions"
)
class EventLoggerActivity : AppCompatActivity() {

private lateinit var binding: ActivityEventLoggerBinding
private val sdkName
get() = binding.sdkNameText.text.toString().ifEmpty { "sdkutils" }
private val sdkVersion
get() = binding.sdkVerText.text.toString().ifEmpty { com.rakuten.tech.mobile.sdkutils.BuildConfig.VERSION_NAME }
private val errorCode
get() = binding.errorCodeText.text.toString()
private val errorMessage
get() = binding.errorMsgText.text.toString()
private val numTimes
get() = binding.numTimesText.text.toString().toIntOrNull() ?: 1
private val eventTypeRadId
get() = binding.eventTypeRadioGrp.checkedRadioButtonId
private val eventType
get() = findViewById<RadioButton>(eventTypeRadId).text.toString().lowercase()
private val infoString
get() = binding.addtnlInfoText.text.toString()
private val info: Map<String, String>?
get() = if (infoString.isEmpty()) null else jsonStringToMap(infoString)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = DataBindingUtil.setContentView(this, R.layout.activity_event_logger)
binding.activity = this
setDefaultsOrHints()

EventLogger.configure(this, BuildConfig.EVENT_LOGGER_API_URL, BuildConfig.EVENT_LOGGER_API_KEY)
}

fun onLogEventButtonClick() {
logEvent()
}

fun onLogUniqueEventButtonClick() {
logEvent(true)
}

fun onCustomButtonClick() {
binding.errorMsgText.setText("")
}

fun onException1ButtonClick() {
binding.errorMsgText.setText(
ArithmeticException().stackTraceToString().take(1000)
)
}

fun onException2ButtonClick() {
binding.errorMsgText.setText(
IllegalArgumentException("Testing").stackTraceToString().take(1000)
)
}

fun onShowEventsCacheClick() {
val intent = Intent(this, EventLoggerCacheActivity::class.java)
this.startActivity(intent)
}

private fun setDefaultsOrHints() {
binding.apply {
sdkNameText.setText("sdkutils")
sdkVerText.setText(com.rakuten.tech.mobile.sdkutils.BuildConfig.VERSION_NAME)
numTimesText.setText("1")
addtnlInfoText.hint = """{ "key": "value" }"""
}
}

@SuppressWarnings("LongMethod")
private fun logEvent(randomizeMessage: Boolean = false) {
when (eventType) {
"critical" -> repeat(numTimes) {
EventLogger.sendCriticalEvent(
sourceName = sdkName,
sourceVersion = sdkVersion,
errorCode = errorCode,
errorMessage = if (randomizeMessage) randomizeString() else errorMessage,
info = info
)
}
"warning" -> repeat(numTimes) {
EventLogger.sendWarningEvent(
sourceName = sdkName,
sourceVersion = sdkVersion,
errorCode = errorCode,
errorMessage = if (randomizeMessage) randomizeString() else errorMessage,
info = info
)
}
}

Toast.makeText(this, "Processed!", Toast.LENGTH_SHORT).show()
}

@SuppressWarnings("SwallowedException")
private fun jsonStringToMap(jsonString: String): Map<String, String>? {
val type = object : TypeToken<Map<String, String>>() {}.type
return try {
Gson().fromJson(jsonString, type)
} catch (e: JsonSyntaxException) {
Toast.makeText(this, "Not a valid Json representation!", Toast.LENGTH_SHORT).show()
return null
}
}

private fun randomizeString(length: Int = 20) = (1..length)
.map { Random.nextInt(33, 127).toChar() } // Ascii alphanumeric + some special characters range
.joinToString("")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.rakuten.tech.mobile.sdkutils.sample

import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.rakuten.tech.mobile.sdkutils.sample.databinding.ActivityEventLoggerCacheBinding
import org.json.JSONObject

@Suppress(
"UndocumentedPublicClass",
"MagicNumber"
)
class EventLoggerCacheActivity : AppCompatActivity() {

private lateinit var binding: ActivityEventLoggerCacheBinding
private lateinit var eventsCache: SharedPreferences

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = DataBindingUtil.setContentView(this, R.layout.activity_event_logger_cache)
eventsCache = this
.getSharedPreferences("com.rakuten.tech.mobile.sdkutils.eventlogger.events", Context.MODE_PRIVATE)
setCacheText()
}

private fun setCacheText() {

if (eventsCache.all.isEmpty()) {
binding.numEventsLabel.text = "Count: 0"
return
}

val textBuilder = StringBuilder(0)
val allEvents = eventsCache.all
binding.numEventsLabel.text = "Count: ${allEvents.size}"
for (event in allEvents) {
textBuilder.append(event.key)
textBuilder.append("\n")
textBuilder.append(
JSONObject(event.value.toString()).toString(4)
)
textBuilder.append("\n\n\n")
}

binding.eventsStorageText.text = textBuilder
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.rakuten.tech.mobile.sdkutils.sample

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.rakuten.tech.mobile.sdkutils.PreferencesUtil
import com.rakuten.tech.mobile.sdkutils.logger.Logger
Expand All @@ -20,7 +21,7 @@ import okhttp3.Response
import java.util.Date

@Suppress("UndocumentedPublicClass", "UndocumentedPublicFunction", "SpreadOperator")
class MainActivity : Activity() {
class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding
private val log = Logger(MainActivity::class.java.simpleName)
Expand Down Expand Up @@ -75,6 +76,11 @@ class MainActivity : Activity() {
})
}

fun onEventLoggerButtonClick() {
val intent = Intent(this, EventLoggerActivity::class.java)
this.startActivity(intent)
}

private fun showToast(message: String) =
Toast.makeText(this, message, Toast.LENGTH_SHORT)
.show()
Expand Down
Loading

0 comments on commit 281ee62

Please sign in to comment.