Klogging
Standalone logging library built in pure Kotlin. Supports structured log events by default through deep integration with Kotlin coroutines. Provides asynchronous event dispatching and automatic context information capture.
Library
Klogging
Overview
Klogging is a standalone logging library built in pure Kotlin, providing "easy and powerful logging with Kotlin idioms" and addressing modern Kotlin development needs through deep integration with Kotlin coroutines. Supporting structured log events by default, it enables high-performance log management in environments where microservices and asynchronous processing are important through asynchronous event dispatching and automatic context information capture. It's a Kotlin-first logging solution that transcends traditional Java logging frameworks.
Details
Klogging is a next-generation logging library gaining attention in coroutine-based Kotlin applications in 2025. Equipped with features addressing modern distributed system requirements including information storage in coroutine scopes and automatic context management, microsecond-level high-precision timestamps, and JDK platform logging implementation. With excellent Spring Boot integration, it significantly improves structured logging and traceability in server-side Kotlin development, positioning itself as an important complement and evolution of traditional SLF4J+Logback approaches.
Key Features
- Coroutine Native: Deep integration with Kotlin coroutines and automatic context management
- Structured Logging by Default: Automatic structured event generation through message templates
- Asynchronous Event Dispatching: High-performance asynchronous log processing functionality
- High-Precision Timestamps: Precise time recording from microsecond to nanosecond level
- Kotlin First: Optimized API design through pure Kotlin implementation
- Spring Boot Integration: Integration functionality supporting enterprise-level adoption
Pros and Cons
Pros
- Context management automated through integration with Kotlin coroutines
- Structured logging by default adapts to modern log analysis and monitoring environments
- Performance improvement in high-load environments through asynchronous processing
- Natural API fully compliant with Kotlin idioms through pure Kotlin implementation
- Advantages in traceability and distributed log management for microservice environments
- Enterprise-level development adoption ease through Spring Boot integration
Cons
- Specialized in Kotlin ecosystem with limited compatibility with other languages
- Relatively new library with smaller maturity and community size compared to SLF4J
- No API compatibility with traditional Java logging, creating learning costs during migration
- Possibility of over-engineering in small-scale projects due to many advanced features
- Less debugging and troubleshooting information available compared to traditional libraries
- High adoption hurdles in conservative organizations due to limited enterprise track record
Reference Pages
Usage Examples
Installation and Basic Setup
// build.gradle.kts
dependencies {
implementation("io.klogging:klogging-jvm:0.10.1")
// For Spring Boot integration
implementation("io.klogging:slf4j-klogging:0.10.1")
}
<!-- Maven pom.xml -->
<dependency>
<groupId>io.klogging</groupId>
<artifactId>klogging-jvm</artifactId>
<version>0.10.1</version>
</dependency>
import io.klogging.Klogging
import io.klogging.NoCoLogging
import io.klogging.config.loggingConfiguration
import kotlinx.coroutines.*
// Basic Klogging configuration
fun main() = runBlocking {
// Console output configuration
loggingConfiguration {
ANSI_CONSOLE()
}
// Simple log output test
val logger = NoCoLogging.logger()
logger.info { "Klogging initialized successfully" }
// Logging in coroutine environment
launch {
val coLogger = Klogging.logger()
coLogger.info { "Coroutine-based logging ready" }
}
}
// Advanced configuration example
fun setupAdvancedKlogging() {
loggingConfiguration {
// Multiple output destination configuration
sink("console", ANSI_CONSOLE())
sink("file", FILE("logs/app.log"))
sink("json", FILE("logs/app.json", JSON_FORMAT))
// Log level configuration
minLevel(Level.INFO)
// Structured logging configuration
logger("com.example.service") {
minLevel(Level.DEBUG)
stopOnMatch = true
}
// Asynchronous dispatch configuration
asyncDispatcher {
bufferSize = 1000
dropOnOverflow = false
}
}
}
Coroutine Integration (Basic Usage)
import io.klogging.Klogging
import io.klogging.NoCoLogging
import io.klogging.events.logContext
import kotlinx.coroutines.*
// Coroutine-integrated logging class
class UserService : Klogging {
suspend fun processUser(userId: String, userData: UserData) = coroutineScope {
// Add log information to coroutine context
launch(logContext("userId" to userId, "operation" to "processUser")) {
logger.info { "Starting user processing" }
try {
// User data validation
validateUserData(userData)
logger.debug { "User data validation completed" }
// Database save
saveUserToDatabase(userData)
logger.info { "User data saved successfully" }
// External API notification
notifyExternalSystems(userId)
logger.info { "External systems notified" }
} catch (e: Exception) {
logger.error(e) { "User processing failed" }
throw e
}
}
}
private suspend fun validateUserData(userData: UserData) {
logger.debug { "Validating user data: ${userData.email}" }
if (userData.email.isBlank()) {
logger.warn { "Invalid email address provided" }
throw IllegalArgumentException("Email is required")
}
if (userData.age < 0 || userData.age > 150) {
logger.warn { "Invalid age: ${userData.age}" }
throw IllegalArgumentException("Invalid age range")
}
}
private suspend fun saveUserToDatabase(userData: UserData) {
logger.debug { "Saving to database" }
delay(100) // Database operation simulation
logger.info { "Database save completed" }
}
private suspend fun notifyExternalSystems(userId: String) {
logger.debug { "Notifying external systems" }
delay(50) // API call simulation
logger.info { "External notification completed" }
}
}
// Usage in non-coroutine environment
class ConfigurationManager : NoCoLogging {
fun loadConfiguration(configPath: String): Configuration {
logger.info { "Loading configuration from: $configPath" }
return try {
val config = parseConfigFile(configPath)
logger.info { "Configuration loaded successfully. Entries: ${config.entries.size}" }
config
} catch (e: Exception) {
logger.error(e) { "Failed to load configuration" }
throw ConfigurationException("Configuration loading failed", e)
}
}
private fun parseConfigFile(path: String): Configuration {
logger.debug { "Parsing configuration file" }
// File parsing processing
return Configuration()
}
}
// Data classes
data class UserData(
val email: String,
val name: String,
val age: Int
)
data class Configuration(
val entries: Map<String, String> = emptyMap()
)
class ConfigurationException(message: String, cause: Throwable) : Exception(message, cause)
// Usage example
suspend fun main() {
loggingConfiguration {
ANSI_CONSOLE()
}
val userService = UserService()
val configManager = ConfigurationManager()
// Coroutine-based processing
val userData = UserData("[email protected]", "John Doe", 30)
userService.processUser("user123", userData)
// Non-coroutine processing
val config = configManager.loadConfiguration("/path/to/config.yaml")
}
Structured Logging and Context Management
import io.klogging.Klogging
import io.klogging.events.logContext
import io.klogging.events.LogEvent
import kotlinx.coroutines.*
import java.util.*
// API service utilizing structured logging
class ApiService : Klogging {
suspend fun handleRequest(requestId: String, endpoint: String, payload: Any) = coroutineScope {
// Request context configuration
launch(logContext(
"requestId" to requestId,
"endpoint" to endpoint,
"timestamp" to System.currentTimeMillis(),
"service" to "ApiService"
)) {
logger.info { "API request received" }
val startTime = System.nanoTime()
try {
// Authentication and authorization check
authenticateRequest(requestId)
// Business logic execution
val result = processRequest(endpoint, payload)
val duration = (System.nanoTime() - startTime) / 1_000_000.0
// Success log (structured)
logger.info {
"API request completed successfully. " +
"Duration: {duration}ms, Result: {resultType}"
} withContext mapOf(
"duration" to duration,
"resultType" to result::class.simpleName,
"resultSize" to result.toString().length
)
} catch (e: AuthenticationException) {
val duration = (System.nanoTime() - startTime) / 1_000_000.0
logger.warn(e) {
"Authentication failed. Duration: {duration}ms"
} withContext mapOf(
"duration" to duration,
"errorType" to "authentication",
"clientIp" to getCurrentClientIp()
)
throw e
} catch (e: Exception) {
val duration = (System.nanoTime() - startTime) / 1_000_000.0
logger.error(e) {
"API request failed. Duration: {duration}ms, Error: {errorMessage}"
} withContext mapOf(
"duration" to duration,
"errorMessage" to e.message,
"errorType" to e::class.simpleName
)
throw e
}
}
}
private suspend fun authenticateRequest(requestId: String) {
logger.debug { "Authenticating request" }
// Authentication processing simulation
delay(10)
if (requestId.startsWith("invalid")) {
throw AuthenticationException("Invalid request ID")
}
logger.debug { "Authentication successful" }
}
private suspend fun processRequest(endpoint: String, payload: Any): Any {
logger.debug { "Processing business logic for endpoint: $endpoint" }
return when (endpoint) {
"/users" -> processUserRequest(payload)
"/orders" -> processOrderRequest(payload)
else -> {
logger.warn { "Unknown endpoint: $endpoint" }
throw IllegalArgumentException("Unsupported endpoint")
}
}
}
private suspend fun processUserRequest(payload: Any): Map<String, Any> {
logger.debug { "Processing user request" }
delay(50) // Processing time simulation
return mapOf(
"status" to "success",
"userId" to UUID.randomUUID().toString(),
"data" to payload
)
}
private suspend fun processOrderRequest(payload: Any): Map<String, Any> {
logger.debug { "Processing order request" }
delay(100) // Processing time simulation
return mapOf(
"status" to "success",
"orderId" to UUID.randomUUID().toString(),
"data" to payload
)
}
private fun getCurrentClientIp(): String {
// Client IP acquisition (implementation depends on environment)
return "192.168.1.100"
}
}
// Custom exception
class AuthenticationException(message: String) : Exception(message)
// Extension function to add context information
infix fun String.withContext(context: Map<String, Any>): String {
return this // In actual Klogging, context is automatically processed
}
// Performance monitoring logger
class PerformanceLogger : Klogging {
suspend fun <T> measureOperation(
operationName: String,
additionalContext: Map<String, Any> = emptyMap(),
operation: suspend () -> T
): T = coroutineScope {
launch(logContext(
"operation" to operationName,
"startTime" to System.currentTimeMillis()
) + additionalContext) {
val startTime = System.nanoTime()
val startMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()
logger.debug { "Starting operation: $operationName" }
try {
val result = operation()
val duration = (System.nanoTime() - startTime) / 1_000_000.0
val endMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()
val memoryDelta = endMemory - startMemory
logger.info {
"Operation completed: {operation}. Duration: {duration}ms, Memory: {memoryDelta}KB"
} withContext mapOf(
"operation" to operationName,
"duration" to duration,
"memoryDelta" to memoryDelta / 1024,
"success" to true
)
return@launch result
} catch (e: Exception) {
val duration = (System.nanoTime() - startTime) / 1_000_000.0
logger.error(e) {
"Operation failed: {operation}. Duration: {duration}ms, Error: {error}"
} withContext mapOf(
"operation" to operationName,
"duration" to duration,
"error" to e.message,
"success" to false
)
throw e
}
}
}
}
// Usage example
suspend fun main() {
loggingConfiguration {
sink("console", ANSI_CONSOLE())
sink("file", FILE("logs/structured.log", JSON_FORMAT))
}
val apiService = ApiService()
val perfLogger = PerformanceLogger()
// API processing with structured logging
perfLogger.measureOperation(
"api_request_processing",
mapOf("endpoint" to "/users", "method" to "POST")
) {
apiService.handleRequest(
"req123",
"/users",
mapOf("name" to "John Doe", "email" to "[email protected]")
)
}
}
Spring Boot Integration and Practical Examples
import io.klogging.Klogging
import io.klogging.NoCoLogging
import io.klogging.config.loggingConfiguration
import io.klogging.events.logContext
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.web.bind.annotation.*
import org.springframework.stereotype.Service
import org.springframework.stereotype.Repository
import kotlinx.coroutines.*
import javax.annotation.PostConstruct
@SpringBootApplication
class KloggingDemoApplication : NoCoLogging {
@PostConstruct
fun setupLogging() {
loggingConfiguration {
// Development environment configuration
ANSI_CONSOLE()
sink("file", FILE("logs/spring-app.log"))
sink("json", FILE("logs/spring-app.json", JSON_FORMAT))
// Package-specific log levels
logger("com.example.controller") { minLevel(Level.DEBUG) }
logger("com.example.service") { minLevel(Level.INFO) }
logger("com.example.repository") { minLevel(Level.WARN) }
// Detailed SQL log configuration
logger("org.springframework.jdbc") { minLevel(Level.DEBUG) }
}
logger.info { "Klogging configuration completed for Spring Boot" }
}
}
fun main(args: Array<String>) {
runApplication<KloggingDemoApplication>(*args)
}
// REST controller
@RestController
@RequestMapping("/api/users")
class UserController(
private val userService: UserService
) : Klogging {
@PostMapping
suspend fun createUser(@RequestBody userRequest: CreateUserRequest): UserResponse = coroutineScope {
val requestId = java.util.UUID.randomUUID().toString()
launch(logContext(
"requestId" to requestId,
"endpoint" to "/api/users",
"method" to "POST",
"controller" to "UserController"
)) {
logger.info { "Creating new user: ${userRequest.email}" }
try {
val user = userService.createUser(userRequest)
logger.info {
"User created successfully. UserId: {userId}, Email: {email}"
} withContext mapOf(
"userId" to user.id,
"email" to user.email,
"requestId" to requestId
)
UserResponse(user.id, user.email, user.name, "created")
} catch (e: UserAlreadyExistsException) {
logger.warn(e) {
"User creation failed - already exists: {email}"
} withContext mapOf(
"email" to userRequest.email,
"requestId" to requestId
)
throw e
} catch (e: Exception) {
logger.error(e) {
"User creation failed: {error}"
} withContext mapOf(
"error" to e.message,
"email" to userRequest.email,
"requestId" to requestId
)
throw e
}
}
}
@GetMapping("/{userId}")
suspend fun getUser(@PathVariable userId: String): UserResponse = coroutineScope {
launch(logContext(
"userId" to userId,
"endpoint" to "/api/users/{userId}",
"method" to "GET"
)) {
logger.debug { "Retrieving user: $userId" }
val user = userService.findUser(userId)
?: throw UserNotFoundException("User not found: $userId")
logger.debug { "User retrieved successfully" }
UserResponse(user.id, user.email, user.name, "retrieved")
}
}
}
// Business service
@Service
class UserService(
private val userRepository: UserRepository,
private val emailService: EmailService
) : Klogging {
suspend fun createUser(request: CreateUserRequest): User = coroutineScope {
launch(logContext("service" to "UserService", "operation" to "createUser")) {
logger.info { "Creating user: ${request.email}" }
// Duplicate check
val existingUser = userRepository.findByEmail(request.email)
if (existingUser != null) {
logger.warn { "User already exists: ${request.email}" }
throw UserAlreadyExistsException("User already exists")
}
// User creation
val user = User(
id = java.util.UUID.randomUUID().toString(),
email = request.email,
name = request.name
)
val savedUser = userRepository.save(user)
logger.info { "User saved to database: ${savedUser.id}" }
// Welcome email sending (asynchronous)
launch {
emailService.sendWelcomeEmail(savedUser)
}
logger.info { "User creation completed: ${savedUser.id}" }
savedUser
}
}
suspend fun findUser(userId: String): User? = coroutineScope {
launch(logContext("service" to "UserService", "operation" to "findUser")) {
logger.debug { "Finding user: $userId" }
val user = userRepository.findById(userId)
if (user != null) {
logger.debug { "User found: ${user.email}" }
} else {
logger.debug { "User not found: $userId" }
}
user
}
}
}
// Repository
@Repository
class UserRepository : Klogging {
// Simple in-memory storage
private val users = mutableMapOf<String, User>()
private val emailIndex = mutableMapOf<String, String>()
suspend fun save(user: User): User = withContext(Dispatchers.IO) {
launch(logContext("repository" to "UserRepository", "operation" to "save")) {
logger.debug { "Saving user to database: ${user.id}" }
users[user.id] = user
emailIndex[user.email] = user.id
// Database save simulation
delay(10)
logger.debug { "User saved successfully: ${user.id}" }
}
user
}
suspend fun findById(userId: String): User? = withContext(Dispatchers.IO) {
launch(logContext("repository" to "UserRepository", "operation" to "findById")) {
logger.debug { "Finding user by ID: $userId" }
// Database search simulation
delay(5)
val user = users[userId]
logger.debug { "User search result: ${if (user != null) "found" else "not found"}" }
user
}
}
suspend fun findByEmail(email: String): User? = withContext(Dispatchers.IO) {
launch(logContext("repository" to "UserRepository", "operation" to "findByEmail")) {
logger.debug { "Finding user by email: $email" }
delay(5)
val userId = emailIndex[email]
val user = userId?.let { users[it] }
logger.debug { "Email search result: ${if (user != null) "found" else "not found"}" }
user
}
}
}
// Email service
@Service
class EmailService : Klogging {
suspend fun sendWelcomeEmail(user: User) = coroutineScope {
launch(logContext(
"service" to "EmailService",
"operation" to "sendWelcomeEmail",
"userId" to user.id
)) {
logger.info { "Sending welcome email to: ${user.email}" }
try {
// Email sending process simulation
delay(100)
logger.info { "Welcome email sent successfully to: ${user.email}" }
} catch (e: Exception) {
logger.error(e) {
"Failed to send welcome email to: {email}"
} withContext mapOf(
"email" to user.email,
"userId" to user.id,
"error" to e.message
)
}
}
}
}
// Data classes
data class CreateUserRequest(
val email: String,
val name: String
)
data class UserResponse(
val id: String,
val email: String,
val name: String,
val status: String
)
data class User(
val id: String,
val email: String,
val name: String
)
// Custom exceptions
class UserAlreadyExistsException(message: String) : Exception(message)
class UserNotFoundException(message: String) : Exception(message)
Advanced Configuration and Customization
import io.klogging.Klogging
import io.klogging.config.*
import io.klogging.events.*
import io.klogging.rendering.*
import io.klogging.sending.*
import kotlinx.coroutines.*
import java.time.LocalDateTime
// Advanced Klogging configuration
object AdvancedKloggingConfig {
fun setupProduction() {
loggingConfiguration {
// Custom rendering configuration
rendering {
// Custom rendering in JSON format
custom("customJson") { event ->
buildString {
append("{")
append("\"timestamp\":\"${event.timestamp}\",")
append("\"level\":\"${event.level}\",")
append("\"logger\":\"${event.logger}\",")
append("\"message\":\"${event.message}\",")
append("\"thread\":\"${event.items["thread"]}\",")
append("\"context\":{")
event.items.filter { it.key != "thread" }
.entries.joinToString(",") { "\"${it.key}\":\"${it.value}\"" }
append("}}")
}
}
// Custom console output
custom("colorConsole") { event ->
val color = when (event.level) {
Level.ERROR -> "\u001B[31m" // Red
Level.WARN -> "\u001B[33m" // Yellow
Level.INFO -> "\u001B[32m" // Green
Level.DEBUG -> "\u001B[36m" // Cyan
else -> "\u001B[0m" // Reset
}
val reset = "\u001B[0m"
"$color[${event.timestamp}] [${event.level}] ${event.logger}: ${event.message}$reset"
}
}
// Custom destination configuration
sending {
// File output configuration
custom("rotatingFile") { events ->
events.forEach { event ->
val logFile = getLogFileForDate(LocalDateTime.now())
writeToFile(logFile, renderEvent(event, "customJson"))
}
}
// External system integration
custom("elasticSearch") { events ->
// ElasticSearch sending implementation
sendToElasticSearch(events)
}
custom("metrics") { events ->
// Metrics collection
collectMetrics(events)
}
}
// Composite sink configuration
sink("production") {
renderer = "customJson"
dispatcher = listOf("rotatingFile", "elasticSearch", "metrics")
}
sink("development") {
renderer = "colorConsole"
dispatcher = listOf("CONSOLE")
}
// Logger-specific configuration
logger("com.example.api") {
minLevel(Level.INFO)
sink("production")
stopOnMatch = true
}
logger("com.example.service") {
minLevel(Level.DEBUG)
sink("development")
stopOnMatch = false
}
// Performance monitoring configuration
logger("performance") {
minLevel(Level.DEBUG)
sink("production")
additionalContext = mapOf(
"application" to "myapp",
"version" to "1.0.0"
)
}
// Filter configuration
filter { event ->
// Sensitive information masking
event.copy(
message = maskSensitiveData(event.message),
items = event.items.mapValues { maskSensitiveData(it.value.toString()) }
)
}
}
}
private fun getLogFileForDate(date: LocalDateTime): String {
return "logs/app-${date.toLocalDate()}.log"
}
private fun writeToFile(filename: String, content: String) {
// File writing implementation
java.io.File(filename).appendText(content + "\n")
}
private fun renderEvent(event: LogEvent, renderer: String): String {
// Event rendering implementation
return event.toString()
}
private suspend fun sendToElasticSearch(events: List<LogEvent>) {
// ElasticSearch sending implementation
withContext(Dispatchers.IO) {
// POST to ElasticSearch with HTTP client
events.forEach { event ->
// Implementation details
}
}
}
private fun collectMetrics(events: List<LogEvent>) {
// Metrics collection implementation
events.forEach { event ->
when (event.level) {
Level.ERROR -> incrementErrorCounter()
Level.WARN -> incrementWarningCounter()
else -> incrementInfoCounter()
}
}
}
private fun maskSensitiveData(text: String): String {
return text
.replace(Regex("password[=:]\\s*\\S+", RegexOption.IGNORE_CASE), "password=***")
.replace(Regex("token[=:]\\s*\\S+", RegexOption.IGNORE_CASE), "token=***")
.replace(Regex("\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b"), "****-****-****-****")
}
private fun incrementErrorCounter() { /* Metrics implementation */ }
private fun incrementWarningCounter() { /* Metrics implementation */ }
private fun incrementInfoCounter() { /* Metrics implementation */ }
}
// Custom logger implementation
class AuditLogger : Klogging {
suspend fun logSecurityEvent(
eventType: String,
userId: String?,
details: Map<String, Any>
) = coroutineScope {
launch(logContext(
"eventType" to "security",
"auditCategory" to eventType,
"userId" to (userId ?: "anonymous"),
"timestamp" to System.currentTimeMillis()
)) {
logger.info {
"Security event: {eventType}. Details: {details}"
} withContext mapOf(
"eventType" to eventType,
"details" to details,
"severity" to determineSeverity(eventType)
)
}
}
suspend fun logBusinessEvent(
operation: String,
entityId: String,
changes: Map<String, Any>
) = coroutineScope {
launch(logContext(
"eventType" to "business",
"operation" to operation,
"entityId" to entityId
)) {
logger.info {
"Business operation: {operation} on entity {entityId}"
} withContext mapOf(
"operation" to operation,
"entityId" to entityId,
"changes" to changes,
"changeCount" to changes.size
)
}
}
private fun determineSeverity(eventType: String): String {
return when (eventType) {
"login_failure", "unauthorized_access" -> "high"
"login_success", "logout" -> "low"
"password_change", "permission_change" -> "medium"
else -> "low"
}
}
}
// Usage example
suspend fun main() {
AdvancedKloggingConfig.setupProduction()
val auditLogger = AuditLogger()
// Security event
auditLogger.logSecurityEvent(
"login_failure",
"user123",
mapOf(
"ip" to "192.168.1.100",
"userAgent" to "Mozilla/5.0...",
"reason" to "invalid_password"
)
)
// Business event
auditLogger.logBusinessEvent(
"user_update",
"user123",
mapOf(
"oldEmail" to "[email protected]",
"newEmail" to "[email protected]",
"updatedBy" to "admin"
)
)
}