LLM-10123 Fix different device_id in FUS and ML v2 logs

Add a possibility for other recorders to have same `device_id` and `machine_id` as in `FUS` recorder.
This will greatly simplify the process of data analysis.
Applicable only for anonymized recorders.

(cherry picked from commit 5cee599f48d69ff77a006233b8103879597e8261)

GitOrigin-RevId: 18c9763ef8bebb874fa85e720c61c196ea8b9b92
This commit is contained in:
Daniil.Bubnov
2024-09-26 17:35:56 +02:00
committed by intellij-monorepo-bot
parent 578c631e2a
commit 7d57293565
5 changed files with 41 additions and 16 deletions

View File

@@ -20,7 +20,8 @@ class IJMapperEventLoggerProvider : StatisticsEventLoggerProvider(
sendFrequencyMs = TimeUnit.HOURS.toMillis(1), sendFrequencyMs = TimeUnit.HOURS.toMillis(1),
maxFileSizeInBytes = 1 * 512, // enough to fill a log file with one entry of the state collector maxFileSizeInBytes = 1 * 512, // enough to fill a log file with one entry of the state collector
sendLogsOnIdeClose = true, sendLogsOnIdeClose = true,
isCharsEscapingRequired = false isCharsEscapingRequired = false,
useDefaultRecorderId = true
) { ) {
/** /**

View File

@@ -21,7 +21,8 @@ class MLEventLoggerProvider : StatisticsEventLoggerProvider(
sendFrequencyMs = TimeUnit.MINUTES.toMillis(10), sendFrequencyMs = TimeUnit.MINUTES.toMillis(10),
maxFileSizeInBytes = 100 * 1024, maxFileSizeInBytes = 100 * 1024,
sendLogsOnIdeClose = true, sendLogsOnIdeClose = true,
isCharsEscapingRequired = false isCharsEscapingRequired = false,
useDefaultRecorderId = true
) { ) {
/** /**

View File

@@ -11,6 +11,19 @@ import com.intellij.testFramework.fixtures.BasePlatformTestCase
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
class EventLogConfigurationTest : BasePlatformTestCase() { class EventLogConfigurationTest : BasePlatformTestCase() {
// Test that two different recorders can have same machine/device id if the alternative recorder id is provided
fun testMachineIdDeviceIdAlternativeRecorder() {
val configuration = EventLogConfiguration.getInstance().getOrCreate("ABC", null)
val alternativeConfiguration = EventLogConfiguration.getInstance().getOrCreate("DEF", "ABC")
assertNotNull(configuration.deviceId)
assertNotNull(configuration.machineId)
assertEquals(configuration.deviceId, alternativeConfiguration.deviceId)
assertEquals(configuration.machineId, alternativeConfiguration.machineId)
}
fun testMachineIdRegeneration() { fun testMachineIdRegeneration() {
doTestRegenerate({ it.machineId }, hashMapOf(MACHINE_ID_SALT to "newSalt", doTestRegenerate({ it.machineId }, hashMapOf(MACHINE_ID_SALT to "newSalt",
MACHINE_ID_SALT_REVISION to "2")) MACHINE_ID_SALT_REVISION to "2"))

View File

@@ -80,6 +80,7 @@ class EventLogConfiguration {
} }
val defaultSessionId: String by lazy { generateSessionId() } val defaultSessionId: String by lazy { generateSessionId() }
internal fun generateSessionId(): String { internal fun generateSessionId(): String {
val presentableHour = StatisticsUtil.getCurrentHourInUTC() val presentableHour = StatisticsUtil.getCurrentHourInUTC()
return "$presentableHour-${UUID.randomUUID().toString().shortedUUID()}" return "$presentableHour-${UUID.randomUUID().toString().shortedUUID()}"
@@ -119,11 +120,12 @@ class EventLogConfiguration {
return if (str.endsWith(".")) str + "0" else str return if (str.endsWith(".")) str + "0" else str
} }
fun getOrCreate(recorderId: String): EventLogRecorderConfiguration { @JvmOverloads
fun getOrCreate(recorderId: String, alternativeRecorderId: String? = null): EventLogRecorderConfiguration {
if (isDefaultRecorderId(recorderId)) return defaultConfiguration if (isDefaultRecorderId(recorderId)) return defaultConfiguration
synchronized(this) { synchronized(this) {
return configurations.getOrPut(recorderId) { EventLogRecorderConfiguration(recorderId, this) } return configurations.getOrPut(recorderId) { EventLogRecorderConfiguration(recorderId = recorderId, eventLogConfiguration = this, alternativeRecorderId = alternativeRecorderId) }
} }
} }
@@ -149,9 +151,13 @@ class EventLogConfiguration {
} }
/**
* @param alternativeRecorderId - when provided, machine id and device id will be generated based on it instead of the `recorderId`
*/
class EventLogRecorderConfiguration internal constructor(private val recorderId: String, class EventLogRecorderConfiguration internal constructor(private val recorderId: String,
private val eventLogConfiguration: EventLogConfiguration, private val eventLogConfiguration: EventLogConfiguration,
val sessionId: String = generateSessionId()) { val sessionId: String = generateSessionId(),
val alternativeRecorderId: String? = null) {
val deviceId: String = getOrGenerateDeviceId() val deviceId: String = getOrGenerateDeviceId()
val bucket: Int = deviceId.asBucket() val bucket: Int = deviceId.asBucket()
@@ -167,11 +173,11 @@ class EventLogRecorderConfiguration internal constructor(private val recorderId:
init { init {
machineIdReference = AtomicLazyValue { machineIdReference = AtomicLazyValue {
val configOptions = EventLogConfigOptionsService.getInstance().getOptions(recorderId) val configOptions = EventLogConfigOptionsService.getInstance().getOptions(alternativeRecorderId ?: recorderId)
generateMachineId(configOptions.machineIdSalt, configOptions.machineIdRevision) generateMachineId(configOptions.machineIdSalt, configOptions.machineIdRevision)
} }
EventLogConfigOptionsService.TOPIC.subscribe(null, object : EventLogRecorderConfigOptionsListener(recorderId) { EventLogConfigOptionsService.TOPIC.subscribe(null, object : EventLogRecorderConfigOptionsListener(alternativeRecorderId ?: recorderId) {
override fun onMachineIdConfigurationChanged(salt: @Nullable String?, revision: Int) { override fun onMachineIdConfigurationChanged(salt: @Nullable String?, revision: Int) {
machineIdReference.updateAndGet { prevValue -> machineIdReference.updateAndGet { prevValue ->
if (salt != null && revision != -1 && revision > prevValue.revision) { if (salt != null && revision != -1 && revision > prevValue.revision) {
@@ -189,7 +195,7 @@ class EventLogRecorderConfiguration internal constructor(private val recorderId:
return MachineId.DISABLED return MachineId.DISABLED
} }
val revision = if (value >= 0) value else DEFAULT_ID_REVISION val revision = if (value >= 0) value else DEFAULT_ID_REVISION
val machineId = MachineIdManager.getAnonymizedMachineId("JetBrains$recorderId", salt) ?: return MachineId.UNKNOWN val machineId = MachineIdManager.getAnonymizedMachineId("JetBrains${alternativeRecorderId ?: recorderId}", salt) ?: return MachineId.UNKNOWN
return MachineId(machineId, revision) return MachineId(machineId, revision)
} }
@@ -219,17 +225,17 @@ class EventLogRecorderConfiguration internal constructor(private val recorderId:
private fun getOrGenerateDeviceId(): String { private fun getOrGenerateDeviceId(): String {
val app = ApplicationManager.getApplication() val app = ApplicationManager.getApplication()
if (app != null && app.isHeadlessEnvironment) { if (app != null && app.isHeadlessEnvironment) {
val property = eventLogConfiguration.getHeadlessDeviceIdProperty(recorderId) val property = eventLogConfiguration.getHeadlessDeviceIdProperty(alternativeRecorderId ?: recorderId)
System.getProperty(property)?.let { System.getProperty(property)?.let {
return it return it
} }
} }
try { try {
return DeviceIdManager.getOrGenerateId(object : DeviceIdManager.DeviceIdToken {}, recorderId) return DeviceIdManager.getOrGenerateId(object : DeviceIdManager.DeviceIdToken {}, alternativeRecorderId ?: recorderId)
} }
catch (e: DeviceIdManager.InvalidDeviceIdTokenException) { catch (_: DeviceIdManager.InvalidDeviceIdTokenException) {
EventLogConfiguration.LOG.warn("Failed retrieving device id for $recorderId") EventLogConfiguration.LOG.warn("Failed retrieving device id for ${alternativeRecorderId ?: recorderId}")
return UNDEFINED_DEVICE_ID return UNDEFINED_DEVICE_ID
} }
} }
@@ -237,13 +243,13 @@ class EventLogRecorderConfiguration internal constructor(private val recorderId:
private fun getOrGenerateSalt(): ByteArray { private fun getOrGenerateSalt(): ByteArray {
val app = ApplicationManager.getApplication() val app = ApplicationManager.getApplication()
if (app != null && app.isHeadlessEnvironment) { if (app != null && app.isHeadlessEnvironment) {
val property = eventLogConfiguration.getHeadlessSaltProperty(recorderId) val property = eventLogConfiguration.getHeadlessSaltProperty(alternativeRecorderId ?: recorderId)
System.getProperty(property)?.let { System.getProperty(property)?.let {
return it.toByteArray(Charsets.UTF_8) return it.toByteArray(Charsets.UTF_8)
} }
} }
return EventLogConfiguration.getOrGenerateSaltFromPrefs(recorderId) return EventLogConfiguration.getOrGenerateSaltFromPrefs(alternativeRecorderId ?: recorderId)
} }
/** /**

View File

@@ -29,12 +29,16 @@ interface StatisticsEventLogger {
fun rollOver() fun rollOver()
} }
/**
* [useDefaultRecorderId] - When enabled, device and machine ids would match FUS(default) recorder. Must NOT be enabled for non-anonymized recorders.
*/
abstract class StatisticsEventLoggerProvider(val recorderId: String, abstract class StatisticsEventLoggerProvider(val recorderId: String,
val version: Int, val version: Int,
val sendFrequencyMs: Long, val sendFrequencyMs: Long,
private val maxFileSizeInBytes: Int, private val maxFileSizeInBytes: Int,
val sendLogsOnIdeClose: Boolean = false, val sendLogsOnIdeClose: Boolean = false,
val isCharsEscapingRequired: Boolean = true) { val isCharsEscapingRequired: Boolean = true,
val useDefaultRecorderId: Boolean = false) {
@Deprecated(message = "Use primary constructor instead") @Deprecated(message = "Use primary constructor instead")
constructor(recorderId: String, constructor(recorderId: String,
@@ -128,7 +132,7 @@ abstract class StatisticsEventLoggerProvider(val recorderId: String,
null null
} }
val eventLogConfiguration = EventLogConfiguration.getInstance() val eventLogConfiguration = EventLogConfiguration.getInstance()
val config = eventLogConfiguration.getOrCreate(recorderId) val config = eventLogConfiguration.getOrCreate(recorderId, if (useDefaultRecorderId) "FUS" else null)
val writer = StatisticsEventLogFileWriter(recorderId, this, maxFileSizeInBytes, isEap, eventLogConfiguration.build) val writer = StatisticsEventLogFileWriter(recorderId, this, maxFileSizeInBytes, isEap, eventLogConfiguration.build)
val configService = EventLogConfigOptionsService.getInstance() val configService = EventLogConfigOptionsService.getInstance()