diff --git a/README.md b/README.md
index 913a876..a56f79e 100644
--- a/README.md
+++ b/README.md
@@ -223,8 +223,79 @@ val retrofit = Retrofit.Builder().build("your_baseUrl", okHttpClient gsonConvert
+### 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.
+
+
+
+
+```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")
+}
+
+```
+
+
+
+
+
+```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")
+)
+
+```
+
+
+
+
## 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.
diff --git a/sample/build.gradle b/sample/build.gradle
index 032a000..c48a70d 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -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 {
@@ -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")}\"")
}
}
}
@@ -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')
}
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index 6b20c2c..3c79e80 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -13,14 +13,27 @@
android:networkSecurityConfig="@xml/network_security_config"
tools:ignore="GoogleAppIndexingWarning">
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/kotlin/com/rakuten/tech/mobile/sdkutils/sample/EventLoggerActivity.kt b/sample/src/main/kotlin/com/rakuten/tech/mobile/sdkutils/sample/EventLoggerActivity.kt
new file mode 100644
index 0000000..4e1f67e
--- /dev/null
+++ b/sample/src/main/kotlin/com/rakuten/tech/mobile/sdkutils/sample/EventLoggerActivity.kt
@@ -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(eventTypeRadId).text.toString().lowercase()
+ private val infoString
+ get() = binding.addtnlInfoText.text.toString()
+ private val info: Map?
+ 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? {
+ val type = object : TypeToken>() {}.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("")
+}
diff --git a/sample/src/main/kotlin/com/rakuten/tech/mobile/sdkutils/sample/EventLoggerCacheActivity.kt b/sample/src/main/kotlin/com/rakuten/tech/mobile/sdkutils/sample/EventLoggerCacheActivity.kt
new file mode 100644
index 0000000..a4e4c23
--- /dev/null
+++ b/sample/src/main/kotlin/com/rakuten/tech/mobile/sdkutils/sample/EventLoggerCacheActivity.kt
@@ -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
+ }
+}
diff --git a/sample/src/main/kotlin/com/rakuten/tech/mobile/sdkutils/sample/MainActivity.kt b/sample/src/main/kotlin/com/rakuten/tech/mobile/sdkutils/sample/MainActivity.kt
index de63e7f..ac4800b 100644
--- a/sample/src/main/kotlin/com/rakuten/tech/mobile/sdkutils/sample/MainActivity.kt
+++ b/sample/src/main/kotlin/com/rakuten/tech/mobile/sdkutils/sample/MainActivity.kt
@@ -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
@@ -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)
@@ -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()
diff --git a/sample/src/main/res/layout/activity_event_logger.xml b/sample/src/main/res/layout/activity_event_logger.xml
new file mode 100644
index 0000000..433207b
--- /dev/null
+++ b/sample/src/main/res/layout/activity_event_logger.xml
@@ -0,0 +1,251 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/layout/activity_event_logger_cache.xml b/sample/src/main/res/layout/activity_event_logger_cache.xml
new file mode 100644
index 0000000..35d298f
--- /dev/null
+++ b/sample/src/main/res/layout/activity_event_logger_cache.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
index 117ca6b..8a320ff 100644
--- a/sample/src/main/res/layout/activity_main.xml
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -43,5 +43,14 @@
android:layout_height="wrap_content"
android:onClick="@{() -> activity.onAppInfoButtonClick()}"
android:text="Show App Info"/>
+
+
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
index 705be27..54b878f 100644
--- a/sample/src/main/res/values/styles.xml
+++ b/sample/src/main/res/values/styles.xml
@@ -3,6 +3,8 @@