CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-ktor--ktor-client-logging

Logging plugin for Ktor HTTP client that provides comprehensive request and response logging capabilities with configurable loggers, levels, and sanitization for sensitive data

Pending
Overview
Eval results
Files

loggers.mddocs/

Loggers

Logger interface and platform-specific implementations for outputting HTTP logging information across different platforms and environments.

Capabilities

Logger Interface

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()
}

Built-in Logger Implementations

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: Logger

Usage 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)
}

JVM-Specific Loggers

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)
    ))
}

Platform-Specific Behavior

JVM Platform

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
}

Android Platform

On Android, Logger.ANDROID provides smart Logcat integration:

val client = HttpClient(CIO) {
    install(Logging) {
        logger = Logger.ANDROID  // Automatically detects Android environment
    }
}

Features:

  • Automatically uses Logcat when available
  • Falls back to SLF4J if configured
  • Breaks up long messages to avoid Android's 4068-character limit
  • Uses "Ktor Client" as the log tag

JavaScript/WASM Platform

val client = HttpClient(JS) {
    install(Logging) {
        logger = Logger.DEFAULT  // Uses console.log equivalent
    }
}

Native/iOS Platform

val client = HttpClient(Darwin) {
    install(Logging) {
        logger = Logger.DEFAULT  // Uses platform-appropriate logging
    }
}

Custom Logger Examples

File Logger

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")
}

Structured JSON Logger

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()
}

Conditional Logger

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
    )
}

Remote Logger

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")
            }
        }
    }
}

Logger Performance Considerations

Asynchronous Logging

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)
}

Buffered Logging

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

docs

configuration.md

index.md

loggers.md

utilities.md

tile.json