Logging plugin for Ktor HTTP client that provides comprehensive request and response logging capabilities with configurable loggers, levels, and sanitization for sensitive data
—
Logger interface and platform-specific implementations for outputting HTTP logging information across different platforms and environments.
The core logging interface that all logger implementations must implement.
/**
* HttpClient Logger interface
*/
interface Logger {
/**
* Add message to log
* @param message The message to log
*/
fun log(message: String)
companion object
}Usage Examples:
// Custom logger implementation
class MyCustomLogger : Logger {
override fun log(message: String) {
// Write to file, database, remote service, etc.
println("HTTP: $message")
}
}
// Use custom logger
install(Logging) {
logger = MyCustomLogger()
}Platform-specific logger implementations provided by the library.
/**
* Default logger to use (platform-specific implementation)
* - JVM: Uses SLF4J LoggerFactory with HttpClient class as logger name
* - JS/WASM: Uses Logger.SIMPLE
* - iOS/Native: Uses Logger.SIMPLE
*/
expect val Logger.Companion.DEFAULT: Logger
/**
* Logger using println for console output
*/
val Logger.Companion.SIMPLE: Logger
/**
* Empty logger for testing purposes (no output)
*/
val Logger.Companion.EMPTY: LoggerUsage Examples:
install(Logging) {
logger = Logger.DEFAULT // Platform-appropriate default
logger = Logger.SIMPLE // Console output on all platforms
logger = Logger.EMPTY // No output (useful for tests)
}Additional logger implementations available only on JVM platforms.
/**
* Android Logger: Logs to the Logcat on Android if the SLF4J provider isn't found.
* Otherwise, uses the Logger.DEFAULT.
* Breaks up long log messages that would be truncated by Android's max log
* length of 4068 characters.
*/
val Logger.Companion.ANDROID: Logger
/**
* A Logger that breaks up log messages into multiple logs no longer than maxLength
* @param maxLength max length allowed for a log message (default: 4000)
* @param minLength if log message is longer than maxLength, attempt to break the log
* message at a new line between minLength and maxLength if one exists (default: 3000)
* @param delegate underlying logger to use (default: Logger.DEFAULT)
*/
class MessageLengthLimitingLogger(
private val maxLength: Int = 4000,
private val minLength: Int = 3000,
private val delegate: Logger = Logger.DEFAULT
) : Logger {
override fun log(message: String)
}Usage Examples:
// Android-optimized logging
install(Logging) {
logger = Logger.ANDROID
}
// Custom message length limiting
install(Logging) {
logger = MessageLengthLimitingLogger(
maxLength = 2000,
minLength = 1500,
delegate = Logger.SIMPLE
)
}
// Chain loggers for complex scenarios
class MultiLogger(private val loggers: List<Logger>) : Logger {
override fun log(message: String) {
loggers.forEach { it.log(message) }
}
}
install(Logging) {
logger = MultiLogger(listOf(
Logger.ANDROID,
MessageLengthLimitingLogger(delegate = Logger.SIMPLE)
))
}On JVM, Logger.DEFAULT uses SLF4J integration:
// Automatic SLF4J integration
val client = HttpClient(CIO) {
install(Logging) {
logger = Logger.DEFAULT // Uses SLF4J LoggerFactory
}
}Dependencies for SLF4J support:
dependencies {
implementation("org.slf4j:slf4j-api:1.7.36")
implementation("ch.qos.logback:logback-classic:1.2.11") // or other SLF4J implementation
}On Android, Logger.ANDROID provides smart Logcat integration:
val client = HttpClient(CIO) {
install(Logging) {
logger = Logger.ANDROID // Automatically detects Android environment
}
}Features:
val client = HttpClient(JS) {
install(Logging) {
logger = Logger.DEFAULT // Uses console.log equivalent
}
}val client = HttpClient(Darwin) {
install(Logging) {
logger = Logger.DEFAULT // Uses platform-appropriate logging
}
}class FileLogger(private val filePath: String) : Logger {
override fun log(message: String) {
File(filePath).appendText("${System.currentTimeMillis()}: $message\n")
}
}
install(Logging) {
logger = FileLogger("/tmp/http.log")
}import kotlinx.serialization.json.*
class JsonLogger : Logger {
private val json = Json { prettyPrint = true }
override fun log(message: String) {
val logEntry = buildJsonObject {
put("timestamp", System.currentTimeMillis())
put("level", "HTTP")
put("message", message)
}
println(json.encodeToString(logEntry))
}
}
install(Logging) {
logger = JsonLogger()
}class ConditionalLogger(
private val condition: () -> Boolean,
private val delegate: Logger = Logger.DEFAULT
) : Logger {
override fun log(message: String) {
if (condition()) {
delegate.log(message)
}
}
}
install(Logging) {
logger = ConditionalLogger(
condition = { BuildConfig.DEBUG }, // Only log in debug builds
delegate = Logger.SIMPLE
)
}class RemoteLogger(
private val endpoint: String,
private val httpClient: HttpClient
) : Logger {
override fun log(message: String) {
// Note: This creates a logging loop risk - use carefully
runBlocking {
try {
httpClient.post(endpoint) {
setBody(message)
}
} catch (e: Exception) {
// Fallback to console to avoid infinite loops
println("Failed to send log: $message")
}
}
}
}class AsyncLogger(private val delegate: Logger) : Logger {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
override fun log(message: String) {
scope.launch {
delegate.log(message)
}
}
}
install(Logging) {
logger = AsyncLogger(Logger.DEFAULT)
}class BufferedLogger(
private val delegate: Logger,
private val bufferSize: Int = 100
) : Logger {
private val buffer = mutableListOf<String>()
@Synchronized
override fun log(message: String) {
buffer.add(message)
if (buffer.size >= bufferSize) {
flush()
}
}
@Synchronized
private fun flush() {
if (buffer.isNotEmpty()) {
delegate.log(buffer.joinToString("\n"))
buffer.clear()
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-client-logging