https://kotlinlang.org logo
Title
e

Emil Kantis

04/17/2023, 8:28 PM
Looking for some early feedback on the notion of having a library focused around logging using context receivers. The idea is to provide a
LoggingContext
which exposes functions to add log events.
context(LoggingContext)
fun logHello() {
   info { "hello" }
}
My main motivation is that it would be really easy to swap the context for a recording context which can be used to easily verify logs. 🙂 Does this fill a need, or are there better ways to achieve this already?
s

simon.vergauwen

04/17/2023, 9:06 PM
A logger like this one, https://github.com/oshai/kotlin-logging/blob/master/src/commonMain/kotlin/io/github/oshai/KLogger.kt Could fill that gap. Since you can just put KLogger in the context. It’s missing proper testing support though. If its important enough to test, or have different implementations, I might put a tiny custom interface on top though.
e

Emil Kantis

04/17/2023, 9:23 PM
Hmm.. and it's multiplatform as well. Nice 🙂
Maybe I'll just do the tiny interface + test infra, and bindings for sfl4j, and the library you linked..
d

dave08

04/18/2023, 2:11 AM
Maybe you could PR to that library, that way everyone could benefit 😉, also, I think Kotest has an extension for testing log output... Although I didn't really look into it yet.
e

Emil Kantis

04/18/2023, 4:11 AM
I did consider that, but doing it as a separate project allows me to support other logging implementations. I'm mostly using SLF4J, and I would like this project to support both. As for Kotest, I was interested in doing something like this a while back, but it was hard. By making the production code utilize context receivers I think it suddenly becomes quite easy 🙂
I kinda like how this code is turning out..
context(LoggingContext)
private suspend fun processInvoice(
   // ...
) {
   val lockResult = acquireLock(companyId, invoice).onSuccess {
      info { "Successfully acquired lock for ${invoice.invoiceNr}." }
   }.onFailure {
      warn { "Failed to acquire lock for ${invoice.invoiceNr}." }
   }
  // ...
}
Testing:
withTestLogging {
               gateways.processInvoices(config, today = LocalDate.of(2022, 4, 27))
            }.shouldContainExactly(
               <http://LogLevel.INFO|LogLevel.INFO>("Fetched supplier null (id=99) with bankgiro 55551234"),
               LogLevel.WARN("Failed to acquire lock for ${clientInvoice.invoiceNr}."),
               LogLevel.WARN("Skipping invoice since DB lock was failed to acquire"),
            )
d

dave08

04/18/2023, 6:25 AM
Your solution seems promising... but for those that have issues with context receivers, it could get pretty messy to provide that context...
s

simon.vergauwen

04/18/2023, 6:44 AM
Lets just make context receivers mainstream Kotlin 🤪 I think it's much simpler than Spring DI, or Dagger for example. Looking at logcapture it still seems you need a DI system in order to properly inject the testing instance into your classes or functions.
d

dave08

04/18/2023, 6:49 AM
Yeah, but wiring together more complex dependencies can get a bit messy when done manually (some have a certain scope, some lazy, some singletons that should be loaded only when needed...)? And I still can't get one of my projects to compile with context receivers... I do agree that they are very tempting though, and I'm probably speaking out of lack of practical experience with them... but alreadyjust the custom
with(....) { }
blocks that one needs to provide to use them and the hacks needed to compile them is a bit scary.
s

simon.vergauwen

04/18/2023, 6:59 AM
some have a certain scope, some lazy, some singletons that should be loaded only when needed...
I find this easier to do manually than with a framework, but that is perhaps preference or familiarity.
but alreadyjust the custom
with(....) { }
blocks that one needs to provide
This is indeed a bit cumbersome, but the boilerplate could be already pushed to a library. Part of the KEEP for context receivers mentions an
n-arity
with method but it requires some planning and engineering from the Kotlin team on how this will be put into K2.
And I still can't get one of my projects to compile with context receivers...
Most problematic piece is that it's currently experimental, and extreme low priority compared to finishing K2. We should see K2 arriving this year though, so 🤞 for stabilisation soon.
e

Emil Kantis

04/18/2023, 7:04 AM
For anyone curious, initial work here: https://github.com/Kantis/context-logging It's almost at a point where a first working snapshot is available.. Seeing some odd issues with native and JS targets though
r

raulraja

04/18/2023, 9:21 AM
@Emil Kantis looks great but why
info { "message" }
instead of
info("message")
. Is that to avoid evaluation of the message at different log levels?
e

Emil Kantis

04/18/2023, 9:25 AM
Yes, that's the idea 🙂 I think it's pretty common to have lazy evaluation of the message in case the computation is expensive