diff --git a/src/main/kotlin/com/statsig/sdk/Evaluator.kt b/src/main/kotlin/com/statsig/sdk/Evaluator.kt index 9481a80..3b2349b 100644 --- a/src/main/kotlin/com/statsig/sdk/Evaluator.kt +++ b/src/main/kotlin/com/statsig/sdk/Evaluator.kt @@ -43,6 +43,7 @@ internal class Evaluator( private var layerOverrides: MutableMap> = HashMap() private var hashLookupTable: MutableMap = HashMap() private val gson = Utils.getGson() + private val logger = options.customLogger private val calendarOne = Calendar.getInstance() private val calendarTwo = Calendar.getInstance() @@ -245,12 +246,14 @@ internal class Evaluator( if (specStore.getEvaluationReason() == EvaluationReason.UNINITIALIZED) { ctx.evaluation.evaluationDetails = createEvaluationDetails(EvaluationReason.UNINITIALIZED) + logger.debug("SpecStore is uninitialized, returning UNINITIALIZED evaluation for layer: $layerName") return } val layer = specStore.getLayerConfig(layerName) if (layer == null) { ctx.evaluation = this.getUnrecognizedEvaluation() + logger.debug("Layer not found: $layerName, returning unrecognized evaluation") return } this.evaluateLayer(ctx, layer) @@ -271,12 +274,14 @@ internal class Evaluator( } if (specStore.getEvaluationReason() == EvaluationReason.UNINITIALIZED) { + logger.debug("SpecStore is uninitialized, returning UNINITIALIZED evaluation for gate: $gateName") ctx.evaluation.evaluationDetails = createEvaluationDetails(EvaluationReason.UNINITIALIZED) return } val gate = specStore.getGate(gateName) if (gate == null) { + logger.debug("Gate not found: $gateName, returning unrecognized evaluation") ctx.evaluation = this.getUnrecognizedEvaluation() return } @@ -308,6 +313,7 @@ internal class Evaluator( val stickyValues = userPersistedValues[config.name] if (stickyValues != null) { + logger.debug("Sticky Evaluation found for experiment: ${config.name} with value: $stickyValues") val stickyEvaluation = ConfigEvaluation.fromStickyValues(stickyValues, this.specStore.getInitTime()) ctx.evaluation = stickyEvaluation return @@ -337,6 +343,7 @@ internal class Evaluator( } val stickyValues = userPersistedValues[config.name] if (stickyValues != null) { + logger.debug("Sticky Evaluation found for layer: ${config.name} with value: $stickyValues") val stickyEvaluation = ConfigEvaluation.fromStickyValues(stickyValues, this.specStore.getInitTime()) val delegate = stickyEvaluation.configDelegate val delegateSpec = if (delegate != null) this.specStore.getConfig(delegate) else null @@ -370,6 +377,7 @@ internal class Evaluator( ctx.evaluation.evaluationDetails = createEvaluationDetails(specStore.getEvaluationReason()) if (!config.enabled) { + logger.debug("${config.name} is not enabled.") ctx.evaluation.booleanValue = false ctx.evaluation.jsonValue = config.defaultValue ctx.evaluation.ruleID = Const.DISABLED @@ -482,7 +490,7 @@ internal class Evaluator( conditionFromString(condition.type.lowercase()) } catch (e: java.lang.IllegalArgumentException) { errorBoundary.logException("evaluateCondition:condition", e) - options.customLogger.warning("[Statsig]: An exception was caught: $e") + logger.error("An exception was caught when evaluating conditions: $e") null } @@ -983,7 +991,7 @@ internal class Evaluator( false } catch (e: Exception) { errorBoundary.logException("versionCompareHelper", e) - options.customLogger.warning("[Statsig]: An exception was caught: $e") + logger.warn("An exception was caught: $e") false } } diff --git a/src/main/kotlin/com/statsig/sdk/SpecStore.kt b/src/main/kotlin/com/statsig/sdk/SpecStore.kt index b3ebdd6..f2aa7b8 100644 --- a/src/main/kotlin/com/statsig/sdk/SpecStore.kt +++ b/src/main/kotlin/com/statsig/sdk/SpecStore.kt @@ -194,7 +194,7 @@ internal class SpecStore( configString = gson.toJson(configSpecs) } catch (e: Exception) { errorBoundary.logException("fireRulesUpdatedCallback", e) - options.customLogger.warning("[Statsig]: An exception was caught: $e") + options.customLogger.error("An exception was caught when fire callback: $e") } if (configString.isEmpty()) { @@ -335,7 +335,7 @@ internal class SpecStore( ) } catch (e: Exception) { errorBoundary.logException("downloadIDList", e) - options.customLogger.warning("[Statsig]: An exception was caught: $e") + options.customLogger.error("An exception was caught when downloading ID lists: $e") maybeDiagnostics?.markEnd( KeyType.GET_ID_LIST, false, diff --git a/src/main/kotlin/com/statsig/sdk/SpecUpdater.kt b/src/main/kotlin/com/statsig/sdk/SpecUpdater.kt index be9268b..c32da6d 100644 --- a/src/main/kotlin/com/statsig/sdk/SpecUpdater.kt +++ b/src/main/kotlin/com/statsig/sdk/SpecUpdater.kt @@ -29,6 +29,7 @@ internal class SpecUpdater( private var idListCallback: suspend (config: Map) -> Unit = { } private var backgroundDownloadConfigs: Job? = null private var backgroundDownloadIDLists: Job? = null + private val logger = options.customLogger private val gson = Utils.getGson() private inline fun Gson.fromJson(json: String) = fromJson(json, object : TypeToken() {}.type) @@ -149,7 +150,7 @@ internal class SpecUpdater( return Pair(gson.fromJson(specs, APIDownloadedConfigs::class.java), null) } catch (e: JsonSyntaxException) { errorBoundary.logException("parseConfigSpecs", e) - options.customLogger.warning("[Statsig]: An exception was caught: $e") + logger.error("An exception was caught when parsing config specs: $e") return Pair(null, FailureDetails(FailureReason.PARSE_RESPONSE_ERROR, exception = e)) } } @@ -166,7 +167,7 @@ internal class SpecUpdater( return Pair(configs, null) } catch (e: Exception) { errorBoundary.logException("downloadConfigSpecs", e) - options.customLogger.warning("[Statsig]: An exception was caught: $e") + logger.warn("An exception was caught: $e") return Pair(null, FailureDetails(FailureReason.PARSE_RESPONSE_ERROR, exception = e)) } } @@ -179,7 +180,7 @@ internal class SpecUpdater( return gson.fromJson>(lists) } catch (e: JsonSyntaxException) { errorBoundary.logException("parseIDLists", e) - options.customLogger.warning("[Statsig]: An exception was caught: $e") + logger.warn("An exception was caught when parsing ID lists: $e") } return null } diff --git a/src/main/kotlin/com/statsig/sdk/Statsig.kt b/src/main/kotlin/com/statsig/sdk/Statsig.kt index 87f1562..25fc6be 100644 --- a/src/main/kotlin/com/statsig/sdk/Statsig.kt +++ b/src/main/kotlin/com/statsig/sdk/Statsig.kt @@ -752,9 +752,9 @@ class Statsig { val initialized = isInitialized() if (!initialized) { if (::statsigServer.isInitialized) { - statsigServer.getCustomLogger().warning("Call and wait for initialize to complete before calling SDK methods.") + statsigServer.getCustomLogger().warn("Call and wait for initialize to complete before calling SDK methods.") } else { - println("Call and wait for initialize to complete before calling SDK methods.") + println("[Statsig] Call and wait for initialize to complete before calling SDK methods.") // this happened before we init so use println } } return initialized diff --git a/src/main/kotlin/com/statsig/sdk/StatsigLogger.kt b/src/main/kotlin/com/statsig/sdk/StatsigLogger.kt index e0872c5..26d1166 100644 --- a/src/main/kotlin/com/statsig/sdk/StatsigLogger.kt +++ b/src/main/kotlin/com/statsig/sdk/StatsigLogger.kt @@ -57,6 +57,8 @@ internal class StatsigLogger( private val gson = Utils.getGson() internal var diagnostics: Diagnostics? = null private var eventQueueSize: Int? = null + private val logger = statsigOptions.customLogger + fun log(event: StatsigEvent) { if (statsigOptions.disableAllLogging) { return @@ -208,6 +210,7 @@ internal class StatsigLogger( addDiagnostics(ContextType.API_CALL) addDiagnostics(ContextType.GET_CLIENT_INITIALIZE_RESPONSE) if (events.size() == 0) { + logger.debug("Event queue is empty.") return@withContext } diff --git a/src/main/kotlin/com/statsig/sdk/StatsigOptions.kt b/src/main/kotlin/com/statsig/sdk/StatsigOptions.kt index d53e60e..494bd88 100644 --- a/src/main/kotlin/com/statsig/sdk/StatsigOptions.kt +++ b/src/main/kotlin/com/statsig/sdk/StatsigOptions.kt @@ -5,19 +5,89 @@ import com.statsig.sdk.datastore.IDataStore import com.statsig.sdk.network.STATSIG_API_URL_BASE import com.statsig.sdk.persistent_storage.IUserPersistentStorage import com.statsig.sdk.persistent_storage.UserPersistedValues +import java.time.Instant +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit private const val TIER_KEY: String = "tier" private const val DEFAULT_INIT_TIME_OUT_MS: Long = 3000L private const val CONFIG_SYNC_INTERVAL_MS: Long = 10 * 1000 private const val ID_LISTS_SYNC_INTERVAL_MS: Long = 60 * 1000 + +enum class LogLevel(val value: Int) { + NONE(0), + DEBUG(1), + INFO(2), + WARN(3), + ERROR(4); + + fun getLevelString(): String { + return when (this) { + ERROR -> "ERROR" + WARN -> "WARN" + INFO -> "INFO" + DEBUG -> "DEBUG" + NONE -> "" + } + } +} + +object OutputLogger { + var logLevel: LogLevel = LogLevel.WARN + + fun error(message: String) { + logMessage(LogLevel.ERROR, message) + } + + fun warn(message: String) { + logMessage(LogLevel.WARN, message) + } + + fun info(message: String) { + logMessage(LogLevel.INFO, message) + } + + fun debug(message: String) { + logMessage(LogLevel.DEBUG, message) + } + + private fun logMessage(level: LogLevel, message: String) { + if (level.value < logLevel.value) return + + val timestamp = DateTimeFormatter.ISO_INSTANT.format(Instant.now().truncatedTo(ChronoUnit.MILLIS)) + println("$timestamp ${level.getLevelString()} [Statsig] $message") + } +} + +// Example Logger Interface you might use +interface LoggerInterface { + fun error(message: String) + fun warn(message: String) + fun info(message: String) + fun debug(message: String) + fun setLogLevel(level: LogLevel) +} + +// Example implementation of LoggerInterface private val defaultLogger = object : LoggerInterface { + override fun error(message: String) { + OutputLogger.error(message) + } - override fun warning(message: String) { - println(message) + override fun warn(message: String) { + OutputLogger.warn(message) } override fun info(message: String) { - println(message) + OutputLogger.info(message) + } + + override fun debug(message: String) { + OutputLogger.debug(message) + } + + override fun setLogLevel(level: LogLevel) { + OutputLogger.logLevel = LogLevel.WARN } } @@ -29,11 +99,6 @@ fun interface RulesUpdatedCallback { fun accept(rules: String) } -interface LoggerInterface { - fun warning(message: String) - fun info(message: String) -} - /** * An object of properties for initializing the sdk with advanced options * @property api the api endpoint to use for initialization and logging diff --git a/src/main/kotlin/com/statsig/sdk/StatsigServer.kt b/src/main/kotlin/com/statsig/sdk/StatsigServer.kt index f7bb2e4..5ed5fa5 100644 --- a/src/main/kotlin/com/statsig/sdk/StatsigServer.kt +++ b/src/main/kotlin/com/statsig/sdk/StatsigServer.kt @@ -271,6 +271,7 @@ private class StatsigServerImpl() : private lateinit var logger: StatsigLogger private lateinit var evaluator: Evaluator private lateinit var diagnostics: Diagnostics + private lateinit var outputLogger: LoggerInterface private var options: StatsigOptions = StatsigOptions() private val mutex = Mutex() private val statsigMetadata = StatsigMetadata() @@ -290,6 +291,7 @@ private class StatsigServerImpl() : statsigScope = CoroutineScope(statsigJob + coroutineExceptionHandler) transport = StatsigTransport(serverSecret, options, statsigMetadata, statsigScope, errorBoundary, sdkConfigs) logger = StatsigLogger(statsigScope, transport, statsigMetadata, options, sdkConfigs) + options.customLogger.also { outputLogger = it } this.options = options } @@ -307,6 +309,7 @@ private class StatsigServerImpl() : { mutex.withLock { // Prevent multiple coroutines from calling this at once. if (this::evaluator.isInitialized && evaluator.isInitialized) { + outputLogger.warn("Cannot re-initialize server that has shutdown. Please recreate the server connection.") throw StatsigIllegalStateException( "Cannot re-initialize server that has shutdown. Please recreate the server connection.", ) @@ -328,6 +331,7 @@ private class StatsigServerImpl() : dataStoreSetUp() } endInitDiagnostics(failureDetails == null) + outputLogger.info("Statsig Server has been successfully initialized.") return@capture InitializationDetails( System.currentTimeMillis() - setupStartTime, isSDKReady = true, @@ -337,6 +341,7 @@ private class StatsigServerImpl() : } }, { + outputLogger.warn("Statsig Server has not been successfully initialized.") return@capture InitializationDetails( System.currentTimeMillis() - setupStartTime, false, @@ -1188,15 +1193,15 @@ private class StatsigServerImpl() : private fun isSDKInitialized(): Boolean { if (!isInitialized()) { // for multi-instance, if the server has not been initialized - getCustomLogger().warning("Call and wait for initialize StatsigServer to complete before calling SDK methods.") + getCustomLogger().warn("Call and wait for initialize StatsigServer to complete before calling SDK methods.") return false } if (statsigJob.isCancelled || statsigJob.isCompleted) { - options.customLogger.info("StatsigServer was shutdown") + outputLogger.info("StatsigServer was shutdown.") return false } if (!this::evaluator.isInitialized || !evaluator.isInitialized) { // If the server was never initialized - options.customLogger.warning("Must initialize a server before calling other APIs") + outputLogger.warn("Must initialize a server before calling other APIs.") return false } return true @@ -1252,7 +1257,7 @@ private class StatsigServerImpl() : throw e } server.getCustomLogger() - .info("[Statsig]: Shutting down Statsig because of unhandled exception from your server") + .info("Shutting down Statsig because of unhandled exception from your server") server.shutdown() throw e } diff --git a/src/main/kotlin/com/statsig/sdk/datastore/LocalFileDataStore.kt b/src/main/kotlin/com/statsig/sdk/datastore/LocalFileDataStore.kt index cc12984..f6c65d8 100644 --- a/src/main/kotlin/com/statsig/sdk/datastore/LocalFileDataStore.kt +++ b/src/main/kotlin/com/statsig/sdk/datastore/LocalFileDataStore.kt @@ -24,6 +24,7 @@ class LocalFileDataStore @JvmOverloads constructor( var autoUpdate: Boolean = false, ) : IDataStore() { private lateinit var options: StatsigOptions + override var dataStoreKey: String get() = filePath set(value) { @@ -50,14 +51,14 @@ class LocalFileDataStore @JvmOverloads constructor( file.createNewFile() } catch (e: Exception) { - options.customLogger.warning("[Statsig]: An error occurred while creating the file: ${e.message}") + options.customLogger.error("An error occurred while creating the file: ${e.message}") } } } override fun get(key: String): String? { if (key != dataStoreKey) { - options.customLogger.warning("[Statsig]: Please provide the correct file path.") + options.customLogger.warn("Please provide the correct file path.") } return try { @@ -69,7 +70,7 @@ class LocalFileDataStore @JvmOverloads constructor( override fun set(key: String, value: String) { if (key != dataStoreKey) { - options.customLogger.warning("[Statsig]: Please provide the correct file path.") + options.customLogger.warn("Please provide the correct file path.") } File(key).writeText(value) diff --git a/src/main/kotlin/com/statsig/sdk/network/GRPCWebsocketWorker.kt b/src/main/kotlin/com/statsig/sdk/network/GRPCWebsocketWorker.kt index a7edc60..2d1f0f9 100644 --- a/src/main/kotlin/com/statsig/sdk/network/GRPCWebsocketWorker.kt +++ b/src/main/kotlin/com/statsig/sdk/network/GRPCWebsocketWorker.kt @@ -32,6 +32,7 @@ internal class GRPCWebsocketWorker( private var diagnostics: Diagnostics? = null private val channel: Channel = ManagedChannelBuilder.forTarget(proxyConfig.proxyAddress).usePlaintext().build() private val stub = StatsigForwardProxyGrpc.newStub(channel) + private val logger = options.customLogger private val observer = object : StreamObserver { override fun onNext(value: ConfigSpecResponse?) { @@ -126,12 +127,12 @@ internal class GRPCWebsocketWorker( streamingFallback?.startBackup(dcsFlowBacker) } if (shouldRetry) { - options.customLogger.warning("[Statsig]: grpcWebSocket: connection error: $throwable") + logger.warn("grpcWebSocket: connection error: $throwable") errorBoundary.logException("grpcWebSocket: connection error", throwable ?: Exception("connection closed"), bypassDedupe = true) streamConfigSpecWithBackoff() connected = false } else { - options.customLogger.warning("[Statsig]: grpcWebSocket: connection error: retry exhausted") + logger.warn("grpcWebSocket: connection error: retry exhausted") errorBoundary.logException( "grpcWebSocket: retry exhausted", Exception("Remaining retry is $remainingRetries, exception is ${throwable?.message}"), @@ -156,7 +157,7 @@ internal class GRPCWebsocketWorker( if (response.lastUpdated >= lastUpdateTime) { lastUpdateTime = response.lastUpdated if (!dcsFlowBacker.tryEmit(response.spec)) { - options.customLogger.warning("[Statsig]: grpcWebSocket: Failed to emit response") + logger.warn("grpcWebSocket: Failed to emit response") errorBoundary.logException( "grpcWebSocket: Failed to emit response", Exception("${response.lastUpdated}"), diff --git a/src/main/kotlin/com/statsig/sdk/network/GRPCWorker.kt b/src/main/kotlin/com/statsig/sdk/network/GRPCWorker.kt index 3d867d5..f2b84f4 100644 --- a/src/main/kotlin/com/statsig/sdk/network/GRPCWorker.kt +++ b/src/main/kotlin/com/statsig/sdk/network/GRPCWorker.kt @@ -28,6 +28,7 @@ internal class GRPCWorker( private var diagnostics: Diagnostics? = null private val channel: ManagedChannel = ManagedChannelBuilder.forTarget(proxyApi).usePlaintext().build() private val stub = StatsigForwardProxyGrpc.newBlockingStub(channel) + private val logger = options.customLogger override suspend fun downloadConfigSpecs(sinceTime: Long): Pair { val request = ConfigSpecRequest.newBuilder().setSdkKey(this.sdkKey).setSinceTime(sinceTime).build() @@ -50,7 +51,7 @@ internal class GRPCWorker( e.message, null, ) - options.customLogger.warning("[Statsig]: An status exception was received from forward proxy: $e") + logger.warn("An status exception was received from forward proxy: $e") return Pair(null, FailureDetails(FailureReason.CONFIG_SPECS_NETWORK_ERROR, exception = e)) } } diff --git a/src/main/kotlin/com/statsig/sdk/network/HTTPHelper.kt b/src/main/kotlin/com/statsig/sdk/network/HTTPHelper.kt index ed82878..0efac32 100644 --- a/src/main/kotlin/com/statsig/sdk/network/HTTPHelper.kt +++ b/src/main/kotlin/com/statsig/sdk/network/HTTPHelper.kt @@ -14,6 +14,7 @@ internal class HTTPHelper( private val errorBoundary: ErrorBoundary, ) { private var diagnostics: Diagnostics? = null + private val logger = options.customLogger private val gson = Utils.getGson() private val json: MediaType = "application/json; charset=utf-8".toMediaType() @@ -46,9 +47,10 @@ internal class HTTPHelper( null, response, ) + logger.info("Received response with status code: ${response.code}") return Pair(response, null) } catch (e: Exception) { - options.customLogger.warning("[Statsig]: An exception was caught: $e") + logger.warn("An exception was caught: $e") if (e is JsonParseException) { errorBoundary.logException("postImpl", e) } diff --git a/src/main/kotlin/com/statsig/sdk/network/HTTPWorker.kt b/src/main/kotlin/com/statsig/sdk/network/HTTPWorker.kt index 8abddc3..2268518 100644 --- a/src/main/kotlin/com/statsig/sdk/network/HTTPWorker.kt +++ b/src/main/kotlin/com/statsig/sdk/network/HTTPWorker.kt @@ -57,6 +57,7 @@ internal class HTTPWorker( private val statsigHttpClient: OkHttpClient private val gson = Utils.getGson() private var diagnostics: Diagnostics? = null + private val logger = options.customLogger val apiForDownloadConfigSpecs = options.endpointProxyConfigs[NetworkEndpoint.DOWNLOAD_CONFIG_SPECS]?.proxyAddress?.let { "$it/v1" } ?: options.apiForDownloadConfigSpecs ?: options.api ?: STATSIG_CDN_URL_BASE val apiForGetIDLists = options.endpointProxyConfigs[NetworkEndpoint.GET_ID_LISTS]?.proxyAddress?.let { "$it/v1" } ?: options.apiForGetIdlists ?: options.api ?: STATSIG_API_URL_BASE val apiForLogEvent = options.endpointProxyConfigs[NetworkEndpoint.LOG_EVENT]?.proxyAddress?.let { "$it/v1" } ?: options.api ?: STATSIG_API_URL_BASE @@ -130,7 +131,7 @@ internal class HTTPWorker( ) response?.use { if (!response.isSuccessful) { - options.customLogger.warning("[Statsig]: Failed to download config specification, HTTP Response ${response.code} received from server") + logger.warn("Failed to download config specification, HTTP Response ${response.code} received from server") return Pair(null, FailureDetails(FailureReason.CONFIG_SPECS_NETWORK_ERROR, statusCode = response.code)) } return Pair(response.body?.string(), null) @@ -162,7 +163,7 @@ internal class HTTPWorker( ) response?.use { if (!response.isSuccessful) { - options.customLogger.warning("[Statsig]: Failed to download config specification, HTTP Response ${response.code} received from server") + logger.warn("Failed to download config specification, HTTP Response ${response.code} received from server") return Pair(null, FailureDetails(FailureReason.CONFIG_SPECS_NETWORK_ERROR, statusCode = response.code)) } return Pair(response.body?.string(), null) @@ -213,7 +214,7 @@ internal class HTTPWorker( private fun setUpProxyAgent(clientBuilder: OkHttpClient.Builder, proxyConfig: ProxyConfig) { if (proxyConfig.proxyHost.isBlank() || proxyConfig.proxyPort !in 1..65535) { - options.customLogger.warning("Invalid proxy configuration: Host is blank or port is out of range") + logger.warn("Invalid proxy configuration: Host is blank or port is out of range") } val proxyAddress = InetSocketAddress(proxyConfig.proxyHost, proxyConfig.proxyPort) @@ -300,15 +301,15 @@ internal class HTTPWorker( if (response.isSuccessful) { return@coroutineScope } else if (!retryCodes.contains(response.code) || currRetry == 0) { - options.customLogger.warning("[Statsig]: Network request failed with status code: ${response.code}") + logger.warn("Network request failed with status code: ${response.code}") logPostLogFailure(eventsCount) return@coroutineScope } else if (retryCodes.contains(response.code) && currRetry > 0) { - options.customLogger.info("[Statsig]: Retrying network request. Retry count: $currRetry. Response code: ${response.code}") + logger.info("Retrying network request. Retry count: $currRetry. Response code: ${response.code}") } } } catch (e: Exception) { - options.customLogger.warning("[Statsig]: An exception was caught: $e") + logger.warn("An exception was caught: $e") if (e is JsonParseException) { errorBoundary.logException("retryPostLogs", e) } diff --git a/src/test/java/com/statsig/sdk/CustomLoggerTest.kt b/src/test/java/com/statsig/sdk/CustomLoggerTest.kt index 38ffb2a..e07c692 100644 --- a/src/test/java/com/statsig/sdk/CustomLoggerTest.kt +++ b/src/test/java/com/statsig/sdk/CustomLoggerTest.kt @@ -5,7 +5,7 @@ import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.RecordedRequest -import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Test class CustomLoggerTest { @@ -13,7 +13,11 @@ class CustomLoggerTest { val infoMessage = mutableListOf() val server = StatsigServer.create() val fakeLogger = object : LoggerInterface { - override fun warning(message: String) { + override fun error(message: String) { + TODO("Not yet implemented") + } + + override fun warn(message: String) { println(message) warningMessage.add(message) } @@ -22,6 +26,14 @@ class CustomLoggerTest { println(message) infoMessage.add(message) } + + override fun debug(message: String) { + TODO("Not yet implemented") + } + + override fun setLogLevel(level: LogLevel) { + TODO("Not yet implemented") + } } @Test @@ -52,7 +64,8 @@ class CustomLoggerTest { ), ) assert(warningMessage.size == 1) - assertEquals(warningMessage[0], "[Statsig]: Failed to download config specification, HTTP Response 404 received from server") + assertTrue(warningMessage[0].contains("Failed to download config specification")) + assertTrue(warningMessage[0].contains("HTTP Response 404 received from server")) server.shutdown() } }