Using coroutines to write to a log file without loosing the order the logs are in

Viewed 1787

I have created a custom logger to write all of my logs to file as well as to the android log. Since file writing is a blocking operation I now want to make the file-write asynchronous with Kotlin coroutines. Just wrapping the write in a coroutine doesn't work because then some logs get switched around and aren't written to file in the correct order. How can I make sure the logs are written sequentially while not blocking the main thread with file operations, optimally using Kotlin coroutines?

2 Answers

@IR42 said right, you should use channels for this purpose.

Use Channel and make one coroutine that is suspended and awaiting for the channel's sender to send the logs (Strings or whatever).

Using the default channel RendezvousChannel, When a sender sends a log the waiting coroutine resumes and if another sender sends the message then that next sender is suspended till the awaiting coroutine pulls the log from channel.

Example demonstrating use of RendezvousChannel

val channel = Channel<String>()    // Channel of Strings
val sendChannel: SendChannel<String> = channel  // Hide that this instance can receive, store this in order to send to this channel

scope.launch(Dispatchers.IO) {
    while (!channel.isClosedForReceive) {
        val log = channel.receive()
        // process the log
    }
}

// Somewhere else
sendChannel.send("Test Log")

You can try to emit logs to MutableSharedFlow to write logs sequentially to a file:

class Logger {
    // make sure `replay`(in case some logs were emitted before sharedFlow is being collected)
    // and `extraBufferCapacity` is enough to handle all logs
    private val sharedFlow = MutableSharedFlow<String>(replay = 64, extraBufferCapacity = 64)
    private val scope = CoroutineScope(Dispatchers.IO)

    init {
        sharedFlow.onEach { log ->
            println("$log")
            // write to file here
            TimeUnit.MILLISECONDS.sleep(100) // simulate writing to file
        }.launchIn(scope)
    }

    fun log(message: String) {
        sharedFlow.tryEmit(message) 
    }
}

fun test() {

    val logger = Logger()

    repeat(10) { item ->
        logger.log("Log $item")
    }
}

Need to be sure there are enough elements set to replay and extraBufferCapacity parameters of MutableSharedFlow to handle all logs.

Related