Closed mariusgherman closed 4 weeks ago
Thank you for choosing Amplitude. We are actively addressing this issue and will keep you updated on any progress.
Hi @mariusgherman, did you have this issue with earlier versions or it starts happening after 1.10.2? I set up a local example and send several events. All events were sent successfully. Not sure how you observe stored events JSON string and sent HTTP payloads. Could you elaborate on it?
@Mercy811 unfortunately i have only used the 1.10.2 (i will try with earlier versions to confirm and post my findings). For HTTP content i setup a localhost simple server that does JSON payload validation and payload logging. I used it as the SDK Configuration#serverUrl and posted events to it.
@Mercy811 i've just tried with SDK version 1.10.1. I'm not seeing anymore malformed events in the server logs.
However, i've set up a SDK Logger using Configuration#loggerProvider
and i'm seeing a few error messages Error when upload event: End of input at character 0 of
Hi @mariusgherman, thanks for your clarification and confirmation. I will investigate more.
Hi @mariusgherman, would like to keep you updated. v1.20.2 starts migrating legacy data. So it’s likely that something wrong in this PR.
Error when upload event: End of input at character 0 of It is probably caused by
getEventsString
returning an invalid JSON string.
Thanks for the info you provided. They really help me narrow the bug down. I will continue working on this.
@mariusgherman Did you use the legacy SDK on the same device?
Hi @mariusgherman is it possible you are creating/using multiple instances of the Amplitude()
? We noticed the event_id's are out of order 69,37,70,38 which makes us think there could be 2 instances of Amplitude sharing the same storage.
@mariusgherman Did you use the legacy SDK on the same device?
we are in the process of upgrading our amplitude legacy SDK to the kotlin SDK, so yes, this will be the production use case indeed
Hi @mariusgherman is it possible you are creating/using multiple instances of the
Amplitude()
? We noticed the event_id's are out of order 69,37,70,38 which makes us think there could be 2 instances of Amplitude sharing the same storage.
Not the case. We are ensuring that we have a single instance of Amplitude though usage of Dagger injection scoping.Could it be the case that we are logging multiple events at the same time from different threads ?
Thanks @mariusgherman.
logging multiple events at the same time from different threads
I don't think this is the case the provided output. A single instance should maintain a single "latestEvenId" that is used to increate the event_id
's. So I think in the multithread case both would read the same value latestEvenId, and the last set lastEventId would win vs having multiple intermixed eventIds.
We noticed each pair has the same event_type, event_properties, timestamp (+/- 1 millisecond), new library, etc - except insert_id. I think, it is highly unlikely that events 37, 38, 40 were migrated from old SDK - in this case they would have legacy library and old timestamps.
Can you please share the Dagger configuration you are using to enforce the single instance of Amplitude?
We also notice a difference in casing (Screen Name/ Screen name) in event_properties for events 72/40, maybe that can help locate where each is being tracked.
@justin-fiedler these are the relevant files where we have declared the analytics instance and the dagger module where we provide the Amplitude single instance wrapper
class AmplitudeAnalyticsProvider(
context: Context,
private val amplitudeConfigProvider: AmplitudeConfigProvider,
coroutineDispatcher: CoroutineDispatcher
) {
private val coroutineScope = CoroutineScope(coroutineDispatcher + SupervisorJob())
private val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
Log.w("Amplitude Config", "Failed to configure device id for amplitude analytics", throwable)
}
internal val amplitudeClient by lazy {
Amplitude(
Configuration(
context = context.applicationContext,
apiKey = amplitudeConfigProvider.getKeyValue(),
serverUrl = amplitudeConfigProvider.getServerUrl(),
trackingSessionEvents = true,
defaultTracking = DefaultTrackingOptions.ALL,
loggerProvider = object : LoggerProvider {
override fun getLogger(amplitude: com.amplitude.core.Amplitude): Logger =
AmplitudeLogger(if (BuildConfig.DEBUG) Logger.LogMode.DEBUG else Logger.LogMode.INFO)
}
)
)
}
fun initialize() {
coroutineScope.launch(coroutineExceptionHandler) {
val deviceId = amplitudeConfigProvider.getDeviceId()
amplitudeClient.setDeviceId(deviceId)
}
}
fun reset() {
amplitudeClient.reset()
}
fun setUserId(userId: String?) {
amplitudeClient.setUserId(userId)
}
fun identify(userProperties: Map<String, Any?>) {
amplitudeClient.identify(userProperties)
}
fun setGroup(groupType: String, groupName: String, groupProperties: Map<String, Any?> = mutableMapOf()) {
amplitudeClient.groupIdentify(groupType, groupName, groupProperties)
}
}
class AmplitudeLogger(override var logMode: Logger.LogMode) : Logger {
override fun debug(message: String) {
Log.d(TAG, message)
}
override fun error(message: String) {
Log.e(TAG, message)
}
override fun info(message: String) {
Log.i(TAG, message)
}
override fun warn(message: String) {
Log.w(TAG, message)
}
}
@Module
class AnalyticsModule {
@ApplicationScope
@Provides
fun providesAmplitudeAnalyticsProvider(
context: Context,
amplitudeConfigProvider: AmplitudeConfigProvider,
coroutineDispatcher: Dispatchers.Default
): AmplitudeAnalyticsProvider {
return AmplitudeAnalyticsProvider(context, amplitudeConfigProvider, coroutineDispatcher)
}
...
}
Thanks @mariusgherman, could you add some logging to confirm that only a single instance is created?
internal val amplitudeClient by lazy {
// LOG 1
Amplitude(
Configuration(
context = context.applicationContext,
apiKey = amplitudeConfigProvider.getKeyValue(),
serverUrl = amplitudeConfigProvider.getServerUrl(),
trackingSessionEvents = true,
defaultTracking = DefaultTrackingOptions.ALL,
loggerProvider = object : LoggerProvider {
override fun getLogger(amplitude: com.amplitude.core.Amplitude): Logger =
AmplitudeLogger(if (BuildConfig.DEBUG) Logger.LogMode.DEBUG else Logger.LogMode.INFO)
}
)
)
}
@ApplicationScope
@Provides
fun providesAmplitudeAnalyticsProvider(
context: Context,
amplitudeConfigProvider: AmplitudeConfigProvider,
coroutineDispatcher: Dispatchers.Default
): AmplitudeAnalyticsProvider {
// LOG 2
return AmplitudeAnalyticsProvider(context, amplitudeConfigProvider, coroutineDispatcher)
}
@justin-fiedler sorry for the late reply so the code looks now like this
class AmplitudeAnalyticsProvider(
context: Context,
private val amplitudeConfigProvider: AmplitudeConfigProvider,
coroutineDispatcher: CoroutineDispatcher
) {
private val coroutineScope = CoroutineScope(coroutineDispatcher + SupervisorJob())
private val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
Log.w("Amplitude Config", "Failed to configure device id for amplitude analytics", throwable)
}
init {
Log.w("AMPLITUDE_TAG", "Wrapper instance init hashcode ${this@AmplitudeAnalyticsProvider.hashCode()}")
}
internal val amplitudeClient by lazy {
Log.w("AMPLITUDE_TAG", "Wrapper instance lazy hashcode ${this@AmplitudeAnalyticsProvider.hashCode()}")
val client = Amplitude(
Configuration(
context = context.applicationContext,
apiKey = amplitudeConfigProvider.getKeyValue(),
trackingSessionEvents = true,
defaultTracking = DefaultTrackingOptions.ALL,
loggerProvider = object : LoggerProvider {
override fun getLogger(amplitude: Amplitude): Logger =
AmplitudeLogger(if (BuildConfig.DEBUG) Logger.LogMode.DEBUG else Logger.LogMode.INFO)
}
)
)
client
}
@ApplicationScope
@Provides
fun providesAmplitudeAnalyticsProvider(
context: Context,
amplitudeConfigProvider: AmplitudeConfigProvider,
coroutineDispatcher: Dispatchers.Default
): AmplitudeAnalyticsProvider {
val amplitudeAnalyticsProvider = AmplitudeAnalyticsProvider(context, amplitudeConfigProvider, coroutineDispatcher)
Log.w("AMPLITUDE_TAG", "Wrapper instance dagger hashcode ${amplitudeAnalyticsProvider.hashCode()}")
return amplitudeAnalyticsProvider
}
and here is the logcat output
Hey @justin-fiedler @yuhao900914 @Mercy811 Are there any updates regarding this. I'm facing this issue on 1.10.4 as well and it is impacting our customers. I don't see any updates on the release notes regarding this fix either. Will updating the SDK help with this issue?
Alternatively it would be great if we could have an option in the SDK to ignore very old events or even clear the old events from the system. If there already is, could you please let me know, it would be greatly appreciated
@ajay-nagarajan this issue is fixed in our newer SDK versions. We validate the data to make sure its a valid json before we send it over to Amplitude. So this should no longer be happening.
Expected Behavior
Generated events JSON should be valid
Current Behavior
Generated events JSON is invalid
Malformed JSON string #1
Malformed JSON string example #2
Possible Solution
com.amplitude.android.utilities.AndroidStorage
implementationSteps to Reproduce
Environment