HTTP client logging plugin for Ktor client framework with configurable logging formats, levels, and platform-specific logger integrations
—
The Ktor Client Logging plugin provides advanced security and filtering capabilities for fine-tuned control over logging behavior.
import io.ktor.client.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.http.*Filter which HTTP requests get logged based on custom criteria.
fun LoggingConfig.filter(predicate: (HttpRequestBuilder) -> Boolean)Parameters:
predicate: Function that receives an HttpRequestBuilder and returns true if the request should be loggedMultiple filters can be added and they work with OR logic - if any filter returns true, the request will be logged.
Logging {
// Only log requests to specific hosts
filter { request ->
request.url.host in listOf("api.production.com", "auth.production.com")
}
// Exclude localhost requests
filter { request ->
!request.url.host.contains("localhost")
}
}Logging {
// Only log non-GET requests
filter { request ->
request.method != HttpMethod.Get
}
// Only log specific methods
filter { request ->
request.method in listOf(HttpMethod.Post, HttpMethod.Put, HttpMethod.Delete)
}
}Logging {
// Only log API endpoints
filter { request ->
request.url.pathSegments.any { it == "api" }
}
// Exclude health check endpoints
filter { request ->
!request.url.encodedPath.contains("/health")
}
// Log only specific resource types
filter { request ->
request.url.pathSegments.any { segment ->
segment in listOf("users", "orders", "products")
}
}
}Logging {
// Only log requests with specific headers
filter { request ->
request.headers.contains("X-Debug-Mode")
}
// Only log authenticated requests
filter { request ->
request.headers.contains(HttpHeaders.Authorization)
}
}Logging {
// Multiple conditions
filter { request ->
request.url.host == "api.production.com" &&
request.method == HttpMethod.Post &&
request.url.pathSegments.contains("sensitive-operation")
}
// Environment-based filtering
filter { request ->
System.getProperty("logging.level") == "debug" ||
request.headers["X-Debug-Session"] != null
}
}Logging {
// All these filters work with OR logic
filter { it.url.host == "api.example.com" } // Log api.example.com
filter { it.method == HttpMethod.Post } // OR log all POST requests
filter { it.headers.contains("X-Debug") } // OR log requests with debug header
}Prevent sensitive header values from appearing in logs by replacing them with placeholder text.
fun LoggingConfig.sanitizeHeader(
placeholder: String = "***",
predicate: (String) -> Boolean
)Parameters:
placeholder: String to replace sensitive header values (default: "***")predicate: Function that receives a header name and returns true if it should be sanitizedMultiple sanitization rules can be added with different placeholders for different header types.
Logging {
// Standard authentication headers
sanitizeHeader { headerName ->
headerName.equals(HttpHeaders.Authorization, ignoreCase = true)
}
// API key headers
sanitizeHeader("████") { headerName ->
headerName.lowercase().contains("api-key") ||
headerName.lowercase().contains("x-api")
}
}Logging {
// Custom security tokens
sanitizeHeader("[REDACTED]") { headerName ->
headerName.startsWith("X-Secret-") ||
headerName.endsWith("-Token") ||
headerName.contains("Password", ignoreCase = true)
}
// Session identifiers
sanitizeHeader("████████") { headerName ->
headerName.lowercase() in listOf("cookie", "set-cookie", "x-session-id")
}
}Logging {
// Headers starting with specific prefixes
sanitizeHeader { headerName ->
val sensitive = listOf("X-Private-", "X-Internal-", "X-Auth-")
sensitive.any { prefix -> headerName.startsWith(prefix, ignoreCase = true) }
}
// Headers containing sensitive keywords
sanitizeHeader("[HIDDEN]") { headerName ->
val keywords = listOf("secret", "private", "token", "key", "password", "credential")
keywords.any { keyword -> headerName.contains(keyword, ignoreCase = true) }
}
}Logging {
// Different visual indicators for different security levels
sanitizeHeader("🔐") { it.equals("Authorization", ignoreCase = true) }
sanitizeHeader("🗝️") { it.contains("api-key", ignoreCase = true) }
sanitizeHeader("🚫") { it.contains("private", ignoreCase = true) }
sanitizeHeader("[CLASSIFIED]") { it.startsWith("X-Secret-") }
}Before sanitization:
-> Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
-> X-API-Key: sk_live_51HqJKaBC123...
-> X-Secret-Token: supersecretvalue123After sanitization:
-> Authorization: ***
-> X-API-Key: ████
-> X-Secret-Token: [REDACTED]val client = HttpClient {
install(Logging) {
// Basic configuration
level = LogLevel.HEADERS
format = LoggingFormat.OkHttp
logger = Logger.DEFAULT
/* Request Filtering */
// Only log production API calls
filter { request ->
request.url.host == "api.production.com"
}
// Or log debug requests from any host
filter { request ->
request.headers.contains("X-Debug-Mode")
}
// Or log error-prone operations
filter { request ->
request.method in listOf(HttpMethod.Post, HttpMethod.Put, HttpMethod.Delete) &&
request.url.pathSegments.any { it in listOf("users", "payments", "orders") }
}
/* Header Sanitization */
// Standard auth headers
sanitizeHeader { it.equals("Authorization", ignoreCase = true) }
// API keys with different placeholder
sanitizeHeader("████") { headerName ->
listOf("x-api-key", "api-key", "x-auth-token").any {
headerName.equals(it, ignoreCase = true)
}
}
// Custom security headers
sanitizeHeader("[CLASSIFIED]") { headerName ->
headerName.startsWith("X-Secret-", ignoreCase = true) ||
headerName.startsWith("X-Private-", ignoreCase = true) ||
headerName.contains("password", ignoreCase = true)
}
// Session and cookie data
sanitizeHeader("🍪") { headerName ->
headerName.lowercase() in listOf("cookie", "set-cookie", "x-session-id")
}
}
}Logging {
// Conservative logging level for production
level = LogLevel.INFO
format = LoggingFormat.OkHttp
// Only log errors and important operations
filter { request ->
// Log authentication attempts
request.url.pathSegments.contains("auth") ||
// Log payment operations
request.url.pathSegments.contains("payments") ||
// Log admin operations
request.headers.contains("X-Admin-Request")
}
// Comprehensive header sanitization
sanitizeHeader { headerName ->
val sensitivePatterns = listOf(
"auth", "token", "key", "secret", "password",
"credential", "private", "session", "cookie"
)
sensitivePatterns.any { pattern ->
headerName.contains(pattern, ignoreCase = true)
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-client-logging-jvm