Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-feeds-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getstream.feeds.android.client.api.logging

/**
* Represents the logging levels for HTTP requests and responses.
* - None: No logging.
* - Basic: Logs request and response lines.
* - Headers: Logs request and response lines along with their respective headers.
* - Body: Logs request and response lines, headers, and bodies (if present).
*/
public enum class HttpLoggingLevel {
/** No logging. */
None,

/** Logs request and response lines. */
Basic,

/** Logs request and response lines along with their respective headers. */
Headers,

/** Logs request and response lines, headers, and bodies (if present). */
Body,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-feeds-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getstream.feeds.android.client.api.logging

/** A logger interface for logging messages at different levels. */
public interface Logger {
/**
* Logs a message at the specified level with an optional throwable.
*
* @param level The log level.
* @param tag A tag to identify the source of the log message.
* @param throwable An optional throwable associated with the log message.
* @param message A lambda that produces the log message.
*/
public fun log(level: Level, tag: String, throwable: Throwable? = null, message: () -> String)

public enum class Level {
Verbose,
Debug,
Info,
Warning,
Error,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-feeds-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getstream.feeds.android.client.api.logging

/**
* Configuration for logging within the FeedsClient.
*
* @property customLogger An optional custom logger implementation. If not provided, a default
* logger will be used.
* @property httpLoggingLevel What level of HTTP logging to use. Defaults to
* [HttpLoggingLevel.Basic].
*/
public class LoggingConfig(
public val customLogger: Logger? = null,
public val httpLoggingLevel: HttpLoggingLevel = HttpLoggingLevel.Basic,
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@
package io.getstream.feeds.android.client.api.model

import io.getstream.feeds.android.client.api.file.FeedUploader
import io.getstream.feeds.android.client.api.logging.LoggingConfig

/**
* Configuration class for the Stream Feeds Android client. This class contains all the
* configuration options needed to customize the behavior of the Stream Feeds client, such as the
* option for customizing the CDN.
*
* @param customUploader Optional [FeedUploader] implementation for overriding the default CDN.
* @param loggingConfig Configuration for logging within the FeedsClient. See [LoggingConfig] for
* more details.
*/
public class FeedsConfig(public val customUploader: FeedUploader? = null)
public class FeedsConfig(
public val customUploader: FeedUploader? = null,
public val loggingConfig: LoggingConfig = LoggingConfig(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import androidx.lifecycle.ProcessLifecycleOwner
import io.getstream.android.core.api.StreamClient
import io.getstream.android.core.api.authentication.StreamTokenManager
import io.getstream.android.core.api.authentication.StreamTokenProvider
import io.getstream.android.core.api.log.StreamLogger
import io.getstream.android.core.api.log.StreamLoggerProvider
import io.getstream.android.core.api.model.config.StreamClientSerializationConfig
import io.getstream.android.core.api.model.config.StreamHttpConfig
Expand Down Expand Up @@ -53,6 +52,8 @@ import io.getstream.feeds.android.client.internal.client.reconnect.FeedWatchHand
import io.getstream.feeds.android.client.internal.client.reconnect.lifecycle.StreamLifecycleObserver
import io.getstream.feeds.android.client.internal.file.StreamFeedUploader
import io.getstream.feeds.android.client.internal.http.FeedsSingleFlightApi
import io.getstream.feeds.android.client.internal.logging.createLoggerProvider
import io.getstream.feeds.android.client.internal.logging.createLoggingInterceptor
import io.getstream.feeds.android.client.internal.repository.ActivitiesRepositoryImpl
import io.getstream.feeds.android.client.internal.repository.AppRepositoryImpl
import io.getstream.feeds.android.client.internal.repository.BookmarksRepositoryImpl
Expand All @@ -73,7 +74,6 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.plus
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
Expand Down Expand Up @@ -159,11 +159,7 @@ internal fun createFeedsClient(
config: FeedsConfig,
): FeedsClient {

val logProvider =
StreamLoggerProvider.Companion.defaultAndroidLogger(
minLevel = StreamLogger.LogLevel.Verbose,
honorAndroidIsLoggable = false,
)
val logProvider = createLoggerProvider(config.loggingConfig.customLogger)
val logger = logProvider.taggedLogger("FeedsClient")

// Setup coroutine scope for the client
Expand Down Expand Up @@ -195,7 +191,9 @@ internal fun createFeedsClient(
// HTTP Configuration
val okHttpBuilder =
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.addInterceptor(
createLoggingInterceptor(logProvider, config.loggingConfig.httpLoggingLevel)
)

val client =
createStreamCoreClient(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-feeds-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getstream.feeds.android.client.internal.logging

import io.getstream.android.core.api.log.StreamLogger
import io.getstream.android.core.api.log.StreamLogger.LogLevel as CoreLogLevel
import io.getstream.android.core.api.log.StreamLoggerProvider
import io.getstream.feeds.android.client.api.logging.Logger

internal class FeedsLoggerProvider(private val customLogger: Logger) : StreamLoggerProvider {
override fun taggedLogger(tag: String): StreamLogger = FeedsTaggedLogger(customLogger, tag)
}

private class FeedsTaggedLogger(private val customLogger: Logger, private val tag: String) :
StreamLogger {
override fun log(level: CoreLogLevel, throwable: Throwable?, message: () -> String) =
customLogger.log(level.map(), tag, throwable, message)
}

private fun CoreLogLevel.map(): Logger.Level =
when (this) {
CoreLogLevel.Verbose -> Logger.Level.Verbose
CoreLogLevel.Debug -> Logger.Level.Debug
CoreLogLevel.Info -> Logger.Level.Info
CoreLogLevel.Warning -> Logger.Level.Warning
CoreLogLevel.Error -> Logger.Level.Error
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-feeds-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getstream.feeds.android.client.internal.logging

import io.getstream.android.core.api.log.StreamLoggerProvider
import io.getstream.feeds.android.client.api.logging.HttpLoggingLevel
import io.getstream.feeds.android.client.api.logging.Logger
import okhttp3.Interceptor
import okhttp3.logging.HttpLoggingInterceptor

internal fun createLoggerProvider(customLogger: Logger?): StreamLoggerProvider =
customLogger?.let(::FeedsLoggerProvider) ?: StreamLoggerProvider.defaultAndroidLogger()

internal fun createLoggingInterceptor(
provider: StreamLoggerProvider,
level: HttpLoggingLevel,
): Interceptor {
val logger = provider.taggedLogger("FeedHTTP")

return HttpLoggingInterceptor(logger = { logger.i { it } }).setLevel(level.toOkHttpLevel())
}

private fun HttpLoggingLevel.toOkHttpLevel(): HttpLoggingInterceptor.Level =
when (this) {
HttpLoggingLevel.None -> HttpLoggingInterceptor.Level.NONE
HttpLoggingLevel.Basic -> HttpLoggingInterceptor.Level.BASIC
HttpLoggingLevel.Headers -> HttpLoggingInterceptor.Level.HEADERS
HttpLoggingLevel.Body -> HttpLoggingInterceptor.Level.BODY
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-feeds-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getstream.feeds.android.client.internal.logging

import io.getstream.android.core.api.log.StreamLogger
import io.getstream.feeds.android.client.api.logging.Logger
import io.mockk.mockk
import io.mockk.verify
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized

@RunWith(Parameterized::class)
internal class CustomLoggerProviderTest(
private val logLevel: StreamLogger.LogLevel,
private val expectedLevel: Logger.Level,
) {
@Test
fun `on taggedLogger, return a logger that forwards the tag`() {
val customLogger = mockk<Logger>(relaxed = true)
val taggedLogger = FeedsLoggerProvider(customLogger).taggedLogger("a tag")
val exception = Exception("an exception")

taggedLogger.log(logLevel, exception) { "a message" }

verify {
customLogger.log(
level = expectedLevel,
tag = "a tag",
throwable = exception,
message = match { it() == "a message" },
)
}
}

companion object {
@JvmStatic
@Parameterized.Parameters
fun data(): Collection<Array<Any>> =
listOf(
arrayOf(StreamLogger.LogLevel.Verbose, Logger.Level.Verbose),
arrayOf(StreamLogger.LogLevel.Debug, Logger.Level.Debug),
arrayOf(StreamLogger.LogLevel.Info, Logger.Level.Info),
arrayOf(StreamLogger.LogLevel.Warning, Logger.Level.Warning),
arrayOf(StreamLogger.LogLevel.Error, Logger.Level.Error),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import io.getstream.android.core.api.model.value.StreamApiKey
import io.getstream.android.core.api.model.value.StreamToken
import io.getstream.android.core.api.model.value.StreamUserId
import io.getstream.feeds.android.client.api.FeedsClient
import io.getstream.feeds.android.client.api.logging.HttpLoggingLevel
import io.getstream.feeds.android.client.api.logging.LoggingConfig
import io.getstream.feeds.android.client.api.model.FeedsConfig
import io.getstream.feeds.android.client.api.model.User
import io.getstream.feeds.android.sample.DemoAppConfig
import javax.inject.Inject
Expand Down Expand Up @@ -93,6 +96,10 @@ constructor(
return credentials.userToken
}
},
config =
FeedsConfig(
loggingConfig = LoggingConfig(httpLoggingLevel = HttpLoggingLevel.Body)
),
)

return client.connect().map { UserState(user = credentials.user, client = client) }
Expand Down